selection and state management between the visualizer and tabular view (#804, #819, #823)

This commit is contained in:
Michael Quigley 2025-01-08 14:11:06 -05:00
parent 0c551c8df2
commit deb0d8ca22
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
4 changed files with 82 additions and 32 deletions

View File

@ -27,8 +27,6 @@ const EnvironmentNode = ({ data }) => {
s[i] = v; s[i] = v;
}); });
setSparkData(s); setSparkData(s);
} else {
console.log("not found", data, environments);
} }
} }
}, [environments]); }, [environments]);

View File

@ -1,13 +1,33 @@
import {Box, Paper} from "@mui/material"; import {Box, Paper} from "@mui/material";
import useStore from "./model/store.ts"; import useStore from "./model/store.ts";
import {MaterialReactTable, type MRT_ColumnDef, useMaterialReactTable} from "material-react-table"; import {
import {useMemo} from "react"; getMRT_RowSelectionHandler,
MaterialReactTable,
type MRT_ColumnDef,
MRT_RowSelectionState,
useMaterialReactTable
} from "material-react-table";
import {useEffect, useMemo, useState} from "react";
import {Node} from "@xyflow/react"; import {Node} from "@xyflow/react";
const data: Node[] = [];
const TabularView = () => { const TabularView = () => {
const overview = useStore((state) => state.overview); const nodes = useStore((state) => state.nodes);
const selectedNode = useStore((state) => state.selectedNode);
const updateSelectedNode = useStore((state) => state.updateSelectedNode);
const [rowSelection, setRowSelection] = useState<MRT_RowSelectionState>({});
useEffect(() => {
if(selectedNode) {
let selection = {};
selection[selectedNode.id] = true;
setRowSelection(selection);
}
}, []);
useEffect(() => {
let sn = nodes.find(node => Object.keys(rowSelection).includes(node.id));
updateSelectedNode(sn);
}, [rowSelection]);
const columns = useMemo<MRT_ColumnDef<Node>[]>( const columns = useMemo<MRT_ColumnDef<Node>[]>(
() => [ () => [
@ -25,11 +45,24 @@ const TabularView = () => {
const table = useMaterialReactTable({ const table = useMaterialReactTable({
columns: columns, columns: columns,
data: overview.nodes, data: nodes,
enableRowSelection: false,
enableMultiRowSelection: false,
getRowId: r => r.id,
onRowSelectionChange: setRowSelection,
state: { rowSelection },
muiTableBodyRowProps: ({ row }) => ({
onClick: () => {
setRowSelection({[row.id]: true})
},
selected: rowSelection[row.id],
sx: {
cursor: 'pointer',
},
}),
positionToolbarAlertBanner: "bottom",
}); });
console.log(overview.nodes);
return ( return (
<Box sx={{ width: "100%", mt: 2 }} height={{ xs: 400, sm: 600, md: 800 }}> <Box sx={{ width: "100%", mt: 2 }} height={{ xs: 400, sm: 600, md: 800 }}>
<Paper> <Paper>

View File

@ -1,15 +1,15 @@
import "@xyflow/react/dist/style.css"; import "@xyflow/react/dist/style.css";
import "./styling/react-flow.css"; import "./styling/react-flow.css";
import { import {
applyNodeChanges,
Background, Background,
Controls, Controls,
MiniMap, MiniMap,
Node, Node,
ReactFlow, ReactFlow,
ReactFlowProvider, ReactFlowProvider,
useEdgesState, useOnViewportChange,
useNodesState, Viewport
useStore as xyStore
} from "@xyflow/react"; } from "@xyflow/react";
import {VisualOverview} from "./model/visualizer.ts"; import {VisualOverview} from "./model/visualizer.ts";
import {useEffect} from "react"; import {useEffect} from "react";
@ -30,16 +30,24 @@ const nodeTypes = {
const Visualizer = () => { const Visualizer = () => {
const overview = useStore((state) => state.overview); const overview = useStore((state) => state.overview);
const selectedNode = useStore((state) => state.selectedNode);
const updateSelectedNode = useStore((state) => state.updateSelectedNode); const updateSelectedNode = useStore((state) => state.updateSelectedNode);
const viewport = useStore((state) => state.viewport); const viewport = useStore((state) => state.viewport);
const updateViewport = useStore((state) => state.updateViewport); const updateViewport = useStore((state) => state.updateViewport);
const [nodes, setNodes, onNodesChange] = useNodesState([]); const nodes = useStore((state) => state.nodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]); const updateNodes = useStore((state) => state.updateNodes);
const transform = xyStore((store) => store.transform); const edges = useStore((state) => state.edges);
const updateEdges = useStore((state) => state.updateEdges);
useEffect(() => { const onNodesChange = (changes) => {
updateViewport(transform); updateNodes(applyNodeChanges(changes, nodes));
}, [transform]); }
useOnViewportChange({
onEnd: (viewport: Viewport) => {
updateViewport(viewport);
}
});
const onSelectionChange = ({ nodes }) => { const onSelectionChange = ({ nodes }) => {
if(nodes.length > 0) { if(nodes.length > 0) {
@ -80,27 +88,30 @@ const Visualizer = () => {
useEffect(() => { useEffect(() => {
if(overview) { if(overview) {
let laidOut = layout(overview.nodes, overview.edges); let laidOut = layout(overview.nodes, overview.edges);
setNodes(laidOut.nodes); let selected = laidOut.nodes.map((n) => ({
setEdges(laidOut.edges); ...n,
selected: selectedNode ? selectedNode.id === n.id : false,
}));
updateNodes(selected);
updateEdges(laidOut.edges);
} }
}, [overview]); }, [overview]);
const defaultViewport = { let fitView = false;
x: viewport[0], if(viewport.x === 0 && viewport.y === 0 && viewport.zoom === 1) {
y: viewport[1], fitView = true;
zoom: viewport[2],
} }
return ( return (
<ReactFlow <ReactFlow
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
nodes={nodes} nodes={nodes}
edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} edges={edges}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
nodesDraggable={false} nodesDraggable={false}
defaultViewport={defaultViewport} defaultViewport={viewport}
fitView={fitView}
> >
<Background /> <Background />
<Controls position="bottom-left" orientation="horizontal" showInteractive={false} /> <Controls position="bottom-left" orientation="horizontal" showInteractive={false} />

View File

@ -1,15 +1,17 @@
import {create} from "zustand"; import {create} from "zustand";
import {Environment} from "../api"; import {Environment} from "../api";
import {VisualOverview} from "./visualizer.ts"; import {VisualOverview} from "./visualizer.ts";
import {Node} from "@xyflow/react"; import {Edge, Node, Viewport} from "@xyflow/react";
import {User} from "./user.ts"; import {User} from "./user.ts";
type StoreState = { type StoreState = {
user: User; user: User;
environments: Array<Environment>;
overview: VisualOverview; overview: VisualOverview;
environments: Array<Environment>;
nodes: Node[];
edges: Edge[];
selectedNode: Node; selectedNode: Node;
viewport: Array<Number>; viewport: Viewport;
}; };
type StoreAction = { type StoreAction = {
@ -17,6 +19,8 @@ type StoreAction = {
updateOverview: (vov: StoreState['overview']) => void, updateOverview: (vov: StoreState['overview']) => void,
updateEnvironments: (environments: StoreState['environments']) => void, updateEnvironments: (environments: StoreState['environments']) => void,
updateSelectedNode: (selectedNode: StoreState['selectedNode']) => void, updateSelectedNode: (selectedNode: StoreState['selectedNode']) => void,
updateNodes: (nodes: StoreState['nodes']) => void,
updateEdges: (edges: StoreState['edges']) => void,
updateViewport: (viewport: StoreState['viewport']) => void, updateViewport: (viewport: StoreState['viewport']) => void,
}; };
@ -24,11 +28,15 @@ const useStore = create<StoreState & StoreAction>((set) => ({
user: null, user: null,
overview: new VisualOverview(), overview: new VisualOverview(),
environments: new Array<Environment>(), environments: new Array<Environment>(),
nodes: [],
edges: [],
selectedNode: null, selectedNode: null,
viewport: [0, 0, 1.5], viewport: {x: 0, y: 0, zoom: 1},
updateUser: (user) => set({user: user}), updateUser: (user) => set({user: user}),
updateOverview: (vov) => set({overview: vov}), updateOverview: (vov) => set({overview: vov}),
updateEnvironments: (environments) => set({environments: environments}), updateEnvironments: (environments) => set({environments: environments}),
updateNodes: (nodes) => set({nodes: nodes}),
updateEdges: (edges) => set({edges: edges}),
updateSelectedNode: (selectedNode) => set({selectedNode: selectedNode}), updateSelectedNode: (selectedNode) => set({selectedNode: selectedNode}),
updateViewport: (viewport) => set({viewport: viewport}) updateViewport: (viewport) => set({viewport: viewport})
})); }));