Skip to content

Commit

Permalink
add dashboard text cards
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Eatherly committed Nov 28, 2017
1 parent 7bcbbe7 commit bb7a3e0
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 33 deletions.
4 changes: 4 additions & 0 deletions frontend/src/metabase/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@
transition: opacity .3s linear;
}

.DashCard .DashCard-actions-persistent {
pointer-events: all;
}

.Dash--editing .DashCard:hover .DashCard-actions {
height: initial;
opacity: 1;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/metabase/dashboard/components/DashCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const DashCardActionButtons = ({ series, onRemove, onAddSeries, onReplaceAllVisu
{ getVisualizationRaw(series).CardVisualization.supportsSeries &&
<AddSeriesButton series={series} onAddSeries={onAddSeries} />
}
{ onReplaceAllVisualizationSettings &&
{ onReplaceAllVisualizationSettings && !getVisualizationRaw(series).CardVisualization.disableSettingsConfig &&
<ChartSettingsButton series={series} onReplaceAllVisualizationSettings={onReplaceAllVisualizationSettings} />
}
<RemoveButton onRemove={onRemove} />
Expand Down
72 changes: 46 additions & 26 deletions frontend/src/metabase/dashboard/components/DashboardHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import { getDashboardActions } from "./DashboardActions";
import ParametersPopover from "./ParametersPopover.jsx";
import Popover from "metabase/components/Popover.jsx";

import { CardApi } from "metabase/services";
import MetabaseSettings from "metabase/lib/settings";
import { createCard } from "metabase/lib/card";

import cx from "classnames";

Expand Down Expand Up @@ -106,6 +108,15 @@ export default class DashboardHeader extends Component {
this.props.onEditingChange(true);
}

async onAddTextBox() {
const newTextCard = createCard("text");
newTextCard.display = "text";
const savedCard = await CardApi.create(newTextCard);
// we have to update the frontend's state of cards
await this.props.fetchCards();
this.props.addCardToDashboard({ dashId: this.props.dashboard.id, cardId: savedCard.id });
}

onDoneEditing() {
this.props.onEditingChange(false);
}
Expand Down Expand Up @@ -188,6 +199,32 @@ export default class DashboardHeader extends Component {
buttons.push(parametersWidget);
}

if (!isFullscreen && canEdit) {
buttons.push(
<ModalWithTrigger
full
key="add"
ref="addQuestionModal"
triggerElement={
<Tooltip tooltip="Add a question">
<span data-metabase-event="Dashboard;Add Card Modal" title="Add a question to this dashboard">
<Icon className={cx("text-brand-hover cursor-pointer", { "Icon--pulse": isEmpty })} name="add" size={16} />
</span>
</Tooltip>
}
>
<AddToDashSelectQuestionModal
dashboard={dashboard}
cards={this.props.cards}
fetchCards={this.props.fetchCards}
addCardToDashboard={this.props.addCardToDashboard}
onEditingChange={this.props.onEditingChange}
onClose={() => this.refs.addQuestionModal.toggle()}
/>
</ModalWithTrigger>
);
}

if (isEditing) {
// Parameters
buttons.push(
Expand All @@ -214,6 +251,15 @@ export default class DashboardHeader extends Component {
</span>
);

// Add text card button
buttons.push(
<Tooltip tooltip="Add a text box">
<a data-metabase-event="Dashboard;Add Text Box" key="add-text" title="Add a text box" className="text-brand-hover cursor-pointer" onClick={() => this.onAddTextBox()}>
<Icon name="string" size={16} />
</a>
</Tooltip>
);

buttons.push(
<ModalWithTrigger
key="history"
Expand Down Expand Up @@ -249,32 +295,6 @@ export default class DashboardHeader extends Component {
);
}

if (!isFullscreen && canEdit) {
buttons.push(
<ModalWithTrigger
full
key="add"
ref="addQuestionModal"
triggerElement={
<Tooltip tooltip="Add a question">
<span data-metabase-event="Dashboard;Add Card Modal" title="Add a question to this dashboard">
<Icon className={cx("text-brand-hover cursor-pointer", { "Icon--pulse": isEmpty })} name="add" size={16} />
</span>
</Tooltip>
}
>
<AddToDashSelectQuestionModal
dashboard={dashboard}
cards={this.props.cards}
fetchCards={this.props.fetchCards}
addCardToDashboard={this.props.addCardToDashboard}
onEditingChange={this.props.onEditingChange}
onClose={() => this.refs.addQuestionModal.toggle()}
/>
</ModalWithTrigger>
);
}

if (!isFullscreen && (
(isPublicLinksEnabled && (isAdmin || dashboard.public_uuid)) ||
(isEmbeddingEnabled && isAdmin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class GridItem extends Component {
let child = React.Children.only(this.props.children);
return (
<DraggableCore
cancel=".react-resizable-handle"
cancel=".react-resizable-handle, .drag-disabled"
onStart={this.onDragHandler("onDragStart")}
onDrag={this.onDragHandler("onDrag")}
onStop={this.onDragHandler("onDragStop")}
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/metabase/dashboard/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export const addCardToDashboard = function({ dashId, cardId }: { dashId: DashCar
visualization_settings: {}
};
dispatch(createAction(ADD_CARD_TO_DASH)(dashcard));
dispatch(fetchCardData(card, dashcard, { reload: true, clear: true }));
// text cards don't have any data to fetch
card.display !== 'text' && dispatch(fetchCardData(card, dashcard, { reload: true, clear: true }));
};
}

Expand Down Expand Up @@ -229,6 +230,8 @@ export const fetchDashboardCardData = createThunkAction(FETCH_DASHBOARD_CARD_DAT
const dashboard = getDashboardComplete(getState());
if (dashboard) {
for (const dashcard of dashboard.ordered_cards) {
// we don't need to fetch card data for text dash cards
if (dashcard.card.display === "text") { continue }
const cards = [dashcard.card].concat(dashcard.series || []);
for (const card of cards) {
dispatch(fetchCardData(card, dashcard, options));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export default class Visualization extends Component {
}

let error = this.props.error || this.state.error;
let loading = !(series && series.length > 0 && _.every(series, (s) => s.data));
let loading = !(series && series.length > 0 && _.every(series, (s) => s.data || s.card.display === "text"));
let noResults = false;

// don't try to load settings unless data is loaded
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/metabase/visualizations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Scalar from "./visualizations/Scalar.jsx";
import Progress from "./visualizations/Progress.jsx";
import Table from "./visualizations/Table.jsx";
import Text from "./visualizations/Text.jsx";
import LineChart from "./visualizations/LineChart.jsx";
import BarChart from "./visualizations/BarChart.jsx";
import RowChart from "./visualizations/RowChart.jsx";
Expand Down Expand Up @@ -114,6 +115,7 @@ const extractRemappedColumns = (data) => {
registerVisualization(Scalar);
registerVisualization(Progress);
registerVisualization(Table);
registerVisualization(Text);
registerVisualization(LineChart);
registerVisualization(AreaChart);
registerVisualization(BarChart);
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/metabase/visualizations/visualizations/Text.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
:local .Text {
display: flex;
flex-direction: column;
justify-content: center;
padding: 1em;
color: #525658;
}
:local .Text.dashboard-is-editing {
padding: 2.8em 1em 1em 1em;
}
:local .Text .text-card-textarea {
padding: .75em;
font-size: 1.12em;
font-family: monospace;
pointer-events: all;
resize: none;
outline: none;
}
:local .Text .text-card-markdown {
overflow: scroll;
pointer-events: all
}
:local .Text .text-card-markdown h1 {
font-size: 1.43em;
}
:local .Text .text-card-markdown h2 {
font-size: 1.27em;
}
:local .Text .text-card-markdown h3 {
font-size: 1em;
}
:local .Text .text-card-markdown p {
font-size: .89em;
}
139 changes: 139 additions & 0 deletions frontend/src/metabase/visualizations/visualizations/Text.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* @flow */

import React, { Component } from "react";
import ReactMarkdown from "react-markdown";
import styles from "./Text.css";

import Icon from "metabase/components/Icon.jsx";

import cx from "classnames";

const HEADER_ICON_SIZE = 16;

const HEADER_ACTION_STYLE = {
padding: 4
};

const DISALLOWED_TYPES = ['table', 'tableHead', 'tableBody', 'tableRow', 'tableCell'];

export default class Text extends Component {
constructor(props, context) {
super(props, context);

this.state = {
isShowingRenderedOutput: false,
text: ""
};
}

static uiName = "Text";
static identifier = "text";
static iconName = "text";

static disableSettingsConfig = true;
static noHeader = true;
static supportsSeries = false;

static minSize = { width: 4, height: 3 };

static checkRenderable() {
// text can always be rendered, nothing needed here
}

static settings = {
"text": {
value: "",
default: ""
}
}

handleTextChange(text) {
this.props.onUpdateVisualizationSettings({ "text": text });
}

onEdit() {
this.setState({ isShowingRenderedOutput: false });
}

onPreview() {
this.setState({ isShowingRenderedOutput: true });
}

render() {
let { className, actionButtons, gridSize, settings, isEditing } = this.props;
let isSmall = gridSize && gridSize.width < 4;

if (isEditing) {
return (
<div className={cx(className, styles.Text, styles[isSmall ? "small" : "large"], styles["dashboard-is-editing"])}>
<TextActionButtons
actionButtons={actionButtons}
isShowingRenderedOutput={this.state.isShowingRenderedOutput}
onEdit={this.onEdit.bind(this)}
onPreview={this.onPreview.bind(this)}
/>
{this.state.isShowingRenderedOutput ?
<ReactMarkdown
className={cx("full flex-full flex flex-column text-card-markdown", styles["text-card-markdown"])}
source={settings.text}
disallowedTypes={DISALLOWED_TYPES}
/>
:
<textarea
className={cx("full flex-full flex flex-column bg-grey-0 bordered drag-disabled", styles["text-card-textarea"])}
name="text"
placeholder="Write here"
value={settings.text}
onChange={(e) => this.handleTextChange(e.target.value)}
/>
}

</div>
);
} else {
return (
<div className={cx(className, styles.Text, styles[isSmall ? "small" : "large"])}>
<ReactMarkdown
className={cx("full flex-full flex flex-column text-card-markdown", styles["text-card-markdown"])}
source={settings.text}
disallowedTypes={DISALLOWED_TYPES}
/>
</div>
);
}
}
}

const TextActionButtons = ({ actionButtons, isShowingRenderedOutput, onEdit, onPreview }) =>
<div className="Card-title">
<div className="absolute top left p1 px2">
<span className="DashCard-actions-persistent flex align-center" style={{ lineHeight: 1 }}>
<a
data-metabase-event={"Dashboard;Text;edit"}
className={cx(" cursor-pointer h3 flex-no-shrink relative mr1", { "text-grey-2 text-grey-4-hover": isShowingRenderedOutput, "text-brand": !isShowingRenderedOutput })}
onClick={onEdit}
style={HEADER_ACTION_STYLE}
>
<span className="flex align-center">
<span className="flex">
<Icon name="editdocument" style={{ top: 0, left: 0 }} size={HEADER_ICON_SIZE} />
</span>
</span>
</a>

<a
data-metabase-event={"Dashboard;Text;preview"}
className={cx(" cursor-pointer h3 flex-no-shrink relative mr1", { "text-grey-2 text-grey-4-hover": !isShowingRenderedOutput, "text-brand": isShowingRenderedOutput })}
onClick={onPreview}
style={HEADER_ACTION_STYLE}
>
<span className="flex align-center">
<span className="flex">
<Icon name="eye" style={{ top: 0, left: 0 }} size={HEADER_ICON_SIZE} />
</span>
</span>
</a>
</span>
</div>
<div className="absolute top right p1 px2">{actionButtons}</div>
</div>
15 changes: 15 additions & 0 deletions frontend/test/visualizations/__support__/visualizations.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ export const TableCard = (name, ...overrides) =>
}
}, ...overrides);

export const TextCard = (name, ...overrides) =>
Card(name, {
card: {
display: "text",
visualization_settings: {
text: ""
}
},
data: {
cols: [],
columns: [],
rows: []
}
}, ...overrides);

export const LineCard = (name, ...overrides) =>
Card(name, {
card: {
Expand Down
Loading

0 comments on commit bb7a3e0

Please sign in to comment.