You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by cc...@apache.org on 2018/04/19 00:04:59 UTC
[incubator-superset] 04/05: [dashboard builder] static layout +
toasts (#4763)
This is an automated email from the ASF dual-hosted git repository.
ccwilliams pushed a commit to branch dashboard-builder
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit 0656955b9a52af8e527d492dcc7f4837ad33c151
Author: Chris Williams <wi...@users.noreply.github.com>
AuthorDate: Wed Apr 4 17:34:11 2018 -0700
[dashboard builder] static layout + toasts (#4763)
* [dashboard-builder] remove spacer component
* [dashboard-builder] better transparent indicator, better grid gutter logic, no dragging top-level tabs, headers are multiples of grid unit, fix row height granularity, update redux state key dashboard => dashboardLayout
* [dashboard-builder] don't blast column child dimensions on resize
* [dashboard-builder] ResizableContainer min size can't be smaller than size, fix row style, role=none on WithPopoverMenu container
* [edit mode] add edit mode to redux and propogate to all <DashboardComponent />s
* [toasts] add Toast component, ToastPresenter container and component, and toast redux actions + reducers
* [dashboard-builder] add info toast when dropResult overflows parent
---
.../v2/actions/{index.js => dashboardLayout.js} | 35 +++---
.../javascripts/dashboard/v2/actions/editMode.js | 9 ++
.../dashboard/v2/actions/messageToasts.js | 49 ++++++++
.../v2/components/BuilderComponentPane.jsx | 2 -
.../dashboard/v2/components/DashboardBuilder.jsx | 28 +++--
.../dashboard/v2/components/DashboardGrid.jsx | 10 +-
.../dashboard/v2/components/DashboardHeader.jsx | 14 +--
.../javascripts/dashboard/v2/components/Toast.jsx | 87 ++++++++++++++
.../dashboard/v2/components/ToastPresenter.jsx | 39 +++++++
.../dashboard/v2/components/dnd/DragDroppable.jsx | 4 +
.../dashboard/v2/components/dnd/handleDrop.js | 2 +-
.../v2/components/gridComponents/Chart.jsx | 17 +--
.../v2/components/gridComponents/Column.jsx | 75 +++++-------
.../v2/components/gridComponents/Divider.jsx | 10 +-
.../v2/components/gridComponents/Header.jsx | 13 ++-
.../dashboard/v2/components/gridComponents/Row.jsx | 69 +++++------
.../v2/components/gridComponents/Spacer.jsx | 106 -----------------
.../dashboard/v2/components/gridComponents/Tab.jsx | 18 ++-
.../v2/components/gridComponents/Tabs.jsx | 39 ++++---
.../v2/components/gridComponents/index.js | 4 -
.../gridComponents/new/DraggableNewComponent.jsx | 1 +
.../v2/components/gridComponents/new/NewSpacer.jsx | 24 ----
.../v2/components/menu/WithPopoverMenu.jsx | 40 ++++---
.../v2/components/resizable/ResizableContainer.jsx | 32 ++++--
.../dashboard/v2/containers/DashboardBuilder.jsx | 7 +-
.../dashboard/v2/containers/DashboardComponent.jsx | 17 +--
.../dashboard/v2/containers/DashboardGrid.jsx | 4 +-
.../dashboard/v2/containers/DashboardHeader.jsx | 16 ++-
.../dashboard/v2/containers/ToastPresenter.jsx | 10 ++
.../reducers/{dashboard.js => dashboardLayout.js} | 5 +-
.../javascripts/dashboard/v2/reducers/editMode.js | 11 ++
.../javascripts/dashboard/v2/reducers/index.js | 12 +-
.../dashboard/v2/reducers/messageToasts.js | 18 +++
.../dashboard/v2/stylesheets/builder.less | 14 ++-
.../v2/stylesheets/components/DashboardBuilder.jsx | 127 ---------------------
.../dashboard/v2/stylesheets/components/chart.less | 4 +-
.../v2/stylesheets/components/column.less | 30 +++--
.../v2/stylesheets/components/divider.less | 2 +-
.../v2/stylesheets/components/header.less | 28 ++++-
.../dashboard/v2/stylesheets/components/index.less | 1 -
.../v2/stylesheets/components/new-component.less | 12 --
.../dashboard/v2/stylesheets/components/row.less | 23 ++--
.../v2/stylesheets/components/spacer.less | 13 ---
.../javascripts/dashboard/v2/stylesheets/grid.less | 24 +---
.../dashboard/v2/stylesheets/index.less | 1 +
.../dashboard/v2/stylesheets/popover-menu.less | 4 +-
.../dashboard/v2/stylesheets/resizable.less | 16 +--
.../dashboard/v2/stylesheets/toast.less | 59 ++++++++++
.../dashboard/v2/stylesheets/variables.less | 8 ++
.../dashboard/v2/util/componentIsResizable.js | 2 -
.../dashboard/v2/util/componentTypes.js | 2 -
.../javascripts/dashboard/v2/util/constants.js | 8 +-
.../dashboard/v2/util/dropOverflowsParent.js | 24 ++++
.../javascripts/dashboard/v2/util/getChildWidth.js | 5 +-
.../dashboard/v2/util/getDropPosition.js | 2 +
.../javascripts/dashboard/v2/util/isValidChild.js | 11 +-
.../dashboard/v2/util/newComponentFactory.js | 6 +-
.../dashboard/v2/util/newComponentIdToType.js | 35 ------
.../javascripts/dashboard/v2/util/propShapes.jsx | 7 ++
superset/assets/src/components/EditableTitle.jsx | 16 ++-
superset/assets/src/dashboard/index.jsx | 5 +-
superset/assets/stylesheets/superset.less | 29 +++--
62 files changed, 715 insertions(+), 630 deletions(-)
diff --git a/superset/assets/javascripts/dashboard/v2/actions/index.js b/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
similarity index 79%
rename from superset/assets/javascripts/dashboard/v2/actions/index.js
rename to superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
index a6c7b77..b6d41c4 100644
--- a/superset/assets/javascripts/dashboard/v2/actions/index.js
+++ b/superset/assets/javascripts/dashboard/v2/actions/dashboardLayout.js
@@ -1,10 +1,8 @@
+import { addInfoToast } from './messageToasts';
+import { CHART_TYPE, MARKDOWN_TYPE, TABS_TYPE } from '../util/componentTypes';
import { DASHBOARD_ROOT_ID, NEW_COMPONENTS_SOURCE_ID } from '../util/constants';
+import dropOverflowsParent from '../util/dropOverflowsParent';
import findParentId from '../util/findParentId';
-import {
- CHART_TYPE,
- MARKDOWN_TYPE,
- TABS_TYPE,
-} from '../util/componentTypes';
// Component CRUD -------------------------------------------------------------
export const UPDATE_COMPONENTS = 'UPDATE_COMPONENTS';
@@ -61,8 +59,8 @@ export function deleteTopLevelTabs() {
export const RESIZE_COMPONENT = 'RESIZE_COMPONENT';
export function resizeComponent({ id, width, height }) {
return (dispatch, getState) => {
- const { dashboard: undoableDashboard } = getState();
- const { present: dashboard } = undoableDashboard;
+ const { dashboardLayout: undoableLayout } = getState();
+ const { present: dashboard } = undoableLayout;
const component = dashboard[id];
if (
@@ -88,8 +86,8 @@ export function resizeComponent({ id, width, height }) {
...child,
meta: {
...child.meta,
- width: width || component.meta.width,
- height: height || component.meta.height,
+ width: width || child.meta.width,
+ height: height || child.meta.height,
},
};
}
@@ -114,6 +112,15 @@ export function moveComponent(dropResult) {
export const HANDLE_COMPONENT_DROP = 'HANDLE_COMPONENT_DROP';
export function handleComponentDrop(dropResult) {
return (dispatch, getState) => {
+ const overflowsParent = dropOverflowsParent(dropResult, getState().dashboardLayout.present);
+
+ if (overflowsParent) {
+ return dispatch(addInfoToast(
+ `Parent does not have enough space for this component.
+ Try decreasing its width or add it to a new row.`,
+ ));
+ }
+
const { source, destination } = dropResult;
const droppedOnRoot = destination && destination.id === DASHBOARD_ROOT_ID;
const isNewComponent = source.id === NEW_COMPONENTS_SOURCE_ID;
@@ -133,14 +140,14 @@ export function handleComponentDrop(dropResult) {
dispatch(moveComponent(dropResult));
}
- // if we moved a tab and the parent tabs no longer has children, delete it.
+ // if we moved a Tab and the parent Tabs no longer has children, delete it.
if (!isNewComponent) {
- const { dashboard: undoableDashboard } = getState();
- const { present: dashboard } = undoableDashboard;
- const sourceComponent = dashboard[source.id];
+ const { dashboardLayout: undoableLayout } = getState();
+ const { present: layout } = undoableLayout;
+ const sourceComponent = layout[source.id];
if (sourceComponent.type === TABS_TYPE && sourceComponent.children.length === 0) {
- const parentId = findParentId({ childId: source.id, components: dashboard });
+ const parentId = findParentId({ childId: source.id, components: layout });
dispatch(deleteComponent(source.id, parentId));
}
}
diff --git a/superset/assets/javascripts/dashboard/v2/actions/editMode.js b/superset/assets/javascripts/dashboard/v2/actions/editMode.js
new file mode 100644
index 0000000..0a849ea
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/actions/editMode.js
@@ -0,0 +1,9 @@
+export const SET_EDIT_MODE = 'SET_EDIT_MODE';
+export function setEditMode(editMode) {
+ return {
+ type: SET_EDIT_MODE,
+ payload: {
+ editMode,
+ },
+ };
+}
diff --git a/superset/assets/javascripts/dashboard/v2/actions/messageToasts.js b/superset/assets/javascripts/dashboard/v2/actions/messageToasts.js
new file mode 100644
index 0000000..af10ead
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/actions/messageToasts.js
@@ -0,0 +1,49 @@
+import { INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from '../util/constants';
+
+function getToastUuid(type) {
+ return `${Math.random().toString(16).slice(2)}-${type}-${Math.random().toString(16).slice(2)}`;
+}
+
+export const ADD_TOAST = 'ADD_TOAST';
+export function addToast({ toastType, text }) {
+ debugger;
+ return {
+ type: ADD_TOAST,
+ payload: {
+ id: getToastUuid(toastType),
+ toastType,
+ text,
+ },
+ };
+}
+
+export const REMOVE_TOAST = 'REMOVE_TOAST';
+export function removeToast(id) {
+ return {
+ type: REMOVE_TOAST,
+ payload: {
+ id,
+ },
+ };
+}
+
+// Different types of toasts
+export const ADD_INFO_TOAST = 'ADD_INFO_TOAST';
+export function addInfoToast(text) {
+ return dispatch => dispatch(addToast({ text, toastType: INFO_TOAST }));
+}
+
+export const ADD_SUCCESS_TOAST = 'ADD_SUCCESS_TOAST';
+export function addSuccessToast(text) {
+ return dispatch => dispatch(addToast({ text, toastType: SUCCESS_TOAST }));
+}
+
+export const ADD_WARNING_TOAST = 'ADD_WARNING_TOAST';
+export function addWarningToast(text) {
+ return dispatch => dispatch(addToast({ text, toastType: WARNING_TOAST }));
+}
+
+export const ADD_DANGER_TOAST = 'ADD_DANGER_TOAST';
+export function addDangerToast(text) {
+ return dispatch => dispatch(addToast({ text, toastType: DANGER_TOAST }));
+}
diff --git a/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx b/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
index 86f3788..efef5a5 100644
--- a/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/BuilderComponentPane.jsx
@@ -6,7 +6,6 @@ import NewColumn from './gridComponents/new/NewColumn';
import NewDivider from './gridComponents/new/NewDivider';
import NewHeader from './gridComponents/new/NewHeader';
import NewRow from './gridComponents/new/NewRow';
-import NewSpacer from './gridComponents/new/NewSpacer';
import NewTabs from './gridComponents/new/NewTabs';
const propTypes = {
@@ -24,7 +23,6 @@ class BuilderComponentPane extends React.PureComponent {
<NewHeader />
<NewDivider />
- <NewSpacer />
<NewTabs />
<NewRow />
diff --git a/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx b/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx
index f371718..8e2d985 100644
--- a/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/DashboardBuilder.jsx
@@ -1,3 +1,4 @@
+import cx from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import HTML5Backend from 'react-dnd-html5-backend';
@@ -9,6 +10,7 @@ import DashboardGrid from '../containers/DashboardGrid';
import IconButton from './IconButton';
import DragDroppable from './dnd/DragDroppable';
import DashboardComponent from '../containers/DashboardComponent';
+import ToastPresenter from '../containers/ToastPresenter';
import WithPopoverMenu from './menu/WithPopoverMenu';
import {
@@ -18,11 +20,10 @@ import {
} from '../util/constants';
const propTypes = {
- editMode: PropTypes.bool,
-
// redux
- dashboard: PropTypes.object.isRequired,
+ dashboardLayout: PropTypes.object.isRequired,
deleteTopLevelTabs: PropTypes.func.isRequired,
+ editMode: PropTypes.bool.isRequired,
handleComponentDrop: PropTypes.func.isRequired,
};
@@ -52,20 +53,20 @@ class DashboardBuilder extends React.Component {
render() {
const { tabIndex } = this.state;
- const { handleComponentDrop, dashboard, deleteTopLevelTabs } = this.props;
- const dashboardRoot = dashboard[DASHBOARD_ROOT_ID];
+ const { handleComponentDrop, dashboardLayout, deleteTopLevelTabs, editMode } = this.props;
+ const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
const rootChildId = dashboardRoot.children[0];
- const topLevelTabs = rootChildId !== DASHBOARD_GRID_ID && dashboard[rootChildId];
+ const topLevelTabs = rootChildId !== DASHBOARD_GRID_ID && dashboardLayout[rootChildId];
const gridComponentId = topLevelTabs
? topLevelTabs.children[Math.min(topLevelTabs.children.length - 1, tabIndex)]
: DASHBOARD_GRID_ID;
- const gridComponent = dashboard[gridComponentId];
+ const gridComponent = dashboardLayout[gridComponentId];
return (
- <div className="dashboard-v2">
- {topLevelTabs ? ( // you cannot drop on/displace tabs if they already exist
+ <div className={cx('dashboard-v2', editMode && 'dashboard-v2--editing')}>
+ {topLevelTabs || !editMode ? ( // you cannot drop on/displace tabs if they already exist
<DashboardHeader />
) : (
<DragDroppable
@@ -74,7 +75,8 @@ class DashboardBuilder extends React.Component {
depth={DASHBOARD_ROOT_DEPTH}
index={0}
orientation="column"
- onDrop={topLevelTabs ? null : handleComponentDrop}
+ onDrop={handleComponentDrop}
+ editMode
>
{({ dropIndicatorProps }) => (
<div>
@@ -94,6 +96,7 @@ class DashboardBuilder extends React.Component {
onClick={deleteTopLevelTabs}
/>,
]}
+ editMode={editMode}
>
<DashboardComponent
id={topLevelTabs.id}
@@ -105,13 +108,14 @@ class DashboardBuilder extends React.Component {
/>
</WithPopoverMenu>}
- <div className="dashboard-builder">
+ <div className="dashboard-content">
<DashboardGrid
gridComponent={gridComponent}
depth={DASHBOARD_ROOT_DEPTH + 1}
/>
- <BuilderComponentPane />
+ {editMode && <BuilderComponentPane />}
</div>
+ <ToastPresenter />
</div>
);
}
diff --git a/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx b/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
index cfe99c7..9f4cb93 100644
--- a/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/DashboardGrid.jsx
@@ -13,6 +13,7 @@ import {
const propTypes = {
depth: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
gridComponent: componentShape.isRequired,
handleComponentDrop: PropTypes.func.isRequired,
resizeComponent: PropTypes.func.isRequired,
@@ -70,7 +71,7 @@ class DashboardGrid extends React.PureComponent {
}
render() {
- const { gridComponent, handleComponentDrop, depth } = this.props;
+ const { gridComponent, handleComponentDrop, depth, editMode } = this.props;
const { isResizing, rowGuideTop } = this.state;
return (
@@ -99,18 +100,19 @@ class DashboardGrid extends React.PureComponent {
))}
{/* render an empty drop target */}
- {gridComponent.children.length === 0 &&
+ {editMode &&
<DragDroppable
component={gridComponent}
depth={depth}
parentComponent={null}
- index={0}
+ index={gridComponent.children.length}
orientation="column"
onDrop={handleComponentDrop}
className="empty-grid-droptarget"
+ editMode
>
{({ dropIndicatorProps }) => dropIndicatorProps &&
- <div {...dropIndicatorProps} />}
+ <div className="drop-indicator drop-indicator--top" />}
</DragDroppable>}
{isResizing && Array(GRID_COLUMN_COUNT).fill(null).map((_, i) => (
diff --git a/superset/assets/javascripts/dashboard/v2/components/DashboardHeader.jsx b/superset/assets/javascripts/dashboard/v2/components/DashboardHeader.jsx
index e0d14c4..ca204e5 100644
--- a/superset/assets/javascripts/dashboard/v2/components/DashboardHeader.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/DashboardHeader.jsx
@@ -7,8 +7,7 @@ import { componentShape } from '../util/propShapes';
import EditableTitle from '../../../components/EditableTitle';
const propTypes = {
- // editMode: PropTypes.bool.isRequired,
- // setEditMode: PropTypes.func.isRequired,
+ editMode: PropTypes.bool.isRequired,
component: componentShape.isRequired,
// redux
@@ -17,6 +16,7 @@ const propTypes = {
onRedo: PropTypes.func.isRequired,
canUndo: PropTypes.bool.isRequired,
canRedo: PropTypes.bool.isRequired,
+ setEditMode: PropTypes.func.isRequired,
};
class DashboardHeader extends React.Component {
@@ -27,8 +27,7 @@ class DashboardHeader extends React.Component {
}
toggleEditMode() {
- console.log('@TODO toggleEditMode');
- // this.props.setEditMode(!this.props.editMode);
+ this.props.setEditMode(!this.props.editMode);
}
handleChangeText(nextText) {
@@ -47,19 +46,18 @@ class DashboardHeader extends React.Component {
}
render() {
- const { component, onUndo, onRedo, canUndo, canRedo } = this.props;
- const editMode = true;
+ const { component, onUndo, onRedo, canUndo, canRedo, editMode } = this.props;
return (
<div className="dashboard-header">
- <h1>
+ <div className="dashboard-component-header header-large">
<EditableTitle
title={component.meta.text}
onSaveTitle={this.handleChangeText}
showTooltip={false}
canEdit={editMode}
/>
- </h1>
+ </div>
<ButtonToolbar>
<ButtonGroup>
<Button
diff --git a/superset/assets/javascripts/dashboard/v2/components/Toast.jsx b/superset/assets/javascripts/dashboard/v2/components/Toast.jsx
new file mode 100644
index 0000000..537388d
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/components/Toast.jsx
@@ -0,0 +1,87 @@
+import { Alert } from 'react-bootstrap';
+import cx from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import { toastShape } from '../util/propShapes';
+import { INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from '../util/constants';
+
+const propTypes = {
+ toast: toastShape.isRequired,
+ onCloseToast: PropTypes.func.isRequired,
+ delay: PropTypes.number,
+ duration: PropTypes.number, // if duration is >0, the toast will close on its own
+};
+
+const defaultProps = {
+ delay: 0,
+ duration: 0,
+};
+
+class Toast extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ };
+
+ this.showToast = this.showToast.bind(this);
+ this.handleClosePress = this.handleClosePress.bind(this);
+ }
+
+ componentDidMount() {
+ const { delay, duration } = this.props;
+
+ setTimeout(this.showToast, delay);
+
+ if (duration > 0) {
+ this.hideTimer = setTimeout(this.handleClosePress, delay + duration);
+ }
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.hideTimer);
+ }
+
+ showToast() {
+ this.setState({ visible: true });
+ }
+
+ handleClosePress() {
+ clearTimeout(this.hideTimer);
+
+ this.setState({ visible: false }, () => {
+ // Wait for the transition
+ setTimeout(() => {
+ this.props.onCloseToast(this.props.toast.id);
+ }, 150);
+ });
+ }
+
+ render() {
+ const { visible } = this.state;
+ const { toast: { toastType, text } } = this.props;
+
+ return (
+ <Alert
+ onDismiss={this.handleClosePress}
+ bsClass={cx(
+ 'alert',
+ 'toast',
+ visible && 'toast--visible',
+ toastType === INFO_TOAST && 'toast--info',
+ toastType === SUCCESS_TOAST && 'toast--success',
+ toastType === WARNING_TOAST && 'toast--warning',
+ toastType === DANGER_TOAST && 'toast--danger',
+ )}
+ >
+ {text}
+ </Alert>
+ );
+ }
+}
+
+Toast.propTypes = propTypes;
+Toast.defaultProps = defaultProps;
+
+export default Toast;
diff --git a/superset/assets/javascripts/dashboard/v2/components/ToastPresenter.jsx b/superset/assets/javascripts/dashboard/v2/components/ToastPresenter.jsx
new file mode 100644
index 0000000..95a0251
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/components/ToastPresenter.jsx
@@ -0,0 +1,39 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Toast from './Toast';
+import { toastShape } from '../util/propShapes';
+
+const propTypes = {
+ toasts: PropTypes.arrayOf(toastShape),
+ removeToast: PropTypes.func.isRequired,
+};
+
+const defaultProps = {
+ toasts: [],
+};
+
+// eslint-disable-next-line react/prefer-stateless-function
+class ToastPresenter extends React.Component {
+ render() {
+ const { toasts, removeToast } = this.props;
+
+ return (
+ toasts.length > 0 &&
+ <div className="toast-presenter">
+ {toasts.map(toast => (
+ <Toast
+ key={toast.id}
+ toast={toast}
+ onCloseToast={removeToast}
+ />
+ ))}
+ </div>
+ );
+ }
+}
+
+ToastPresenter.propTypes = propTypes;
+ToastPresenter.defaultProps = defaultProps;
+
+export default ToastPresenter;
diff --git a/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx b/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
index 89664e5..775e092 100644
--- a/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/dnd/DragDroppable.jsx
@@ -18,6 +18,7 @@ const propTypes = {
index: PropTypes.number.isRequired,
style: PropTypes.object,
onDrop: PropTypes.func,
+ editMode: PropTypes.bool.isRequired,
// from react-dnd
isDragging: PropTypes.bool.isRequired,
@@ -70,8 +71,11 @@ class DragDroppable extends React.Component {
isDragging,
isDraggingOver,
style,
+ editMode,
} = this.props;
+ if (!editMode) return children({});
+
const { dropIndicator } = this.state;
return (
diff --git a/superset/assets/javascripts/dashboard/v2/components/dnd/handleDrop.js b/superset/assets/javascripts/dashboard/v2/components/dnd/handleDrop.js
index 2207ca6..f27b604 100644
--- a/superset/assets/javascripts/dashboard/v2/components/dnd/handleDrop.js
+++ b/superset/assets/javascripts/dashboard/v2/components/dnd/handleDrop.js
@@ -2,7 +2,7 @@ import getDropPosition, { DROP_TOP, DROP_RIGHT, DROP_BOTTOM, DROP_LEFT } from '.
export default function handleDrop(props, monitor, Component) {
// this may happen due to throttling
- if (!Component.mounted || !Component.props.onDrop) return undefined;
+ if (!Component.mounted) return undefined;
Component.setState(() => ({ dropIndicator: null }));
const dropPosition = getDropPosition(monitor, Component);
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx
index 7ca506d..668d268 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Chart.jsx
@@ -9,10 +9,7 @@ import ResizableContainer from '../resizable/ResizableContainer';
import WithPopoverMenu from '../menu/WithPopoverMenu';
import { componentShape } from '../../util/propShapes';
import { ROW_TYPE } from '../../util/componentTypes';
-import {
- GRID_MIN_COLUMN_COUNT,
- GRID_MIN_ROW_UNITS,
-} from '../../util/constants';
+import { GRID_MIN_COLUMN_COUNT, GRID_MIN_ROW_UNITS } from '../../util/constants';
const propTypes = {
id: PropTypes.string.isRequired,
@@ -21,6 +18,7 @@ const propTypes = {
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
// grid related
availableColumnCount: PropTypes.number.isRequired,
@@ -71,6 +69,7 @@ class Chart extends React.Component {
onResize,
onResizeStop,
handleComponentDrop,
+ editMode,
} = this.props;
return (
@@ -82,6 +81,7 @@ class Chart extends React.Component {
depth={depth}
onDrop={handleComponentDrop}
disableDragDrop={isFocused}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<ResizableContainer
@@ -97,16 +97,19 @@ class Chart extends React.Component {
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
+ editMode={editMode}
>
- <HoverMenu innerRef={dragSourceRef} position="top">
- <DragHandle position="top" />
- </HoverMenu>
+ {editMode &&
+ <HoverMenu innerRef={dragSourceRef} position="top">
+ <DragHandle position="top" />
+ </HoverMenu>}
<WithPopoverMenu
onChangeFocus={this.handleChangeFocus}
menuItems={[
<DeleteComponentButton onDelete={this.handleDeleteComponent} />,
]}
+ editMode={editMode}
>
<div className="dashboard-component dashboard-component-chart">
<div className="fa fa-area-chart" />
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx
index d51870d..fe5a721 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Column.jsx
@@ -15,12 +15,7 @@ import WithPopoverMenu from '../menu/WithPopoverMenu';
import backgroundStyleOptions from '../../util/backgroundStyleOptions';
import { componentShape } from '../../util/propShapes';
-import {
- BACKGROUND_TRANSPARENT,
- GRID_GUTTER_SIZE,
-} from '../../util/constants';
-
-const GUTTER = 'GUTTER';
+import { BACKGROUND_TRANSPARENT } from '../../util/constants';
const propTypes = {
id: PropTypes.string.isRequired,
@@ -29,6 +24,7 @@ const propTypes = {
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
// grid related
availableColumnCount: PropTypes.number.isRequired,
@@ -95,23 +91,14 @@ class Column extends React.PureComponent {
onResize,
onResizeStop,
handleComponentDrop,
+ editMode,
} = this.props;
- const columnItems = [];
-
- (columnComponent.children || []).forEach((id, childIndex) => {
- columnItems.push(id);
- if (childIndex < columnComponent.children.length - 1) {
- columnItems.push(GUTTER);
- }
- });
-
+ const columnItems = columnComponent.children || [];
const backgroundStyle = backgroundStyleOptions.find(
opt => opt.value === (columnComponent.meta.background || BACKGROUND_TRANSPARENT),
);
- console.log('occupied/avail cols', columnComponent.meta.width, '/', availableColumnCount, 'min width', minColumnWidth)
-
return (
<DragDroppable
component={columnComponent}
@@ -120,6 +107,7 @@ class Column extends React.PureComponent {
index={index}
depth={depth}
onDrop={handleComponentDrop}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<ResizableContainer
@@ -133,6 +121,7 @@ class Column extends React.PureComponent {
onResizeStart={onResizeStart}
onResize={onResize}
onResizeStop={onResizeStop}
+ editMode={editMode}
>
<WithPopoverMenu
isFocused={this.state.isFocused}
@@ -145,6 +134,7 @@ class Column extends React.PureComponent {
onChange={this.handleChangeBackground}
/>,
]}
+ editMode={editMode}
>
<div
className={cx(
@@ -153,35 +143,30 @@ class Column extends React.PureComponent {
backgroundStyle.className,
)}
>
- <HoverMenu innerRef={dragSourceRef} position="top">
- <DragHandle position="top" />
- <DeleteComponentButton onDelete={this.handleDeleteComponent} />
- <IconButton
- onClick={this.handleChangeFocus}
- className="fa fa-cog"
- />
- </HoverMenu>
-
- {columnItems.map((componentId, itemIndex) => {
- if (componentId === GUTTER) {
- return <div key={`gutter-${itemIndex}`} style={{ height: GRID_GUTTER_SIZE }} />;
- }
-
- return (
- <DashboardComponent
- key={componentId}
- id={componentId}
- parentId={columnComponent.id}
- depth={depth + 1}
- index={itemIndex / 2} // account for gutters!
- availableColumnCount={columnComponent.meta.width}
- columnWidth={columnWidth}
- onResizeStart={onResizeStart}
- onResize={onResize}
- onResizeStop={onResizeStop}
+ {editMode &&
+ <HoverMenu innerRef={dragSourceRef} position="top">
+ <DragHandle position="top" />
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+ <IconButton
+ onClick={this.handleChangeFocus}
+ className="fa fa-cog"
/>
- );
- })}
+ </HoverMenu>}
+
+ {columnItems.map((componentId, itemIndex) => (
+ <DashboardComponent
+ key={componentId}
+ id={componentId}
+ parentId={columnComponent.id}
+ depth={depth + 1}
+ index={itemIndex}
+ availableColumnCount={columnComponent.meta.width}
+ columnWidth={columnWidth}
+ onResizeStart={onResizeStart}
+ onResize={onResize}
+ onResizeStop={onResizeStop}
+ />
+ ))}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</div>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Divider.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Divider.jsx
index ff29c3f..b3010e9 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Divider.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Divider.jsx
@@ -13,6 +13,7 @@ const propTypes = {
depth: PropTypes.number.isRequired,
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
handleComponentDrop: PropTypes.func.isRequired,
deleteComponent: PropTypes.func.isRequired,
};
@@ -35,6 +36,7 @@ class Divider extends React.PureComponent {
parentComponent,
index,
handleComponentDrop,
+ editMode,
} = this.props;
return (
@@ -45,12 +47,14 @@ class Divider extends React.PureComponent {
index={index}
depth={depth}
onDrop={handleComponentDrop}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<div ref={dragSourceRef}>
- <HoverMenu position="left">
- <DeleteComponentButton onDelete={this.handleDeleteComponent} />
- </HoverMenu>
+ {editMode &&
+ <HoverMenu position="left">
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+ </HoverMenu>}
<div className="dashboard-component dashboard-component-divider" />
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Header.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Header.jsx
index d8744d6..97945a9 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Header.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Header.jsx
@@ -22,6 +22,7 @@ const propTypes = {
depth: PropTypes.number.isRequired,
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
// redux
handleComponentDrop: PropTypes.func.isRequired,
@@ -79,6 +80,7 @@ class Header extends React.PureComponent {
parentComponent,
index,
handleComponentDrop,
+ editMode,
} = this.props;
const headerStyle = headerStyleOptions.find(
@@ -98,12 +100,14 @@ class Header extends React.PureComponent {
depth={depth}
onDrop={handleComponentDrop}
disableDragDrop={isFocused}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<div ref={dragSourceRef}>
- <HoverMenu position="left">
- <DragHandle position="left" />
- </HoverMenu>
+ {editMode &&
+ <HoverMenu position="left">
+ <DragHandle position="left" />
+ </HoverMenu>}
<WithPopoverMenu
onChangeFocus={this.handleChangeFocus}
@@ -122,6 +126,7 @@ class Header extends React.PureComponent {
/>,
<DeleteComponentButton onDelete={this.handleDeleteComponent} />,
]}
+ editMode={editMode}
>
<div
className={cx(
@@ -133,7 +138,7 @@ class Header extends React.PureComponent {
>
<EditableTitle
title={component.meta.text}
- canEdit={isFocused}
+ canEdit={editMode}
onSaveTitle={this.handleChangeText}
showTooltip={false}
/>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx
index a60524f..9866bc8 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Row.jsx
@@ -13,9 +13,7 @@ import WithPopoverMenu from '../menu/WithPopoverMenu';
import { componentShape } from '../../util/propShapes';
import backgroundStyleOptions from '../../util/backgroundStyleOptions';
-import { GRID_GUTTER_SIZE, BACKGROUND_TRANSPARENT } from '../../util/constants';
-
-const GUTTER = 'GUTTER';
+import { BACKGROUND_TRANSPARENT } from '../../util/constants';
const propTypes = {
id: PropTypes.string.isRequired,
@@ -24,6 +22,7 @@ const propTypes = {
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
// grid related
availableColumnCount: PropTypes.number.isRequired,
@@ -92,17 +91,10 @@ class Row extends React.PureComponent {
onResize,
onResizeStop,
handleComponentDrop,
+ editMode,
} = this.props;
- const rowItems = [];
-
- // this adds a gutter between each child in the row.
- (rowComponent.children || []).forEach((id, childIndex) => {
- rowItems.push(id);
- if (childIndex < rowComponent.children.length - 1) {
- rowItems.push(GUTTER);
- }
- });
+ const rowItems = rowComponent.children || [];
const backgroundStyle = backgroundStyleOptions.find(
opt => opt.value === (rowComponent.meta.background || BACKGROUND_TRANSPARENT),
@@ -116,6 +108,7 @@ class Row extends React.PureComponent {
index={index}
depth={depth}
onDrop={handleComponentDrop}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<WithPopoverMenu
@@ -129,6 +122,7 @@ class Row extends React.PureComponent {
onChange={this.handleChangeBackground}
/>,
]}
+ editMode={editMode}
>
<div
className={cx(
@@ -137,35 +131,30 @@ class Row extends React.PureComponent {
backgroundStyle.className,
)}
>
- <HoverMenu innerRef={dragSourceRef} position="left">
- <DragHandle position="left" />
- <DeleteComponentButton onDelete={this.handleDeleteComponent} />
- <IconButton
- onClick={this.handleChangeFocus}
- className="fa fa-cog"
- />
- </HoverMenu>
-
- {rowItems.map((componentId, itemIndex) => {
- if (componentId === GUTTER) {
- return <div key={`gutter-${itemIndex}`} style={{ width: GRID_GUTTER_SIZE }} />;
- }
-
- return (
- <DashboardComponent
- key={componentId}
- id={componentId}
- parentId={rowComponent.id}
- depth={depth + 1}
- index={itemIndex / 2} // account for gutters!
- availableColumnCount={availableColumnCount - occupiedColumnCount}
- columnWidth={columnWidth}
- onResizeStart={onResizeStart}
- onResize={onResize}
- onResizeStop={onResizeStop}
+ {editMode &&
+ <HoverMenu innerRef={dragSourceRef} position="left">
+ <DragHandle position="left" />
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+ <IconButton
+ onClick={this.handleChangeFocus}
+ className="fa fa-cog"
/>
- );
- })}
+ </HoverMenu>}
+
+ {rowItems.map((componentId, itemIndex) => (
+ <DashboardComponent
+ key={componentId}
+ id={componentId}
+ parentId={rowComponent.id}
+ depth={depth + 1}
+ index={itemIndex}
+ availableColumnCount={availableColumnCount - occupiedColumnCount}
+ columnWidth={columnWidth}
+ onResizeStart={onResizeStart}
+ onResize={onResize}
+ onResizeStop={onResizeStop}
+ />
+ ))}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
</div>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Spacer.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Spacer.jsx
deleted file mode 100644
index 7a287d8..0000000
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Spacer.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import DeleteComponentButton from '../DeleteComponentButton';
-import DragDroppable from '../dnd/DragDroppable';
-import HoverMenu from '../menu/HoverMenu';
-import ResizableContainer from '../resizable/ResizableContainer';
-import { componentShape } from '../../util/propShapes';
-
-const propTypes = {
- id: PropTypes.string.isRequired,
- parentId: PropTypes.string.isRequired,
- component: componentShape.isRequired,
- parentComponent: componentShape.isRequired,
- index: PropTypes.number.isRequired,
- depth: PropTypes.number.isRequired,
-
- // grid related
- availableColumnCount: PropTypes.number.isRequired,
- columnWidth: PropTypes.number.isRequired,
- onResizeStart: PropTypes.func.isRequired,
- onResize: PropTypes.func.isRequired,
- onResizeStop: PropTypes.func.isRequired,
-
- // dnd
- deleteComponent: PropTypes.func.isRequired,
- handleComponentDrop: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
-};
-
-class Spacer extends React.PureComponent {
- constructor(props) {
- super(props);
- this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
- }
-
- handleDeleteComponent() {
- const { deleteComponent, id, parentId } = this.props;
- deleteComponent(id, parentId);
- }
-
- render() {
- const {
- component,
- parentComponent,
- index,
- depth,
- availableColumnCount,
- columnWidth,
- onResizeStart,
- onResize,
- onResizeStop,
- handleComponentDrop,
- } = this.props;
-
- const orientation = depth % 2 === 0 ? 'row' : 'column';
- const hoverMenuPosition = orientation === 'row' ? 'left' : 'top';
- const adjustableWidth = orientation === 'column';
- const adjustableHeight = orientation === 'row';
-
- console.log('spacer', availableColumnCount)
-
- return (
- <DragDroppable
- component={component}
- parentComponent={parentComponent}
- orientation={orientation}
- index={index}
- depth={depth}
- onDrop={handleComponentDrop}
- >
- {({ dropIndicatorProps, dragSourceRef }) => (
- <ResizableContainer
- id={component.id}
- adjustableWidth={adjustableWidth}
- adjustableHeight={adjustableHeight}
- widthStep={columnWidth}
- widthMultiple={component.meta.width || 1}
- heightMultiple={adjustableHeight ? component.meta.height || 1 : undefined}
- minWidthMultiple={1}
- minHeightMultiple={1}
- maxWidthMultiple={availableColumnCount + (component.meta.width || 0)}
- onResizeStart={onResizeStart}
- onResize={onResize}
- onResizeStop={onResizeStop}
- >
- <HoverMenu position={hoverMenuPosition}>
- <DeleteComponentButton onDelete={this.handleDeleteComponent} />
- </HoverMenu>
-
- <div ref={dragSourceRef} className="grid-spacer" />
-
- {dropIndicatorProps && <div {...dropIndicatorProps} />}
- </ResizableContainer>
- )}
- </DragDroppable>
- );
- }
-}
-
-Spacer.propTypes = propTypes;
-Spacer.defaultProps = defaultProps;
-
-export default Spacer;
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tab.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tab.jsx
index 9548a4b..218c4e7 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tab.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tab.jsx
@@ -7,6 +7,7 @@ import EditableTitle from '../../../../components/EditableTitle';
import DeleteComponentButton from '../DeleteComponentButton';
import WithPopoverMenu from '../menu/WithPopoverMenu';
import { componentShape } from '../../util/propShapes';
+import { DASHBOARD_ROOT_DEPTH } from '../../util/constants';
export const RENDER_TAB = 'RENDER_TAB';
export const RENDER_TAB_CONTENT = 'RENDER_TAB_CONTENT';
@@ -21,6 +22,7 @@ const propTypes = {
renderType: PropTypes.oneOf([RENDER_TAB, RENDER_TAB_CONTENT]).isRequired,
onDropOnTab: PropTypes.func,
onDeleteTab: PropTypes.func,
+ editMode: PropTypes.bool.isRequired,
// grid related
availableColumnCount: PropTypes.number,
@@ -77,9 +79,9 @@ export default class Tab extends React.PureComponent {
}
handleDeleteComponent() {
- const { onDeleteTab, index, deleteComponent, id, parentId } = this.props;
- deleteComponent(id, parentId);
- onDeleteTab(index);
+ const { index, id, parentId } = this.props;
+ this.props.deleteComponent(id, parentId);
+ this.props.onDeleteTab(index);
}
handleDrop(dropResult) {
@@ -126,6 +128,7 @@ export default class Tab extends React.PureComponent {
parentComponent,
index,
depth,
+ editMode,
} = this.props;
return (
@@ -136,7 +139,11 @@ export default class Tab extends React.PureComponent {
index={index}
depth={depth}
onDrop={this.handleDrop}
- disableDragDrop={isFocused}
+ // disable drag drop of top-level Tab's to prevent invalid nesting of a child in
+ // itself, e.g. if a top-level Tab has a Tabs child, dragging the Tab into the Tabs would
+ // reusult in circular children
+ disableDragDrop={isFocused || depth === DASHBOARD_ROOT_DEPTH + 1}
+ editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (
<div className="dragdroppable-tab" ref={dragSourceRef}>
@@ -145,10 +152,11 @@ export default class Tab extends React.PureComponent {
menuItems={parentComponent.children.length <= 1 ? [] : [
<DeleteComponentButton onDelete={this.handleDeleteComponent} />,
]}
+ editMode={editMode}
>
<EditableTitle
title={component.meta.text}
- canEdit={isFocused}
+ canEdit={editMode && isFocused}
onSaveTitle={this.handleChangeText}
showTooltip={false}
/>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tabs.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tabs.jsx
index cc5f637..1f5f0c6 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tabs.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/Tabs.jsx
@@ -22,7 +22,8 @@ const propTypes = {
parentComponent: componentShape.isRequired,
index: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
- renderTabContent: PropTypes.bool,
+ renderTabContent: PropTypes.bool, // whether to render tabs + content or just tabs
+ editMode: PropTypes.bool.isRequired,
// grid related
availableColumnCount: PropTypes.number,
@@ -40,11 +41,11 @@ const propTypes = {
};
const defaultProps = {
- onChangeTab: null,
children: null,
renderTabContent: true,
availableColumnCount: 0,
columnWidth: 0,
+ onChangeTab() {},
onResizeStart() {},
onResize() {},
onResizeStop() {},
@@ -70,14 +71,9 @@ class Tabs extends React.PureComponent {
}
handleClickTab(tabIndex) {
- const { onChangeTab, component, createComponent } = this.props;
+ const { component, createComponent } = this.props;
- if (tabIndex !== NEW_TAB_INDEX && tabIndex !== this.state.tabIndex) {
- this.setState(() => ({ tabIndex }));
- if (onChangeTab) {
- onChangeTab({ tabIndex, tabId: component.children[tabIndex] });
- }
- } else if (tabIndex === NEW_TAB_INDEX) {
+ if (tabIndex === NEW_TAB_INDEX) {
createComponent({
destination: {
id: component.id,
@@ -89,6 +85,9 @@ class Tabs extends React.PureComponent {
type: TAB_TYPE,
},
});
+ } else if (tabIndex !== this.state.tabIndex) {
+ this.setState(() => ({ tabIndex }));
+ this.props.onChangeTab({ tabIndex, tabId: component.children[tabIndex] });
}
}
@@ -132,6 +131,7 @@ class Tabs extends React.PureComponent {
onResizeStop,
handleComponentDrop,
renderTabContent,
+ editMode,
} = this.props;
const { tabIndex: selectedTabIndex } = this.state;
@@ -145,13 +145,15 @@ class Tabs extends React.PureComponent {
index={index}
depth={depth}
onDrop={handleComponentDrop}
+ editMode={editMode}
>
{({ dropIndicatorProps: tabsDropIndicatorProps, dragSourceRef: tabsDragSourceRef }) => (
<div className="dashboard-component dashboard-component-tabs">
- <HoverMenu innerRef={tabsDragSourceRef} position="left">
- <DragHandle position="left" />
- <DeleteComponentButton onDelete={this.handleDeleteComponent} />
- </HoverMenu>
+ {editMode &&
+ <HoverMenu innerRef={tabsDragSourceRef} position="left">
+ <DragHandle position="left" />
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />
+ </HoverMenu>}
<BootstrapTabs
id={tabsComponent.id}
@@ -202,11 +204,12 @@ class Tabs extends React.PureComponent {
</BootstrapTab>
))}
- {tabIds.length < MAX_TAB_COUNT &&
- <BootstrapTab
- eventKey={NEW_TAB_INDEX}
- title={<div className="fa fa-plus" />}
- />}
+ {editMode &&
+ tabIds.length < MAX_TAB_COUNT &&
+ <BootstrapTab
+ eventKey={NEW_TAB_INDEX}
+ title={<div className="fa fa-plus" />}
+ />}
</BootstrapTabs>
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/index.js b/superset/assets/javascripts/dashboard/v2/components/gridComponents/index.js
index 3a3fad5..96c9a19 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/index.js
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/index.js
@@ -5,7 +5,6 @@ import {
HEADER_TYPE,
INVISIBLE_ROW_TYPE,
ROW_TYPE,
- SPACER_TYPE,
TAB_TYPE,
TABS_TYPE,
} from '../../util/componentTypes';
@@ -15,7 +14,6 @@ import Column from './Column';
import Divider from './Divider';
import Header from './Header';
import Row from './Row';
-import Spacer from './Spacer';
import Tab from './Tab';
import Tabs from './Tabs';
@@ -24,7 +22,6 @@ export { default as Column } from './Column';
export { default as Divider } from './Divider';
export { default as Header } from './Header';
export { default as Row } from './Row';
-export { default as Spacer } from './Spacer';
export { default as Tab } from './Tab';
export { default as Tabs } from './Tabs';
@@ -35,7 +32,6 @@ export default {
[HEADER_TYPE]: Header,
[INVISIBLE_ROW_TYPE]: Row,
[ROW_TYPE]: Row,
- [SPACER_TYPE]: Spacer,
[TAB_TYPE]: Tab,
[TABS_TYPE]: Tabs,
};
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx
index 778f58e..eebd6e0 100644
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/DraggableNewComponent.jsx
@@ -26,6 +26,7 @@ export default class DraggableNewComponent extends React.PureComponent {
parentComponent={{ id: NEW_COMPONENTS_SOURCE_ID, type: NEW_COMPONENT_SOURCE_TYPE }}
index={0}
depth={0}
+ editMode
>
{({ dragSourceRef }) => (
<div ref={dragSourceRef} className="new-component">
diff --git a/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/NewSpacer.jsx b/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/NewSpacer.jsx
deleted file mode 100644
index 7287770..0000000
--- a/superset/assets/javascripts/dashboard/v2/components/gridComponents/new/NewSpacer.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { SPACER_TYPE } from '../../../util/componentTypes';
-import { NEW_SPACER_ID } from '../../../util/constants';
-import DraggableNewComponent from './DraggableNewComponent';
-
-const propTypes = {
-};
-
-export default class DraggableNewChart extends React.PureComponent {
- render() {
- return (
- <DraggableNewComponent
- id={NEW_SPACER_ID}
- type={SPACER_TYPE}
- label="Spacer"
- className="spacer-placeholder fa fa-arrows"
- />
- );
- }
-}
-
-DraggableNewChart.propTypes = propTypes;
diff --git a/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx b/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
index 2054090..f213442 100644
--- a/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/menu/WithPopoverMenu.jsx
@@ -9,6 +9,7 @@ const propTypes = {
onChangeFocus: PropTypes.func,
isFocused: PropTypes.bool,
shouldFocus: PropTypes.func,
+ editMode: PropTypes.bool.isRequired,
};
const defaultProps = {
@@ -32,10 +33,14 @@ class WithPopoverMenu extends React.PureComponent {
}
componentWillReceiveProps(nextProps) {
- if (nextProps.isFocused && !this.state.isFocused) {
+ if (nextProps.editMode && nextProps.isFocused && !this.state.isFocused) {
document.addEventListener('click', this.handleClick, true);
document.addEventListener('drag', this.handleClick, true);
this.setState({ isFocused: true });
+ } else if (this.state.isFocused && !nextProps.editMode) {
+ document.removeEventListener('click', this.handleClick, true);
+ document.removeEventListener('drag', this.handleClick, true);
+ this.setState({ isFocused: false });
}
}
@@ -49,10 +54,14 @@ class WithPopoverMenu extends React.PureComponent {
}
handleClick(event) {
- const { onChangeFocus, shouldFocus: shouldFocusThunk } = this.props;
- const shouldFocus = shouldFocusThunk(event, this.container);
+ const { onChangeFocus, shouldFocus: shouldFocusFunc, disableClick, editMode } = this.props;
+ const shouldFocus = shouldFocusFunc(event, this.container);
+
+ if (!editMode) {
+ return;
+ }
- if (shouldFocus && !this.state.isFocused) {
+ if (!disableClick && shouldFocus && !this.state.isFocused) {
// if not focused, set focus and add a window event listener to capture outside clicks
// this enables us to not set a click listener for ever item on a dashboard
document.addEventListener('click', this.handleClick, true);
@@ -72,27 +81,28 @@ class WithPopoverMenu extends React.PureComponent {
}
render() {
- const { children, menuItems, disableClick } = this.props;
+ const { children, menuItems, editMode } = this.props;
const { isFocused } = this.state;
return (
<div
ref={this.setRef}
- onClick={!disableClick && this.handleClick}
- role="button" // @TODO consider others?
- tabIndex="0"
+ onClick={this.handleClick}
+ role="none"
className={cx(
'with-popover-menu',
- isFocused && 'with-popover-menu--focused',
+ editMode && isFocused && 'with-popover-menu--focused',
)}
>
{children}
- {isFocused && menuItems.length ?
- <div className="popover-menu" >
- {menuItems.map((node, i) => (
- <div className="menu-item" key={`menu-item-${i}`}>{node}</div>
- ))}
- </div> : null}
+ {editMode &&
+ isFocused &&
+ menuItems.length > 0 &&
+ <div className="popover-menu" >
+ {menuItems.map((node, i) => (
+ <div className="menu-item" key={`menu-item-${i}`}>{node}</div>
+ ))}
+ </div>}
</div>
);
}
diff --git a/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx b/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx
index fbb7d1d..a532ff0 100644
--- a/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx
+++ b/superset/assets/javascripts/dashboard/v2/components/resizable/ResizableContainer.jsx
@@ -5,11 +5,7 @@ import cx from 'classnames';
import ResizableHandle from './ResizableHandle';
import resizableConfig from '../../util/resizableConfig';
-import {
- GRID_BASE_UNIT,
- GRID_ROW_HEIGHT_UNIT,
- GRID_GUTTER_SIZE,
-} from '../../util/constants';
+import { GRID_BASE_UNIT, GRID_GUTTER_SIZE } from '../../util/constants';
const propTypes = {
id: PropTypes.string.isRequired,
@@ -25,10 +21,14 @@ const propTypes = {
maxWidthMultiple: PropTypes.number,
minHeightMultiple: PropTypes.number,
maxHeightMultiple: PropTypes.number,
+ staticHeight: PropTypes.number,
staticHeightMultiple: PropTypes.number,
+ staticWidth: PropTypes.number,
+ staticWidthMultiple: PropTypes.number,
onResizeStop: PropTypes.func,
onResize: PropTypes.func,
onResizeStart: PropTypes.func,
+ editMode: PropTypes.bool.isRequired,
};
const defaultProps = {
@@ -37,14 +37,17 @@ const defaultProps = {
adjustableHeight: true,
gutterWidth: GRID_GUTTER_SIZE,
widthStep: GRID_BASE_UNIT,
- heightStep: GRID_ROW_HEIGHT_UNIT,
+ heightStep: GRID_BASE_UNIT,
widthMultiple: null,
heightMultiple: null,
minWidthMultiple: 1,
maxWidthMultiple: Infinity,
minHeightMultiple: 1,
maxHeightMultiple: Infinity,
+ staticHeight: null,
staticHeightMultiple: null,
+ staticWidth: null,
+ staticWidthMultiple: null,
onResizeStop: null,
onResize: null,
onResizeStart: null,
@@ -99,9 +102,9 @@ class ResizableContainer extends React.PureComponent {
if (onResizeStop) {
const nextWidthMultiple =
- Math.round(widthMultiple + (delta.width / (widthStep + gutterWidth)));
+ widthMultiple + Math.round(delta.width / (widthStep + gutterWidth));
const nextHeightMultiple =
- Math.round(heightMultiple + (delta.height / heightStep));
+ heightMultiple + Math.round(delta.height / heightStep);
onResizeStop({
id,
@@ -131,6 +134,7 @@ class ResizableContainer extends React.PureComponent {
minHeightMultiple,
maxHeightMultiple,
gutterWidth,
+ editMode,
} = this.props;
const size = {
@@ -146,6 +150,14 @@ class ResizableContainer extends React.PureComponent {
|| undefined,
};
+ if (!editMode) {
+ return (
+ <div style={{ ...size }}>
+ {children}
+ </div>
+ );
+ }
+
let enableConfig = resizableConfig.notAdjustable;
if (adjustableWidth && adjustableHeight) enableConfig = resizableConfig.widthAndHeight;
else if (adjustableWidth) enableConfig = resizableConfig.widthOnly;
@@ -164,10 +176,10 @@ class ResizableContainer extends React.PureComponent {
? (minHeightMultiple * heightStep)
: undefined}
maxWidth={adjustableWidth
- ? (maxWidthMultiple * (widthStep + gutterWidth)) - gutterWidth
+ ? Math.max(size.width, (maxWidthMultiple * (widthStep + gutterWidth)) - gutterWidth)
: undefined}
maxHeight={adjustableHeight
- ? (maxHeightMultiple * heightStep)
+ ? Math.max(size.height, maxHeightMultiple * heightStep)
: undefined}
size={size}
onResizeStart={this.handleResizeStart}
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
index 6bd8658..b8d717e 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardBuilder.jsx
@@ -5,11 +5,12 @@ import DashboardBuilder from '../components/DashboardBuilder';
import {
deleteTopLevelTabs,
handleComponentDrop,
-} from '../actions';
+} from '../actions/dashboardLayout';
-function mapStateToProps({ dashboard: undoableDashboard }) {
+function mapStateToProps({ dashboardLayout: undoableLayout, editMode }) {
return {
- dashboard: undoableDashboard.present,
+ dashboardLayout: undoableLayout.present,
+ editMode,
};
}
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx
index f7e86cc..add5a6d 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardComponent.jsx
@@ -14,7 +14,7 @@ import {
deleteComponent,
updateComponents,
handleComponentDrop,
-} from '../actions';
+} from '../actions/dashboardLayout';
const propTypes = {
component: componentShape.isRequired,
@@ -25,28 +25,29 @@ const propTypes = {
handleComponentDrop: PropTypes.func.isRequired,
};
-function mapStateToProps({ dashboard: undoableDashboard }, ownProps) {
- const components = undoableDashboard.present;
+function mapStateToProps({ dashboardLayout: undoableLayout, editMode }, ownProps) {
+ const dashboardLayout = undoableLayout.present;
const { id, parentId } = ownProps;
- const component = components[id];
+ const component = dashboardLayout[id];
const props = {
component,
- parentComponent: components[parentId],
+ parentComponent: dashboardLayout[parentId],
+ editMode,
};
// rows and columns need more data about their child dimensions
// doing this allows us to not pass the entire component lookup to all Components
if (props.component.type === ROW_TYPE) {
- props.occupiedColumnCount = getTotalChildWidth({ id, components });
+ props.occupiedColumnCount = getTotalChildWidth({ id, components: dashboardLayout });
} else if (props.component.type === COLUMN_TYPE) {
props.minColumnWidth = GRID_MIN_COLUMN_COUNT;
component.children.forEach((childId) => {
// rows don't have widths, so find the width of its children
- if (components[childId].type === ROW_TYPE) {
+ if (dashboardLayout[childId].type === ROW_TYPE) {
props.minColumnWidth = Math.max(
props.minColumnWidth,
- getTotalChildWidth({ id: childId, components }),
+ getTotalChildWidth({ id: childId, components: dashboardLayout }),
);
}
});
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
index eb01616..67b2396 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardGrid.jsx
@@ -5,7 +5,7 @@ import DashboardGrid from '../components/DashboardGrid';
import {
handleComponentDrop,
resizeComponent,
-} from '../actions';
+} from '../actions/dashboardLayout';
function mapDispatchToProps(dispatch) {
return bindActionCreators({
@@ -14,4 +14,4 @@ function mapDispatchToProps(dispatch) {
}, dispatch);
}
-export default connect(null, mapDispatchToProps)(DashboardGrid);
+export default connect(({ editMode }) => ({ editMode }), mapDispatchToProps)(DashboardGrid);
diff --git a/superset/assets/javascripts/dashboard/v2/containers/DashboardHeader.jsx b/superset/assets/javascripts/dashboard/v2/containers/DashboardHeader.jsx
index 52e7e7a..8855d2c 100644
--- a/superset/assets/javascripts/dashboard/v2/containers/DashboardHeader.jsx
+++ b/superset/assets/javascripts/dashboard/v2/containers/DashboardHeader.jsx
@@ -1,4 +1,4 @@
-import { ActionCreators as UndoActionCreators } from 'redux-undo'
+import { ActionCreators as UndoActionCreators } from 'redux-undo';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
@@ -8,13 +8,16 @@ import { DASHBOARD_HEADER_ID } from '../util/constants';
import {
updateComponents,
handleComponentDrop,
-} from '../actions';
+} from '../actions/dashboardLayout';
-function mapStateToProps({ dashboard: undoableDashboard }) {
+import { setEditMode } from '../actions/editMode';
+
+function mapStateToProps({ dashboardLayout: undoableLayout, editMode }) {
return {
- component: undoableDashboard.present[DASHBOARD_HEADER_ID],
- canUndo: undoableDashboard.past.length > 0,
- canRedo: undoableDashboard.future.length > 0,
+ component: undoableLayout.present[DASHBOARD_HEADER_ID],
+ canUndo: undoableLayout.past.length > 0,
+ canRedo: undoableLayout.future.length > 0,
+ editMode,
};
}
@@ -24,6 +27,7 @@ function mapDispatchToProps(dispatch) {
handleComponentDrop,
onUndo: UndoActionCreators.undo,
onRedo: UndoActionCreators.redo,
+ setEditMode,
}, dispatch);
}
diff --git a/superset/assets/javascripts/dashboard/v2/containers/ToastPresenter.jsx b/superset/assets/javascripts/dashboard/v2/containers/ToastPresenter.jsx
new file mode 100644
index 0000000..7e70abc
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/containers/ToastPresenter.jsx
@@ -0,0 +1,10 @@
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import ToastPresenter from '../components/ToastPresenter';
+
+import { removeToast } from '../actions/messageToasts';
+
+export default connect(
+ ({ messageToasts: toasts }) => ({ toasts }),
+ dispatch => bindActionCreators({ removeToast }, dispatch),
+)(ToastPresenter);
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/dashboard.js b/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
similarity index 98%
rename from superset/assets/javascripts/dashboard/v2/reducers/dashboard.js
rename to superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
index 9b03861..994ac47 100644
--- a/superset/assets/javascripts/dashboard/v2/reducers/dashboard.js
+++ b/superset/assets/javascripts/dashboard/v2/reducers/dashboardLayout.js
@@ -10,7 +10,6 @@ import {
ROW_TYPE,
TAB_TYPE,
TABS_TYPE,
-
} from '../util/componentTypes';
import {
@@ -20,7 +19,7 @@ import {
MOVE_COMPONENT,
CREATE_TOP_LEVEL_TABS,
DELETE_TOP_LEVEL_TABS,
-} from '../actions';
+} from '../actions/dashboardLayout';
const actionHandlers = {
[UPDATE_COMPONENTS](state, action) {
@@ -224,7 +223,7 @@ const actionHandlers = {
},
};
-export default function dashboardReducer(state = {}, action) {
+export default function layoutReducer(state = {}, action) {
if (action.type in actionHandlers) {
const handler = actionHandlers[action.type];
return handler(state, action);
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/editMode.js b/superset/assets/javascripts/dashboard/v2/reducers/editMode.js
new file mode 100644
index 0000000..b1a1630
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/reducers/editMode.js
@@ -0,0 +1,11 @@
+import { SET_EDIT_MODE } from '../actions/editMode';
+
+export default function editModeReducer(editMode = false, action) {
+ switch (action.type) {
+ case SET_EDIT_MODE:
+ return action.payload.editMode;
+
+ default:
+ return editMode;
+ }
+}
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/index.js b/superset/assets/javascripts/dashboard/v2/reducers/index.js
index 9c0575e..731734d 100644
--- a/superset/assets/javascripts/dashboard/v2/reducers/index.js
+++ b/superset/assets/javascripts/dashboard/v2/reducers/index.js
@@ -1,13 +1,17 @@
import { combineReducers } from 'redux';
import undoable, { distinctState } from 'redux-undo';
-import dashboard from './dashboard';
+import dashboardLayout from './dashboardLayout';
+import editMode from './editMode';
+import messageToasts from './messageToasts';
-const undoableDashboard = undoable(dashboard, {
- limit: 10,
+const undoableLayout = undoable(dashboardLayout, {
+ limit: 15,
filter: distinctState(),
});
export default combineReducers({
- dashboard: undoableDashboard,
+ dashboardLayout: undoableLayout,
+ editMode,
+ messageToasts,
});
diff --git a/superset/assets/javascripts/dashboard/v2/reducers/messageToasts.js b/superset/assets/javascripts/dashboard/v2/reducers/messageToasts.js
new file mode 100644
index 0000000..1f5728a
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/reducers/messageToasts.js
@@ -0,0 +1,18 @@
+import { ADD_TOAST, REMOVE_TOAST } from '../actions/messageToasts';
+
+export default function messageToastsReducer(toasts = [], action) {
+ switch (action.type) {
+ case ADD_TOAST: {
+ const { payload: toast } = action;
+ return [toast, ...toasts];
+ }
+
+ case REMOVE_TOAST: {
+ const { payload: { id } } = action;
+ return [...toasts].filter(toast => toast.id !== id);
+ }
+
+ default:
+ return toasts;
+ }
+}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/builder.less b/superset/assets/javascripts/dashboard/v2/stylesheets/builder.less
index 5f1a5b0..3651c57 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/builder.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/builder.less
@@ -14,7 +14,7 @@
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1); /* @TODO color */
}
-.dashboard-builder {
+.dashboard-content {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
@@ -32,12 +32,12 @@
padding-left: 8px; /* note this is added to tab-level padding, to match header */
}
-.dashboard-builder .grid-container .dashboard-component-tabs {
+.dashboard-content .grid-container .dashboard-component-tabs {
box-shadow: none;
padding-left: 0;
}
-.dashboard-builder > div:first-child {
+.dashboard-content > div:first-child {
width: 100%;
flex-grow: 1;
position: relative;
@@ -62,3 +62,11 @@
background: @almost-black !important;
color: white !important;
}
+
+.background--transparent {
+ background-color: transparent;
+}
+
+.background--white {
+ background-color: white;
+}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/DashboardBuilder.jsx b/superset/assets/javascripts/dashboard/v2/stylesheets/components/DashboardBuilder.jsx
deleted file mode 100644
index e011ad4..0000000
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/DashboardBuilder.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import HTML5Backend from 'react-dnd-html5-backend';
-import { DragDropContext } from 'react-dnd';
-
-import BuilderComponentPane from './BuilderComponentPane';
-import DashboardHeader from '../containers/DashboardHeader';
-import DashboardGrid from './DashboardGrid';
-import IconButton from './IconButton';
-import DragDroppable from './dnd/DragDroppable';
-import DashboardComponent from '../containers/DashboardComponent';
-import WithPopoverMenu from './menu/WithPopoverMenu';
-
-import {
- DASHBOARD_GRID_ID,
- DASHBOARD_ROOT_ID,
- DASHBOARD_ROOT_DEPTH,
-} from '../util/constants';
-
-const propTypes = {
- editMode: PropTypes.bool,
-
- // redux
- dashboard: PropTypes.object.isRequired,
- deleteTopLevelTabs: PropTypes.func.isRequired,
- updateComponents: PropTypes.func.isRequired,
- handleComponentDrop: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
- editMode: true,
-};
-
-class DashboardBuilder extends React.Component {
- static shouldFocusTabs(event, container) {
- // don't focus the tabs when we click on a tab
- return event.target.tagName === 'UL' || (
- /icon-button/.test(event.target.className) && container.contains(event.target)
- );
- }
-
- constructor(props) {
- super(props);
- this.state = {
- tabIndex: 0, // top-level tabs
- };
- this.handleChangeTab = this.handleChangeTab.bind(this);
- }
-
- handleChangeTab({ tabIndex }) {
- this.setState(() => ({ tabIndex }));
- }
-
- render() {
- const { tabIndex } = this.state;
- const { handleComponentDrop, updateComponents, dashboard, deleteTopLevelTabs } = this.props;
- const dashboardRoot = dashboard[DASHBOARD_ROOT_ID];
- const rootChildId = dashboardRoot.children[0];
- const topLevelTabs = rootChildId !== DASHBOARD_GRID_ID && dashboard[rootChildId];
-
- const gridComponentId = topLevelTabs
- ? topLevelTabs.children[Math.min(topLevelTabs.children.length - 1, tabIndex)]
- : DASHBOARD_GRID_ID;
-
- const gridComponent = dashboard[gridComponentId];
-
- return (
- <div className="dashboard-v2">
- {topLevelTabs ? ( // you cannot drop on/displace tabs if they already exist
- <DashboardHeader />
- ) : (
- <DragDroppable
- component={dashboardRoot}
- parentComponent={null}
- depth={DASHBOARD_ROOT_DEPTH}
- index={0}
- orientation="column"
- onDrop={topLevelTabs ? null : handleComponentDrop}
- >
- {({ dropIndicatorProps }) => (
- <div>
- <DashboardHeader />
- {dropIndicatorProps && <div {...dropIndicatorProps} />}
- </div>
- )}
- </DragDroppable>)}
-
- {topLevelTabs &&
- <WithPopoverMenu
- shouldFocus={DashboardBuilder.shouldFocusTabs}
- menuItems={[
- <IconButton
- className="fa fa-level-down"
- label="Collapse tab content"
- onClick={deleteTopLevelTabs}
- />,
- ]}
- >
- <DashboardComponent
- id={topLevelTabs.id}
- parentId={DASHBOARD_ROOT_ID}
- depth={DASHBOARD_ROOT_DEPTH + 1}
- index={0}
- renderTabContent={false}
- onChangeTab={this.handleChangeTab}
- />
- </WithPopoverMenu>}
-
- <div className="dashboard-builder">
- <DashboardGrid
- gridComponent={gridComponent}
- dashboard={dashboard}
- handleComponentDrop={handleComponentDrop}
- updateComponents={updateComponents}
- depth={DASHBOARD_ROOT_DEPTH + 1}
- />
- <BuilderComponentPane />
- </div>
- </div>
- );
- }
-}
-
-DashboardBuilder.propTypes = propTypes;
-DashboardBuilder.defaultProps = defaultProps;
-
-export default DragDropContext(HTML5Backend)(DashboardBuilder);
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
index 2bdf3cc..141c3e9 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/chart.less
@@ -14,8 +14,6 @@
opacity: 0.3;
}
-.grid-container--resizing .dashboard-component-chart,
-.dashboard-builder--dragging .dashboard-component-chart,
-.dashboard-component-chart:hover {
+.dashboard-v2--editing .dashboard-component-chart:hover {
box-shadow: inset 0 0 0 1px @gray-light;
}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
index 31ae21d..9565112 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/column.less
@@ -1,18 +1,34 @@
.grid-column {
width: 100%;
- min-height: 56px;
}
-.grid-column > .hover-menu--top {
- top: -20px;
+/* gutters between elements in a column */
+.grid-column > :not(:only-child):not(.hover-menu):not(:last-child) {
+ margin-bottom: 16px;
+}
+
+.dashboard-v2--editing .grid-column:after {
+ border: 1px dashed transparent;
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 1px;
+ left: 0;
+ z-index: 1;
+ pointer-events: none;
+}
+
+.dashboard-v2--editing .grid-column:hover:after {
+ border: 1px solid @gray-light;
}
-.grid-column.background--transparent {
- background-color: transparent;
+.grid-column > .hover-menu--top {
+ top: -20px;
}
-.grid-column.background--white {
- background-color: white;
+.grid-column--empty {
+ min-height: 72px;
}
.grid-column--empty:before {
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/divider.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/divider.less
index f1d3d86..e4625d3 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/divider.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/divider.less
@@ -1,6 +1,6 @@
.dashboard-component-divider {
width: 100%;
- padding: 24px 0; /* this is padding not margin to enable a larger mouse target */
+ padding: 8px 0; /* this is padding not margin to enable a larger mouse target */
background-color: transparent;
}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/header.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/header.less
index 77066da..37c7598 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/header.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/header.less
@@ -2,25 +2,45 @@
width: 100%;
line-height: 1em;
font-weight: 700;
- background-color: inherit;
padding: 16px 0;
color: @almost-black;
}
+.dashboard-header .dashboard-component-header {
+ font-weight: 300;
+ width: auto;
+}
+
+.dragdroppable-row .dashboard-component-header {
+ cursor: move;
+}
+
+/* note: sizes should be a multiple of the 8px grid unit so that rows in the grid align */
.header-small {
font-size: 16px;
}
.header-medium {
- font-size: 22px;
+ font-size: 24px;
}
.header-large {
font-size: 32px;
}
-.dragdroppable-row .dragdroppable-row .dashboard-component-header,
-.dragdroppable-row .dragdroppable-row .dashboard-component-divider {
+.background--white .dashboard-component-header,
+.dashboard-component-header.background--white,
+.dashboard-component-tabs .dashboard-component-header,
+.dashboard-component-tabs .dashboard-component-divider {
padding-left: 16px;
padding-right: 16px;
}
+
+/*
+ * grids add margin between items, so don't double pad within columns
+ * we'll not worry about double padding on top as it can serve as a visual separator
+ */
+// .grid-content > :not(:only-child):not(:last-child) .dashboard-component-header,
+.grid-column > :not(:only-child):not(:last-child) .dashboard-component-header {
+ margin-bottom: -16px;
+}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/index.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/index.less
index 5da54e5..5a1803e 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/index.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/index.less
@@ -4,5 +4,4 @@
@import './header.less';
@import './new-component.less';
@import './row.less';
-@import './spacer.less';
@import './tabs.less';
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/new-component.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/new-component.less
index e36fee2..decb1ad 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/new-component.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/new-component.less
@@ -22,18 +22,6 @@
font-size: 1.5em;
}
-.new-component-placeholder.spacer-placeholder {
- font-size: 1em;
-}
-
.new-component-placeholder.fa-window-restore {
font-size: 1em;
}
-
-.new-component-placeholder.spacer-placeholder:after {
- content: "";
- position: absolute;
- height: 60%;
- width: 60%;
- border: 1px dashed @gray;
-}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less
index 2036815..956966d 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/components/row.less
@@ -5,19 +5,28 @@
align-items: flex-start;
width: 100%;
height: fit-content;
- background-color: transparent;
}
-.grid-row.background--transparent {
- background-color: transparent;
+/* gutters between elements in a row */
+.grid-row > :not(:only-child):not(:last-child):not(.hover-menu) {
+ margin-right: 16px;
}
-.grid-row.background--white {
- background-color: white;
+/* hover indicator */
+.dashboard-v2--editing .grid-row:after {
+ border: 1px dashed transparent;
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 1px;
+ left: 0;
+ z-index: 1;
+ pointer-events: none;
}
-.dashboard-component-header.grid-row--white {
- padding-left: 16px;
+.dashboard-v2--editing .grid-row:hover:after {
+ border: 1px solid @gray-light;
}
.grid-row.grid-row--empty {
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/components/spacer.less b/superset/assets/javascripts/dashboard/v2/stylesheets/components/spacer.less
deleted file mode 100644
index 8716c21..0000000
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/components/spacer.less
+++ /dev/null
@@ -1,13 +0,0 @@
-.grid-spacer {
- width: 100%;
- height: 100%;
- background-color: transparent;
-}
-
-.dragdroppable .grid-spacer {
- cursor: move;
-}
-
-.dragdroppable:hover .grid-spacer {
- box-shadow: inset 0 0 0 1px @gray-light;
-}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less b/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less
index 7c55dee..45b8a42 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/grid.less
@@ -9,6 +9,11 @@
flex-direction: column;
}
+/* gutters between rows */
+.grid-content > div:not(:only-child):not(:last-child):not(.empty-grid-droptarget) {
+ margin-bottom: 16px;
+}
+
.empty-grid-droptarget {
width: 100%;
height: 100%;
@@ -33,22 +38,3 @@
pointer-events: none;
z-index: 10;
}
-
-
-.grid-container .grid-row:after,
-.grid-container .grid-column:after {
- border: 1px dashed transparent;
- content: "";
- position: absolute;
- width: 100%;
- height: 100%;
- top: 1px;
- left: 0;
- z-index: 1;
- pointer-events: none;
-}
-
-.grid-container .grid-row:hover:after,
-.grid-container .grid-column:hover:after {
- border: 1px solid @gray-light;
-}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/index.less b/superset/assets/javascripts/dashboard/v2/stylesheets/index.less
index d2a41a8..49ff5da 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/index.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/index.less
@@ -8,3 +8,4 @@
@import './popover-menu.less';
@import './resizable.less';
@import './components/index.less';
+@import './toast.less';
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/popover-menu.less b/superset/assets/javascripts/dashboard/v2/stylesheets/popover-menu.less
index a36ab1c..848949b 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/popover-menu.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/popover-menu.less
@@ -124,5 +124,7 @@
}
.background-style-option.background--transparent:before {
- background: @gray-light;
+ background-image: linear-gradient(45deg, @gray 25%, transparent 25%), linear-gradient(-45deg, @gray 25%, transparent 25%), linear-gradient(45deg, transparent 75%, @gray 75%), linear-gradient(-45deg, transparent 75%, @gray 75%);
+ background-size: 8px 8px;
+ background-position: 0 0, 0 4px, 4px -4px, -4px 0px
}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less b/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less
index 3ce5cfd..7bdd5f8 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/resizable.less
@@ -29,8 +29,8 @@
border-width: 0 1.5px 1.5px 0;
border-right-color: @gray;
border-bottom-color: @gray;
- right: 16;
- bottom: 16;
+ right: 16px;
+ bottom: 16px;
width: 8px;
height: 8px;
}
@@ -38,22 +38,18 @@
.resize-handle--right {
width: 2px;
height: 20px;
- right: -2px;
- top: 47%;
+ right: 2px;
+ top: ~"calc(50% - 9px)"; /* escape for .less */
position: absolute;
border-left: 1px solid @gray;
border-right: 1px solid @gray;
}
- .grid-spacer + span .resize-handle--right {
- right: 3px;
- }
-
.resize-handle--bottom {
height: 2px;
width: 20px;
- bottom: 10px;
- left: 47%;
+ bottom: 2px;
+ left: ~"calc(50% - 10px)"; /* escape for .less */
position: absolute;
border-top: 1px solid @gray;
border-bottom: 1px solid @gray;
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/toast.less b/superset/assets/javascripts/dashboard/v2/stylesheets/toast.less
new file mode 100644
index 0000000..a508637
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/toast.less
@@ -0,0 +1,59 @@
+.toast-presenter {
+ position: fixed;
+ bottom: 16px;
+ left: 50%;
+ transform: translate(-50%, 0);
+ width: 500px;
+ z-index: 3000; // top of the world
+}
+
+.toast {
+ background: white;
+ color: @almost-black;
+ opacity: 0;
+ position: relative;
+ white-space: pre-line;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15);
+ border-radius: 2px;
+ will-change: transform, opacity;
+ transform: translateY(-100%);
+ transition: transform .3s, opacity .3s;
+}
+
+.toast > button {
+ color: @almost-black;
+}
+
+.toast > button:hover {
+ color: @gray-dark;
+}
+
+.toast--visible {
+ transform: translateY(0);
+ opacity: 1;
+}
+
+.toast:after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 4px;
+ height: 100%;
+}
+
+.toast--info:after {
+ background: linear-gradient(to bottom, @pink, @purple);
+}
+
+.toast--success:after {
+ background: @success;
+}
+
+.toast--warning:after {
+ background: @warning;
+}
+
+.toast--danger:after {
+ background: @danger;
+}
diff --git a/superset/assets/javascripts/dashboard/v2/stylesheets/variables.less b/superset/assets/javascripts/dashboard/v2/stylesheets/variables.less
index f3a61df..254af23 100644
--- a/superset/assets/javascripts/dashboard/v2/stylesheets/variables.less
+++ b/superset/assets/javascripts/dashboard/v2/stylesheets/variables.less
@@ -5,3 +5,11 @@
@gray: #879399;
@gray-light: #CFD8DC;
@gray-bg: #f5f5f5;
+
+/* toasts */
+@pink: #E32364;
+@purple: #2C2261;
+
+@success: #00BFA5;
+@warning: #FFAB00;
+@danger: @pink;
diff --git a/superset/assets/javascripts/dashboard/v2/util/componentIsResizable.js b/superset/assets/javascripts/dashboard/v2/util/componentIsResizable.js
index ab701a7..c0016f3 100644
--- a/superset/assets/javascripts/dashboard/v2/util/componentIsResizable.js
+++ b/superset/assets/javascripts/dashboard/v2/util/componentIsResizable.js
@@ -1,5 +1,4 @@
import {
- SPACER_TYPE,
COLUMN_TYPE,
CHART_TYPE,
MARKDOWN_TYPE,
@@ -7,7 +6,6 @@ import {
export default function componentIsResizable(entity) {
return [
- SPACER_TYPE,
COLUMN_TYPE,
CHART_TYPE,
MARKDOWN_TYPE,
diff --git a/superset/assets/javascripts/dashboard/v2/util/componentTypes.js b/superset/assets/javascripts/dashboard/v2/util/componentTypes.js
index c667138..2866898 100644
--- a/superset/assets/javascripts/dashboard/v2/util/componentTypes.js
+++ b/superset/assets/javascripts/dashboard/v2/util/componentTypes.js
@@ -8,7 +8,6 @@ export const HEADER_TYPE = 'DASHBOARD_HEADER_TYPE';
export const MARKDOWN_TYPE = 'DASHBOARD_MARKDOWN_TYPE';
export const NEW_COMPONENT_SOURCE_TYPE = 'NEW_COMPONENT_SOURCE_TYPE';
export const ROW_TYPE = 'DASHBOARD_ROW_TYPE';
-export const SPACER_TYPE = 'DASHBOARD_SPACER_TYPE';
export const TABS_TYPE = 'DASHBOARD_TABS_TYPE';
export const TAB_TYPE = 'DASHBOARD_TAB_TYPE';
@@ -23,7 +22,6 @@ export default {
MARKDOWN_TYPE,
NEW_COMPONENT_SOURCE_TYPE,
ROW_TYPE,
- SPACER_TYPE,
TABS_TYPE,
TAB_TYPE,
};
diff --git a/superset/assets/javascripts/dashboard/v2/util/constants.js b/superset/assets/javascripts/dashboard/v2/util/constants.js
index e892456..36ef71b 100644
--- a/superset/assets/javascripts/dashboard/v2/util/constants.js
+++ b/superset/assets/javascripts/dashboard/v2/util/constants.js
@@ -10,7 +10,6 @@ export const NEW_DIVIDER_ID = 'NEW_DIVIDER_ID';
export const NEW_HEADER_ID = 'NEW_HEADER_ID';
export const NEW_MARKDOWN_ID = 'NEW_MARKDOWN_ID';
export const NEW_ROW_ID = 'NEW_ROW_ID';
-export const NEW_SPACER_ID = 'NEW_SPACER_ID';
export const NEW_TAB_ID = 'NEW_TAB_ID';
export const NEW_TABS_ID = 'NEW_TABS_ID';
@@ -18,7 +17,6 @@ export const NEW_TABS_ID = 'NEW_TABS_ID';
export const DASHBOARD_ROOT_DEPTH = 0;
export const GRID_BASE_UNIT = 8;
export const GRID_GUTTER_SIZE = 2 * GRID_BASE_UNIT;
-export const GRID_ROW_HEIGHT_UNIT = 2 * GRID_BASE_UNIT;
export const GRID_COLUMN_COUNT = 12;
export const GRID_MIN_COLUMN_COUNT = 3;
export const GRID_MIN_ROW_UNITS = 5;
@@ -33,3 +31,9 @@ export const LARGE_HEADER = 'LARGE_HEADER';
// Style types
export const BACKGROUND_WHITE = 'BACKGROUND_WHITE';
export const BACKGROUND_TRANSPARENT = 'BACKGROUND_TRANSPARENT';
+
+// Toast types
+export const INFO_TOAST = 'INFO_TOAST';
+export const SUCCESS_TOAST = 'SUCCESS_TOAST';
+export const WARNING_TOAST = 'WARNING_TOAST';
+export const DANGER_TOAST = 'DANGER_TOAST';
diff --git a/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js b/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js
new file mode 100644
index 0000000..0fd0c4e
--- /dev/null
+++ b/superset/assets/javascripts/dashboard/v2/util/dropOverflowsParent.js
@@ -0,0 +1,24 @@
+import { COLUMN_TYPE } from '../util/componentTypes';
+import { GRID_COLUMN_COUNT, NEW_COMPONENTS_SOURCE_ID } from './constants';
+import findParentId from './findParentId';
+import getChildWidth from './getChildWidth';
+import newComponentFactory from './newComponentFactory';
+
+export default function doesChildOverflowParent(dropResult, components) {
+ const { source, destination, dragging } = dropResult;
+ const isNewComponent = source.id === NEW_COMPONENTS_SOURCE_ID;
+
+ const grandparentId = findParentId({ childId: destination.id, components });
+
+ const child = isNewComponent ? newComponentFactory(dragging.type) : components[dragging.id] || {};
+ const parent = components[destination.id] || {};
+ const grandparent = components[grandparentId] || {};
+
+ const grandparentWidth = (grandparent.meta && grandparent.meta.width) || GRID_COLUMN_COUNT;
+ const parentWidth = (parent.meta && parent.meta.width) || grandparentWidth;
+ const parentChildWidth = parent.type === COLUMN_TYPE
+ ? 0 : getChildWidth({ id: destination.id, components });
+ const childWidth = (child.meta && child.meta.width) || 0;
+
+ return parentWidth - parentChildWidth < childWidth;
+}
diff --git a/superset/assets/javascripts/dashboard/v2/util/getChildWidth.js b/superset/assets/javascripts/dashboard/v2/util/getChildWidth.js
index 516624d..aa32b96 100644
--- a/superset/assets/javascripts/dashboard/v2/util/getChildWidth.js
+++ b/superset/assets/javascripts/dashboard/v2/util/getChildWidth.js
@@ -1,4 +1,4 @@
-export default function getTotalChildWidth({ id, components, recurse = false }) {
+export default function getTotalChildWidth({ id, components }) {
const component = components[id];
if (!component) return 0;
@@ -7,9 +7,6 @@ export default function getTotalChildWidth({ id, components, recurse = false })
(component.children || []).forEach((childId) => {
const child = components[childId];
width += child.meta.width || 0;
- if (recurse) {
- width += getTotalChildWidth({ id: childId, components, recurse }) || 0;
- }
});
return width;
diff --git a/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js b/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js
index 6a3bd0e..9605db2 100644
--- a/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js
+++ b/superset/assets/javascripts/dashboard/v2/util/getDropPosition.js
@@ -6,6 +6,8 @@ export const DROP_RIGHT = 'DROP_RIGHT';
export const DROP_BOTTOM = 'DROP_BOTTOM';
export const DROP_LEFT = 'DROP_LEFT';
+// this defines how close the mouse must be to the edge of a component to display
+// a sibling type drop indicator
const SIBLING_DROP_THRESHOLD = 15;
export default function getDropPosition(monitor, Component) {
diff --git a/superset/assets/javascripts/dashboard/v2/util/isValidChild.js b/superset/assets/javascripts/dashboard/v2/util/isValidChild.js
index 9c6ae8e..66942f0 100644
--- a/superset/assets/javascripts/dashboard/v2/util/isValidChild.js
+++ b/superset/assets/javascripts/dashboard/v2/util/isValidChild.js
@@ -23,7 +23,6 @@ import {
HEADER_TYPE,
MARKDOWN_TYPE,
ROW_TYPE,
- SPACER_TYPE,
TABS_TYPE,
TAB_TYPE,
} from './componentTypes';
@@ -48,7 +47,6 @@ const parentMaxDepthLookup = {
[DIVIDER_TYPE]: depthOne,
[HEADER_TYPE]: depthOne,
[ROW_TYPE]: depthOne,
- [SPACER_TYPE]: depthOne,
[TABS_TYPE]: depthOne,
},
@@ -56,7 +54,6 @@ const parentMaxDepthLookup = {
[CHART_TYPE]: depthFour,
[MARKDOWN_TYPE]: depthFour,
[COLUMN_TYPE]: depthTwo,
- [SPACER_TYPE]: depthFour,
},
[TABS_TYPE]: {
@@ -69,7 +66,6 @@ const parentMaxDepthLookup = {
[DIVIDER_TYPE]: depthTwo,
[HEADER_TYPE]: depthTwo,
[ROW_TYPE]: depthTwo,
- [SPACER_TYPE]: depthTwo,
[TABS_TYPE]: depthTwo,
},
@@ -78,7 +74,6 @@ const parentMaxDepthLookup = {
[HEADER_TYPE]: depthThree,
[MARKDOWN_TYPE]: depthThree,
[ROW_TYPE]: depthThree,
- [SPACER_TYPE]: depthThree,
},
// these have no valid children
@@ -86,11 +81,13 @@ const parentMaxDepthLookup = {
[DIVIDER_TYPE]: {},
[HEADER_TYPE]: {},
[MARKDOWN_TYPE]: {},
- [SPACER_TYPE]: {},
};
export default function isValidChild({ parentType, childType, parentDepth }) {
- if (!parentType || !childType || typeof parentDepth !== 'number') return false;
+ if (!parentType || !childType || typeof parentDepth !== 'number') {
+ return false;
+ }
+
const maxParentDepth = (parentMaxDepthLookup[parentType] || {})[childType];
return typeof maxParentDepth === 'number' && parentDepth <= maxParentDepth;
diff --git a/superset/assets/javascripts/dashboard/v2/util/newComponentFactory.js b/superset/assets/javascripts/dashboard/v2/util/newComponentFactory.js
index 9bc01a7..af69eb8 100644
--- a/superset/assets/javascripts/dashboard/v2/util/newComponentFactory.js
+++ b/superset/assets/javascripts/dashboard/v2/util/newComponentFactory.js
@@ -5,7 +5,6 @@ import {
HEADER_TYPE,
MARKDOWN_TYPE,
ROW_TYPE,
- SPACER_TYPE,
TABS_TYPE,
TAB_TYPE,
} from './componentTypes';
@@ -16,7 +15,7 @@ import {
} from './constants';
const typeToDefaultMetaData = {
- [CHART_TYPE]: { width: 3, height: 15 },
+ [CHART_TYPE]: { width: 3, height: 30 },
[COLUMN_TYPE]: { width: 3, background: BACKGROUND_TRANSPARENT },
[DIVIDER_TYPE]: null,
[HEADER_TYPE]: {
@@ -24,9 +23,8 @@ const typeToDefaultMetaData = {
headerSize: MEDIUM_HEADER,
background: BACKGROUND_TRANSPARENT,
},
- [MARKDOWN_TYPE]: { width: 3, height: 15 },
+ [MARKDOWN_TYPE]: { width: 3, height: 30 },
[ROW_TYPE]: { background: BACKGROUND_TRANSPARENT },
- [SPACER_TYPE]: {},
[TABS_TYPE]: null,
[TAB_TYPE]: { text: 'New Tab' },
};
diff --git a/superset/assets/javascripts/dashboard/v2/util/newComponentIdToType.js b/superset/assets/javascripts/dashboard/v2/util/newComponentIdToType.js
deleted file mode 100644
index 38d1c7c..0000000
--- a/superset/assets/javascripts/dashboard/v2/util/newComponentIdToType.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- CHART_TYPE,
- COLUMN_TYPE,
- DIVIDER_TYPE,
- HEADER_TYPE,
- MARKDOWN_TYPE,
- ROW_TYPE,
- SPACER_TYPE,
- TABS_TYPE,
- TAB_TYPE,
-} from './componentTypes';
-
-import {
- NEW_CHART_ID,
- NEW_COLUMN_ID,
- NEW_DIVIDER_ID,
- NEW_HEADER_ID,
- NEW_MARKDOWN_ID,
- NEW_ROW_ID,
- NEW_SPACER_ID,
- NEW_TABS_ID,
- NEW_TAB_ID,
-} from './constants';
-
-export default {
- [NEW_CHART_ID]: CHART_TYPE, // @TODO we will have to encode real chart ids => type in the future
- [NEW_COLUMN_ID]: COLUMN_TYPE,
- [NEW_DIVIDER_ID]: DIVIDER_TYPE,
- [NEW_HEADER_ID]: HEADER_TYPE,
- [NEW_MARKDOWN_ID]: MARKDOWN_TYPE,
- [NEW_ROW_ID]: ROW_TYPE,
- [NEW_SPACER_ID]: SPACER_TYPE,
- [NEW_TABS_ID]: TABS_TYPE,
- [NEW_TAB_ID]: TAB_TYPE,
-};
diff --git a/superset/assets/javascripts/dashboard/v2/util/propShapes.jsx b/superset/assets/javascripts/dashboard/v2/util/propShapes.jsx
index d701cc2..8acc192 100644
--- a/superset/assets/javascripts/dashboard/v2/util/propShapes.jsx
+++ b/superset/assets/javascripts/dashboard/v2/util/propShapes.jsx
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import componentTypes from './componentTypes';
import backgroundStyleOptions from './backgroundStyleOptions';
import headerStyleOptions from './headerStyleOptions';
+import { INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST } from './constants';
export const componentShape = PropTypes.shape({ // eslint-disable-line
id: PropTypes.string.isRequired,
@@ -22,3 +23,9 @@ export const componentShape = PropTypes.shape({ // eslint-disable-line
background: PropTypes.oneOf(backgroundStyleOptions.map(opt => opt.value)),
}),
});
+
+export const toastShape = PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ toastType: PropTypes.oneOf([INFO_TOAST, SUCCESS_TOAST, WARNING_TOAST, DANGER_TOAST]).isRequired,
+ text: PropTypes.string.isRequired,
+});
diff --git a/superset/assets/src/components/EditableTitle.jsx b/superset/assets/src/components/EditableTitle.jsx
index a7e3f17..45fea1d 100644
--- a/superset/assets/src/components/EditableTitle.jsx
+++ b/superset/assets/src/components/EditableTitle.jsx
@@ -116,7 +116,7 @@ class EditableTitle extends React.PureComponent {
}
render() {
- let input = (
+ let content = (
<input
required
type={this.state.isEditing ? 'text' : 'button'}
@@ -129,19 +129,25 @@ class EditableTitle extends React.PureComponent {
/>
);
if (this.props.showTooltip) {
- input = (
+ content = (
<TooltipWrapper
label="title"
tooltip={this.props.canEdit ? t('click to edit title') :
this.props.noPermitTooltip || t('You don\'t have the rights to alter this title.')}
>
- {input}
+ {content}
</TooltipWrapper>
);
}
return (
- <span className={cx('editable-title', this.props.canEdit && 'editable-title--editable')}>
- {input}
+ <span
+ className={cx(
+ 'editable-title',
+ this.props.canEdit && 'editable-title--editable',
+ this.state.isEditing && 'editable-title--editing',
+ )}
+ >
+ {content}
</span>
);
}
diff --git a/superset/assets/src/dashboard/index.jsx b/superset/assets/src/dashboard/index.jsx
index bb21a43..1aadc58 100644
--- a/superset/assets/src/dashboard/index.jsx
+++ b/superset/assets/src/dashboard/index.jsx
@@ -19,12 +19,15 @@ initJQueryAjax();
const appContainer = document.getElementById('app');
// const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
// const initState = Object.assign({}, getInitialState(bootstrapData));
+
const initState = {
- dashboard: {
+ dashboardLayout: {
past: [],
present: emptyDashboardLayout,
future: [],
},
+ editMode: true,
+ messageToasts: [],
};
const store = createStore(
diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less
index e9f508b..743daa8 100644
--- a/superset/assets/stylesheets/superset.less
+++ b/superset/assets/stylesheets/superset.less
@@ -233,32 +233,37 @@ table.table-no-hover tr:hover {
border: none;
box-shadow: none;
padding: 0;
+ cursor: initial;
}
.editable-title input[type="button"] {
- border-color: transparent;
- background: transparent;
- font-size: inherit;
- line-height: inherit;
- white-space: normal;
- text-align: left;
+ border-color: transparent;
+ background: transparent;
+ font-size: inherit;
+ line-height: inherit;
+ white-space: normal;
+ text-align: left;
}
-.editable-title--editable input[type="button"]:hover {
- cursor: text;
+.editable-title.editable-title--editable {
+ cursor: pointer;
+}
+
+.editable-title.editable-title--editing {
+ cursor: text;
}
.m-r-5 {
- margin-right: 5px;
+ margin-right: 5px;
}
.m-r-3 {
- margin-right: 3px;
+ margin-right: 3px;
}
.m-t-5 {
- margin-top: 5px;
+ margin-top: 5px;
}
.m-t-10 {
- margin-top: 10px;
+ margin-top: 10px;
}
.m-b-10 {
margin-bottom: 10px;
--
To stop receiving notification emails like this one, please contact
ccwilliams@apache.org.