From 96b2cb84cc1263f6ee6ab7f75f04382ab56c78cf Mon Sep 17 00:00:00 2001 From: Vasiliy Trushin Date: Fri, 31 Jan 2025 20:47:33 +0300 Subject: [PATCH 01/13] base dnd --- statshouse-ui/package-lock.json | 135 +++++++++++++++ statshouse-ui/package.json | 2 + .../src/components2/Dashboard/Dashboard.tsx | 4 +- .../Dashboard/DashboardLayoutNew.tsx | 161 ++++++++++++++++++ 4 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 statshouse-ui/src/components2/Dashboard/DashboardLayoutNew.tsx diff --git a/statshouse-ui/package-lock.json b/statshouse-ui/package-lock.json index 983055ba2..364171dd5 100644 --- a/statshouse-ui/package-lock.json +++ b/statshouse-ui/package-lock.json @@ -49,6 +49,7 @@ "react": "^19.0.0", "react-data-grid": "^7.0.0-beta.47", "react-dom": "^19.0.0", + "react-grid-layout": "^1.5.0", "react-markdown": "^9.0.3", "react-router-dom": "^7.1.1", "react-window": "^1.8.11", @@ -69,6 +70,7 @@ "@types/node": "^20.17.12", "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", + "@types/react-grid-layout": "^1.3.5", "@typescript-eslint/eslint-plugin": "^8.19.1", "@typescript-eslint/parser": "^8.19.1", "prettier": "^3.4.2" @@ -4889,6 +4891,16 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-grid-layout": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", + "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-window": { "version": "1.8.8", "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", @@ -7053,6 +7065,12 @@ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -12331,6 +12349,47 @@ "react": "^19.0.0" } }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-grid-layout": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.0.tgz", + "integrity": "sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "fast-equals": "^4.0.3", + "prop-types": "^15.8.1", + "react-draggable": "^4.4.5", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -12362,6 +12421,19 @@ "react": ">=18" } }, + "node_modules/react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "license": "MIT", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, "node_modules/react-router": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", @@ -12625,6 +12697,12 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -17486,6 +17564,15 @@ "devOptional": true, "requires": {} }, + "@types/react-grid-layout": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", + "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-window": { "version": "1.8.8", "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", @@ -18966,6 +19053,11 @@ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, + "fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + }, "fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -22704,6 +22796,35 @@ "scheduler": "^0.25.0" } }, + "react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "requires": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "dependencies": { + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + } + } + }, + "react-grid-layout": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.0.tgz", + "integrity": "sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w==", + "requires": { + "clsx": "^2.0.0", + "fast-equals": "^4.0.3", + "prop-types": "^15.8.1", + "react-draggable": "^4.4.5", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -22727,6 +22848,15 @@ "vfile": "^6.0.0" } }, + "react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + }, "react-router": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", @@ -22910,6 +23040,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", diff --git a/statshouse-ui/package.json b/statshouse-ui/package.json index da678c9d1..37c8ea701 100644 --- a/statshouse-ui/package.json +++ b/statshouse-ui/package.json @@ -45,6 +45,7 @@ "react": "^19.0.0", "react-data-grid": "^7.0.0-beta.47", "react-dom": "^19.0.0", + "react-grid-layout": "^1.5.0", "react-markdown": "^9.0.3", "react-router-dom": "^7.1.1", "react-window": "^1.8.11", @@ -65,6 +66,7 @@ "@types/node": "^20.17.12", "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", + "@types/react-grid-layout": "^1.3.5", "@typescript-eslint/eslint-plugin": "^8.19.1", "@typescript-eslint/parser": "^8.19.1", "prettier": "^3.4.2" diff --git a/statshouse-ui/src/components2/Dashboard/Dashboard.tsx b/statshouse-ui/src/components2/Dashboard/Dashboard.tsx index c33b6974f..7c4d1b815 100644 --- a/statshouse-ui/src/components2/Dashboard/Dashboard.tsx +++ b/statshouse-ui/src/components2/Dashboard/Dashboard.tsx @@ -23,6 +23,7 @@ import { produce } from '~immer/dist/immer'; import { HistoryList } from '../HistoryList'; const PATH_VERSION_PARAM = '&dv'; +import { DashboardLayoutNew } from './DashboardLayoutNew'; export type DashboardProps = { className?: string; @@ -148,7 +149,8 @@ export const Dashboard = memo(function Dashboard({ className }: DashboardProps) {variablesLength > 0 && tabNum === '-1' && !tvModeEnable && ( )} - + {/* */} + {tabNum === '-2' && } {tabNum === '-3' && dashboardId && ( ({ + groups, + orderGroup, + orderPlot, + dashboardLayoutEdit, + isEmbed, + addDashboardGroup, + setNextDashboardSchemePlot, + }) + ); + + const [layouts, setLayouts] = useState({}); + const itemsGroup = prepareItemsGroup({ groups, orderGroup, orderPlot }); + + const nextGroupKey = useMemo(() => getNextGroupKey({ orderGroup }), [orderGroup]); + + const [isDragging, setIsDragging] = useState(false); + + const onLayoutChange = useCallback((layout: Layout[], layouts: Layouts) => { + // setLayouts(layouts); + // if (dashboardLayoutEdit) { + // const formattedLayout = [ + // { + // groupKey: nextGroupKey, + // plots: layout.map((item) => item.i), + // }, + // ]; + // setNextDashboardSchemePlot(formattedLayout); + // } + }, []); + + const onAddGroup = useCallback( + (e: React.MouseEvent) => { + const groupKey = e.currentTarget.getAttribute('data-index-group') ?? '0'; + addDashboardGroup(groupKey); + }, + [addDashboardGroup] + ); + + const onDragStart = useCallback(() => { + setIsDragging(true); + }, []); + + const onDragStop = useCallback(() => { + setIsDragging(false); + }, []); + + return ( +
+
+ {itemsGroup.map(({ groupKey, plots }) => ( + + {groups[groupKey]?.show !== false && ( + + {plots.map((plotKey, index) => ( + + + + ))} + + )} + + ))} + + {dashboardLayoutEdit && + (isDragging ? ( +
+
+
Drop here for create new group
+
+ ) : ( +
+
+ +
+ ))} +
+
+ ); +}); From 31ef3f716eb20639c16ba5fa3b93fcd972d39a18 Mon Sep 17 00:00:00 2001 From: Vasiliy Trushin Date: Fri, 7 Feb 2025 13:51:46 +0300 Subject: [PATCH 02/13] test fix --- .../Dashboard/DashboardLayoutNew.tsx | 143 +++++++++++++----- 1 file changed, 106 insertions(+), 37 deletions(-) diff --git a/statshouse-ui/src/components2/Dashboard/DashboardLayoutNew.tsx b/statshouse-ui/src/components2/Dashboard/DashboardLayoutNew.tsx index ff26d7ccc..49e007dfa 100644 --- a/statshouse-ui/src/components2/Dashboard/DashboardLayoutNew.tsx +++ b/statshouse-ui/src/components2/Dashboard/DashboardLayoutNew.tsx @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -import { memo, useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Responsive, WidthProvider } from 'react-grid-layout'; import type { Layout, Layouts } from 'react-grid-layout'; import { useStatsHouseShallow } from '@/store2'; @@ -20,6 +20,7 @@ import cn from 'classnames'; import { getNextGroupKey } from '@/store2/urlStore/updateParamsPlotStruct'; import { toNumber } from '@/common/helpers'; import css from './style.module.css'; +import { GroupKey } from '@/url2'; const ResponsiveGridLayout = WidthProvider(Responsive); @@ -56,25 +57,100 @@ export const DashboardLayoutNew = memo(function DashboardLayoutNew({ className } ); const [layouts, setLayouts] = useState({}); - const itemsGroup = prepareItemsGroup({ groups, orderGroup, orderPlot }); + const [isDragging, setIsDragging] = useState(false); + const [draggedPlotKey, setDraggedPlotKey] = useState(null); + const [draggedGroupKey, setDraggedGroupKey] = useState(null); + const [selectTargetGroup, setSelectTargetGroup] = useState(null); + + const itemsGroup = useMemo( + () => prepareItemsGroup({ groups, orderGroup, orderPlot }), + [groups, orderGroup, orderPlot] + ); + + const itemsGroupRef = useRef(itemsGroup); const nextGroupKey = useMemo(() => getNextGroupKey({ orderGroup }), [orderGroup]); - const [isDragging, setIsDragging] = useState(false); + // Обновляем ref при изменении itemsGroup + useEffect(() => { + itemsGroupRef.current = itemsGroup; + }, [itemsGroup]); + + const save = useCallback( + (plotKey: string | null, targetGroup: GroupKey | null) => { + if (plotKey != null && targetGroup != null) { + const updatedItemsGroup = itemsGroupRef.current.map((group) => { + if (group.groupKey === draggedGroupKey) { + return { + ...group, + plots: group.plots.filter((p) => p !== plotKey), + }; + } + if (group.groupKey === targetGroup) { + return { + ...group, + plots: [...group.plots, plotKey], + }; + } + return group; + }); + + // Если это новая группа + if (!updatedItemsGroup.find((g) => g.groupKey === targetGroup)) { + updatedItemsGroup.push({ + groupKey: targetGroup, + plots: [plotKey], + }); + } - const onLayoutChange = useCallback((layout: Layout[], layouts: Layouts) => { - // setLayouts(layouts); - // if (dashboardLayoutEdit) { - // const formattedLayout = [ - // { - // groupKey: nextGroupKey, - // plots: layout.map((item) => item.i), - // }, - // ]; - // setNextDashboardSchemePlot(formattedLayout); - // } + setNextDashboardSchemePlot(updatedItemsGroup); + } + }, + [draggedGroupKey, setNextDashboardSchemePlot] + ); + + const onDragStart = useCallback((layout: Layout[], oldItem: Layout) => { + setIsDragging(true); + const [groupKey, plotKey] = oldItem.i.split('::'); + setDraggedPlotKey(plotKey); + setDraggedGroupKey(groupKey); + setSelectTargetGroup(null); }, []); + const onDragStop = useCallback( + (layout: Layout[], oldItem: Layout, newItem: Layout, placeholder: Layout, e: MouseEvent, element: HTMLElement) => { + setIsDragging(false); + + const dropElement = document.elementsFromPoint(e.clientX, e.clientY); + const targetGroup = dropElement.find((e) => e.getAttribute('data-group'))?.getAttribute('data-group') ?? null; + + if (targetGroup && draggedPlotKey) { + setSelectTargetGroup(targetGroup); + save(draggedPlotKey, targetGroup); + } + + setDraggedPlotKey(null); + setDraggedGroupKey(null); + setSelectTargetGroup(null); + }, + [draggedPlotKey, save] + ); + + const onLayoutChange = useCallback( + (layout: Layout[], layouts: Layouts) => { + setLayouts(layouts); + // Обрабатываем изменения layout только если это не перетаскивание между группами + if (dashboardLayoutEdit && !isDragging && !selectTargetGroup) { + const updatedItemsGroup = itemsGroup.map((group) => ({ + ...group, + plots: layout.filter((item) => item.i.startsWith(`${group.groupKey}::`)).map((item) => item.i.split('::')[1]), + })); + setNextDashboardSchemePlot(updatedItemsGroup); + } + }, + [dashboardLayoutEdit, isDragging, itemsGroup, selectTargetGroup, setNextDashboardSchemePlot] + ); + const onAddGroup = useCallback( (e: React.MouseEvent) => { const groupKey = e.currentTarget.getAttribute('data-index-group') ?? '0'; @@ -83,14 +159,6 @@ export const DashboardLayoutNew = memo(function DashboardLayoutNew({ className } [addDashboardGroup] ); - const onDragStart = useCallback(() => { - setIsDragging(true); - }, []); - - const onDragStop = useCallback(() => { - setIsDragging(false); - }, []); - return (
@@ -113,16 +181,19 @@ export const DashboardLayoutNew = memo(function DashboardLayoutNew({ className } isResizable={dashboardLayoutEdit} // compactType="horizontal" // preventCollision - onLayoutChange={onLayoutChange} + // onLayoutChange={onLayoutChange} onDragStart={onDragStart} onDragStop={onDragStop} > {plots.map((plotKey, index) => ( ))} - {dashboardLayoutEdit && - (isDragging ? ( -
-
+ {dashboardLayoutEdit && ( +
+
+ {isDragging ? (
Drop here for create new group
-
- ) : ( -
-
+ ) : ( -
- ))} + )} +
+ )}
); From cc89a3a6d28d4016d7c5374a5c812098cb81791b Mon Sep 17 00:00:00 2001 From: Vasiliy Trushin Date: Sun, 23 Feb 2025 15:27:52 +0300 Subject: [PATCH 03/13] test version --- .../components2/Dashboard/DashboardGroup.tsx | 25 +- .../Dashboard/DashboardLayoutNew.tsx | 313 +++++++++++------- .../components2/Plot/PlotView/PlotHeader.tsx | 6 +- .../Plot/PlotView/style.module.css | 4 + statshouse-ui/src/url2/queryParams.ts | 13 + 5 files changed, 223 insertions(+), 138 deletions(-) diff --git a/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx b/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx index 71f61094f..bed275f4e 100644 --- a/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx +++ b/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx @@ -92,16 +92,17 @@ export const DashboardGroup = memo(function DashboardGroup({ children, groupKey, [setDashboardGroup] ); - const onEditGroupSize = useCallback( - (e: React.ChangeEvent) => { - const groupKey = e.currentTarget.getAttribute('data-group') ?? '0'; - const size = e.currentTarget.value ?? '2'; - setDashboardGroup(groupKey, (g) => { - g.size = size; - }); - }, - [setDashboardGroup] - ); + // const onEditGroupSize = useCallback( + // (e: React.ChangeEvent) => { + // const groupKey = e.currentTarget.getAttribute('data-group') ?? '0'; + // const size = e.currentTarget.value ?? '2'; + + // setDashboardGroup(groupKey, (g) => { + // g.size = size; + // }); + // }, + // [setDashboardGroup] + // ); const onAddGroup = useCallback( (e: React.MouseEvent) => { @@ -155,7 +156,7 @@ export const DashboardGroup = memo(function DashboardGroup({ children, groupKey, onInput={onEditGroupName} placeholder="Enter group name" /> - + */}
diff --git a/statshouse-ui/src/components2/Plot/PlotView/PlotHeader.tsx b/statshouse-ui/src/components2/Plot/PlotView/PlotHeader.tsx index e48edcb05..223fb6d5f 100644 --- a/statshouse-ui/src/components2/Plot/PlotView/PlotHeader.tsx +++ b/statshouse-ui/src/components2/Plot/PlotView/PlotHeader.tsx @@ -163,7 +163,11 @@ export const PlotHeader = memo(function PlotHeader({ plotKey, isDashboard }: Plo {dashboardLayoutEdit ? (
>; + // orderPlot: PlotData[]; orderPlot: PlotKey[]; variables: Partial>; orderVariables: VariableKey[]; From 575ef92091b6622bedd853c0d85c2cef7d0a783b Mon Sep 17 00:00:00 2001 From: Vasiliy Trushin Date: Wed, 26 Feb 2025 12:17:03 +0300 Subject: [PATCH 04/13] fixes --- statshouse-ui/src/common/helpers.ts | 64 +++ .../src/common/prepareItemsGroup.tsx | 45 +- .../components2/Dashboard/DashboardGroup.tsx | 3 +- .../Dashboard/DashboardLayoutNew.tsx | 503 +++++++++++------- .../src/components2/Dashboard/constants.ts | 63 +++ .../src/components2/Dashboard/types.ts | 18 + statshouse-ui/src/store2/urlStore/urlStore.ts | 46 +- statshouse-ui/src/url2/getDefault.ts | 2 + statshouse-ui/src/url2/queryParams.ts | 23 +- 9 files changed, 528 insertions(+), 239 deletions(-) create mode 100644 statshouse-ui/src/components2/Dashboard/constants.ts create mode 100644 statshouse-ui/src/components2/Dashboard/types.ts diff --git a/statshouse-ui/src/common/helpers.ts b/statshouse-ui/src/common/helpers.ts index e9341bd8d..de62f2534 100644 --- a/statshouse-ui/src/common/helpers.ts +++ b/statshouse-ui/src/common/helpers.ts @@ -7,6 +7,9 @@ import { produce } from 'immer'; import { mapKeyboardEnToRu, mapKeyboardRuToEn, toggleKeyboard } from './toggleKeyboard'; import type uPlot from 'uplot'; +import { BREAKPOINT_WIDTH } from '@/components2/Dashboard/constants'; +import { GroupInfo } from '@/url2'; +import { BreakpointKey, LayoutScheme } from '@/components2/Dashboard/types'; export function isArray(item: unknown): item is unknown[] { return Array.isArray(item); @@ -427,3 +430,64 @@ export const bwd = (v: number) => { } return Math.pow(2, v) - 1; }; + +const getBreakpointKey = (width: number): BreakpointKey => { + if (width >= BREAKPOINT_WIDTH.xxxl) return 'xxxl'; + if (width >= BREAKPOINT_WIDTH.xxl) return 'xxl'; + if (width >= BREAKPOINT_WIDTH.xl) return 'xl'; + if (width >= BREAKPOINT_WIDTH.lg) return 'lg'; + if (width >= BREAKPOINT_WIDTH.md) return 'md'; + if (width >= BREAKPOINT_WIDTH.sm) return 'sm'; + if (width >= BREAKPOINT_WIDTH.xs) return 'xs'; + return 'xxs'; +}; + +export const getBreakpointConfig = () => { + const width = window.innerWidth; + return { breakpointKey: getBreakpointKey(width) }; +}; + +export const calculateMaxRows = (plots: string[], cols: number, layout?: { y: number; h: number }[]) => { + if (!layout?.length) { + return Math.ceil(plots.length / cols) + 1; + } + + const maxOccupiedRow = layout.reduce((max, item) => { + const itemLastRow = item.y + item.h; + return Math.max(max, itemLastRow); + }, 0); + + return maxOccupiedRow + 1; +}; + +export const calculateDynamicRowHeight = (width: number, baseWidth: number = 2700, baseHeight: number = 290) => { + if (width <= baseWidth) { + return baseHeight; + } + + // Calculate how many additional "blocks" of 300px exist after baseWidth + const extraWidth = width - baseWidth; + const extraBlocks = Math.floor(extraWidth / 300); + + // Add 40px height for each additional 300px of width + const finalHeight = baseHeight + extraBlocks * 25; + + return finalHeight; +}; + +export const updateGroupWithLayout = ( + groupInfo: GroupInfo, + groupKey: string, + layouts?: LayoutScheme, + breakpointKey: string = 'lg' +) => { + const layoutScheme = layouts?.groupKey === groupKey; + + if (layoutScheme) { + groupInfo.layouts = { + ...groupInfo.layouts, + [breakpointKey]: layouts.layout, + }; + } + return groupInfo; +}; diff --git a/statshouse-ui/src/common/prepareItemsGroup.tsx b/statshouse-ui/src/common/prepareItemsGroup.tsx index 01348ef57..48667bd50 100644 --- a/statshouse-ui/src/common/prepareItemsGroup.tsx +++ b/statshouse-ui/src/common/prepareItemsGroup.tsx @@ -4,7 +4,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -import type { QueryParams } from '@/url2'; +import { DEFAULT_LAYOUT_COORDS } from '@/components2/Dashboard/constants'; +import type { GroupKey, PlotKey, QueryParams } from '@/url2'; +import type { Layout } from 'react-grid-layout'; export function prepareItemsGroup({ orderGroup, @@ -20,3 +22,44 @@ export function prepareItemsGroup({ }; }); } + +type PrepareItemsGroupWithLayoutProps = { + groups: QueryParams['groups']; + orderGroup: QueryParams['orderGroup']; + orderPlot: PlotKey[]; + breakpoint?: string; +}; + +type PrepareItemsGroupWithLayoutResult = { + itemsGroup: { groupKey: GroupKey; plots: PlotKey[] }[]; + layoutsCoords: { groupKey: GroupKey; layout: Layout | Layout[] | { x: number; y: number; w: number; h: number } }[]; +}; + +export function prepareItemsGroupWithLayout({ + groups, + orderGroup, + orderPlot, + breakpoint = 'lg', +}: PrepareItemsGroupWithLayoutProps): PrepareItemsGroupWithLayoutResult { + const orderP = [...orderPlot]; + const itemsGroup = orderGroup + .filter((groupKey) => groups[groupKey]?.show !== false) + .map((groupKey) => { + const count = groups[groupKey]?.count ?? 0; + const groupPlots = orderP.splice(0, count); + + return { + groupKey, + plots: groupPlots, + }; + }); + + const layoutsCoords = orderGroup + .filter((groupKey) => groups[groupKey]?.show !== false) + .map((groupKey) => ({ + groupKey, + layout: groups[groupKey]?.layouts?.[breakpoint] ?? DEFAULT_LAYOUT_COORDS, + })); + + return { itemsGroup, layoutsCoords }; +} diff --git a/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx b/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx index bed275f4e..c4a770add 100644 --- a/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx +++ b/statshouse-ui/src/components2/Dashboard/DashboardGroup.tsx @@ -13,6 +13,7 @@ import { ReactComponent as SVGChevronCompactDown } from 'bootstrap-icons/icons/c import { ReactComponent as SVGTrash } from 'bootstrap-icons/icons/trash.svg'; import { ReactComponent as SVGPlus } from 'bootstrap-icons/icons/plus.svg'; import cn from 'classnames'; +import css from './style.module.css'; import { GroupKey } from '@/url2'; import { Button, TextArea, Tooltip } from '@/components/UI'; import { DashboardGroupTooltipTitle } from './DashboardGroupTooltipTitle'; @@ -140,7 +141,7 @@ export const DashboardGroup = memo(function DashboardGroup({ children, groupKey, >