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
The following commit(s) were added to refs/heads/dashboard-builder by this push: new a19a260 [dashboard builder] static layout + toasts (#4763) a19a260 is described below commit a19a26074d6074593c03b02fd1ee234e1d816937 Author: Chris Williams <willias...@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 --- .../javascripts/components/EditableTitle.jsx | 16 ++- superset/assets/javascripts/dashboard/index.jsx | 5 +- .../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/stylesheets/superset.less | 29 +++-- 62 files changed, 715 insertions(+), 630 deletions(-) diff --git a/superset/assets/javascripts/components/EditableTitle.jsx b/superset/assets/javascripts/components/EditableTitle.jsx index a7e3f17..45fea1d 100644 --- a/superset/assets/javascripts/components/EditableTitle.jsx +++ b/superset/assets/javascripts/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/javascripts/dashboard/index.jsx b/superset/assets/javascripts/dashboard/index.jsx index bb21a43..1aadc58 100644 --- a/superset/assets/javascripts/dashboard/index.jsx +++ b/superset/assets/javascripts/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/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/stylesheets/superset.less b/superset/assets/stylesheets/superset.less index a7a69b1..84089d0 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 ccwilli...@apache.org.