From a5e0229d5e93b5760bf482127b2284cf43ce44d1 Mon Sep 17 00:00:00 2001 From: Grace Guo Date: Fri, 4 May 2018 11:34:44 -0700 Subject: [PATCH 01/15] add slider and sticky --- .../assets/src/dashboard/containers/SliceAdder.js | 6 +++++- .../dashboard/stylesheets/builder-sidepane.less | 14 +++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/superset/assets/src/dashboard/containers/SliceAdder.js b/superset/assets/src/dashboard/containers/SliceAdder.js index e3d931dc51e53..15a114959d678 100644 --- a/superset/assets/src/dashboard/containers/SliceAdder.js +++ b/superset/assets/src/dashboard/containers/SliceAdder.js @@ -4,7 +4,10 @@ import { connect } from 'react-redux'; import { fetchAllSlices } from '../actions/sliceEntities'; import SliceAdder from '../components/SliceAdder'; -function mapStateToProps({ sliceEntities, dashboardInfo, dashboardState }) { +function mapStateToProps( + { sliceEntities, dashboardInfo, dashboardState }, + ownProps, +) { return { userId: dashboardInfo.userId, selectedSliceIds: dashboardState.sliceIds, @@ -13,6 +16,7 @@ function mapStateToProps({ sliceEntities, dashboardInfo, dashboardState }) { errorMessage: sliceEntities.errorMessage, lastUpdated: sliceEntities.lastUpdated, editMode: dashboardState.editMode, + height: ownProps.height, }; } diff --git a/superset/assets/src/dashboard/stylesheets/builder-sidepane.less b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less index 133d558334656..9733e1ea473e6 100644 --- a/superset/assets/src/dashboard/stylesheets/builder-sidepane.less +++ b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less @@ -1,7 +1,13 @@ .dashboard-builder-sidepane { + <<<<<<>>>>>>add slider and sticky box-shadow: -4px 0 4px 0 rgba(0, 0, 0, 0.1); .dashboard-builder-sidepane-header { @@ -35,7 +41,7 @@ width: @builder-pane-width * 2; height: 100%; display: flex; - transition: all .5s ease; + transition: all 0.5s ease; &.slide-in { left: -@builder-pane-width; @@ -61,6 +67,12 @@ } } + .component-layer { + .new-component.static { + cursor: pointer; + } + } + .chart-card-container { padding: 16px; From 8a14d25c6118216469ebf316cfd4dd11ad6ad6a8 Mon Sep 17 00:00:00 2001 From: Grace Guo Date: Tue, 24 Apr 2018 15:56:51 -0700 Subject: [PATCH 02/15] dashboard header, slice header UI improvement --- superset/assets/src/dashboard/containers/SliceAdder.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/superset/assets/src/dashboard/containers/SliceAdder.js b/superset/assets/src/dashboard/containers/SliceAdder.js index 15a114959d678..e3d931dc51e53 100644 --- a/superset/assets/src/dashboard/containers/SliceAdder.js +++ b/superset/assets/src/dashboard/containers/SliceAdder.js @@ -4,10 +4,7 @@ import { connect } from 'react-redux'; import { fetchAllSlices } from '../actions/sliceEntities'; import SliceAdder from '../components/SliceAdder'; -function mapStateToProps( - { sliceEntities, dashboardInfo, dashboardState }, - ownProps, -) { +function mapStateToProps({ sliceEntities, dashboardInfo, dashboardState }) { return { userId: dashboardInfo.userId, selectedSliceIds: dashboardState.sliceIds, @@ -16,7 +13,6 @@ function mapStateToProps( errorMessage: sliceEntities.errorMessage, lastUpdated: sliceEntities.lastUpdated, editMode: dashboardState.editMode, - height: ownProps.height, }; } From 6a2f5652ba512e90e8a40415ca5f32a5ccd8e415 Mon Sep 17 00:00:00 2001 From: Grace Guo Date: Fri, 27 Apr 2018 13:57:24 -0700 Subject: [PATCH 03/15] make builder pane floating --- .../assets/src/dashboard/components/BuilderComponentPane.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx index 66bfc14e5b876..48380b34287a2 100644 --- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx +++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx @@ -1,3 +1,4 @@ +/* eslint-env browser */ import React from 'react'; import cx from 'classnames'; import { StickyContainer, Sticky } from 'react-sticky'; From 6abfeb8d466e910e8e8dfd5978e5dd617f255330 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 2 May 2018 09:31:32 -0700 Subject: [PATCH 04/15] [dashboard builder] add sticky top-level tabs, refactor for performant tabs --- .../dashboard/components/DashboardBuilder.jsx | 118 +++++++---- .../dashboard/components/DashboardGrid.jsx | 188 +++++++++--------- .../components/gridComponents/Tab.jsx | 2 +- .../components/gridComponents/Tabs.jsx | 42 ++-- .../components/menu/WithPopoverMenu.jsx | 5 +- 5 files changed, 200 insertions(+), 155 deletions(-) diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx index 79eb35d6c9063..2ed6328b10576 100644 --- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx +++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx @@ -1,8 +1,13 @@ import cx from 'classnames'; -import React from 'react'; -import PropTypes from 'prop-types'; -import HTML5Backend from 'react-dnd-html5-backend'; import { DragDropContext } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; +// ParentSize uses resize observer so the dashboard will update size +// when its container size changes, due to e.g., builder side panel opening +import ParentSize from '@vx/responsive/build/components/ParentSize'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { Sticky, StickyContainer } from 'react-sticky'; +import { TabContainer, TabContent, TabPane } from 'react-bootstrap'; import BuilderComponentPane from './BuilderComponentPane'; import DashboardHeader from '../containers/DashboardHeader'; @@ -55,28 +60,25 @@ class DashboardBuilder extends React.Component { } render() { - const { tabIndex } = this.state; const { handleComponentDrop, dashboardLayout, deleteTopLevelTabs, editMode, } = this.props; + + const { tabIndex } = this.state; const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID]; const rootChildId = dashboardRoot.children[0]; 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 = dashboardLayout[gridComponentId]; + const childIds = topLevelTabs ? topLevelTabs.children : [DASHBOARD_GRID_ID]; return ( -
+ {topLevelTabs || !editMode ? ( // you cannot drop on/displace tabs if they already exist ) : ( @@ -99,38 +101,80 @@ class DashboardBuilder extends React.Component { )} {topLevelTabs && ( - , - ]} - editMode={editMode} - > - - + + {({ style }) => ( + , + ]} + editMode={editMode} + style={{ zIndex: 1000, ...style }} + > + + + )} + )}
- +
+ + {({ width }) => ( + /* + We use a TabContainer irrespective of whether top-level tabs exist to maintain + a consistent React component tree. This avoids expensive mounts/unmounts of + the entire dashboard upon adding/removing top-level tabs, which would otherwise + happen because of React's diffing algorithm + */ + + + {childIds.map((id, index) => ( + // Matching the key of the first TabPane irrespective of topLevelTabs + // lets us keep the same React component tree when !!topLevelTabs changes. + // This avoids expensive mounts/unmounts of the entire dashboard. + + + + ))} + + + )} + +
+ {this.props.editMode && this.props.showBuilderPane && }
-
+ ); } } diff --git a/superset/assets/src/dashboard/components/DashboardGrid.jsx b/superset/assets/src/dashboard/components/DashboardGrid.jsx index 3e6fc0cba8cac..7d57cb22ddba4 100644 --- a/superset/assets/src/dashboard/components/DashboardGrid.jsx +++ b/superset/assets/src/dashboard/components/DashboardGrid.jsx @@ -1,8 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -// ParentSize uses resize observer so the dashboard will update size -// when its container size changes, due to e.g., builder side panel opening -import ParentSize from '@vx/responsive/build/components/ParentSize'; import { componentShape } from '../util/propShapes'; import DashboardComponent from '../containers/DashboardComponent'; @@ -16,6 +13,7 @@ const propTypes = { gridComponent: componentShape.isRequired, handleComponentDrop: PropTypes.func.isRequired, resizeComponent: PropTypes.func.isRequired, + width: PropTypes.number.isRequired, }; const defaultProps = {}; @@ -78,99 +76,101 @@ class DashboardGrid extends React.PureComponent { } render() { - const { gridComponent, handleComponentDrop, depth, editMode } = this.props; + const { + gridComponent, + handleComponentDrop, + depth, + editMode, + width, + } = this.props; + + const columnPlusGutterWidth = + (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT; + + const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE; const { isResizing, rowGuideTop } = this.state; - return ( -
- - {({ width }) => { - const columnPlusGutterWidth = - (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT; - const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE; - return width < 50 ? null : ( -
- {editMode && ( - - {({ dropIndicatorProps }) => - dropIndicatorProps && ( -
- ) - } - - )} - - {gridComponent.children.map((id, index) => ( - - ))} - - {/* render an empty drop target */} - {editMode && ( - - {({ dropIndicatorProps }) => - dropIndicatorProps && ( -
- ) - } - - )} - - {isResizing && - Array(GRID_COLUMN_COUNT) - .fill(null) - .map((_, i) => ( -
- ))} - - {isResizing && - rowGuideTop && ( -
- )} -
- ); - }} - + return width < 100 ? null : ( +
+
+ {editMode && ( + + {({ dropIndicatorProps }) => + dropIndicatorProps && ( +
+ ) + } + + )} + + {gridComponent.children.map((id, index) => ( + + ))} + + {/* render an empty drop target */} + {editMode && ( + + {({ dropIndicatorProps }) => + dropIndicatorProps && ( +
+ ) + } + + )} + + {isResizing && + Array(GRID_COLUMN_COUNT) + .fill(null) + .map((_, i) => ( +
+ ))} + + {isResizing && + rowGuideTop && ( +
+ )} +
); } diff --git a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx index d73bc0cb7255a..63619c1574fcb 100644 --- a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx @@ -136,7 +136,7 @@ export default class Tab extends React.PureComponent { // 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} + disableDragDrop={depth === DASHBOARD_ROOT_DEPTH + 1} editMode={editMode} > {({ dropIndicatorProps, dragSourceRef }) => ( diff --git a/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx index 585041f3cb51f..813961d2281e4 100644 --- a/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx @@ -164,7 +164,11 @@ class Tabs extends React.PureComponent { id={tabsComponent.id} activeKey={selectedTabIndex} onSelect={this.handleClickTab} - animation={false} + // these are important for performant loading of tabs. also, there is a + // react-bootstrap bug where mountOnEnter has no effect unless animation=true + animation + mountOnEnter + unmountOnExit={false} > {tabIds.map((tabId, tabIndex) => ( // react-bootstrap doesn't render a Tab if we move this to its own Tab.jsx so we @@ -187,27 +191,21 @@ class Tabs extends React.PureComponent { /> } > - {/* - react-bootstrap renders all children with display:none, so we don't - render potentially-expensive charts (this also enables lazy loading - their content) - */} - {tabIndex === selectedTabIndex && - renderTabContent && ( - - )} + {renderTabContent && ( + + )} ))} diff --git a/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx b/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx index 8a87fca1af7de..2a047ac573a18 100644 --- a/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx +++ b/superset/assets/src/dashboard/components/menu/WithPopoverMenu.jsx @@ -10,6 +10,7 @@ const propTypes = { isFocused: PropTypes.bool, shouldFocus: PropTypes.func, editMode: PropTypes.bool.isRequired, + style: PropTypes.object, }; const defaultProps = { @@ -20,6 +21,7 @@ const defaultProps = { menuItems: [], isFocused: false, shouldFocus: (event, container) => container.contains(event.target), + style: null, }; class WithPopoverMenu extends React.PureComponent { @@ -84,7 +86,7 @@ class WithPopoverMenu extends React.PureComponent { } render() { - const { children, menuItems, editMode } = this.props; + const { children, menuItems, editMode, style } = this.props; const { isFocused } = this.state; return ( @@ -96,6 +98,7 @@ class WithPopoverMenu extends React.PureComponent { 'with-popover-menu', editMode && isFocused && 'with-popover-menu--focused', )} + style={style} > {children} {editMode && From ef74a92c1059836f58caaf1cfaaab4f24733fcf0 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 2 May 2018 14:51:39 -0700 Subject: [PATCH 05/15] [dashboard builder] visually distinct containers, icons for undo-redo, fix some isValidChild bugs --- .../dashboard/components/DashboardGrid.jsx | 22 ++++++- .../src/dashboard/components/Header.jsx | 4 +- .../components/gridComponents/ChartHolder.jsx | 1 - .../components/gridComponents/Column.jsx | 25 ++++---- .../components/gridComponents/Row.jsx | 23 ++++---- .../dashboard/containers/DashboardHeader.jsx | 5 +- .../stylesheets/components/column.less | 6 +- .../stylesheets/components/header.less | 6 ++ .../dashboard/stylesheets/components/row.less | 6 +- .../src/dashboard/stylesheets/grid.less | 14 ++++- .../src/dashboard/stylesheets/hover-menu.less | 59 +++++++++++++++---- .../dashboard/stylesheets/popover-menu.less | 28 +++++---- .../assets/src/dashboard/util/isValidChild.js | 9 +-- .../assets/src/visualizations/nvd3_vis.js | 2 + 14 files changed, 139 insertions(+), 71 deletions(-) diff --git a/superset/assets/src/dashboard/components/DashboardGrid.jsx b/superset/assets/src/dashboard/components/DashboardGrid.jsx index 7d57cb22ddba4..77503bb1fcac1 100644 --- a/superset/assets/src/dashboard/components/DashboardGrid.jsx +++ b/superset/assets/src/dashboard/components/DashboardGrid.jsx @@ -26,6 +26,7 @@ class DashboardGrid extends React.PureComponent { rowGuideTop: null, }; + this.handleTopDropTargetDrop = this.handleTopDropTargetDrop.bind(this); this.handleResizeStart = this.handleResizeStart.bind(this); this.handleResize = this.handleResize.bind(this); this.handleResizeStop = this.handleResizeStop.bind(this); @@ -75,6 +76,19 @@ class DashboardGrid extends React.PureComponent { })); } + handleTopDropTargetDrop(dropResult) { + if (dropResult) { + this.props.handleComponentDrop({ + ...dropResult, + destination: { + ...dropResult.destination, + // force appending as the first child if top drop target + index: 0, + }, + }); + } + } + render() { const { gridComponent, @@ -93,6 +107,7 @@ class DashboardGrid extends React.PureComponent { return width < 100 ? null : (
+ {/* empty drop target makes top droppable */} {editMode && ( {({ dropIndicatorProps }) => @@ -126,7 +142,7 @@ class DashboardGrid extends React.PureComponent { /> ))} - {/* render an empty drop target */} + {/* empty drop target makes bottom droppable */} {editMode && ( {({ dropIndicatorProps }) => diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx index 242102e1238f3..b21cbef845608 100644 --- a/superset/assets/src/dashboard/components/Header.jsx +++ b/superset/assets/src/dashboard/components/Header.jsx @@ -107,13 +107,13 @@ class Header extends React.PureComponent { {editMode && ( )} {editMode && ( )} diff --git a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx index a68423061f3cf..27cf7ea2de326 100644 --- a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -122,7 +122,6 @@ class ChartHolder extends React.Component { /> {editMode && ( - diff --git a/superset/assets/src/dashboard/components/gridComponents/Column.jsx b/superset/assets/src/dashboard/components/gridComponents/Column.jsx index a71d7325332e5..7249034e69110 100644 --- a/superset/assets/src/dashboard/components/gridComponents/Column.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/Column.jsx @@ -142,6 +142,18 @@ class Column extends React.PureComponent { ]} editMode={editMode} > + {editMode && ( + + + + + + )}
- {editMode && ( - - - - - - )} - {columnItems.map((componentId, itemIndex) => ( + {editMode && ( + + + + + + )}
- {editMode && ( - - - - - - )} - {rowItems.map((componentId, itemIndex) => ( .grid-column:after, -.dashboard--editing .grid-column:hover:after { +.dashboard--editing .hover-menu:hover + .grid-column:after { border: 1px dashed @gray-light; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1); } -.grid-column > .hover-menu--top { - top: -20px; -} - .grid-column--empty { min-height: 72px; } diff --git a/superset/assets/src/dashboard/stylesheets/components/header.less b/superset/assets/src/dashboard/stylesheets/components/header.less index 8b93164c850eb..b08c32ea93b74 100644 --- a/superset/assets/src/dashboard/stylesheets/components/header.less +++ b/superset/assets/src/dashboard/stylesheets/components/header.less @@ -15,6 +15,12 @@ margin-right: 8px; } +.dashboard-header .undo-action, +.dashboard-header .redo-action { + line-height: 18px; + font-size: 12px; +} + .dragdroppable-row .dashboard-component-header { cursor: move; } diff --git a/superset/assets/src/dashboard/stylesheets/components/row.less b/superset/assets/src/dashboard/stylesheets/components/row.less index 7df5675f966c6..382417eb009da 100644 --- a/superset/assets/src/dashboard/stylesheets/components/row.less +++ b/superset/assets/src/dashboard/stylesheets/components/row.less @@ -14,7 +14,8 @@ } /* hover indicator */ -.dashboard--editing .grid-row:after { +.dashboard--editing .grid-row:after, +.dashboard--editing .dashboard-component-tabs > .hover-menu:hover + div:after { border: 1px dashed transparent; content: ''; position: absolute; @@ -29,7 +30,8 @@ .dashboard--editing .resizable-container.resizable-container--resizing:hover > .grid-row:after, -.dashboard--editing .grid-row:hover:after { +.dashboard--editing .hover-menu:hover + .grid-row:after, +.dashboard--editing .dashboard-component-tabs > .hover-menu:hover + div:after { border: 1px dashed @gray-light; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1); } diff --git a/superset/assets/src/dashboard/stylesheets/grid.less b/superset/assets/src/dashboard/stylesheets/grid.less index a12ac97fd5782..8328220e30438 100644 --- a/superset/assets/src/dashboard/stylesheets/grid.less +++ b/superset/assets/src/dashboard/stylesheets/grid.less @@ -7,6 +7,11 @@ width: 100%; } +/* A bit more space for hover menus */ +.dashboard--editing .grid-container { + margin: 24px 28px; +} + /* this is the ParentSize wrapper */ .grid-container > div:first-child { height: inherit !important; @@ -20,11 +25,16 @@ } /* gutters between rows */ -.grid-content > div:not(:only-child):not(:last-child):not(.empty-grid-droptarget) { +.grid-content + > div:not(:only-child):not(:last-child):not(.empty-grid-droptarget--bottom):not(.empty-grid-droptarget--top) { margin-bottom: 16px; } -.empty-grid-droptarget { +.grid-content > .empty-grid-droptarget--top { + height: 24px; + margin-top: -24px; +} +.empty-grid-droptarget--bottom { width: 100%; height: 100%; } diff --git a/superset/assets/src/dashboard/stylesheets/hover-menu.less b/superset/assets/src/dashboard/stylesheets/hover-menu.less index 77edb0675a263..0863f6f18c93e 100644 --- a/superset/assets/src/dashboard/stylesheets/hover-menu.less +++ b/superset/assets/src/dashboard/stylesheets/hover-menu.less @@ -1,14 +1,16 @@ .hover-menu { opacity: 0; position: absolute; - z-index: 2; + z-index: 1000; + font-size: 14px; } .hover-menu--left { - width: 24px; - height: 100%; - top: 0; - left: -24px; + width: 30px; + top: 50%; + transform: translate(0, -50%); + left: -30px; + padding: 8px 0; display: flex; flex-direction: column; justify-content: center; @@ -19,21 +21,52 @@ margin-bottom: 12px; } -.dragdroppable-row .dragdroppable-row .hover-menu--left { - left: 1px; -} - .hover-menu--top { - width: 100%; - height: 24px; - top: 0; - left: 0; + height: 30px; + top: -28px; + left: 50%; + transform: translate(-50%); + padding: 0 8px; display: flex; flex-direction: row; justify-content: center; align-items: center; } +/* Special cases */ + +/* A row within a column has inset hover menu */ +.dragdroppable-column .dragdroppable-row .hover-menu--left { + left: -15px; + background: rgba(255, 255, 255, 0.5); + border: 1px dashed @gray-light; +} + +/* A column within a column or tabs has inset hover menu */ +.dragdroppable-column .dragdroppable-column .hover-menu--top, +.dashboard-component-tabs .dragdroppable-column .hover-menu--top { + top: -15px; + background: rgba(255, 255, 255, 0.5); + border: 1px dashed @gray-light; +} + +/* move Tabs hover menu to top near actual Tabs */ +.dashboard-component-tabs > .hover-menu--left { + top: 0; + transform: unset; + background: transparent; +} + +/* push Chart actions to upper right */ +.dragdroppable-column .dashboard-component-chart-holder > .hover-menu--top { + right: 8px; + top: 8px; + background: transparent; + border: none; + transform: unset; + left: unset; +} + .hover-menu--top > :nth-child(n):not(:only-child):not(:last-child) { margin-right: 12px; } diff --git a/superset/assets/src/dashboard/stylesheets/popover-menu.less b/superset/assets/src/dashboard/stylesheets/popover-menu.less index 848949b8cae47..d69006c788fc6 100644 --- a/superset/assets/src/dashboard/stylesheets/popover-menu.less +++ b/superset/assets/src/dashboard/stylesheets/popover-menu.less @@ -3,13 +3,14 @@ outline: none; } -.grid-row.grid-row--empty .with-popover-menu { /* drop indicator doesn't show up without this */ +.grid-row.grid-row--empty .with-popover-menu { + /* drop indicator doesn't show up without this */ width: 100%; height: 100%; } .with-popover-menu--focused:after { - content: ""; + content: ''; position: absolute; top: 1; left: -1; @@ -34,15 +35,15 @@ box-shadow: 0 1px 2px 1px rgba(0, 0, 0, 0.2); font-size: 14px; cursor: default; - z-index: 10; + z-index: 1000; } /* the focus menu doesn't account for parent padding */ .dashboard-component-tabs li .with-popover-menu--focused:after { top: -12px; - left: -2px; - width: ~"calc(100% + 4px)"; /* escape for .less */ - height: ~"calc(100% + 28px)"; + left: -8px; + width: ~'calc(100% + 16px)'; /* escape for .less */ + height: ~'calc(100% + 28px)'; } .dashboard-component-tabs li .popover-menu { @@ -57,7 +58,7 @@ /* vertical spacer after each menu item */ .popover-menu .menu-item:not(:only-child):not(:last-child):after { - content: ""; + content: ''; width: 1; height: 100%; background: @gray-light; @@ -86,12 +87,12 @@ background: @gray-light; } -.popover-dropdown .caret { /* without this the caret doesn't take up full width / is clipped */ +.popover-dropdown .caret { + /* without this the caret doesn't take up full width / is clipped */ width: auto; border-top-color: transparent; } - .hover-dropdown li.dropdown-item.active a, .popover-menu li.dropdown-item.active a { background: white; @@ -105,7 +106,7 @@ } .background-style-option:before { - content: ""; + content: ''; width: 1em; height: 1em; margin-right: 8px; @@ -124,7 +125,10 @@ } .background-style-option.background--transparent:before { - 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-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 + background-position: 0 0, 0 4px, 4px -4px, -4px 0px; } diff --git a/superset/assets/src/dashboard/util/isValidChild.js b/superset/assets/src/dashboard/util/isValidChild.js index d789f45b5d62d..a885c31561ffd 100644 --- a/superset/assets/src/dashboard/util/isValidChild.js +++ b/superset/assets/src/dashboard/util/isValidChild.js @@ -33,6 +33,7 @@ const depthOne = rootDepth + 1; const depthTwo = rootDepth + 2; const depthThree = rootDepth + 3; const depthFour = rootDepth + 4; +const depthFive = rootDepth + 5; // when moving components around the depth of child is irrelevant, note these are parent depths const parentMaxDepthLookup = { @@ -53,7 +54,7 @@ const parentMaxDepthLookup = { [ROW_TYPE]: { [CHART_TYPE]: depthFour, [MARKDOWN_TYPE]: depthFour, - [COLUMN_TYPE]: depthTwo, + [COLUMN_TYPE]: depthFour, }, [TABS_TYPE]: { @@ -70,9 +71,9 @@ const parentMaxDepthLookup = { }, [COLUMN_TYPE]: { - [CHART_TYPE]: depthThree, - [HEADER_TYPE]: depthThree, - [MARKDOWN_TYPE]: depthThree, + [CHART_TYPE]: depthFive, + [HEADER_TYPE]: depthFive, + [MARKDOWN_TYPE]: depthFive, [ROW_TYPE]: depthThree, }, diff --git a/superset/assets/src/visualizations/nvd3_vis.js b/superset/assets/src/visualizations/nvd3_vis.js index bf87287c78e9c..162145892adb1 100644 --- a/superset/assets/src/visualizations/nvd3_vis.js +++ b/superset/assets/src/visualizations/nvd3_vis.js @@ -458,6 +458,8 @@ export default function nvd3Vis(slice, payload) { customizeToolTip(chart, xAxisFormatter, [yAxisFormatter1, yAxisFormatter2]); chart.showLegend(width > BREAKPOINTS.small); } + // This is needed for correct chart dimensions if a chart is rendered in a hidden container + chart.width(width); chart.height(height); slice.container.css('height', height + 'px'); From 8010a8948cc4a76fe87d741dd92a03a63f184503 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 2 May 2018 17:17:20 -0700 Subject: [PATCH 06/15] [dashboard builder] better undo redo <> save changes state, notify upon reaching undo limit --- .../src/dashboard/actions/dashboardLayout.js | 60 +++++++++++++++++-- .../src/dashboard/actions/dashboardState.js | 29 ++++++++- .../src/dashboard/components/Dashboard.jsx | 7 +-- .../dashboard/components/DashboardBuilder.jsx | 8 +++ .../src/dashboard/components/Header.jsx | 49 +++++++++++---- .../src/dashboard/containers/Dashboard.jsx | 2 - .../dashboard/containers/DashboardHeader.jsx | 20 +++---- .../src/dashboard/reducers/dashboardState.js | 5 ++ .../src/dashboard/reducers/getInitialState.js | 1 + .../reducers/undoableDashboardLayout.js | 3 +- .../assets/src/dashboard/util/constants.js | 3 + 11 files changed, 149 insertions(+), 38 deletions(-) diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js index 5a04de543062f..d8ff739b66d4f 100644 --- a/superset/assets/src/dashboard/actions/dashboardLayout.js +++ b/superset/assets/src/dashboard/actions/dashboardLayout.js @@ -1,5 +1,12 @@ +import { ActionCreators as UndoActionCreators } from 'redux-undo'; + import { addInfoToast } from './messageToasts'; -import { setUnsavedChanges } from './dashboardState'; +import { + setUnsavedChanges, + onChange, + removeSliceFromDashboard, + addSliceToDashboard, +} from './dashboardState'; import { CHART_TYPE, MARKDOWN_TYPE, TABS_TYPE } from '../util/componentTypes'; import { DASHBOARD_ROOT_ID, @@ -21,7 +28,7 @@ export function updateComponents(nextComponents) { } export const DELETE_COMPONENT = 'DELETE_COMPONENT'; -export function deleteComponent(id, parentId) { +function deleteLayoutComponent(id, parentId) { return { type: DELETE_COMPONENT, payload: { @@ -31,6 +38,16 @@ export function deleteComponent(id, parentId) { }; } +export function deleteComponent(id, parentId) { + return (dispatch, getState) => { + dispatch(deleteLayoutComponent(id, parentId)); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} + export const CREATE_COMPONENT = 'CREATE_COMPONENT'; export function createComponent(dropResult) { return { @@ -64,7 +81,7 @@ export function deleteTopLevelTabs() { export const RESIZE_COMPONENT = 'RESIZE_COMPONENT'; export function resizeComponent({ id, width, height }) { return (dispatch, getState) => { - const { dashboardLayout: undoableLayout } = getState(); + const { dashboardLayout: undoableLayout, dashboardState } = getState(); const { present: dashboard } = undoableLayout; const component = dashboard[id]; const widthChanged = width && component.meta.width !== width; @@ -99,7 +116,9 @@ export function resizeComponent({ id, width, height }) { }); dispatch(updateComponents(updatedComponents)); - dispatch(setUnsavedChanges(true)); + if (!dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } } }; } @@ -149,9 +168,10 @@ export function handleComponentDrop(dropResult) { dispatch(moveComponent(dropResult)); } + const { dashboardLayout: undoableLayout, dashboardState } = getState(); + // if we moved a Tab and the parent Tabs no longer has children, delete it. if (!isNewComponent) { - const { dashboardLayout: undoableLayout } = getState(); const { present: layout } = undoableLayout; const sourceComponent = layout[source.id]; @@ -167,8 +187,36 @@ export function handleComponentDrop(dropResult) { } } - dispatch(setUnsavedChanges(true)); + if (!dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } return null; }; } + +// Undo redo ------------------------------------------------------------------ +export function undoLayoutAction() { + return (dispatch, getState) => { + dispatch(UndoActionCreators.undo()); + + const { dashboardLayout, dashboardState } = getState(); + + if ( + dashboardLayout.past.length === 0 && + !dashboardState.maxUndoHistoryExceeded + ) { + dispatch(setUnsavedChanges(false)); + } + }; +} + +export function redoLayoutAction() { + return (dispatch, getState) => { + dispatch(UndoActionCreators.redo()); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js index d80ec831a8e93..93250f1a83349 100644 --- a/superset/assets/src/dashboard/actions/dashboardState.js +++ b/superset/assets/src/dashboard/actions/dashboardState.js @@ -5,6 +5,7 @@ import { addChart, removeChart, refreshChart } from '../../chart/chartAction'; import { chart as initChart } from '../../chart/chartReducer'; import { fetchDatasourceMetadata } from '../../dashboard/actions/datasources'; import { applyDefaultFormData } from '../../explore/stores/store'; +import { addWarningToast } from './messageToasts'; export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; export function setUnsavedChanges(hasUnsavedChanges) { @@ -168,9 +169,31 @@ export function addSliceToDashboard(id) { }; } -export function removeSliceFromDashboard(chart) { +export function removeSliceFromDashboard(id) { return dispatch => { - dispatch(removeSlice(chart.id)); - dispatch(removeChart(chart.id)); + dispatch(removeSlice(id)); + dispatch(removeChart(id)); + }; +} + +// Undo history --------------------------------------------------------------- +export const SET_MAX_UNDO_HISTORY_EXCEEDED = 'SET_MAX_UNDO_HISTORY_EXCEEDED'; +export function setMaxUndoHistoryExceeded(maxUndoHistoryExceeded = true) { + return { + type: SET_MAX_UNDO_HISTORY_EXCEEDED, + payload: { maxUndoHistoryExceeded }, + }; +} + +export function approachingMaxUndoHistoryToast() { + return (dispatch, getState) => { + const { dashboardLayout } = getState(); + const historyLength = dashboardLayout.past.length; + + return dispatch( + addWarningToast( + `You have used all ${historyLength} undo slots and will not be able to fully undo subsequent actions. You may save your current state to reset the history.`, + ), + ); }; } diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx b/superset/assets/src/dashboard/components/Dashboard.jsx index 2d85ebf1cd633..369ed466228d1 100644 --- a/superset/assets/src/dashboard/components/Dashboard.jsx +++ b/superset/assets/src/dashboard/components/Dashboard.jsx @@ -27,7 +27,6 @@ import '../stylesheets/index.less'; const propTypes = { actions: PropTypes.shape({ addSliceToDashboard: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, removeSliceFromDashboard: PropTypes.func.isRequired, runQuery: PropTypes.func.isRequired, }).isRequired, @@ -98,16 +97,12 @@ class Dashboard extends React.PureComponent { key => currentChartIds.indexOf(key) === -1, ); this.props.actions.addSliceToDashboard(newChartId); - this.props.actions.onChange(); } else if (currentChartIds.length > nextChartIds.length) { // remove chart const removedChartId = currentChartIds.find( key => nextChartIds.indexOf(key) === -1, ); - this.props.actions.removeSliceFromDashboard( - this.props.charts[removedChartId], - ); - this.props.actions.onChange(); + this.props.actions.removeSliceFromDashboard(removedChartId); } } diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx index 2ed6328b10576..38d7089672149 100644 --- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx +++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx @@ -1,3 +1,4 @@ +/* eslint-env browser */ import cx from 'classnames'; import { DragDropContext } from 'react-dnd'; import HTML5Backend from 'react-dnd-html5-backend'; @@ -57,6 +58,13 @@ class DashboardBuilder extends React.Component { handleChangeTab({ tabIndex }) { this.setState(() => ({ tabIndex })); + setTimeout(() => { + if (window) + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }, 100); } render() { diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx index b21cbef845608..f9e4b0edd5f24 100644 --- a/superset/assets/src/dashboard/components/Header.jsx +++ b/superset/assets/src/dashboard/components/Header.jsx @@ -6,10 +6,10 @@ import Controls from './Controls'; import EditableTitle from '../../components/EditableTitle'; import Button from '../../components/Button'; import FaveStar from '../../components/FaveStar'; -// import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger'; import SaveModal from './SaveModal'; import { chartPropShape } from '../util/propShapes'; import { t } from '../../locales'; +import { UNDO_LIMIT } from '../util/constants'; const propTypes = { dashboardInfo: PropTypes.object.isRequired, @@ -31,23 +31,44 @@ const propTypes = { showBuilderPane: PropTypes.bool.isRequired, toggleBuilderPane: PropTypes.func.isRequired, hasUnsavedChanges: PropTypes.bool.isRequired, + maxUndoHistoryExceeded: PropTypes.bool.isRequired, // redux onUndo: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired, - canUndo: PropTypes.bool.isRequired, - canRedo: PropTypes.bool.isRequired, + undoLength: PropTypes.number.isRequired, + redoLength: PropTypes.number.isRequired, + setMaxUndoHistoryExceeded: PropTypes.func.isRequired, + approachingMaxUndoHistoryToast: PropTypes.func.isRequired, }; class Header extends React.PureComponent { constructor(props) { super(props); + this.state = { + didNotifyApproachingMaxUndoHistoryToast: false, + }; this.handleChangeText = this.handleChangeText.bind(this); this.toggleEditMode = this.toggleEditMode.bind(this); this.forceRefresh = this.forceRefresh.bind(this); } + componentWillReceiveProps(nextProps) { + if ( + nextProps.undoLength >= UNDO_LIMIT && + !this.props.maxUndoHistoryExceeded + ) { + this.props.setMaxUndoHistoryExceeded(); + } else if ( + UNDO_LIMIT - nextProps.undoLength <= 1 && + !this.state.didNotifyApproachingMaxUndoHistoryToast + ) { + this.setState(() => ({ didNotifyApproachingMaxUndoHistoryToast: true })); + this.props.approachingMaxUndoHistoryToast(); + } + } + forceRefresh() { return this.props.fetchCharts(Object.values(this.props.charts), true); } @@ -72,8 +93,8 @@ class Header extends React.PureComponent { expandedSlices, onUndo, onRedo, - canUndo, - canRedo, + undoLength, + redoLength, onChange, onSave, editMode, @@ -93,7 +114,7 @@ class Header extends React.PureComponent { onSaveTitle={this.handleChangeText} showTooltip={editMode} /> - + {editMode && ( - )} {editMode && ( - )} diff --git a/superset/assets/src/dashboard/containers/Dashboard.jsx b/superset/assets/src/dashboard/containers/Dashboard.jsx index 9af0e81f8c817..bcf2ace2190c7 100644 --- a/superset/assets/src/dashboard/containers/Dashboard.jsx +++ b/superset/assets/src/dashboard/containers/Dashboard.jsx @@ -4,7 +4,6 @@ import { connect } from 'react-redux'; import { addSliceToDashboard, removeSliceFromDashboard, - onChange, } from '../actions/dashboardState'; import { runQuery } from '../../chart/chartAction'; import Dashboard from '../components/Dashboard'; @@ -37,7 +36,6 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators( { addSliceToDashboard, - onChange, removeSliceFromDashboard, runQuery, }, diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx index d257ac0138374..dd6f11607f98b 100644 --- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx +++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx @@ -1,4 +1,3 @@ -import { ActionCreators as UndoActionCreators } from 'redux-undo'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; @@ -13,11 +12,10 @@ import { updateDashboardTitle, onChange, onSave, + setMaxUndoHistoryExceeded, + approachingMaxUndoHistoryToast, } from '../actions/dashboardState'; -import { - handleComponentDrop, - undoLayoutAction, -} from '../actions/dashboardLayout'; +import { undoLayoutAction, redoLayoutAction } from '../actions/dashboardLayout'; function mapStateToProps({ dashboardLayout: undoableLayout, @@ -27,8 +25,8 @@ function mapStateToProps({ }) { return { dashboardInfo, - canUndo: undoableLayout.past.length > 0, - canRedo: undoableLayout.future.length > 0, + undoLength: undoableLayout.past.length, + redoLength: undoableLayout.future.length, layout: undoableLayout.present, filters: dashboard.filters, dashboardTitle: dashboard.title, @@ -37,6 +35,7 @@ function mapStateToProps({ userId: dashboardInfo.userId, isStarred: !!dashboard.isStarred, hasUnsavedChanges: !!dashboard.hasUnsavedChanges, + maxUndoHistoryExceeded: !!dashboard.maxUndoHistoryExceeded, editMode: !!dashboard.editMode, showBuilderPane: !!dashboard.showBuilderPane, }; @@ -45,9 +44,8 @@ function mapStateToProps({ function mapDispatchToProps(dispatch) { return bindActionCreators( { - handleComponentDrop, - onUndo: UndoActionCreators.undo, - onRedo: UndoActionCreators.redo, + onUndo: undoLayoutAction, + onRedo: redoLayoutAction, setEditMode, toggleBuilderPane, fetchFaveStar, @@ -57,6 +55,8 @@ function mapDispatchToProps(dispatch) { updateDashboardTitle, onChange, onSave, + setMaxUndoHistoryExceeded, + approachingMaxUndoHistoryToast, }, dispatch, ); diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js index 7b5a17a907c2d..d7eeacc86b66b 100644 --- a/superset/assets/src/dashboard/reducers/dashboardState.js +++ b/superset/assets/src/dashboard/reducers/dashboardState.js @@ -9,6 +9,7 @@ import { REMOVE_SLICE, REMOVE_FILTER, SET_EDIT_MODE, + SET_MAX_UNDO_HISTORY_EXCEEDED, SET_UNSAVED_CHANGES, TOGGLE_BUILDER_PANE, TOGGLE_EXPAND_SLICE, @@ -55,6 +56,10 @@ export default function dashboardStateReducer(state = {}, action) { [SET_EDIT_MODE]() { return { ...state, editMode: action.editMode }; }, + [SET_MAX_UNDO_HISTORY_EXCEEDED]() { + const { maxUndoHistoryExceeded = true } = action.payload; + return { ...state, maxUndoHistoryExceeded }; + }, [TOGGLE_BUILDER_PANE]() { return { ...state, showBuilderPane: !state.showBuilderPane }; }, diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index d0b4d7b2479e9..534c066076da3 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -107,6 +107,7 @@ export default function(bootstrapData) { editMode: false, showBuilderPane: false, hasUnsavedChanges: false, + maxUndoHistoryExceeded: false, }, dashboardLayout, messageToasts: [], diff --git a/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js index b78c273334bec..11269194568a2 100644 --- a/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js +++ b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js @@ -1,4 +1,5 @@ import undoable, { includeAction } from 'redux-undo'; +import { UNDO_LIMIT } from '../util/constants'; import { UPDATE_COMPONENTS, DELETE_COMPONENT, @@ -13,7 +14,7 @@ import { import dashboardLayout from './dashboardLayout'; export default undoable(dashboardLayout, { - limit: 15, + limit: UNDO_LIMIT + 1, // length of history seems max out at limit - 1, so increment by 1 filter: includeAction([ UPDATE_COMPONENTS, DELETE_COMPONENT, diff --git a/superset/assets/src/dashboard/util/constants.js b/superset/assets/src/dashboard/util/constants.js index f35614c26958b..e4ca108b977b5 100644 --- a/superset/assets/src/dashboard/util/constants.js +++ b/superset/assets/src/dashboard/util/constants.js @@ -37,3 +37,6 @@ export const INFO_TOAST = 'INFO_TOAST'; export const SUCCESS_TOAST = 'SUCCESS_TOAST'; export const WARNING_TOAST = 'WARNING_TOAST'; export const DANGER_TOAST = 'DANGER_TOAST'; + +// undo-redo +export const UNDO_LIMIT = 50; From 0558f81a8d721c8cee9619f498903d668518847f Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 2 May 2018 17:46:56 -0700 Subject: [PATCH 07/15] [dashboard builder] hook up edit + create component actions to saved-state pop. --- .../src/dashboard/actions/dashboardLayout.js | 31 ++++++++++++++----- .../src/dashboard/actions/dashboardState.js | 11 ++++++- .../src/dashboard/components/Header.jsx | 22 +++++++------ .../dashboard/containers/DashboardHeader.jsx | 8 ++--- .../src/dashboard/reducers/dashboardState.js | 6 +++- .../reducers/undoableDashboardLayout.js | 4 ++- 6 files changed, 57 insertions(+), 25 deletions(-) diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js index d8ff739b66d4f..f3c6a0146e3aa 100644 --- a/superset/assets/src/dashboard/actions/dashboardLayout.js +++ b/superset/assets/src/dashboard/actions/dashboardLayout.js @@ -1,12 +1,7 @@ import { ActionCreators as UndoActionCreators } from 'redux-undo'; import { addInfoToast } from './messageToasts'; -import { - setUnsavedChanges, - onChange, - removeSliceFromDashboard, - addSliceToDashboard, -} from './dashboardState'; +import { setUnsavedChanges } from './dashboardState'; import { CHART_TYPE, MARKDOWN_TYPE, TABS_TYPE } from '../util/componentTypes'; import { DASHBOARD_ROOT_ID, @@ -18,7 +13,7 @@ import findParentId from '../util/findParentId'; // Component CRUD ------------------------------------------------------------- export const UPDATE_COMPONENTS = 'UPDATE_COMPONENTS'; -export function updateComponents(nextComponents) { +function updateLayoutComponents(nextComponents) { return { type: UPDATE_COMPONENTS, payload: { @@ -27,6 +22,16 @@ export function updateComponents(nextComponents) { }; } +export function updateComponents(nextComponents) { + return (dispatch, getState) => { + dispatch(updateLayoutComponents(nextComponents)); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} + export const DELETE_COMPONENT = 'DELETE_COMPONENT'; function deleteLayoutComponent(id, parentId) { return { @@ -49,7 +54,7 @@ export function deleteComponent(id, parentId) { } export const CREATE_COMPONENT = 'CREATE_COMPONENT'; -export function createComponent(dropResult) { +function createLayoutComponent(dropResult) { return { type: CREATE_COMPONENT, payload: { @@ -58,6 +63,16 @@ export function createComponent(dropResult) { }; } +export function createComponent(dropResult) { + return (dispatch, getState) => { + dispatch(createLayoutComponent(dropResult)); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} + // Tabs ----------------------------------------------------------------------- export const CREATE_TOP_LEVEL_TABS = 'CREATE_TOP_LEVEL_TABS'; export function createTopLevelTabs(dropResult) { diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js index 93250f1a83349..2ed008156926c 100644 --- a/superset/assets/src/dashboard/actions/dashboardState.js +++ b/superset/assets/src/dashboard/actions/dashboardState.js @@ -1,5 +1,6 @@ /* eslint camelcase: 0 */ import $ from 'jquery'; +import { ActionCreators as UndoActionCreators } from 'redux-undo'; import { addChart, removeChart, refreshChart } from '../../chart/chartAction'; import { chart as initChart } from '../../chart/chartReducer'; @@ -85,6 +86,14 @@ export function onSave() { return { type: ON_SAVE }; } +export function saveDashboard() { + return dispatch => { + dispatch(onSave()); + // clear undo history + dispatch(UndoActionCreators.clearHistory()); + }; +} + export function fetchCharts(chartList = [], force = false, interval = 0) { return (dispatch, getState) => { const timeout = getState().dashboardInfo.common.conf @@ -185,7 +194,7 @@ export function setMaxUndoHistoryExceeded(maxUndoHistoryExceeded = true) { }; } -export function approachingMaxUndoHistoryToast() { +export function maxUndoHistoryToast() { return (dispatch, getState) => { const { dashboardLayout } = getState(); const historyLength = dashboardLayout.past.length; diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx index f9e4b0edd5f24..c4143b2565b54 100644 --- a/superset/assets/src/dashboard/components/Header.jsx +++ b/superset/assets/src/dashboard/components/Header.jsx @@ -39,14 +39,14 @@ const propTypes = { undoLength: PropTypes.number.isRequired, redoLength: PropTypes.number.isRequired, setMaxUndoHistoryExceeded: PropTypes.func.isRequired, - approachingMaxUndoHistoryToast: PropTypes.func.isRequired, + maxUndoHistoryToast: PropTypes.func.isRequired, }; class Header extends React.PureComponent { constructor(props) { super(props); this.state = { - didNotifyApproachingMaxUndoHistoryToast: false, + didNotifyMaxUndoHistoryToast: false, }; this.handleChangeText = this.handleChangeText.bind(this); @@ -56,16 +56,17 @@ class Header extends React.PureComponent { componentWillReceiveProps(nextProps) { if ( - nextProps.undoLength >= UNDO_LIMIT && + UNDO_LIMIT - nextProps.undoLength <= 0 && + !this.state.didNotifyMaxUndoHistoryToast + ) { + this.setState(() => ({ didNotifyMaxUndoHistoryToast: true })); + this.props.maxUndoHistoryToast(); + } + if ( + nextProps.undoLength > UNDO_LIMIT && !this.props.maxUndoHistoryExceeded ) { this.props.setMaxUndoHistoryExceeded(); - } else if ( - UNDO_LIMIT - nextProps.undoLength <= 1 && - !this.state.didNotifyApproachingMaxUndoHistoryToast - ) { - this.setState(() => ({ didNotifyApproachingMaxUndoHistoryToast: true })); - this.props.approachingMaxUndoHistoryToast(); } } @@ -132,7 +133,8 @@ class Header extends React.PureComponent { onClick={onUndo} disabled={undoLength < 1} > -
+
+ {undoLength} of {UNDO_LIMIT} )} diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx index dd6f11607f98b..7acc9d89330d0 100644 --- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx +++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx @@ -11,9 +11,9 @@ import { startPeriodicRender, updateDashboardTitle, onChange, - onSave, + saveDashboard, setMaxUndoHistoryExceeded, - approachingMaxUndoHistoryToast, + maxUndoHistoryToast, } from '../actions/dashboardState'; import { undoLayoutAction, redoLayoutAction } from '../actions/dashboardLayout'; @@ -54,9 +54,9 @@ function mapDispatchToProps(dispatch) { startPeriodicRender, updateDashboardTitle, onChange, - onSave, + onSave: saveDashboard, setMaxUndoHistoryExceeded, - approachingMaxUndoHistoryToast, + maxUndoHistoryToast, }, dispatch, ); diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js index d7eeacc86b66b..2d44399827b44 100644 --- a/superset/assets/src/dashboard/reducers/dashboardState.js +++ b/superset/assets/src/dashboard/reducers/dashboardState.js @@ -77,7 +77,11 @@ export default function dashboardStateReducer(state = {}, action) { return { ...state, hasUnsavedChanges: true }; }, [ON_SAVE]() { - return { ...state, hasUnsavedChanges: false }; + return { + ...state, + hasUnsavedChanges: false, + maxUndoHistoryExceeded: false, + }; }, // filters diff --git a/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js index 11269194568a2..45e36ee64728e 100644 --- a/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js +++ b/superset/assets/src/dashboard/reducers/undoableDashboardLayout.js @@ -14,7 +14,9 @@ import { import dashboardLayout from './dashboardLayout'; export default undoable(dashboardLayout, { - limit: UNDO_LIMIT + 1, // length of history seems max out at limit - 1, so increment by 1 + // +1 because length of history seems max out at limit - 1 + // +1 again so we can detect if we've exceeded the limit + limit: UNDO_LIMIT + 2, filter: includeAction([ UPDATE_COMPONENTS, DELETE_COMPONENT, From 084fbc93fc53a5a449291d517447a001947e2786 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 3 May 2018 16:24:27 -0700 Subject: [PATCH 08/15] [dashboard builder] visual refinement, refactor Dashboard header content and updates into layout for undo-redo, refactor save dashboard modal to use toasts instead of notify. --- .../src/dashboard/actions/dashboardLayout.js | 17 ++++++++++ .../src/dashboard/actions/dashboardState.js | 7 +--- .../src/dashboard/components/Controls.jsx | 4 +++ .../src/dashboard/components/Header.jsx | 11 +++++-- .../src/dashboard/components/SaveModal.jsx | 32 +++++++++++-------- .../dashboard/containers/DashboardHeader.jsx | 19 +++++++++-- .../src/dashboard/reducers/getInitialState.js | 29 ++++++++++++----- .../stylesheets/components/header.less | 2 +- .../stylesheets/components/tabs.less | 13 +++++--- .../src/dashboard/stylesheets/hover-menu.less | 8 ++--- .../assets/src/dashboard/util/constants.js | 1 + .../util/dashboardLayoutConverter.js | 28 +++------------- .../src/dashboard/util/newComponentFactory.js | 1 - .../assets/src/dashboard/util/propShapes.jsx | 1 - superset/assets/stylesheets/superset.less | 6 ++-- 15 files changed, 107 insertions(+), 72 deletions(-) diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js index f3c6a0146e3aa..f8f8606acc0d0 100644 --- a/superset/assets/src/dashboard/actions/dashboardLayout.js +++ b/superset/assets/src/dashboard/actions/dashboardLayout.js @@ -7,6 +7,7 @@ import { DASHBOARD_ROOT_ID, NEW_COMPONENTS_SOURCE_ID, GRID_MIN_COLUMN_COUNT, + DASHBOARD_HEADER_ID, } from '../util/constants'; import dropOverflowsParent from '../util/dropOverflowsParent'; import findParentId from '../util/findParentId'; @@ -32,6 +33,22 @@ export function updateComponents(nextComponents) { }; } +export function updateDashboardTitle(text) { + return (dispatch, getState) => { + const { dashboardLayout } = getState(); + dispatch( + updateComponents({ + [DASHBOARD_HEADER_ID]: { + ...dashboardLayout.present[DASHBOARD_HEADER_ID], + meta: { + text, + }, + }, + }), + ); + }; +} + export const DELETE_COMPONENT = 'DELETE_COMPONENT'; function deleteLayoutComponent(id, parentId) { return { diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js index 2ed008156926c..10c0a26316f18 100644 --- a/superset/assets/src/dashboard/actions/dashboardState.js +++ b/superset/assets/src/dashboard/actions/dashboardState.js @@ -23,11 +23,6 @@ export function removeFilter(sliceId, col, vals, refresh = true) { return { type: REMOVE_FILTER, sliceId, col, vals, refresh }; } -export const UPDATE_DASHBOARD_TITLE = 'UPDATE_DASHBOARD_TITLE'; -export function updateDashboardTitle(title) { - return { type: UPDATE_DASHBOARD_TITLE, title }; -} - export const ADD_SLICE = 'ADD_SLICE'; export function addSlice(slice) { return { type: ADD_SLICE, slice }; @@ -89,7 +84,7 @@ export function onSave() { export function saveDashboard() { return dispatch => { dispatch(onSave()); - // clear undo history + // clear layout undo history dispatch(UndoActionCreators.clearHistory()); }; } diff --git a/superset/assets/src/dashboard/components/Controls.jsx b/superset/assets/src/dashboard/components/Controls.jsx index d815b3ceba139..07b6c3378e2a9 100644 --- a/superset/assets/src/dashboard/components/Controls.jsx +++ b/superset/assets/src/dashboard/components/Controls.jsx @@ -27,6 +27,8 @@ function updateDom(css) { } const propTypes = { + addSuccessToast: PropTypes.func.isRequired, + addDangerToast: PropTypes.func.isRequired, dashboardInfo: PropTypes.object.isRequired, dashboardTitle: PropTypes.string.isRequired, layout: PropTypes.object.isRequired, @@ -109,6 +111,8 @@ class Controls extends React.PureComponent { triggerNode={{t('Set auto-refresh interval')}} /> -
- {undoLength} of {UNDO_LIMIT} +
)} @@ -166,6 +167,8 @@ class Header extends React.PureComponent { ) : ( { + this.modal.close(); + this.props.onSave(); if (saveType === 'newDashboard') { window.location = `/superset/dashboard/${resp.id}/`; } else { - notify.success(t('This dashboard was saved successfully.')); + this.props.addSuccessToast( + t('This dashboard was saved successfully.'), + ); } }, - error(error) { - saveModal.close(); + error: error => { + this.modal.close(); const errorMsg = getAjaxErrorMsg(error); - notify.error( - `${t( - 'Sorry, there was an error saving this dashboard: ', - )}${errorMsg}`, + this.props.addDangerToast( + `${t('Sorry, there was an error saving this dashboard: ')} + ${errorMsg}`, ); }, }); @@ -115,7 +117,9 @@ class SaveModal extends React.PureComponent { this.saveDashboardRequest(data, url, saveType); } else if (saveType === 'newDashboard') { if (!newDashName) { - notify.error('You must pick a name for the new dashboard'); + this.props.addDangerToast( + t('You must pick a name for the new dashboard'), + ); } else { data.dashboard_title = newDashName; url = `/superset/copy_dash/${dashboardId}/`; diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx index 7acc9d89330d0..fe7e7bb84eb14 100644 --- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx +++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx @@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import DashboardHeader from '../components/Header'; + import { setEditMode, toggleBuilderPane, @@ -9,13 +10,21 @@ import { saveFaveStar, fetchCharts, startPeriodicRender, - updateDashboardTitle, onChange, saveDashboard, setMaxUndoHistoryExceeded, maxUndoHistoryToast, } from '../actions/dashboardState'; -import { undoLayoutAction, redoLayoutAction } from '../actions/dashboardLayout'; + +import { + undoLayoutAction, + redoLayoutAction, + updateDashboardTitle, +} from '../actions/dashboardLayout'; + +import { addSuccessToast, addDangerToast } from '../actions/messageToasts'; + +import { DASHBOARD_HEADER_ID } from '../util/constants'; function mapStateToProps({ dashboardLayout: undoableLayout, @@ -29,7 +38,9 @@ function mapStateToProps({ redoLength: undoableLayout.future.length, layout: undoableLayout.present, filters: dashboard.filters, - dashboardTitle: dashboard.title, + dashboardTitle: ( + (undoableLayout.present[DASHBOARD_HEADER_ID] || {}).meta || {} + ).text, expandedSlices: dashboard.expandedSlices, charts, userId: dashboardInfo.userId, @@ -44,6 +55,8 @@ function mapStateToProps({ function mapDispatchToProps(dispatch) { return bindActionCreators( { + addSuccessToast, + addDangerToast, onUndo: undoLayoutAction, onRedo: redoLayoutAction, setEditMode, diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index 534c066076da3..8b9385957dace 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -7,7 +7,8 @@ import { getParam } from '../../modules/utils'; import { applyDefaultFormData } from '../../explore/stores/store'; import { getColorFromScheme } from '../../modules/colors'; import layoutConverter from '../util/dashboardLayoutConverter'; -import { DASHBOARD_ROOT_ID } from '../util/constants'; +import { DASHBOARD_VERSION_KEY, DASHBOARD_HEADER_ID } from '../util/constants'; +import { DASHBOARD_HEADER_TYPE } from '../util/componentTypes'; export default function(bootstrapData) { const { user_id, datasources, common } = bootstrapData; @@ -35,19 +36,32 @@ export default function(bootstrapData) { } // dashboard layout - const positionJson = dashboard.position_json; - let layout; - if (!positionJson || !positionJson[DASHBOARD_ROOT_ID]) { - layout = layoutConverter(dashboard); - } else { - layout = positionJson; + const { position_json: positionJson } = dashboard; + + if (positionJson && positionJson.DASHBOARD_ROOT_ID) { + positionJson[DASHBOARD_VERSION_KEY] = 'v2'; } + const layout = + !positionJson || positionJson[DASHBOARD_VERSION_KEY] !== 'v2' + ? layoutConverter(dashboard) + : positionJson; + + // store the header as a layout component so we can undo/redo changes + layout[DASHBOARD_HEADER_ID] = { + id: DASHBOARD_HEADER_ID, + type: DASHBOARD_HEADER_TYPE, + meta: { + text: dashboard.dashboard_title, + }, + }; + const dashboardLayout = { past: [], present: layout, future: [], }; + delete dashboard.position_json; delete dashboard.css; @@ -99,7 +113,6 @@ export default function(bootstrapData) { common, }, dashboardState: { - title: dashboard.dashboard_title, sliceIds, refresh: false, filters, diff --git a/superset/assets/src/dashboard/stylesheets/components/header.less b/superset/assets/src/dashboard/stylesheets/components/header.less index b08c32ea93b74..023c78c1f3e4e 100644 --- a/superset/assets/src/dashboard/stylesheets/components/header.less +++ b/superset/assets/src/dashboard/stylesheets/components/header.less @@ -1,6 +1,6 @@ .dashboard-component-header { width: 100%; - line-height: 1em; + line-height: 1.1; font-weight: 700; padding: 16px 0; color: @almost-black; diff --git a/superset/assets/src/dashboard/stylesheets/components/tabs.less b/superset/assets/src/dashboard/stylesheets/components/tabs.less index f67c1510074eb..02039b49b1b03 100644 --- a/superset/assets/src/dashboard/stylesheets/components/tabs.less +++ b/superset/assets/src/dashboard/stylesheets/components/tabs.less @@ -30,12 +30,12 @@ } .dashboard-component-tabs .nav-tabs > li.active > a:after { - content: ""; + content: ''; position: absolute; height: 3px; width: 100%; bottom: 0; - background: linear-gradient(to right, #E32464, #2C2261); + background: linear-gradient(to right, #e32464, #2c2261); } .dashboard-component-tabs .nav-tabs > li > a:hover { @@ -53,9 +53,10 @@ cursor: move; } +/* These expande the outline border + drop indicator for tabs */ .dashboard-component-tabs .nav-tabs > li .drop-indicator { top: -12px !important; - height: ~"calc(100% + 24px)" !important; + height: ~'calc(100% + 24px)' !important; } .dashboard-component-tabs .nav-tabs > li .drop-indicator--left { @@ -69,7 +70,7 @@ .dashboard-component-tabs .nav-tabs > li .drop-indicator--top, .dashboard-component-tabs .nav-tabs > li .drop-indicator--bottom { left: -12px !important; - width: ~"calc(100% + 24px)" !important; /* escape for .less */ + width: ~'calc(100% + 24px)' !important; /* escape for .less */ opacity: 0.4; } @@ -78,3 +79,7 @@ font-size: 14px; margin-top: 3px; } + +.dashboard-component-tabs li .editable-title input[type='button'] { + cursor: pointer; +} diff --git a/superset/assets/src/dashboard/stylesheets/hover-menu.less b/superset/assets/src/dashboard/stylesheets/hover-menu.less index 0863f6f18c93e..a51507b62889a 100644 --- a/superset/assets/src/dashboard/stylesheets/hover-menu.less +++ b/superset/assets/src/dashboard/stylesheets/hover-menu.less @@ -38,16 +38,16 @@ /* A row within a column has inset hover menu */ .dragdroppable-column .dragdroppable-row .hover-menu--left { left: -15px; - background: rgba(255, 255, 255, 0.5); - border: 1px dashed @gray-light; + background: white; + border: 1px solid @gray-light; } /* A column within a column or tabs has inset hover menu */ .dragdroppable-column .dragdroppable-column .hover-menu--top, .dashboard-component-tabs .dragdroppable-column .hover-menu--top { top: -15px; - background: rgba(255, 255, 255, 0.5); - border: 1px dashed @gray-light; + background: white; + border: 1px solid @gray-light; } /* move Tabs hover menu to top near actual Tabs */ diff --git a/superset/assets/src/dashboard/util/constants.js b/superset/assets/src/dashboard/util/constants.js index e4ca108b977b5..d682687623cf1 100644 --- a/superset/assets/src/dashboard/util/constants.js +++ b/superset/assets/src/dashboard/util/constants.js @@ -2,6 +2,7 @@ export const DASHBOARD_GRID_ID = 'DASHBOARD_GRID_ID'; export const DASHBOARD_HEADER_ID = 'DASHBOARD_HEADER_ID'; export const DASHBOARD_ROOT_ID = 'DASHBOARD_ROOT_ID'; +export const DASHBOARD_VERSION_KEY = 'DASHBOARD_VERSION_KEY'; export const NEW_COMPONENTS_SOURCE_ID = 'NEW_COMPONENTS_SOURCE_ID'; export const NEW_CHART_ID = 'NEW_CHART_ID'; diff --git a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js index 6b58e8b603e54..7730955345a78 100644 --- a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js +++ b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js @@ -5,14 +5,14 @@ import { ROW_TYPE, COLUMN_TYPE, CHART_TYPE, - DASHBOARD_HEADER_TYPE, DASHBOARD_ROOT_TYPE, DASHBOARD_GRID_TYPE, } from './componentTypes'; + import { DASHBOARD_GRID_ID, - DASHBOARD_HEADER_ID, DASHBOARD_ROOT_ID, + DASHBOARD_VERSION_KEY, } from './constants'; const MAX_RECURSIVE_LEVEL = 6; @@ -55,7 +55,6 @@ function getBoundary(positions) { function getRowContainer() { return { - version: 'v2', type: ROW_TYPE, id: `DASHBOARD_ROW_TYPE-${generateId()}`, children: [], @@ -67,7 +66,6 @@ function getRowContainer() { function getColContainer() { return { - version: 'v2', type: COLUMN_TYPE, id: `DASHBOARD_COLUMN_TYPE-${generateId()}`, children: [], @@ -88,7 +86,6 @@ function getChartHolder(item) { }; return { - version: 'v2', type: CHART_TYPE, id: `DASHBOARD_CHART_TYPE-${generateId()}`, children: [], @@ -111,21 +108,6 @@ function getChildrenSum(items, attr, layout) { ); } -// function getChildrenMax(items, attr, layout) { -// return Math.max.apply(null, items.map((childId) => { -// const child = layout[childId]; -// if (child.type === ROW_TYPE && attr === 'width') { -// // rows don't have widths themselves -// return getChildrenSum(child.children, attr, layout); -// } else if (child.type === COLUMN_TYPE && attr === 'height') { -// // columns don't have heights themselves -// return getChildrenSum(child.children, attr, layout); -// } -// -// return child.meta[attr]; -// })); -// } - function sortByRowId(item1, item2) { return item1.row - item2.row; } @@ -322,6 +304,7 @@ export default function(dashboard) { }); const root = { + [DASHBOARD_VERSION_KEY]: 'v2', [DASHBOARD_ROOT_ID]: { type: DASHBOARD_ROOT_TYPE, id: DASHBOARD_ROOT_ID, @@ -332,11 +315,8 @@ export default function(dashboard) { id: DASHBOARD_GRID_ID, children: [], }, - [DASHBOARD_HEADER_ID]: { - type: DASHBOARD_HEADER_TYPE, - id: DASHBOARD_HEADER_ID, - }, }; + doConvert(positions, 0, root[DASHBOARD_GRID_ID], root); // remove row's width/height and col's height diff --git a/superset/assets/src/dashboard/util/newComponentFactory.js b/superset/assets/src/dashboard/util/newComponentFactory.js index 4e2de37e439f6..8d259afa4b658 100644 --- a/superset/assets/src/dashboard/util/newComponentFactory.js +++ b/superset/assets/src/dashboard/util/newComponentFactory.js @@ -34,7 +34,6 @@ function uuid(type) { export default function entityFactory(type, meta) { return { - version: 'v0', type, id: uuid(type), children: [], diff --git a/superset/assets/src/dashboard/util/propShapes.jsx b/superset/assets/src/dashboard/util/propShapes.jsx index 73a10b02a9086..c8e198180f050 100644 --- a/superset/assets/src/dashboard/util/propShapes.jsx +++ b/superset/assets/src/dashboard/util/propShapes.jsx @@ -66,7 +66,6 @@ export const slicePropShape = PropTypes.shape({ }); export const dashboardStatePropShape = PropTypes.shape({ - title: PropTypes.string.isRequired, sliceIds: PropTypes.object.isRequired, refresh: PropTypes.bool.isRequired, filters: PropTypes.object, diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less index d75655141f2ba..0e8ffad469321 100644 --- a/superset/assets/stylesheets/superset.less +++ b/superset/assets/stylesheets/superset.less @@ -114,7 +114,6 @@ span.title-block { } .nvtooltip { - //position: relative !important; z-index: 888; transition: opacity 0ms linear; -moz-transition: opacity 0ms linear; @@ -238,13 +237,14 @@ table.table-no-hover tr:hover { line-height: inherit; white-space: normal; text-align: left; + cursor: initial; } -.editable-title.editable-title--editable { +.editable-title.editable-title--editable input[type="button"] { cursor: pointer; } -.editable-title.editable-title--editing { +.editable-title.editable-title--editing input[type="button"] { cursor: text; } From 8519cd6c6d1b23746a0caca56b56dcb7c910a2a7 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 4 May 2018 00:45:01 -0700 Subject: [PATCH 09/15] [dashboard builder] refactor chart name update logic to use layout for undo redo, save slice name changes on dashboard save --- .../src/dashboard/actions/sliceEntities.js | 35 ------------------- .../src/dashboard/components/SliceHeader.jsx | 26 ++++---------- .../components/gridComponents/Chart.jsx | 8 +++-- .../components/gridComponents/ChartHolder.jsx | 18 +++++++++- .../assets/src/dashboard/containers/Chart.jsx | 4 +-- .../src/dashboard/reducers/getInitialState.js | 18 +++++++++- superset/views/core.py | 29 ++++++++++++--- 7 files changed, 74 insertions(+), 64 deletions(-) diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js b/superset/assets/src/dashboard/actions/sliceEntities.js index 6922753bacda1..37781f904356f 100644 --- a/superset/assets/src/dashboard/actions/sliceEntities.js +++ b/superset/assets/src/dashboard/actions/sliceEntities.js @@ -1,41 +1,6 @@ /* eslint camelcase: 0 */ -/* global notify */ import $ from 'jquery'; -export const UPDATE_SLICE_NAME = 'UPDATE_SLICE_NAME'; -export function updateSliceName(key, sliceName) { - return { type: UPDATE_SLICE_NAME, key, sliceName }; -} - -export function saveSliceName(slice, sliceName) { - const oldName = slice.slice_name; - return dispatch => { - const sliceParams = {}; - sliceParams.slice_id = slice.slice_id; - sliceParams.action = 'overwrite'; - sliceParams.slice_name = sliceName; - - const url = `${slice.slice_url}&${Object.keys(sliceParams) - .map(key => `${key}=${sliceParams[key]}`) - .join('&')}`; - const key = slice.slice_id; - return $.ajax({ - url, - type: 'POST', - success: () => { - dispatch(updateSliceName(key, sliceName)); - notify.success('This slice name was saved successfully.'); - }, - error: () => { - // if server-side reject the overwrite action, - // revert to old state - dispatch(updateSliceName(key, oldName)); - notify.error("You don't have the rights to alter this slice"); - }, - }); - }; -} - export const SET_ALL_SLICES = 'SET_ALL_SLICES'; export function setAllSlices(slices) { return { type: SET_ALL_SLICES, slices }; diff --git a/superset/assets/src/dashboard/components/SliceHeader.jsx b/superset/assets/src/dashboard/components/SliceHeader.jsx index bcdaedf2073cf..0c572d803bd3f 100644 --- a/superset/assets/src/dashboard/components/SliceHeader.jsx +++ b/superset/assets/src/dashboard/components/SliceHeader.jsx @@ -20,6 +20,7 @@ const propTypes = { editMode: PropTypes.bool, annotationQuery: PropTypes.object, annotationError: PropTypes.object, + sliceName: PropTypes.string, }; const defaultProps = { @@ -36,21 +37,10 @@ const defaultProps = { cachedDttm: null, isCached: false, isExpanded: false, + sliceName: '', }; class SliceHeader extends React.PureComponent { - constructor(props) { - super(props); - - this.onSaveTitle = this.onSaveTitle.bind(this); - } - - onSaveTitle(newTitle) { - if (this.props.updateSliceName) { - this.props.updateSliceName(this.props.slice.slice_id, newTitle); - } - } - render() { const { slice, @@ -62,6 +52,7 @@ class SliceHeader extends React.PureComponent { exploreChart, exportCSV, innerRef, + sliceName, } = this.props; const annoationsLoading = t('Annotation layers are still loading.'); @@ -71,13 +62,10 @@ class SliceHeader extends React.PureComponent {
{!!Object.values(this.props.annotationQuery).length && ( {/* diff --git a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx index 27cf7ea2de326..bc9f430158ce0 100644 --- a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import Chart from '../../containers/Chart'; import DeleteComponentButton from '../DeleteComponentButton'; import DragDroppable from '../dnd/DragDroppable'; -import DragHandle from '../dnd/DragHandle'; import HoverMenu from '../menu/HoverMenu'; import ResizableContainer from '../resizable/ResizableContainer'; import { componentShape } from '../../util/propShapes'; @@ -35,6 +34,7 @@ const propTypes = { // dnd deleteComponent: PropTypes.func.isRequired, + updateComponents: PropTypes.func.isRequired, handleComponentDrop: PropTypes.func.isRequired, }; @@ -49,6 +49,7 @@ class ChartHolder extends React.Component { this.handleChangeFocus = this.handleChangeFocus.bind(this); this.handleDeleteComponent = this.handleDeleteComponent.bind(this); + this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this); } handleChangeFocus(nextFocus) { @@ -60,6 +61,19 @@ class ChartHolder extends React.Component { deleteComponent(id, parentId); } + handleUpdateSliceName(nextName) { + const { component, updateComponents } = this.props; + updateComponents({ + [component.id]: { + ...component, + meta: { + ...component.meta, + chartName: nextName, + }, + }, + }); + } + render() { const { isFocused } = this.state; @@ -119,6 +133,8 @@ class ChartHolder extends React.Component { id={component.meta.chartId} width={widthMultiple * columnWidth} height={component.meta.height * GRID_BASE_UNIT - CHART_MARGIN} + sliceName={component.meta.chartName} + updateSliceName={this.handleUpdateSliceName} /> {editMode && ( diff --git a/superset/assets/src/dashboard/containers/Chart.jsx b/superset/assets/src/dashboard/containers/Chart.jsx index 470176bf8877b..61627d21fc44e 100644 --- a/superset/assets/src/dashboard/containers/Chart.jsx +++ b/superset/assets/src/dashboard/containers/Chart.jsx @@ -8,7 +8,7 @@ import { } from '../actions/dashboardState'; import { refreshChart } from '../../chart/chartAction'; import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilters'; -import { saveSliceName } from '../actions/sliceEntities'; +import { updateComponents } from '../actions/dashboardLayout'; import Chart from '../components/gridComponents/Chart'; function mapStateToProps( @@ -46,7 +46,7 @@ function mapStateToProps( function mapDispatchToProps(dispatch) { return bindActionCreators( { - saveSliceName, + updateComponents, toggleExpandSlice, addFilter, refreshChart, diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index 8b9385957dace..447c2915625c3 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -8,7 +8,7 @@ import { applyDefaultFormData } from '../../explore/stores/store'; import { getColorFromScheme } from '../../modules/colors'; import layoutConverter from '../util/dashboardLayoutConverter'; import { DASHBOARD_VERSION_KEY, DASHBOARD_HEADER_ID } from '../util/constants'; -import { DASHBOARD_HEADER_TYPE } from '../util/componentTypes'; +import { DASHBOARD_HEADER_TYPE, CHART_TYPE } from '../util/componentTypes'; export default function(bootstrapData) { const { user_id, datasources, common } = bootstrapData; @@ -65,6 +65,14 @@ export default function(bootstrapData) { delete dashboard.position_json; delete dashboard.css; + // creat a lookup to sync layout names with slice names + const chartIdToLayoutId = {}; + Object.values(layout).forEach(layoutComponent => { + if (layoutComponent.type === CHART_TYPE) { + chartIdToLayoutId[layoutComponent.meta.chartId] = layoutComponent.id; + } + }); + const chartQueries = {}; const slices = {}; const sliceIds = new Set(); @@ -90,6 +98,14 @@ export default function(bootstrapData) { }; sliceIds.add(key); + + // sync layout names with current slice names in case a slice was edited + // in explore since the layout was updated. name updates go through layout for undo/redo + // functionality and python updates slice names based on layout upon dashboard save + const layoutId = chartIdToLayoutId[key]; + if (layoutId && layout[layoutId]) { + layout[layoutId].meta.chartName = slice.slice_name; + } }); return { diff --git a/superset/views/core.py b/superset/views/core.py index acedd779b964f..edb3ac25f266d 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -1556,8 +1556,15 @@ def copy_dash(self, dashboard_id): new_slice.dashboards.append(dash) old_to_new_sliceids['{}'.format(slc.id)] =\ '{}'.format(new_slice.id) - for d in data['positions']: - d['slice_id'] = old_to_new_sliceids[d['slice_id']] + + # update chartId of layout entities + for value in data['positions'].values(): + if isinstance(value, dict) and value.get('meta') \ + and value.get('meta').get('chartId'): + + old_id = value.get('meta').get('chartId') + new_id = old_to_new_sliceids[old_id] + value['meta']['chartId'] = new_id else: dash.slices = original_dash.slices dash.params = original_dash.params @@ -1580,6 +1587,7 @@ def save_dash(self, dashboard_id): .filter_by(id=dashboard_id).first()) check_ownership(dash, raise_if_false=True) data = json.loads(request.form.get('data')) + original_slice_names = {(slc.id): slc.slice_name for slc in dash.slices} self._set_dash_metadata(dash, data) session.merge(dash) session.commit() @@ -1591,15 +1599,28 @@ def _set_dash_metadata(dashboard, data): positions = data['positions'] # find slices in the position data slice_ids = [] + slice_id_to_name = {} for value in positions.values(): - if value.get('meta') and value.get('meta').get('chartId'): - slice_ids.append(int(value.get('meta').get('chartId'))) + if isinstance(value, dict) and value.get('meta') and value.get('meta').get('chartId'): + slice_id = int(value.get('meta').get('chartId')) + slice_ids.append(slice_id) + slice_id_to_name[slice_id] = value.get('meta').get('chartName') + session = db.session() Slice = models.Slice # noqa current_slices = session.query(Slice).filter( Slice.id.in_(slice_ids)).all() dashboard.slices = current_slices + + # update slice names. this assumes user has permissions to update the slice + for slc in dashboard.slices: + new_name = slice_id_to_name[slc.id] + if slc.slice_name != new_name: + slc.slice_name = new_name + session.merge(slc) + session.flush() + dashboard.position_json = json.dumps(positions, indent=4, sort_keys=True) md = dashboard.params_dict dashboard.css = data.get('css') From efc900aabfd64362b16241e1d174465a3422f388 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 4 May 2018 11:08:00 -0700 Subject: [PATCH 10/15] [dashboard builder] fix layout converter slice_id + chartId type casting, don't change grid size upon edit (perf) --- .../src/dashboard/stylesheets/grid.less | 5 --- .../src/dashboard/stylesheets/hover-menu.less | 12 +++---- .../util/dashboardLayoutConverter.js | 33 ++++++++----------- superset/views/core.py | 9 ++--- 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/superset/assets/src/dashboard/stylesheets/grid.less b/superset/assets/src/dashboard/stylesheets/grid.less index 8328220e30438..9d09ac7017f48 100644 --- a/superset/assets/src/dashboard/stylesheets/grid.less +++ b/superset/assets/src/dashboard/stylesheets/grid.less @@ -7,11 +7,6 @@ width: 100%; } -/* A bit more space for hover menus */ -.dashboard--editing .grid-container { - margin: 24px 28px; -} - /* this is the ParentSize wrapper */ .grid-container > div:first-child { height: inherit !important; diff --git a/superset/assets/src/dashboard/stylesheets/hover-menu.less b/superset/assets/src/dashboard/stylesheets/hover-menu.less index a51507b62889a..ca07fb3ec4e32 100644 --- a/superset/assets/src/dashboard/stylesheets/hover-menu.less +++ b/superset/assets/src/dashboard/stylesheets/hover-menu.less @@ -6,10 +6,10 @@ } .hover-menu--left { - width: 30px; + width: 24px; top: 50%; transform: translate(0, -50%); - left: -30px; + left: -24px; padding: 8px 0; display: flex; flex-direction: column; @@ -22,8 +22,8 @@ } .hover-menu--top { - height: 30px; - top: -28px; + height: 24px; + top: -24px; left: 50%; transform: translate(-50%); padding: 0 8px; @@ -37,7 +37,7 @@ /* A row within a column has inset hover menu */ .dragdroppable-column .dragdroppable-row .hover-menu--left { - left: -15px; + left: -12px; background: white; border: 1px solid @gray-light; } @@ -45,7 +45,7 @@ /* A column within a column or tabs has inset hover menu */ .dragdroppable-column .dragdroppable-column .hover-menu--top, .dashboard-component-tabs .dragdroppable-column .hover-menu--top { - top: -15px; + top: -12px; background: white; border: 1px solid @gray-light; } diff --git a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js index 7730955345a78..f3f6061c4dcf0 100644 --- a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js +++ b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js @@ -76,22 +76,18 @@ function getColContainer() { } function getChartHolder(item) { - const { row, col, size_x, size_y, slice_id } = item; - const converted = { - row: Math.round(row / GRID_RATIO), - col: Math.floor((col - 1) / GRID_RATIO) + 1, - size_x: Math.max(1, Math.floor(size_x / GRID_RATIO)), - size_y: Math.max(1, Math.round(size_y / GRID_RATIO)), - slice_id, - }; + const { size_x, size_y, slice_id } = item; + + const width = Math.max(1, Math.floor(size_x / GRID_RATIO)); + const height = Math.max(1, Math.round(size_y / GRID_RATIO)); return { type: CHART_TYPE, id: `DASHBOARD_CHART_TYPE-${generateId()}`, children: [], meta: { - width: converted.size_x, - height: Math.round(converted.size_y * 100 / ROW_HEIGHT), + width, + height: Math.round(height * 100 / ROW_HEIGHT), chartId: parseInt(slice_id, 10), }, }; @@ -271,10 +267,10 @@ export default function(dashboard) { // position data clean up. some dashboard didn't have position_json let { position_json } = dashboard; - const posDict = {}; + const positionDict = {}; if (Array.isArray(position_json)) { position_json.forEach(position => { - posDict[position.slice_id] = position; + positionDict[position.slice_id] = position; }); } else { position_json = []; @@ -285,22 +281,21 @@ export default function(dashboard) { Math.max.apply(null, position_json.map(pos => pos.row + pos.size_y)), ); let newSliceCounter = 0; - dashboard.slices.forEach(slice => { - const sliceId = slice.slice_id; - let pos = posDict[sliceId]; - if (!pos) { + dashboard.slices.forEach(({ slice_id }) => { + let position = positionDict[slice_id]; + if (!position) { // append new slices to dashboard bottom, 3 slices per row - pos = { + position = { col: (newSliceCounter % 3) * 16 + 1, row: lastRowId + Math.floor(newSliceCounter / 3) * 16, size_x: 16, size_y: 16, - slice_id: String(sliceId), + slice_id, }; newSliceCounter += 1; } - positions.push(pos); + positions.push(position); }); const root = { diff --git a/superset/views/core.py b/superset/views/core.py index edb3ac25f266d..fde5be7ba1618 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -1554,8 +1554,7 @@ def copy_dash(self, dashboard_id): session.add(new_slice) session.flush() new_slice.dashboards.append(dash) - old_to_new_sliceids['{}'.format(slc.id)] =\ - '{}'.format(new_slice.id) + old_to_new_sliceids[slc.id] = new_slice.id # update chartId of layout entities for value in data['positions'].values(): @@ -1601,8 +1600,10 @@ def _set_dash_metadata(dashboard, data): slice_ids = [] slice_id_to_name = {} for value in positions.values(): - if isinstance(value, dict) and value.get('meta') and value.get('meta').get('chartId'): - slice_id = int(value.get('meta').get('chartId')) + if isinstance(value, dict) and value.get('meta') \ + and value.get('meta').get('chartId'): + + slice_id = value.get('meta').get('chartId') slice_ids.append(slice_id) slice_id_to_name[slice_id] = value.get('meta').get('chartName') From 630469840a01e7af91b71764065af13947505f01 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 4 May 2018 11:19:14 -0700 Subject: [PATCH 11/15] [dashboard builder] don't set version key in getInitialState --- superset/assets/src/dashboard/reducers/getInitialState.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index 447c2915625c3..ba24b36bff1ec 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -38,10 +38,6 @@ export default function(bootstrapData) { // dashboard layout const { position_json: positionJson } = dashboard; - if (positionJson && positionJson.DASHBOARD_ROOT_ID) { - positionJson[DASHBOARD_VERSION_KEY] = 'v2'; - } - const layout = !positionJson || positionJson[DASHBOARD_VERSION_KEY] !== 'v2' ? layoutConverter(dashboard) From bbe8319149d588558f7ea96519bce0589c0d439b Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 4 May 2018 17:29:49 -0700 Subject: [PATCH 12/15] [dashboard builder] make top level tabs addition/removal undoable, fix double sticky tabs + side panel. --- .../src/dashboard/actions/dashboardLayout.js | 24 +++++++++++++++++-- .../components/BuilderComponentPane.jsx | 22 ++++++++++++++--- .../dashboard/components/DashboardBuilder.jsx | 12 +++++++--- .../stylesheets/components/header.less | 2 +- .../assets/src/dashboard/stylesheets/dnd.less | 4 ++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js index f8f8606acc0d0..c64ea0dde53d9 100644 --- a/superset/assets/src/dashboard/actions/dashboardLayout.js +++ b/superset/assets/src/dashboard/actions/dashboardLayout.js @@ -92,7 +92,7 @@ export function createComponent(dropResult) { // Tabs ----------------------------------------------------------------------- export const CREATE_TOP_LEVEL_TABS = 'CREATE_TOP_LEVEL_TABS'; -export function createTopLevelTabs(dropResult) { +function createTopLevelTabsAction(dropResult) { return { type: CREATE_TOP_LEVEL_TABS, payload: { @@ -101,14 +101,34 @@ export function createTopLevelTabs(dropResult) { }; } +export function createTopLevelTabs(dropResult) { + return (dispatch, getState) => { + dispatch(createTopLevelTabsAction(dropResult)); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} + export const DELETE_TOP_LEVEL_TABS = 'DELETE_TOP_LEVEL_TABS'; -export function deleteTopLevelTabs() { +function deleteTopLevelTabsAction() { return { type: DELETE_TOP_LEVEL_TABS, payload: {}, }; } +export function deleteTopLevelTabs(dropResult) { + return (dispatch, getState) => { + dispatch(deleteTopLevelTabsAction(dropResult)); + + if (!getState().dashboardState.hasUnsavedChanges) { + dispatch(setUnsavedChanges(true)); + } + }; +} + // Resize --------------------------------------------------------------------- export const RESIZE_COMPONENT = 'RESIZE_COMPONENT'; export function resizeComponent({ id, width, height }) { diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx index 48380b34287a2..ac6d8e0b51392 100644 --- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx +++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx @@ -1,4 +1,5 @@ /* eslint-env browser */ +import PropTypes from 'prop-types'; import React from 'react'; import cx from 'classnames'; import { StickyContainer, Sticky } from 'react-sticky'; @@ -11,6 +12,14 @@ import NewTabs from './gridComponents/new/NewTabs'; import SliceAdder from '../containers/SliceAdder'; import { t } from '../../locales'; +const propTypes = { + topOffset: PropTypes.number, +}; + +const defaultProps = { + topOffset: 0, +}; + class BuilderComponentPane extends React.PureComponent { constructor(props) { super(props); @@ -29,11 +38,15 @@ class BuilderComponentPane extends React.PureComponent { } render() { + const { topOffset } = this.props; return ( - - {({ style, calculatedHeight }) => ( -
+ + {({ style, calculatedHeight, isSticky }) => ( +
@@ -81,4 +94,7 @@ class BuilderComponentPane extends React.PureComponent { } } +BuilderComponentPane.propTypes = propTypes; +BuilderComponentPane.defaultProps = defaultProps; + export default BuilderComponentPane; diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx index 38d7089672149..13538dd75db65 100644 --- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx +++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx @@ -25,6 +25,8 @@ import { DASHBOARD_ROOT_DEPTH, } from '../util/constants'; +const TABS_HEIGHT = 47; + const propTypes = { // redux dashboardLayout: PropTypes.object.isRequired, @@ -109,7 +111,7 @@ class DashboardBuilder extends React.Component { )} {topLevelTabs && ( - + {({ style }) => ( , ]} editMode={editMode} - style={{ zIndex: 1000, ...style }} + style={editMode ? { zIndex: 100, ...style } : null} > {this.props.editMode && - this.props.showBuilderPane && } + this.props.showBuilderPane && ( + + )}
diff --git a/superset/assets/src/dashboard/stylesheets/components/header.less b/superset/assets/src/dashboard/stylesheets/components/header.less index 023c78c1f3e4e..940310336c19d 100644 --- a/superset/assets/src/dashboard/stylesheets/components/header.less +++ b/superset/assets/src/dashboard/stylesheets/components/header.less @@ -21,7 +21,7 @@ font-size: 12px; } -.dragdroppable-row .dashboard-component-header { +.dashboard--editing .dragdroppable-row .dashboard-component-header { cursor: move; } diff --git a/superset/assets/src/dashboard/stylesheets/dnd.less b/superset/assets/src/dashboard/stylesheets/dnd.less index 835b62bfd251a..0a10c61c220c4 100644 --- a/superset/assets/src/dashboard/stylesheets/dnd.less +++ b/superset/assets/src/dashboard/stylesheets/dnd.less @@ -65,11 +65,11 @@ float: left; height: 2px; margin: 1px; - width: 2px + width: 2px; } .drag-handle-dot:after { - content: ""; + content: ''; background: #aaa; float: left; height: 2px; From 50f98853b12261f0937ccb782897d1ae8bd4b3a4 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Mon, 7 May 2018 10:53:04 -0700 Subject: [PATCH 13/15] [dashboard builder] fix sticky tabs offset bug --- superset/assets/src/dashboard/components/DashboardBuilder.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx index 13538dd75db65..7f929480be5c9 100644 --- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx +++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx @@ -111,7 +111,7 @@ class DashboardBuilder extends React.Component { )} {topLevelTabs && ( - + {({ style }) => ( , ]} editMode={editMode} - style={editMode ? { zIndex: 100, ...style } : null} + style={{ zIndex: 100, ...style }} > Date: Mon, 7 May 2018 11:54:01 -0700 Subject: [PATCH 14/15] [dashboard builder] fix drag preview width, css polish, fix rebase issue --- .../components/BuilderComponentPane.jsx | 20 +++++++-------- .../src/dashboard/components/SliceAdder.jsx | 1 + .../components/dnd/AddSliceDragPreview.jsx | 17 ++++++++----- .../stylesheets/builder-sidepane.less | 25 ++++++++----------- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx index ac6d8e0b51392..6d3956781dac0 100644 --- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx +++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx @@ -52,21 +52,21 @@ class BuilderComponentPane extends React.PureComponent { >
- {t('Add')} + {t('Insert new component')}
-
-
- {t('Chart')} -
+
+
{t('Chart')}
+ +
- {t('Components')} + {t('Other components')}
@@ -74,13 +74,13 @@ class BuilderComponentPane extends React.PureComponent {
-
+
- + {t('Add chart')}
diff --git a/superset/assets/src/dashboard/components/SliceAdder.jsx b/superset/assets/src/dashboard/components/SliceAdder.jsx index 86b0f2779f50e..05c4270f4cc35 100644 --- a/superset/assets/src/dashboard/components/SliceAdder.jsx +++ b/superset/assets/src/dashboard/components/SliceAdder.jsx @@ -182,6 +182,7 @@ class SliceAdder extends React.Component { diff --git a/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx b/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx index 94cab4227c234..91fc0558b36fb 100644 --- a/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx +++ b/superset/assets/src/dashboard/components/dnd/AddSliceDragPreview.jsx @@ -9,6 +9,16 @@ import { CHART_TYPE, } from '../../util/componentTypes'; +const staticCardStyles = { + position: 'fixed', + background: 'white', + pointerEvents: 'none', + top: 0, + left: 0, + zIndex: 100, + width: 376 - 2 * 16, +}; + const propTypes = { dragItem: PropTypes.shape({ index: PropTypes.number.isRequired, @@ -41,12 +51,7 @@ function AddSliceDragPreview({ dragItem, slices, isDragging, currentOffset }) { return !shouldRender ? null : ( >>>>>>add slider and sticky box-shadow: -4px 0 4px 0 rgba(0, 0, 0, 0.1); .dashboard-builder-sidepane-header { @@ -22,7 +16,6 @@ height: 18px; width: 25px; color: @almost-black; - float: none; opacity: 1; } @@ -67,10 +60,8 @@ } } - .component-layer { - .new-component.static { - cursor: pointer; - } + .new-component-label { + flex-grow: 1; } .chart-card-container { @@ -112,21 +103,27 @@ display: flex; padding: 16px; + /* the input is wrapped in a div */ + .search-input { + flex-grow: 1; + margin-left: 16px; + } + .dropdown.btn-group button, input { font-size: 14px; line-height: 16px; padding: 7px 12px; height: 32px; + border: 1px solid @gray-light; } input { - margin-left: 16px; - width: 169px; - border: 1px solid @gray; + width: 100%; &:focus { outline: none; + border-color: @gray; } } } From 5c55dfec124d72563bef5733dc05def9b164b01e Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Mon, 7 May 2018 12:18:12 -0700 Subject: [PATCH 15/15] [dashboard builder] fix side pane labels and hove z-index --- .../components/BuilderComponentPane.jsx | 18 ++++++++++++------ .../src/dashboard/stylesheets/hover-menu.less | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx index 6d3956781dac0..b42650ef55798 100644 --- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx +++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx @@ -52,7 +52,7 @@ class BuilderComponentPane extends React.PureComponent { >
- {t('Insert new component')} + {t('Saved components')}
-
{t('Chart')}
+
+ {t('Charts & filters')} +
- {t('Other components')} + {t('Containers')}
- - + +
+ {t('More components')} +
+ +
- {t('Add chart')} + {t('All components')}
diff --git a/superset/assets/src/dashboard/stylesheets/hover-menu.less b/superset/assets/src/dashboard/stylesheets/hover-menu.less index ca07fb3ec4e32..4f624015efaad 100644 --- a/superset/assets/src/dashboard/stylesheets/hover-menu.less +++ b/superset/assets/src/dashboard/stylesheets/hover-menu.less @@ -1,7 +1,7 @@ .hover-menu { opacity: 0; position: absolute; - z-index: 1000; + z-index: 10; font-size: 14px; }