mirror of https://github.com/apache/superset.git
fix(dashboard): narrow empty drop area (#26313)
This commit is contained in:
parent
d0ffe9af7c
commit
300ddaedf9
|
@ -86,72 +86,10 @@ import {
|
|||
import { getRootLevelTabsComponent, shouldFocusTabs } from './utils';
|
||||
import DashboardContainer from './DashboardContainer';
|
||||
import { useNativeFilters } from './state';
|
||||
import DashboardWrapper from './DashboardWrapper';
|
||||
|
||||
type DashboardBuilderProps = {};
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
flex: 1;
|
||||
/* Special cases */
|
||||
|
||||
/* A row within a column has inset hover menu */
|
||||
.dragdroppable-column .dragdroppable-row .hover-menu--left {
|
||||
left: ${theme.gridUnit * -3}px;
|
||||
background: ${theme.colors.grayscale.light5};
|
||||
border: 1px solid ${theme.colors.grayscale.light2};
|
||||
}
|
||||
|
||||
.dashboard-component-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 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: ${theme.gridUnit * -3}px;
|
||||
background: ${theme.colors.grayscale.light5};
|
||||
border: 1px solid ${theme.colors.grayscale.light2};
|
||||
}
|
||||
|
||||
/* move Tabs hover menu to top near actual Tabs */
|
||||
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
|
||||
top: 0;
|
||||
transform: unset;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* push Chart actions to upper right */
|
||||
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
|
||||
.dragdroppable .dashboard-component-header .hover-menu--top {
|
||||
right: ${theme.gridUnit * 2}px;
|
||||
top: ${theme.gridUnit * 2}px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
transform: unset;
|
||||
left: unset;
|
||||
}
|
||||
div:hover > .hover-menu-container .hover-menu,
|
||||
.hover-menu-container .hover-menu:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 ${theme.gridUnit * 2}px 0;
|
||||
}
|
||||
|
||||
i.danger {
|
||||
color: ${theme.colors.error.base};
|
||||
}
|
||||
|
||||
i.warning {
|
||||
color: ${theme.colors.alert.base};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
// @z-index-above-dashboard-charts + 1 = 11
|
||||
const FiltersPanel = styled.div<{ width: number; hidden: boolean }>`
|
||||
grid-column: 1;
|
||||
|
@ -317,7 +255,7 @@ const DashboardContentWrapper = styled.div`
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
& > .empty-droptarget:first-child {
|
||||
& > .empty-droptarget:first-child:not(.empty-droptarget--full) {
|
||||
height: ${theme.gridUnit * 4}px;
|
||||
top: -2px;
|
||||
z-index: 10;
|
||||
|
@ -640,7 +578,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
|
|||
: theme.gridUnit * 8;
|
||||
|
||||
return (
|
||||
<StyledDiv>
|
||||
<DashboardWrapper>
|
||||
{showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && (
|
||||
<>
|
||||
<ResizableSidebar
|
||||
|
@ -749,7 +687,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
|
|||
`}
|
||||
/>
|
||||
)}
|
||||
</StyledDiv>
|
||||
</DashboardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from 'spec/helpers/testing-library';
|
||||
import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
|
||||
|
||||
import DashboardWrapper from './DashboardWrapper';
|
||||
|
||||
test('should render children', () => {
|
||||
const { getByTestId } = render(
|
||||
<DashboardWrapper>
|
||||
<div data-test="mock-children" />
|
||||
</DashboardWrapper>,
|
||||
{ useRedux: true, useDnd: true },
|
||||
);
|
||||
expect(getByTestId('mock-children')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should update the style on dragging state', () => {
|
||||
const defaultProps = {
|
||||
label: <span>Test label</span>,
|
||||
tooltipTitle: 'This is a tooltip title',
|
||||
onRemove: jest.fn(),
|
||||
onMoveLabel: jest.fn(),
|
||||
onDropLabel: jest.fn(),
|
||||
type: 'test',
|
||||
index: 0,
|
||||
};
|
||||
const { container, getByText } = render(
|
||||
<DashboardWrapper>
|
||||
<OptionControlLabel
|
||||
{...defaultProps}
|
||||
index={1}
|
||||
label={<span>Label 1</span>}
|
||||
/>
|
||||
<OptionControlLabel
|
||||
{...defaultProps}
|
||||
index={2}
|
||||
label={<span>Label 2</span>}
|
||||
/>
|
||||
</DashboardWrapper>,
|
||||
{
|
||||
useRedux: true,
|
||||
useDnd: true,
|
||||
initialState: {
|
||||
dashboardState: {
|
||||
editMode: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(
|
||||
container.getElementsByClassName('dragdroppable--dragging'),
|
||||
).toHaveLength(0);
|
||||
fireEvent.dragStart(getByText('Label 1'));
|
||||
expect(
|
||||
container.getElementsByClassName('dragdroppable--dragging'),
|
||||
).toHaveLength(1);
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import { css, styled } from '@superset-ui/core';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDragDropManager } from 'react-dnd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
flex: 1;
|
||||
/* Special cases */
|
||||
|
||||
&.dragdroppable--dragging
|
||||
.dashboard-component-tabs-content
|
||||
> .empty-droptarget.empty-droptarget--full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* A row within a column has inset hover menu */
|
||||
.dragdroppable-column .dragdroppable-row .hover-menu--left {
|
||||
left: ${theme.gridUnit * -3}px;
|
||||
background: ${theme.colors.grayscale.light5};
|
||||
border: 1px solid ${theme.colors.grayscale.light2};
|
||||
}
|
||||
|
||||
.dashboard-component-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 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: ${theme.gridUnit * -3}px;
|
||||
background: ${theme.colors.grayscale.light5};
|
||||
border: 1px solid ${theme.colors.grayscale.light2};
|
||||
}
|
||||
|
||||
/* move Tabs hover menu to top near actual Tabs */
|
||||
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
|
||||
top: 0;
|
||||
transform: unset;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* push Chart actions to upper right */
|
||||
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
|
||||
.dragdroppable .dashboard-component-header .hover-menu--top {
|
||||
right: ${theme.gridUnit * 2}px;
|
||||
top: ${theme.gridUnit * 2}px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
transform: unset;
|
||||
left: unset;
|
||||
}
|
||||
div:hover > .hover-menu-container .hover-menu,
|
||||
.hover-menu-container .hover-menu:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 ${theme.gridUnit * 2}px 0;
|
||||
}
|
||||
|
||||
i.danger {
|
||||
color: ${theme.colors.error.base};
|
||||
}
|
||||
|
||||
i.warning {
|
||||
color: ${theme.colors.alert.base};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const DashboardWrapper: React.FC<Props> = ({ children }) => {
|
||||
const editMode = useSelector<RootState, boolean>(
|
||||
state => state.dashboardState.editMode,
|
||||
);
|
||||
const dragDropManager = useDragDropManager();
|
||||
const [isDragged, setIsDragged] = React.useState(
|
||||
dragDropManager.getMonitor().isDragging(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const monitor = dragDropManager.getMonitor();
|
||||
const unsub = monitor.subscribeToStateChange(() => {
|
||||
setIsDragged(monitor.isDragging());
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [dragDropManager]);
|
||||
|
||||
return (
|
||||
<StyledDiv
|
||||
className={classNames({
|
||||
'dragdroppable--dragging': editMode && isDragged,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardWrapper;
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { addAlpha, css, styled, t } from '@superset-ui/core';
|
||||
import { EmptyStateBig } from 'src/components/EmptyState';
|
||||
import { componentShape } from '../util/propShapes';
|
||||
|
@ -76,10 +77,14 @@ const GridContent = styled.div`
|
|||
& > .empty-droptarget:first-child {
|
||||
height: ${theme.gridUnit * 12}px;
|
||||
margin-top: ${theme.gridUnit * -6}px;
|
||||
margin-bottom: ${theme.gridUnit * -6}px;
|
||||
}
|
||||
|
||||
& > .empty-droptarget:only-child {
|
||||
& > .empty-droptarget:last-child {
|
||||
height: ${theme.gridUnit * 12}px;
|
||||
margin-top: ${theme.gridUnit * -6}px;
|
||||
}
|
||||
|
||||
& > .empty-droptarget.empty-droptarget--full:only-child {
|
||||
height: 80vh;
|
||||
}
|
||||
`}
|
||||
|
@ -270,10 +275,14 @@ class DashboardGrid extends React.PureComponent {
|
|||
index={0}
|
||||
orientation="column"
|
||||
onDrop={this.handleTopDropTargetDrop}
|
||||
className="empty-droptarget"
|
||||
className={classNames({
|
||||
'empty-droptarget': true,
|
||||
'empty-droptarget--full':
|
||||
gridComponent?.children?.length === 0,
|
||||
})}
|
||||
editMode
|
||||
>
|
||||
{renderDraggableContentBottom}
|
||||
{renderDraggableContentTop}
|
||||
</DragDroppable>
|
||||
)}
|
||||
{gridComponent?.children?.map((id, index) => (
|
||||
|
@ -304,7 +313,7 @@ class DashboardGrid extends React.PureComponent {
|
|||
className="empty-droptarget"
|
||||
editMode
|
||||
>
|
||||
{renderDraggableContentTop}
|
||||
{renderDraggableContentBottom}
|
||||
</DragDroppable>
|
||||
)}
|
||||
{isResizing &&
|
||||
|
|
|
@ -90,6 +90,11 @@ const DragDroppableStyles = styled.div`
|
|||
z-index: 10;
|
||||
}
|
||||
|
||||
&.empty-droptarget--full > .drop-indicator--top {
|
||||
height: 100%;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
& {
|
||||
.drop-indicator {
|
||||
display: block;
|
||||
|
@ -99,7 +104,7 @@ const DragDroppableStyles = styled.div`
|
|||
}
|
||||
|
||||
.drop-indicator--top {
|
||||
top: 0;
|
||||
top: ${-theme.gridUnit - 2}px;
|
||||
left: 0;
|
||||
height: ${theme.gridUnit}px;
|
||||
width: 100%;
|
||||
|
@ -107,7 +112,7 @@ const DragDroppableStyles = styled.div`
|
|||
}
|
||||
|
||||
.drop-indicator--bottom {
|
||||
top: 100%;
|
||||
bottom: ${-theme.gridUnit - 2}px;
|
||||
left: 0;
|
||||
height: ${theme.gridUnit}px;
|
||||
width: 100%;
|
||||
|
@ -116,7 +121,7 @@ const DragDroppableStyles = styled.div`
|
|||
|
||||
.drop-indicator--right {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
left: calc(100% - ${theme.gridUnit}px);
|
||||
height: 100%;
|
||||
width: ${theme.gridUnit}px;
|
||||
min-height: ${theme.gridUnit * 4}px;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
|
@ -173,7 +174,10 @@ class Tab extends React.PureComponent {
|
|||
depth={depth}
|
||||
onDrop={this.handleTopDropTargetDrop}
|
||||
editMode
|
||||
className="empty-droptarget"
|
||||
className={classNames({
|
||||
'empty-droptarget': true,
|
||||
'empty-droptarget--full': tabComponent.children.length === 0,
|
||||
})}
|
||||
>
|
||||
{renderDraggableContentTop}
|
||||
</DragDroppable>
|
||||
|
@ -234,7 +238,7 @@ class Tab extends React.PureComponent {
|
|||
/>
|
||||
))}
|
||||
{/* Make bottom of tab droppable */}
|
||||
{editMode && (
|
||||
{editMode && tabComponent.children.length > 0 && (
|
||||
<DragDroppable
|
||||
component={tabComponent}
|
||||
parentComponent={tabParentComponent}
|
||||
|
|
Loading…
Reference in New Issue