diff --git a/frontend/src/app/components/ChartDrill/ChartDrillContextMenu.tsx b/frontend/src/app/components/ChartDrill/ChartDrillContextMenu.tsx index 2731bc811..5e8e86411 100644 --- a/frontend/src/app/components/ChartDrill/ChartDrillContextMenu.tsx +++ b/frontend/src/app/components/ChartDrill/ChartDrillContextMenu.tsx @@ -80,6 +80,7 @@ const ChartDrillContextMenu: FC<{ } else { rows = groupSection?.rows?.filter(v => v.uid === allFields[0].uid); } + rows = rows?.filter(row => row.type === DataViewFieldType.DATE); return getRuntimeDateLevelFields(rows); }, [drillOption, chartConfig?.datas, currentFields]); diff --git a/frontend/src/app/components/ChartGraph/BasicBarChart/BasicBarChart.tsx b/frontend/src/app/components/ChartGraph/BasicBarChart/BasicBarChart.tsx index adfebe76b..84751299e 100644 --- a/frontend/src/app/components/ChartGraph/BasicBarChart/BasicBarChart.tsx +++ b/frontend/src/app/components/ChartGraph/BasicBarChart/BasicBarChart.tsx @@ -242,6 +242,7 @@ class BasicBarChart extends Chart implements IChartLifecycle { return { tooltip: { trigger: 'item', + confine: true, formatter: this.getTooltipFormatterFunc( chartDataSet, groupConfigs, diff --git a/frontend/src/app/components/ChartGraph/BasicDoubleYChart/BasicDoubleYChart.tsx b/frontend/src/app/components/ChartGraph/BasicDoubleYChart/BasicDoubleYChart.tsx index 92b5a739d..5c763d069 100644 --- a/frontend/src/app/components/ChartGraph/BasicDoubleYChart/BasicDoubleYChart.tsx +++ b/frontend/src/app/components/ChartGraph/BasicDoubleYChart/BasicDoubleYChart.tsx @@ -180,6 +180,7 @@ class BasicDoubleYChart extends Chart { axisPointer: { type: 'cross', }, + confine: true, formatter: this.getTooltipFormmaterFunc( styleConfigs, groupConfigs, @@ -365,10 +366,10 @@ class BasicDoubleYChart extends Chart { ); const _yAxisTemplate = (position, name): DoubleYChartYAxis => { - const [showAxis, inverse, font, showLabel] = getStyles( + const [showAxis, inverse, font, showLabel, showTitleAndUnit] = getStyles( styles, [`${position}Y`], - ['showAxis', 'inverseAxis', 'font', 'showLabel'], + ['showAxis', 'inverseAxis', 'font', 'showLabel', 'showTitleAndUnit'], ); const [format] = getStyles( styles, @@ -379,7 +380,7 @@ class BasicDoubleYChart extends Chart { type: 'value', position, showTitleAndUnit: true, - name, + name: showTitleAndUnit ? name : null, nameLocation: 'middle', nameGap: 50, nameRotate: 90, diff --git a/frontend/src/app/components/ChartGraph/BasicDoubleYChart/config.ts b/frontend/src/app/components/ChartGraph/BasicDoubleYChart/config.ts index 78b1bfb79..7ed307a8b 100644 --- a/frontend/src/app/components/ChartGraph/BasicDoubleYChart/config.ts +++ b/frontend/src/app/components/ChartGraph/BasicDoubleYChart/config.ts @@ -253,6 +253,12 @@ const config: ChartConfig = { color: '#495057', }, }, + { + label: 'common.showTitleAndUnit', + key: 'showTitleAndUnit', + default: true, + comType: 'checkbox', + }, { label: 'yAxis.open', key: 'modal', @@ -331,6 +337,12 @@ const config: ChartConfig = { color: '#495057', }, }, + { + label: 'common.showTitleAndUnit', + key: 'showTitleAndUnit', + default: true, + comType: 'checkbox', + }, { label: 'yAxis.open', key: 'modal', diff --git a/frontend/src/app/components/ChartGraph/BasicLineChart/BasicLineChart.tsx b/frontend/src/app/components/ChartGraph/BasicLineChart/BasicLineChart.tsx index 8ae2ca14c..87dc66973 100644 --- a/frontend/src/app/components/ChartGraph/BasicLineChart/BasicLineChart.tsx +++ b/frontend/src/app/components/ChartGraph/BasicLineChart/BasicLineChart.tsx @@ -212,6 +212,7 @@ class BasicLineChart extends Chart { return { tooltip: { trigger: 'item', + confine: true, formatter: this.getTooltipFormmaterFunc( chartDataSet, groupConfigs, diff --git a/frontend/src/app/components/ChartGraph/BasicOutlineMapChart/BasicOutlineMapChart.tsx b/frontend/src/app/components/ChartGraph/BasicOutlineMapChart/BasicOutlineMapChart.tsx index 0a9af4330..f8cd10729 100644 --- a/frontend/src/app/components/ChartGraph/BasicOutlineMapChart/BasicOutlineMapChart.tsx +++ b/frontend/src/app/components/ChartGraph/BasicOutlineMapChart/BasicOutlineMapChart.tsx @@ -291,14 +291,22 @@ class BasicOutlineMapChart extends Chart { areaEmphasisColor, enableFocus, borderStyle, + roam, ] = getStyles( styleConfigs, ['map'], - ['level', 'areaColor', 'areaEmphasisColor', 'focusArea', 'borderStyle'], + [ + 'level', + 'areaColor', + 'areaEmphasisColor', + 'focusArea', + 'borderStyle', + 'roam', + ], ); return { map: mapLevelName, - roam: 'move', + roam: roam && 'move', emphasis: { focus: enableFocus ? 'self' : 'none', itemStyle: { diff --git a/frontend/src/app/components/ChartGraph/BasicPieChart/BasicPieChart.tsx b/frontend/src/app/components/ChartGraph/BasicPieChart/BasicPieChart.tsx index fa0895648..7bf558f6c 100644 --- a/frontend/src/app/components/ChartGraph/BasicPieChart/BasicPieChart.tsx +++ b/frontend/src/app/components/ChartGraph/BasicPieChart/BasicPieChart.tsx @@ -243,10 +243,11 @@ class BasicPieChart extends Chart { } private getPieSeriesImpl(styleConfigs: ChartStyleConfig[]): PieSeriesImpl { + const [avoidOverlap] = getStyles(styleConfigs, ['label'], ['avoidOverlap']); return { type: 'pie', sampling: 'average', - avoidLabelOverlap: false, + avoidLabelOverlap: avoidOverlap, ...this.getLabelStyle(styleConfigs), ...this.getSeriesStyle(styleConfigs), ...getGridStyle(styleConfigs), diff --git a/frontend/src/app/components/ChartGraph/BasicPieChart/config.ts b/frontend/src/app/components/ChartGraph/BasicPieChart/config.ts index 410bb0d3d..de7639128 100644 --- a/frontend/src/app/components/ChartGraph/BasicPieChart/config.ts +++ b/frontend/src/app/components/ChartGraph/BasicPieChart/config.ts @@ -111,6 +111,12 @@ const config: ChartConfig = { default: true, comType: 'checkbox', }, + { + label: 'label.avoidOverlap', + key: 'avoidOverlap', + default: false, + comType: 'checkbox', + }, ], }, { @@ -284,6 +290,7 @@ const config: ChartConfig = { showName: '维度值', showPercent: '百分比', showValue: '指标值', + avoidOverlap: '强制显示所有标签', }, legend: { title: '图例', @@ -333,6 +340,7 @@ const config: ChartConfig = { showName: 'Show Name', showPercent: 'Show Percentage', showValue: 'Show Value', + avoidOverlap: 'Force display of all labels', }, legend: { title: 'Legend', diff --git a/frontend/src/app/components/ChartGraph/BasicTableChart/AntdTableWrapper.tsx b/frontend/src/app/components/ChartGraph/BasicTableChart/AntdTableWrapper.tsx index 8ae04d806..f8a8a6a31 100644 --- a/frontend/src/app/components/ChartGraph/BasicTableChart/AntdTableWrapper.tsx +++ b/frontend/src/app/components/ChartGraph/BasicTableChart/AntdTableWrapper.tsx @@ -16,8 +16,10 @@ * limitations under the License. */ -import { Table } from 'antd'; +import { ConfigProvider, Table } from 'antd'; +import { antdLocales } from 'locales/i18n'; import { FC, memo } from 'react'; +import { useTranslation } from 'react-i18next'; import styled from 'styled-components/macro'; interface TableStyleConfigProps { @@ -47,6 +49,8 @@ const AntdTableWrapper: FC<{ summaryFn?: (data) => { total: number; summarys: [] }; }> = memo( ({ dataSource, columns, children, summaryFn, tableStyleConfig, ...rest }) => { + const { i18n } = useTranslation(); + const getTableSummaryRow = pageData => { if (!summaryFn) { return undefined; @@ -68,13 +72,15 @@ const AntdTableWrapper: FC<{ }; return ( - + + + ); }, ); diff --git a/frontend/src/app/components/ChartGraph/NormalOutlineMapChart/config.ts b/frontend/src/app/components/ChartGraph/NormalOutlineMapChart/config.ts index 3aff2168d..2b90630d8 100644 --- a/frontend/src/app/components/ChartGraph/NormalOutlineMapChart/config.ts +++ b/frontend/src/app/components/ChartGraph/NormalOutlineMapChart/config.ts @@ -97,6 +97,12 @@ const config: ChartConfig = { color: '#ced4da', }, }, + { + label: 'map.roam', + key: 'roam', + default: true, + comType: 'checkbox', + }, ], }, { @@ -310,6 +316,7 @@ const config: ChartConfig = { focusArea: '聚焦选中区域', areaColor: '区域颜色', areaEmphasisColor: '选中区域高亮颜色', + roam: '鼠标平移', }, levelType: { china: '中国-省级地图', @@ -362,6 +369,7 @@ const config: ChartConfig = { focusArea: 'Focus Area', areaColor: 'Area Color', areaEmphasisColor: 'Area Emphasis Color', + roam: 'Mouse translating', }, levelType: { china: 'China', diff --git a/frontend/src/app/components/ChartGraph/ScatterOutlineMapChart/config.ts b/frontend/src/app/components/ChartGraph/ScatterOutlineMapChart/config.ts index b879d65d6..cb7969258 100644 --- a/frontend/src/app/components/ChartGraph/ScatterOutlineMapChart/config.ts +++ b/frontend/src/app/components/ChartGraph/ScatterOutlineMapChart/config.ts @@ -102,6 +102,12 @@ const config: ChartConfig = { color: '#ced4da', }, }, + { + label: 'map.roam', + key: 'roam', + default: true, + comType: 'checkbox', + }, { label: 'map.cycleRatio', key: 'cycleRatio', @@ -319,6 +325,7 @@ const config: ChartConfig = { cycleRatio: '气泡大像素比', areaColor: '区域颜色', areaEmphasisColor: '选中区域高亮颜色', + roam: '鼠标平移', }, levelType: { china: '中国-省级地图', @@ -369,6 +376,7 @@ const config: ChartConfig = { areaColor: 'Area Color', areaEmphasisColor: 'Area Emphasis Color', cycleRatio: 'Cycle Ratio', + roam: 'Mouse translating', }, levelType: { china: 'China', diff --git a/frontend/src/app/components/ChartIFrameContainer/ChartIFrameEventBroker.ts b/frontend/src/app/components/ChartIFrameContainer/ChartIFrameEventBroker.ts index 114338b66..19fc2ba9a 100644 --- a/frontend/src/app/components/ChartIFrameContainer/ChartIFrameEventBroker.ts +++ b/frontend/src/app/components/ChartIFrameContainer/ChartIFrameEventBroker.ts @@ -149,8 +149,8 @@ class ChartIFrameEventBroker { private registerListener(c: IChart): void { this.subscribe(ChartLifecycle.Mounted, c?.onMount); - this.subscribe(ChartLifecycle.Updated, c?.onUpdated); this.subscribe(ChartLifecycle.Resize, c?.onResize); + this.subscribe(ChartLifecycle.Updated, c?.onUpdated); this.subscribe(ChartLifecycle.UnMount, c?.onUnMount); } diff --git a/frontend/src/app/components/ChartIFrameContainer/ChartIFrameLifecycleAdapter.tsx b/frontend/src/app/components/ChartIFrameContainer/ChartIFrameLifecycleAdapter.tsx index 7c235d2e5..649b6042c 100644 --- a/frontend/src/app/components/ChartIFrameContainer/ChartIFrameLifecycleAdapter.tsx +++ b/frontend/src/app/components/ChartIFrameContainer/ChartIFrameLifecycleAdapter.tsx @@ -151,9 +151,8 @@ const ChartIFrameLifecycleAdapter: FC<{ }, [chart?.meta?.id, isShown]); /** - * Chart Update Event - * Dependency: 'config', 'dataset', 'widgetSpecialConfig', - * 'containerStatus', 'document', 'window', 'isShown', 'drillOption', 'selectedItems' + * Chart Resize Event + * Dependency: 'style.width', 'style.height', 'document', 'window', 'isShown' */ useEffect(() => { if ( @@ -167,28 +166,27 @@ const ChartIFrameLifecycleAdapter: FC<{ ) { return; } + eventBrokerRef.current?.publish( - ChartLifecycle.Updated, + ChartLifecycle.Resize, buildBrokerOption(), buildBrokerContext(), ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - config, - dataset, - widgetSpecialConfig, - containerStatus, + style.width, + style.height, document, window, isShown, - drillOption, - selectedItems, + containerStatus, isLoadingData, ]); /** - * Chart Resize Event - * Dependency: 'style.width', 'style.height', 'document', 'window', 'isShown' + * Chart Update Event + * Dependency: 'config', 'dataset', 'widgetSpecialConfig', + * 'containerStatus', 'document', 'window', 'isShown', 'drillOption', 'selectedItems' */ useEffect(() => { if ( @@ -202,20 +200,22 @@ const ChartIFrameLifecycleAdapter: FC<{ ) { return; } - eventBrokerRef.current?.publish( - ChartLifecycle.Resize, + ChartLifecycle.Updated, buildBrokerOption(), buildBrokerContext(), ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - style.width, - style.height, + config, + dataset, + widgetSpecialConfig, + containerStatus, document, window, isShown, - containerStatus, + drillOption, + selectedItems, isLoadingData, ]); diff --git a/frontend/src/app/components/SaveToDashboard.tsx b/frontend/src/app/components/SaveToDashboard.tsx index 4e9e45f55..9928aff48 100644 --- a/frontend/src/app/components/SaveToDashboard.tsx +++ b/frontend/src/app/components/SaveToDashboard.tsx @@ -25,6 +25,7 @@ import { getCascadeAccess } from 'app/pages/MainPage/Access'; import { PermissionLevels, ResourceTypes, + VizResourceSubTypes, } from 'app/pages/MainPage/pages/PermissionPage/constants'; import { useAddViz } from 'app/pages/MainPage/pages/VizPage/hooks/useAddViz'; import { SaveFormContext } from 'app/pages/MainPage/pages/VizPage/SaveFormContext'; @@ -136,7 +137,7 @@ const SaveToDashboard: FC = memo( selectable: v.relType !== 'FOLDER', })), null, - [], + [VizResourceSubTypes.Folder], { getIcon, filter: filterTreeNode }, ); }, [vizData, getIcon, filterTreeNode]); diff --git a/frontend/src/app/components/SplitPane/Pane.tsx b/frontend/src/app/components/SplitPane/Pane.tsx new file mode 100644 index 000000000..33f252f1b --- /dev/null +++ b/frontend/src/app/components/SplitPane/Pane.tsx @@ -0,0 +1,49 @@ +import { CSSProperties, LegacyRef, PureComponent, ReactNode } from 'react'; + +interface PaneProps { + className: string; + children: ReactNode; + size?: string | number; + split?: 'vertical' | 'horizontal'; + style?: CSSProperties; + eleRef: LegacyRef; +} + +export class Pane extends PureComponent { + render() { + const { + children, + className, + split, + style: styleProps, + size, + eleRef, + } = this.props; + + const classes = ['Pane', split, className]; + + let style: CSSProperties = { + flex: 1, + position: 'relative', + outline: 'none', + }; + + if (size !== undefined) { + if (split === 'vertical') { + style.width = size; + } else { + style.height = size; + style.display = 'flex'; + } + style.flex = 'none'; + } + + style = Object.assign({}, style, styleProps || {}); + + return ( +
+ {children} +
+ ); + } +} diff --git a/frontend/src/app/components/SplitPane/Resizer.tsx b/frontend/src/app/components/SplitPane/Resizer.tsx new file mode 100644 index 000000000..348ee6534 --- /dev/null +++ b/frontend/src/app/components/SplitPane/Resizer.tsx @@ -0,0 +1,65 @@ +import { Component, CSSProperties, MouseEvent, TouchEvent } from 'react'; + +export const RESIZER_DEFAULT_CLASSNAME = 'Resizer'; + +interface ResizerProps { + className: string; + onClick?: (e: MouseEvent) => void; + onDoubleClick?: (e: MouseEvent) => void; + onMouseDown: (e: MouseEvent) => void; + onTouchStart: (e: TouchEvent) => void; + onTouchEnd: (e: TouchEvent) => void; + split?: 'vertical' | 'horizontal'; + style?: CSSProperties; + resizerClassName?: string; +} + +export class Resizer extends Component { + static defaultProps = { + resizerClassName: RESIZER_DEFAULT_CLASSNAME, + }; + + render() { + const { + className, + onClick, + onDoubleClick, + onMouseDown, + onTouchEnd, + onTouchStart, + resizerClassName, + split, + style, + } = this.props; + const classes = [resizerClassName, split, className]; + + return ( + onMouseDown(event)} + onTouchStart={event => { + event.preventDefault(); + onTouchStart(event); + }} + onTouchEnd={event => { + event.preventDefault(); + onTouchEnd(event); + }} + onClick={event => { + if (onClick) { + event.preventDefault(); + onClick(event); + } + }} + onDoubleClick={event => { + if (onDoubleClick) { + event.preventDefault(); + onDoubleClick(event); + } + }} + /> + ); + } +} diff --git a/frontend/src/app/components/SplitPane/index.tsx b/frontend/src/app/components/SplitPane/index.tsx new file mode 100644 index 000000000..e6607251d --- /dev/null +++ b/frontend/src/app/components/SplitPane/index.tsx @@ -0,0 +1,454 @@ +import { Children, Component, CSSProperties, ReactNode } from 'react'; +import styled from 'styled-components/macro'; +import { Pane } from './Pane'; +import { Resizer, RESIZER_DEFAULT_CLASSNAME } from './Resizer'; + +interface SplitPaneProps { + allowResize?: boolean; + children: ReactNode; + className?: string; + primary?: 'first' | 'second'; + minSize?: string | number; + maxSize?: string | number; + // eslint-disable-next-line react/no-unused-prop-types + defaultSize?: string | number; + size?: string | number; + split?: 'vertical' | 'horizontal'; + onDragStarted?: () => void; + onDragFinished?: (draggedSize: string | number) => void; + onChange?: (newSize: string | number) => void; + onResizerClick?: () => void; + onResizerDoubleClick?: () => void; + style?: CSSProperties; + resizerStyle?: CSSProperties; + paneClassName?: string; + pane1ClassName?: string; + pane2ClassName?: string; + paneStyle?: CSSProperties; + pane1Style?: CSSProperties; + pane2Style?: CSSProperties; + resizerClassName?: string; + step?: number; +} + +interface SplitPaneStates { + active: boolean; + position?: number; + draggedSize?: number; + resized: boolean; + pane1Size: string | number; + pane2Size: string | number; + instanceProps: { + size?: string | number; + }; +} + +export class SplitPane extends Component { + constructor(props) { + super(props); + + this.onMouseDown = this.onMouseDown.bind(this); + this.onTouchStart = this.onTouchStart.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + this.onTouchMove = this.onTouchMove.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + + // order of setting panel sizes. + // 1. size + // 2. getDefaultSize(defaultSize, minsize, maxSize) + + const { size, defaultSize, minSize, maxSize, primary } = props; + + const initialSize = + size !== undefined + ? size + : getDefaultSize(defaultSize, minSize, maxSize, null); + + this.state = { + active: false, + resized: false, + pane1Size: primary === 'first' ? initialSize : undefined, + pane2Size: primary === 'second' ? initialSize : undefined, + + // these are props that are needed in static functions. ie: gDSFP + instanceProps: { + size, + }, + }; + } + + private splitPane: HTMLDivElement | null = null; + private pane1: HTMLDivElement | null = null; + private pane2: HTMLDivElement | null = null; + + static defaultProps = { + allowResize: true, + minSize: 50, + primary: 'first', + split: 'vertical', + paneClassName: '', + pane1ClassName: '', + pane2ClassName: '', + }; + + componentDidMount() { + document.addEventListener('mouseup', this.onMouseUp); + document.addEventListener('mousemove', this.onMouseMove); + document.addEventListener('touchmove', this.onTouchMove); + this.setState(SplitPane.getSizeUpdate(this.props, this.state)); + } + + static getDerivedStateFromProps(nextProps, prevState) { + return SplitPane.getSizeUpdate(nextProps, prevState); + } + + componentWillUnmount() { + document.removeEventListener('mouseup', this.onMouseUp); + document.removeEventListener('mousemove', this.onMouseMove); + document.removeEventListener('touchmove', this.onTouchMove); + } + + onMouseDown(event) { + const eventWithTouches = Object.assign({}, event, { + touches: [{ clientX: event.clientX, clientY: event.clientY }], + }); + this.onTouchStart(eventWithTouches); + } + + onTouchStart(event) { + const { allowResize, onDragStarted, split } = this.props; + if (allowResize) { + unFocus(document, window); + const position = + split === 'vertical' + ? event.touches[0].clientX + : event.touches[0].clientY; + + if (typeof onDragStarted === 'function') { + onDragStarted(); + } + this.setState({ + active: true, + position, + }); + } + } + + onMouseMove(event) { + const eventWithTouches = Object.assign({}, event, { + touches: [{ clientX: event.clientX, clientY: event.clientY }], + }); + this.onTouchMove(eventWithTouches); + } + + onTouchMove(event) { + const { allowResize, maxSize, minSize, onChange, split, step } = this.props; + const { active, position } = this.state; + + if (allowResize && active) { + unFocus(document, window); + const isPrimaryFirst = this.props.primary === 'first'; + const ref = isPrimaryFirst ? this.pane1 : this.pane2; + const ref2 = isPrimaryFirst ? this.pane2 : this.pane1; + if (ref && ref2) { + const node = ref; + const node2 = ref2; + + if (node.getBoundingClientRect) { + const width = node.getBoundingClientRect().width; + const height = node.getBoundingClientRect().height; + const current = + split === 'vertical' + ? event.touches[0].clientX + : event.touches[0].clientY; + const size = split === 'vertical' ? width : height; + let positionDelta = position! - current; + if (step) { + if (Math.abs(positionDelta) < step) { + return; + } + // Integer division + // eslint-disable-next-line no-bitwise + positionDelta = ~~(positionDelta / step) * step; + } + let sizeDelta = isPrimaryFirst ? positionDelta : -positionDelta; + + const pane1Order = parseInt(window.getComputedStyle(node).order); + const pane2Order = parseInt(window.getComputedStyle(node2).order); + if (pane1Order > pane2Order) { + sizeDelta = -sizeDelta; + } + + let newMaxSize = Number(maxSize); + if (maxSize !== undefined && Number(maxSize) <= 0) { + const splitPane = this.splitPane; + if (split === 'vertical') { + newMaxSize = + splitPane!.getBoundingClientRect().width + Number(maxSize); + } else { + newMaxSize = + splitPane!.getBoundingClientRect().height + Number(maxSize); + } + } + + let newSize = size - sizeDelta; + const newPosition = position! - positionDelta; + + if (newSize < Number(minSize)) { + newSize = Number(minSize); + } else if (maxSize !== undefined && newSize > newMaxSize) { + newSize = newMaxSize; + } else { + this.setState({ + position: newPosition, + resized: true, + }); + } + + if (onChange) onChange(newSize); + + this.setState({ + draggedSize: newSize, + [isPrimaryFirst ? 'pane1Size' : 'pane2Size']: newSize, + }); + } + } + } + } + + onMouseUp() { + const { allowResize, onDragFinished } = this.props; + const { active, draggedSize } = this.state; + if (allowResize && active) { + if (typeof onDragFinished === 'function') { + onDragFinished(draggedSize!); + } + this.setState({ active: false }); + } + } + + // we have to check values since gDSFP is called on every render and more in StrictMode + static getSizeUpdate(props, state) { + const newState: Partial = {}; + const { instanceProps } = state; + + if (instanceProps.size === props.size && props.size !== undefined) { + return {}; + } + + const newSize = + props.size !== undefined + ? props.size + : getDefaultSize( + props.defaultSize, + props.minSize, + props.maxSize, + state.draggedSize, + ); + + if (props.size !== undefined) { + newState.draggedSize = newSize; + } + + const isPanel1Primary = props.primary === 'first'; + + newState[isPanel1Primary ? 'pane1Size' : 'pane2Size'] = newSize; + newState[isPanel1Primary ? 'pane2Size' : 'pane1Size'] = undefined; + + newState.instanceProps = { size: props.size }; + + return newState; + } + + render() { + const { + allowResize, + children, + className, + onResizerClick, + onResizerDoubleClick, + paneClassName, + pane1ClassName, + pane2ClassName, + paneStyle, + pane1Style: pane1StyleProps, + pane2Style: pane2StyleProps, + resizerClassName, + resizerStyle, + split, + style: styleProps, + } = this.props; + + const { pane1Size, pane2Size } = this.state; + + const disabledClass = allowResize ? '' : 'disabled'; + const resizerClassNamesIncludingDefault = resizerClassName + ? `${resizerClassName} ${RESIZER_DEFAULT_CLASSNAME}` + : resizerClassName; + + const notNullChildren = removeNullChildren(children); + + const style: CSSProperties = { + display: 'flex', + flex: 1, + height: '100%', + // position: 'absolute', + outline: 'none', + overflow: 'hidden', + userSelect: 'text', + ...styleProps, + }; + + if (split === 'vertical') { + Object.assign(style, { + flexDirection: 'row', + // left: 0, + // right: 0, + }); + } else { + Object.assign(style, { + // bottom: 0, + flexDirection: 'column', + minHeight: '100%', + // top: 0, + width: '100%', + }); + } + + const classes = ['SplitPane', className, split, disabledClass]; + + const pane1Style = { ...paneStyle, ...pane1StyleProps }; + const pane2Style = { ...paneStyle, ...pane2StyleProps }; + + const pane1Classes = ['Pane1', paneClassName, pane1ClassName].join(' '); + const pane2Classes = ['Pane2', paneClassName, pane2ClassName].join(' '); + + return ( + { + this.splitPane = node; + }} + style={style} + > + { + this.pane1 = node; + }} + size={pane1Size} + split={split} + style={pane1Style} + > + {notNullChildren[0]} + + + { + this.pane2 = node; + }} + size={pane2Size} + split={split} + style={pane2Style} + > + {notNullChildren[1]} + + + ); + } +} + +const Wrapper = styled.div` + .Resizer { + z-index: 1; + box-sizing: border-box; + background: transparent; + background-clip: padding-box; + } + + .Resizer:hover { + /* transition: all 2s ease; */ + } + + .Resizer.horizontal { + width: 100%; + height: 10px; + margin: -5px 0; + cursor: row-resize; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + } + + .Resizer.horizontal:hover { + background-color: ${p => p.theme.primary}; + border-top-width: 4px; + border-bottom-width: 4px; + } + + .Resizer.vertical { + width: 10px; + margin: 0 -5px; + cursor: col-resize; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + } + + .Resizer.vertical:hover { + background-color: ${p => p.theme.primary}; + border-right-width: 4px; + border-left-width: 4px; + } + .Resizer.disabled { + cursor: not-allowed; + } + .Resizer.disabled:hover { + border-color: transparent; + } +`; + +function unFocus(document, window) { + if (document.selection) { + document.selection.empty(); + } else { + try { + window.getSelection().removeAllRanges(); + // eslint-disable-next-line no-empty + } catch (e) {} + } +} + +function getDefaultSize( + defaultSize: string | number, + minSize: string | number, + maxSize: string | number, + draggedSize?: string | number | null, +) { + if (typeof draggedSize === 'number') { + const min = typeof minSize === 'number' ? minSize : 0; + const max = + typeof maxSize === 'number' && maxSize >= 0 ? maxSize : Infinity; + return Math.max(min, Math.min(max, draggedSize)); + } + if (defaultSize !== undefined) { + return defaultSize; + } + return minSize; +} + +function removeNullChildren(children) { + return Children.toArray(children).filter(c => c); +} diff --git a/frontend/src/app/components/Tree/index.tsx b/frontend/src/app/components/Tree/index.tsx index 1575e5457..dde2e578a 100644 --- a/frontend/src/app/components/Tree/index.tsx +++ b/frontend/src/app/components/Tree/index.tsx @@ -1,6 +1,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Empty, Tree as AntTree, TreeProps as AntTreeProps } from 'antd'; import classnames from 'classnames'; +import { MutableRefObject } from 'react'; import styled from 'styled-components/macro'; import { FONT_SIZE_BODY, @@ -14,12 +15,19 @@ import { interface TreeProps extends AntTreeProps { loading: boolean; + wrapperRef?: MutableRefObject | null; } -export function Tree({ loading, treeData, ...treeProps }: TreeProps) { +export function Tree({ + loading, + wrapperRef, + treeData, + ...treeProps +}: TreeProps) { return ( {loading ? ( diff --git a/frontend/src/app/pages/DashBoardPage/hooks/useRenderWidget.ts b/frontend/src/app/pages/DashBoardPage/hooks/useRenderWidget.ts index 9e2502d3a..52c4e4589 100644 --- a/frontend/src/app/pages/DashBoardPage/hooks/useRenderWidget.ts +++ b/frontend/src/app/pages/DashBoardPage/hooks/useRenderWidget.ts @@ -36,10 +36,10 @@ export default function useRenderWidget( const renderWidget = useCallback(() => { const canView = isElView(cacheWhRef.current); - if (canView) { + if (canView || boardType === 'free') { onRenderedWidgetById(widget.id); } - }, [cacheWhRef, onRenderedWidgetById, widget.id]); + }, [cacheWhRef, onRenderedWidgetById, widget.id, boardType]); //监听board滚动 useEffect(() => { diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/AutoBoardEditor.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/AutoBoardEditor.tsx index 46143a151..d266b134a 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/AutoBoardEditor.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/AutoBoardEditor.tsx @@ -54,7 +54,6 @@ import { SPACE_MD, SPACE_XS, } from 'styles/StyleConstants'; -import { isEmptyArray } from 'utils/object'; import BoardOverlay from '../components/BoardOverlay'; import DeviceList from '../components/DeviceList'; import { editBoardStackActions, editDashBoardInfoActions } from '../slice'; @@ -127,7 +126,7 @@ export const AutoBoardEditor: React.FC<{}> = memo(() => { return (
= memo(() => { ref={ref} > {sortedLayoutWidgets.length ? ( -
+ <>
= memo(() => { {boardChildren}
- {!isEmptyArray(editingWidgetIds) && } -
+ {!!editingWidgetIds && } + ) : (
@@ -212,6 +211,7 @@ const Wrapper = styled.div<{}>` `; const StyledContainer = styled(StyledBackground)<{ curWH: number[] }>` + position: relative; display: flex; flex-direction: column; min-height: 0; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/index.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/index.tsx index 1b5dd788e..1cd36231b 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/index.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/AutoEditor/index.tsx @@ -16,24 +16,46 @@ * limitations under the License. */ +import { SplitPane } from 'app/components/SplitPane'; +import { dispatchResize } from 'app/utils/dispatchResize'; import { memo, useContext } from 'react'; import styled from 'styled-components/macro'; -import { LEVEL_1 } from 'styles/StyleConstants'; import { WidgetActionContext } from '../../../components/ActionProvider/WidgetActionProvider'; import { BoardToolBar } from '../components/BoardToolBar/BoardToolBar'; import { LayerTreePanel } from '../components/LayerPanel/LayerTreePanel'; import SlideSetting from '../components/SlideSetting/SlideSetting'; import { AutoBoardEditor } from './AutoBoardEditor'; + export const AutoEditor: React.FC<{}> = memo(() => { const { onEditClearActiveWidgets } = useContext(WidgetActionContext); + const clearSelectedWidgets = e => { + e.stopPropagation(); + onEditClearActiveWidgets(); + }; + return ( - + - + - - - + + + + + ); }); @@ -45,11 +67,3 @@ const Wrapper = styled.div` min-height: 0; overflow: hidden; `; - -const Editor = styled.div` - z-index: ${LEVEL_1}; - display: flex; - flex: 1; - min-height: 0; - overflow-x: auto; -`; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/FreeBoardEditor.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/FreeBoardEditor.tsx index 147bcf05c..48760381e 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/FreeBoardEditor.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/FreeBoardEditor.tsx @@ -22,7 +22,6 @@ import { WidgetWrapProvider } from 'app/pages/DashBoardPage/components/WidgetPro import { memo, useContext } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components/macro'; -import { isEmptyArray } from 'utils/object'; import SlideBackground from '../../../components/FreeBoardBackground'; import useClientRect from '../../../hooks/useClientRect'; import useSlideStyle from '../../../hooks/useSlideStyle'; @@ -76,7 +75,7 @@ export const FreeBoardEditor: React.FC<{}> = memo(() => { ))} - {!isEmptyArray(editingWidgetIds) && } + {!!editingWidgetIds && }
diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/WidgetOfFreeEdit.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/WidgetOfFreeEdit.tsx index e89bb3013..a3bf0d4ed 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/WidgetOfFreeEdit.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/WidgetOfFreeEdit.tsx @@ -73,7 +73,7 @@ export const WidgetOfFreeEdit: React.FC<{}> = () => { }, [height, width, x, y]); const move = useCallback( - (selectedIds: string[], deltaX: number, deltaY: number) => { + (selectedIds: string, deltaX: number, deltaY: number) => { if (!selectedIds.includes(widget.id)) return; setCurXY(c => [c[0] + deltaX, c[1] + deltaY]); }, @@ -115,7 +115,11 @@ export const WidgetOfFreeEdit: React.FC<{}> = () => { (e, data) => { e.stopPropagation(); const { deltaX, deltaY } = data; - widgetMove.emit(selectedIds.concat(widget.id), deltaX, deltaY); + widgetMove.emit( + !!selectedIds ? `${selectedIds},${widget.id}` : widget.id, + deltaX, + deltaY, + ); }, [selectedIds, widget.id], ); @@ -161,7 +165,7 @@ export const WidgetOfFreeEdit: React.FC<{}> = () => { e.stopPropagation(); }; - if (editingWidgetIds?.includes(widget?.id)) { + if (editingWidgetIds.includes(widget?.id)) { style['zIndex'] = LEVEL_DASHBOARD_EDIT_OVERLAY + 1; } diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/index.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/index.tsx index 762e5e374..5ebcef709 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/index.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/FreeEditor/index.tsx @@ -16,36 +16,44 @@ * limitations under the License. */ -import { Layout } from 'antd'; -import { memo } from 'react'; -import { useDispatch } from 'react-redux'; +import { SplitPane } from 'app/components/SplitPane'; +import { WidgetActionContext } from 'app/pages/DashBoardPage/components/ActionProvider/WidgetActionProvider'; +import { memo, useContext } from 'react'; import styled from 'styled-components/macro'; import { BoardToolBar } from '../components/BoardToolBar/BoardToolBar'; import { LayerTreePanel } from '../components/LayerPanel/LayerTreePanel'; import SlideSetting from '../components/SlideSetting/SlideSetting'; -import { editDashBoardInfoActions, editWidgetInfoActions } from '../slice'; import { FreeBoardEditor } from './FreeBoardEditor'; export const FreeEditor: React.FC = memo(() => { - const dispatch = useDispatch(); + const { onEditClearActiveWidgets } = useContext(WidgetActionContext); const clearSelectedWidgets = e => { e.stopPropagation(); - dispatch(editWidgetInfoActions.clearSelectedWidgets()); - dispatch(editDashBoardInfoActions.changeShowBlockMask(true)); + onEditClearActiveWidgets(); }; return ( - - - - - {/* */} - + + + + + - - - + + + ); }); @@ -57,9 +65,3 @@ const Wrapper = styled.div` min-height: 0; overflow: hidden; `; - -const Editor = styled.div` - display: flex; - flex: 1; - min-height: 0; -`; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/CopyPaste/CopyPaste.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/CopyPaste/CopyPaste.tsx index 0010528d6..2670e4dce 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/CopyPaste/CopyPaste.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/CopyPaste/CopyPaste.tsx @@ -32,12 +32,12 @@ export const CopyBtn: FC<{ const selectedIds = useSelector(selectSelectedIds); const onCopy = () => { - fn(selectedIds); + fn(); }; return ( } /> diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/DelWidgetsBtn.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/DelWidgetsBtn.tsx index 0545210ae..57956c983 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/DelWidgetsBtn.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/BoardToolBar/DelWidgetsBtn.tsx @@ -30,7 +30,7 @@ export const DelWidgetsBtn: FC<{ return ( } /> diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTree.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTree.tsx index 4bd309403..7c9e5fbbc 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTree.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTree.tsx @@ -18,11 +18,11 @@ import { Tree } from 'app/components'; import { renderIcon } from 'app/hooks/useGetVizIcon'; +import useResizeObserver from 'app/hooks/useResizeObserver'; import { WidgetActionContext } from 'app/pages/DashBoardPage/components/ActionProvider/WidgetActionProvider'; import widgetManager from 'app/pages/DashBoardPage/components/WidgetManager'; import { FC, memo, useCallback, useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { isEmptyArray } from 'utils/object'; import { stopPPG } from 'utils/utils'; import { dropLayerNodeAction } from '../../slice/actions/actions'; import { @@ -30,7 +30,7 @@ import { selectLayerTree, selectSelectedIds, } from '../../slice/selectors'; -import { LayerTreeItem } from './LayerTreeItem'; +import { EventLayerNode, LayerTreeItem } from './LayerTreeItem'; export const LayerTree: FC<{}> = memo(() => { const dispatch = useDispatch(); @@ -38,7 +38,12 @@ export const LayerTree: FC<{}> = memo(() => { const renderTreeItem = useCallback(n => , []); const { onEditSelectWidget } = useContext(WidgetActionContext); const editingWidgetIds = useSelector(selectEditingWidgetIds); - const selectedKeys = useSelector(selectSelectedIds); + const selectedIds = useSelector(selectSelectedIds); + + const { height, ref } = useResizeObserver({ + refreshMode: 'debounce', + refreshRate: 200, + }); const treeSelect = useCallback( (_, { node, nativeEvent }) => { @@ -57,14 +62,27 @@ export const LayerTree: FC<{}> = memo(() => { ); const onDrop = useCallback( - info => dispatch(dropLayerNodeAction(info)), + info => { + const dragNode = info.dragNode as EventLayerNode; + const targetNode = info.node as EventLayerNode; + let dropPosition = 'NORMAL'; + + if (targetNode.dragOverGapTop) { + dropPosition = 'TOP'; + } + if (targetNode.dragOver && !targetNode.isLeaf) { + dropPosition = 'FOLDER'; + } + + dispatch(dropLayerNodeAction(dragNode, targetNode, dropPosition)); + }, [dispatch], ); return ( = memo(() => { onClick={stopPPG} onDrop={onDrop} treeData={treeData} - selectedKeys={selectedKeys} + selectedKeys={selectedIds ? selectedIds.split(',') : []} + height={height} + wrapperRef={ref} defaultExpandAll /> ); diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreeItem.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreeItem.tsx index b9b7c07fc..a1044d196 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreeItem.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreeItem.tsx @@ -29,7 +29,6 @@ import { stopPPG } from 'utils/utils'; export interface LayerNode extends TreeDataNode { key: string; parentId: string; - selected: boolean; children: LayerNode[]; content: any; widgetIndex: number; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreePanel.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreePanel.tsx index c311a5e15..de93106d6 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreePanel.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/LayerTreePanel.tsx @@ -42,7 +42,7 @@ export const LayerTreePanel: FC<{}> = memo(() => { const Panel = styled.div` display: flex; flex-direction: column; - width: 200px; + height: 100%; background-color: ${p => p.theme.componentBackground}; box-shadow: ${p => p.theme.shadowSider}; `; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/utils.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/utils.ts index 49f5806e9..1ff1bc7e3 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/utils.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/LayerPanel/utils.ts @@ -18,16 +18,15 @@ import { ORIGINAL_TYPE_MAP } from 'app/pages/DashBoardPage/constants'; import { Widget } from 'app/pages/DashBoardPage/types/widgetTypes'; -import { TabWidgetContent, WidgetInfo } from '../../../Board/slice/types'; -import { EventLayerNode, LayerNode } from './LayerTreeItem'; +import { TabWidgetContent } from '../../../Board/slice/types'; +import { LayerNode } from './LayerTreeItem'; export const widgetMapToTree = (args: { widgetMap: Record; - widgetInfoMap: Record; parentId: string; tree: LayerNode[] | undefined; }) => { - const { widgetMap, widgetInfoMap, parentId, tree } = args; + const { widgetMap, parentId, tree } = args; const widgets = Object.values(widgetMap).filter(widget => { if (!parentId && !widget.parentId) { @@ -70,14 +69,12 @@ export const widgetMapToTree = (args: { boardId: widget.dashboardId, content: widget.config.content, originalType: widget.config.originalType, - selected: widgetInfoMap[widget.id].selected, }; if (widget.config.originalType === ORIGINAL_TYPE_MAP.group) { treeNode.isLeaf = false; treeNode.children = widgetMapToTree({ widgetMap, - widgetInfoMap, parentId: widget.id, tree: treeNode.children, }); @@ -85,7 +82,6 @@ export const widgetMapToTree = (args: { treeNode.isLeaf = false; treeNode.children = widgetMapToTree({ widgetMap, - widgetInfoMap, parentId: widget.id, tree: treeNode.children, }); @@ -95,105 +91,29 @@ export const widgetMapToTree = (args: { return tree as LayerNode[]; }; -export function parentIsGroup( - widgetMap: Record, - parentId?: string, -) { - if (!parentId) return true; - if ( - widgetMap[parentId] && - widgetMap[parentId].config.originalType !== ORIGINAL_TYPE_MAP.group - ) { - return false; - } - return true; -} - -export function getChildrenValList( +export function getDropInfo( widgetMap: Record, + id: string, pid: string, ) { - const widgetList = Object.values(widgetMap).filter( - widget => widget.parentId === pid, - ); + const parent = widgetMap[pid]; + const inTabs = parent?.config.originalType === ORIGINAL_TYPE_MAP.tab; + let siblings: string[] = []; - if (parentIsGroup(widgetMap, pid)) { - // group - return widgetList - .map(widget => ({ - id: widget.id, - index: widget.config.index, - })) - .sort((b, a) => a.index - b.index); + if (inTabs) { + const itemMap = (parent.config.content as TabWidgetContent).itemMap; + siblings = Object.values(itemMap || {}) + .filter(i => i.childWidgetId && i.childWidgetId !== id) + .sort((a, b) => b.index - a.index) + .map(item => item.childWidgetId); } else { - // container - const container = widgetMap[pid]; - const childItemMap = (container.config.content as TabWidgetContent).itemMap; - return Object.values(childItemMap || {}) - .map(item => ({ - id: item.childWidgetId, - index: item.index, - })) - .sort((b, a) => a.index - b.index); + siblings = Object.values(widgetMap) + .filter(widget => widget.parentId === pid && widget.id !== id) + .sort((a, b) => b.config.index - a.config.index) + .map(({ id }) => id); } -} -export function getNewDragNodeValue(args: { - widgetMap: Record; - dragNode: EventLayerNode; - targetNode: EventLayerNode; -}) { - const { widgetMap, dragNode, targetNode } = args; - const newVal = { - index: 0, - parentId: targetNode.parentId, - parentIsGroup: true, + return { + siblings, + inTabs, }; - if (targetNode.dragOverGapTop) { - const indexList = getChildrenValList(widgetMap, targetNode.parentId); - newVal.index = indexList[0].index + 1; - - return newVal; - } - if (targetNode.dragOver) { - if (targetNode.isLeaf) { - // dragOver Leaf - const indexList = getChildrenValList(widgetMap, targetNode.parentId); - const targetIndex = indexList.findIndex(t => t.id === targetNode.key); - if (targetIndex < indexList.length - 1) { - let indexA = indexList[targetIndex].index; - let indexC = indexList[targetIndex + 1].index; - newVal.index = (indexA + indexC) / 2; - } else { - newVal.index = indexList[targetIndex].index - 1; - } - newVal.parentId = targetNode.parentId; - newVal.parentIsGroup = parentIsGroup(widgetMap, targetNode.parentId); - return newVal; - } else { - // dragOver folder - const indexList = getChildrenValList(widgetMap, targetNode.key); - if (indexList.length < 1) { - newVal.index = dragNode.widgetIndex; - } else { - newVal.index = indexList[0].index + 1; - } - newVal.parentId = targetNode.key; - newVal.parentIsGroup = parentIsGroup(widgetMap, targetNode.key); - return newVal; - } - } else if (!targetNode.dragOver) { - const indexList = getChildrenValList(widgetMap, targetNode.parentId); - const targetIndex = indexList.findIndex(t => t.id === targetNode.key); - if (targetIndex < indexList.length - 1) { - let indexA = indexList[targetIndex].index; - let indexC = indexList[targetIndex + 1].index; - newVal.index = (indexA + indexC) / 2; - } else { - newVal.index = indexList[targetIndex].index - 1; - } - newVal.parentId = targetNode.parentId; - newVal.parentIsGroup = parentIsGroup(widgetMap, targetNode.parentId); - return newVal; - } - return newVal; } diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/SlideSetting/SlideSetting.tsx b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/SlideSetting/SlideSetting.tsx index 1425ec9f5..a70824d4a 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/SlideSetting/SlideSetting.tsx +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/components/SlideSetting/SlideSetting.tsx @@ -27,16 +27,17 @@ import WidgetSetting from './WidgetSetting'; export const SlideSetting: FC<{}> = memo(() => { const { boardId } = useContext(BoardContext); const selectedIds = useSelector(selectSelectedIds); - const setType = useMemo( - () => (selectedIds.length === 1 ? 'widget' : 'board'), - [selectedIds.length], - ); + const { type, selectedIdArr } = useMemo(() => { + const selectedIdArr = selectedIds ? selectedIds.split(',') : []; + const type = selectedIdArr.length === 1 ? 'widget' : 'board'; + return { type, selectedIdArr }; + }, [selectedIds]); return ( - {setType === 'board' && } - {setType === 'widget' && ( + {type === 'board' && } + {type === 'widget' && ( @@ -52,8 +53,7 @@ export default SlideSetting; const Wrapper = styled.div<{}>` display: flex; flex-direction: column; - width: 330px; - min-width: 330px; + height: 100%; min-height: 0; background-color: ${p => p.theme.componentBackground}; box-shadow: ${p => p.theme.shadowSider}; diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/actions/actions.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/actions/actions.ts index 4c3c54026..4610f26e8 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/actions/actions.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/actions/actions.ts @@ -25,7 +25,6 @@ import { import { boardActions } from 'app/pages/DashBoardPage/pages/Board/slice'; import { BoardState, - ContainerItem, Dashboard, DataChart, TabWidgetContent, @@ -58,7 +57,7 @@ import { ORIGINAL_TYPE_MAP } from '../../../../constants'; import { selectWidgetInfoMap } from '../../../Board/slice/selector'; import { syncBoardWidgetChartDataAsync } from '../../../Board/slice/thunk'; import { EventLayerNode } from '../../components/LayerPanel/LayerTreeItem'; -import { getNewDragNodeValue } from '../../components/LayerPanel/utils'; +import { getDropInfo } from '../../components/LayerPanel/utils'; import { selectAllWidgetInfoMap } from '../selectors'; import { getEditChartWidgetDataAsync, @@ -494,45 +493,48 @@ export const editorWidgetClearLinkageAction = }); }; -export const dropLayerNodeAction = info => (dispatch, getState) => { - const dragNode = info.dragNode as EventLayerNode; - const targetNode = info.node as EventLayerNode; - const editBoard = getState().editBoard as HistoryEditBoard; - const widgetMap = editBoard.stack.present.widgetRecord; - - /* - 1 -> group - 2 -> container - */ - //拖拽节点来自 group - const newDragVal = getNewDragNodeValue({ widgetMap, dragNode, targetNode }); - if (newDragVal.parentIsGroup) { - // 拖进 group - dispatch( - editBoardStackActions.dropWidgetToGroup({ - sourceId: dragNode.key, - newIndex: newDragVal.index, - targetId: newDragVal.parentId, - }), +export const dropLayerNodeAction = + ( + dragNode: EventLayerNode, + targetNode: EventLayerNode, + dropPosition: string, + ) => + (dispatch, getState) => { + const editBoard = getState().editBoard as HistoryEditBoard; + const widgetMap = editBoard.stack.present.widgetRecord; + const newParentId = + dropPosition === 'FOLDER' ? targetNode.key : targetNode.parentId; + const { siblings, inTabs } = getDropInfo( + widgetMap, + dragNode.key, + newParentId, ); - } else { - // 拖进 tab - const dragWidget = widgetMap[dragNode.key]; - const newItem: ContainerItem = { - index: newDragVal.index, - name: dragWidget.config.name, - tabId: dragWidget.config.clientId, - childWidgetId: dragWidget.id, - }; + let reorderedChildren: string[] = []; + + switch (dropPosition) { + case 'TOP': + reorderedChildren = [dragNode.key, ...siblings]; + break; + case 'FOLDER': + reorderedChildren = [dragNode.key, ...siblings]; + break; + default: + const targetIndex = siblings.findIndex(s => s === targetNode.key); + siblings.splice(targetIndex + 1, 0, dragNode.key); + reorderedChildren = [...siblings]; + break; + } + dispatch( - editBoardStackActions.dropWidgetToTab({ - newItem, - targetId: newDragVal.parentId, + editBoardStackActions.dropWidgetLayer({ + id: dragNode.key, + currentParentId: dragNode.parentId, + newParentId, + children: reorderedChildren, + inTabs, }), ); - return; - } -}; + }; export const selectWidgetAction = (args: { multipleKey: boolean; id: string; selected: boolean }) => diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/childSlice/stackSlice.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/childSlice/stackSlice.ts index 7fde62b1b..a779d58d0 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/childSlice/stackSlice.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/childSlice/stackSlice.ts @@ -440,84 +440,64 @@ export const editBoardStackSlice = createSlice({ } }, - dropWidgetToGroup( + dropWidgetLayer( state, - action: PayloadAction<{ - sourceId: string; - newIndex: number; - targetId: string; + { + payload: { id, currentParentId, newParentId, children, inTabs }, + }: PayloadAction<{ + id: string; + currentParentId: string; + newParentId: string; + children: string[]; + inTabs: boolean; }>, ) { const widgetMap = state.widgetRecord; - const { sourceId, newIndex, targetId } = action.payload; + const currentParent = widgetMap[currentParentId]; + const newParent = widgetMap[newParentId]; - const dragWidget = widgetMap[sourceId]; - // - const dragParent = widgetMap[dragWidget.parentId || '']; - if (dragParent) { - dragParent.config.children = dragParent.config.children?.filter( - t => t !== dragWidget.id, + if (currentParent) { + currentParent.config.children = currentParent.config.children?.filter( + c => c !== id, ); - if (dragParent.config.originalType === ORIGINAL_TYPE_MAP.tab) { - const srcTabItemMap = (dragParent.config.content as TabWidgetContent) - .itemMap; - - const srcItem = Object.values(srcTabItemMap).find( - item => item.childWidgetId === dragWidget.id, + if (currentParent.config.originalType === ORIGINAL_TYPE_MAP.tab) { + const currentParentItemMap = ( + currentParent.config.content as TabWidgetContent + ).itemMap; + const draggedItem = Object.values(currentParentItemMap).find( + ({ childWidgetId }) => childWidgetId === id, ); - if (srcItem) { - delete srcTabItemMap[srcItem.tabId]; + if (draggedItem) { + delete currentParentItemMap[draggedItem.tabId]; } } } - // - dragWidget.config.index = newIndex; - dragWidget.parentId = targetId; - if (widgetMap[targetId]) { - widgetMap[targetId].config.children?.push(dragWidget.id); - } - // - }, - dropWidgetToTab( - state, - action: PayloadAction<{ - newItem: ContainerItem; - targetId: string; - }>, - ) { - const { newItem, targetId } = action.payload; - const widgetMap = state.widgetRecord; - const dragWidget = widgetMap[newItem.childWidgetId]; - // - const dragParent = widgetMap[dragWidget.parentId || '']; - if (dragParent) { - dragParent.config.children = dragParent.config.children?.filter( - t => t !== dragWidget.id, - ); - if (dragParent.config.originalType === ORIGINAL_TYPE_MAP.tab) { - const srcTabItemMap = (dragParent.config.content as TabWidgetContent) - .itemMap; - - const srcItem = Object.values(srcTabItemMap).find( - item => item.childWidgetId === dragWidget.id, - ); - if (srcItem) { - delete srcTabItemMap[srcItem.tabId]; - } + if (inTabs) { + (newParent.config.content as TabWidgetContent).itemMap = {}; + } else { + if (newParent) { + newParent.config.children = [...children]; } } - // - const targetTabWidget = widgetMap[targetId]; - const targetTabItemMap = ( - targetTabWidget.config.content as TabWidgetContent - ).itemMap; - targetTabItemMap[dragWidget.config.clientId] = newItem; - dragWidget.parentId = targetId; + children.forEach((id, index) => { + const childWidget = widgetMap[id]; + childWidget.config.index = children.length - 1 - index; + childWidget.parentId = newParentId; - // + if (inTabs) { + (newParent.config.content as TabWidgetContent).itemMap[ + childWidget.config.clientId + ] = { + index: childWidget.config.index, + name: childWidget.config.name, + tabId: childWidget.config.clientId, + childWidgetId: childWidget.id, + }; + } + }); }, /* MediaWidgetConfig */ changeMediaWidgetConfig( diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/events.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/events.ts index 0ab19c534..47c0cbeae 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/events.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/events.ts @@ -4,17 +4,17 @@ const eventBus = new EventEmitter(); const WIDGET_MOVE = 'widgetMove'; const WIDGET_MOVE_END = 'widgetMoveEnd'; const BOARD_SCROLL = 'boardScroll'; -eventBus.setMaxListeners(100); +eventBus.setMaxListeners(1000); interface FnWidgetMove { - (selectedIds: string[], deltaX: number, deltaY: number): void; + (selectedIdStr: string, deltaX: number, deltaY: number): void; } export const widgetMove = { on: (fn: FnWidgetMove) => { eventBus.addListener(WIDGET_MOVE, fn); }, - emit: (selectedIds: string[], deltaX: number, deltaY: number) => { - eventBus.emit(WIDGET_MOVE, selectedIds, deltaX, deltaY); + emit: (selectedIdStr: string, deltaX: number, deltaY: number) => { + eventBus.emit(WIDGET_MOVE, selectedIdStr, deltaX, deltaY); }, off: (fn: FnWidgetMove) => { eventBus.removeListener(WIDGET_MOVE, fn); diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/index.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/index.ts index 6f834e305..061859876 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/index.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/index.ts @@ -362,8 +362,7 @@ const filterActions = [ editBoardStackActions.updateBoardConfigByKey, editBoardStackActions.updateWidgetStyleConfigByPath, editBoardStackActions.changeFreeWidgetRect, - editBoardStackActions.dropWidgetToTab, - editBoardStackActions.dropWidgetToGroup, + editBoardStackActions.dropWidgetLayer, ].map(ele => ele.toString()); const editBoardStackReducer = undoable(editBoardStackSlice.reducer, { undoType: BOARD_UNDO.undo, diff --git a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/selectors.ts b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/selectors.ts index fd251c088..7398487d0 100644 --- a/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/selectors.ts +++ b/frontend/src/app/pages/DashBoardPage/pages/BoardEditor/slice/selectors.ts @@ -111,8 +111,10 @@ export const selectAllWidgetInfoMap = (state: { editBoard: EditBoardState }) => export const selectEditingWidgetIds = (state: { editBoard: EditBoardState }) => Object.values(state.editBoard.widgetInfoRecord) - ?.filter(r => r.editing) - ?.map(r => r.id) || []; + .filter(r => r.editing) + .map(r => r.id) + .sort() + .join(','); export const selectWidgetInfoById = createSelector( [selectAllWidgetInfoMap, (_, widgetId: string) => widgetId], @@ -135,7 +137,9 @@ export const selectSelectedIds = createSelector( WidgetsInfo => Object.values(WidgetsInfo) .filter(widgetInfo => widgetInfo.selected) - .map(widgetInfo => widgetInfo.id) || [], + .map(widgetInfo => widgetInfo.id) + .sort() + .join(','), ); export const selectWidgetInfoDatachartId = createSelector( @@ -147,11 +151,10 @@ export const selectWidgetInfoDatachartId = createSelector( ); export const selectLayerTree = createSelector( - [selectAllWidgetMap, selectAllWidgetInfoMap], - (widgetMap, widgetInfoMap) => { + [selectAllWidgetMap], + widgetMap => { return widgetMapToTree({ widgetMap, - widgetInfoMap, parentId: '', tree: [], }); diff --git a/frontend/src/app/pages/DashBoardPage/utils/board.ts b/frontend/src/app/pages/DashBoardPage/utils/board.ts index 9951b1881..e681607ee 100644 --- a/frontend/src/app/pages/DashBoardPage/utils/board.ts +++ b/frontend/src/app/pages/DashBoardPage/utils/board.ts @@ -83,7 +83,7 @@ export const getScheduleBoardInfo = ( let newBoardInfo: BoardInfo = { ...boardInfo }; const needFetchItems = Object.values(widgetMap) .filter(widget => { - if (widget.viewIds && widget.viewIds.length > 0) { + if (widget.viewIds && widget.viewIds.length > 0 && widget.viewIds[0]) { return true; } return false;