From 0c551c8df254bac59521937687c1bb4706364205 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 8 Jan 2025 11:58:38 -0500 Subject: [PATCH] initial tabular view mode and additional state to manage viewport transform (#819, #823) --- ui100/package-lock.json | 188 ++++++++++++++++++++++++++++++++++++++ ui100/package.json | 1 + ui100/src/ApiConsole.tsx | 4 +- ui100/src/SearchPanel.tsx | 11 --- ui100/src/TabularView.tsx | 42 +++++++++ ui100/src/Visualizer.tsx | 18 +++- ui100/src/model/store.ts | 8 +- 7 files changed, 255 insertions(+), 17 deletions(-) delete mode 100644 ui100/src/SearchPanel.tsx create mode 100644 ui100/src/TabularView.tsx diff --git a/ui100/package-lock.json b/ui100/package-lock.json index afb9a6083..0c70c742b 100644 --- a/ui100/package-lock.json +++ b/ui100/package-lock.json @@ -15,6 +15,7 @@ "@mui/x-charts": "^7.23.2", "@xyflow/react": "^12.3.5", "d3-hierarchy": "^3.1.2", + "material-react-table": "^3.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.0.1", @@ -1414,6 +1415,73 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/@mui/x-date-pickers": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.3.tgz", + "integrity": "sha512-bjTYX/QzD5ZhVZNNnastMUS3j2Hy4p4IXmJgPJ0vKvQBvUdfEO+ZF42r3PJNNde0FVT1MmTzkmdTlz0JZ6ukdw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-internals": "7.23.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, "node_modules/@mui/x-internals": { "version": "7.23.0", "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.0.tgz", @@ -1806,6 +1874,82 @@ "win32" ] }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz", + "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", + "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3337,6 +3481,16 @@ "node": ">= 0.4" } }, + "node_modules/highlight-words": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-2.0.0.tgz", + "integrity": "sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==", + "license": "MIT", + "engines": { + "node": ">= 20", + "npm": ">= 9" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3604,6 +3758,34 @@ "yallist": "^3.0.2" } }, + "node_modules/material-react-table": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.1.0.tgz", + "integrity": "sha512-/zPn38QhxQE7mkwLex4CojX3UP2+/+/u7NVq7CHS5d6P8LdTteJPVYXVzym/uhaXzAzFB1ojsbP7zI/y6iUdtQ==", + "license": "MIT", + "dependencies": { + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.6", + "@tanstack/react-virtual": "3.11.2", + "highlight-words": "2.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kevinvandy" + }, + "peerDependencies": { + "@emotion/react": ">=11.13", + "@emotion/styled": ">=11.13", + "@mui/icons-material": ">=6", + "@mui/material": ">=6", + "@mui/x-date-pickers": ">=7.15", + "react": ">=18.0", + "react-dom": ">=18.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3997,6 +4179,12 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", diff --git a/ui100/package.json b/ui100/package.json index ed724f3cc..be0d86b1f 100644 --- a/ui100/package.json +++ b/ui100/package.json @@ -17,6 +17,7 @@ "@mui/x-charts": "^7.23.2", "@xyflow/react": "^12.3.5", "d3-hierarchy": "^3.1.2", + "material-react-table": "^3.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.0.1", diff --git a/ui100/src/ApiConsole.tsx b/ui100/src/ApiConsole.tsx index 8c44020f3..6efa45656 100644 --- a/ui100/src/ApiConsole.tsx +++ b/ui100/src/ApiConsole.tsx @@ -9,7 +9,7 @@ import EnvironmentPanel from "./EnvironmentPanel.tsx"; import SharePanel from "./SharePanel.tsx"; import AccessPanel from "./AccessPanel.tsx"; import useStore from "./model/store.ts"; -import SearchPanel from "./SearchPanel.tsx"; +import TabularView from "./TabularView.tsx"; interface ApiConsoleProps { logout: () => void; @@ -32,7 +32,7 @@ const ApiConsole = ({ logout }: ApiConsoleProps) => { if(showVisualizer) { setMainPanel(); } else { - setMainPanel(); + setMainPanel(); } } }, []); diff --git a/ui100/src/SearchPanel.tsx b/ui100/src/SearchPanel.tsx deleted file mode 100644 index 6b70a8053..000000000 --- a/ui100/src/SearchPanel.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import {Paper} from "@mui/material"; - -const SearchPanel = () => { - return ( - -

SearchPanel

-
- ); -}; - -export default SearchPanel; \ No newline at end of file diff --git a/ui100/src/TabularView.tsx b/ui100/src/TabularView.tsx new file mode 100644 index 000000000..a6ad2295f --- /dev/null +++ b/ui100/src/TabularView.tsx @@ -0,0 +1,42 @@ +import {Box, Paper} from "@mui/material"; +import useStore from "./model/store.ts"; +import {MaterialReactTable, type MRT_ColumnDef, useMaterialReactTable} from "material-react-table"; +import {useMemo} from "react"; +import {Node} from "@xyflow/react"; + +const data: Node[] = []; + +const TabularView = () => { + const overview = useStore((state) => state.overview); + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'data.label', + header: 'Label' + }, + { + accessorKey: 'type', + header: 'Type', + } + ], + [], + ); + + const table = useMaterialReactTable({ + columns: columns, + data: overview.nodes, + }); + + console.log(overview.nodes); + + return ( + + + + + + ); +}; + +export default TabularView; \ No newline at end of file diff --git a/ui100/src/Visualizer.tsx b/ui100/src/Visualizer.tsx index ff2fd5e98..0d0aa8beb 100644 --- a/ui100/src/Visualizer.tsx +++ b/ui100/src/Visualizer.tsx @@ -8,7 +8,8 @@ import { ReactFlow, ReactFlowProvider, useEdgesState, - useNodesState + useNodesState, + useStore as xyStore } from "@xyflow/react"; import {VisualOverview} from "./model/visualizer.ts"; import {useEffect} from "react"; @@ -30,8 +31,15 @@ const nodeTypes = { const Visualizer = () => { const overview = useStore((state) => state.overview); const updateSelectedNode = useStore((state) => state.updateSelectedNode); + const viewport = useStore((state) => state.viewport); + const updateViewport = useStore((state) => state.updateViewport); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const transform = xyStore((store) => store.transform); + + useEffect(() => { + updateViewport(transform); + }, [transform]); const onSelectionChange = ({ nodes }) => { if(nodes.length > 0) { @@ -77,6 +85,12 @@ const Visualizer = () => { } }, [overview]); + const defaultViewport = { + x: viewport[0], + y: viewport[1], + zoom: viewport[2], + } + return ( { onEdgesChange={onEdgesChange} onSelectionChange={onSelectionChange} nodesDraggable={false} - fitView + defaultViewport={defaultViewport} > diff --git a/ui100/src/model/store.ts b/ui100/src/model/store.ts index d7af47538..e04beab47 100644 --- a/ui100/src/model/store.ts +++ b/ui100/src/model/store.ts @@ -9,13 +9,15 @@ type StoreState = { environments: Array; overview: VisualOverview; selectedNode: Node; + viewport: Array; }; type StoreAction = { updateUser: (user: StoreState['user']) => void, updateOverview: (vov: StoreState['overview']) => void, updateEnvironments: (environments: StoreState['environments']) => void, - updateSelectedNode: (selectedNode: StoreState['selectedNode']) => void + updateSelectedNode: (selectedNode: StoreState['selectedNode']) => void, + updateViewport: (viewport: StoreState['viewport']) => void, }; const useStore = create((set) => ({ @@ -23,10 +25,12 @@ const useStore = create((set) => ({ overview: new VisualOverview(), environments: new Array(), selectedNode: null, + viewport: [0, 0, 1.5], updateUser: (user) => set({user: user}), updateOverview: (vov) => set({overview: vov}), updateEnvironments: (environments) => set({environments: environments}), - updateSelectedNode: (selectedNode) => set({selectedNode: selectedNode}) + updateSelectedNode: (selectedNode) => set({selectedNode: selectedNode}), + updateViewport: (viewport) => set({viewport: viewport}) })); export default useStore;