json / src /store /useGraph.ts
xinnni's picture
Upload 146 files
f909d7c verified
import { ViewPort } from "react-zoomable-ui/dist/ViewPort";
import { CanvasDirection } from "reaflow/dist/layout/elkLayout";
import { create } from "zustand";
import { getChildrenEdges } from "src/lib/utils/graph/getChildrenEdges";
import { getOutgoers } from "src/lib/utils/graph/getOutgoers";
import { parser } from "src/lib/utils/json/jsonParser";
import { NodeData, EdgeData } from "src/types/graph";
import useJson from "./useJson";
export interface Graph {
viewPort: ViewPort | null;
direction: CanvasDirection;
loading: boolean;
graphCollapsed: boolean;
foldNodes: boolean;
fullscreen: boolean;
collapseAll: boolean;
nodes: NodeData[];
edges: EdgeData[];
collapsedNodes: string[];
collapsedEdges: string[];
collapsedParents: string[];
selectedNode: NodeData | null;
path: string;
}
const initialStates: Graph = {
viewPort: null,
direction: "RIGHT",
loading: true,
graphCollapsed: false,
foldNodes: false,
fullscreen: false,
collapseAll: false,
nodes: [],
edges: [],
collapsedNodes: [],
collapsedEdges: [],
collapsedParents: [],
selectedNode: null,
path: "",
};
interface GraphActions {
setGraph: (json?: string, options?: Partial<Graph>[]) => void;
setLoading: (loading: boolean) => void;
setDirection: (direction: CanvasDirection) => void;
setViewPort: (ref: ViewPort) => void;
setSelectedNode: (nodeData: NodeData) => void;
focusFirstNode: () => void;
expandNodes: (nodeId: string) => void;
expandGraph: () => void;
collapseNodes: (nodeId: string) => void;
collapseGraph: () => void;
getCollapsedNodeIds: () => string[];
getCollapsedEdgeIds: () => string[];
toggleFold: (value: boolean) => void;
toggleFullscreen: (value: boolean) => void;
toggleCollapseAll: (value: boolean) => void;
zoomIn: () => void;
zoomOut: () => void;
centerView: () => void;
clearGraph: () => void;
setZoomFactor: (zoomFactor: number) => void;
}
const useGraph = create<Graph & GraphActions>((set, get) => ({
...initialStates,
toggleCollapseAll: collapseAll => {
set({ collapseAll });
get().collapseGraph();
},
clearGraph: () => set({ nodes: [], edges: [], loading: false }),
getCollapsedNodeIds: () => get().collapsedNodes,
getCollapsedEdgeIds: () => get().collapsedEdges,
setSelectedNode: nodeData => set({ selectedNode: nodeData }),
setGraph: (data, options) => {
const { nodes, edges } = parser(data ?? useJson.getState().json);
if (get().collapseAll) {
set({ nodes, edges, ...options });
get().collapseGraph();
} else {
set({
nodes,
edges,
collapsedParents: [],
collapsedNodes: [],
collapsedEdges: [],
graphCollapsed: false,
...options,
});
}
},
setDirection: (direction = "RIGHT") => {
set({ direction });
setTimeout(() => get().centerView(), 200);
},
setLoading: loading => set({ loading }),
expandNodes: nodeId => {
const [childrenNodes, matchingNodes] = getOutgoers(
nodeId,
get().nodes,
get().edges,
get().collapsedParents
);
const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
const nodesConnectedToParent = childrenEdges.reduce((nodes: string[], edge) => {
edge.from && !nodes.includes(edge.from) && nodes.push(edge.from);
edge.to && !nodes.includes(edge.to) && nodes.push(edge.to);
return nodes;
}, []);
const matchingNodesConnectedToParent = matchingNodes.filter(node =>
nodesConnectedToParent.includes(node)
);
const nodeIds = childrenNodes.map(node => node.id).concat(matchingNodesConnectedToParent);
const edgeIds = childrenEdges.map(edge => edge.id);
const collapsedParents = get().collapsedParents.filter(cp => cp !== nodeId);
const collapsedNodes = get().collapsedNodes.filter(nodeId => !nodeIds.includes(nodeId));
const collapsedEdges = get().collapsedEdges.filter(edgeId => !edgeIds.includes(edgeId));
set({
collapsedParents,
collapsedNodes,
collapsedEdges,
graphCollapsed: !!collapsedNodes.length,
});
},
collapseNodes: nodeId => {
const [childrenNodes] = getOutgoers(nodeId, get().nodes, get().edges);
const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
const nodeIds = childrenNodes.map(node => node.id);
const edgeIds = childrenEdges.map(edge => edge.id);
set({
collapsedParents: get().collapsedParents.concat(nodeId),
collapsedNodes: get().collapsedNodes.concat(nodeIds),
collapsedEdges: get().collapsedEdges.concat(edgeIds),
graphCollapsed: !!get().collapsedNodes.concat(nodeIds).length,
});
},
collapseGraph: () => {
const edges = get().edges;
const tos = edges.map(edge => edge.to);
const froms = edges.map(edge => edge.from);
const parentNodesIds = froms.filter(id => !tos.includes(id));
const secondDegreeNodesIds = edges
.filter(edge => parentNodesIds.includes(edge.from))
.map(edge => edge.to);
const collapsedParents = get()
.nodes.filter(node => !parentNodesIds.includes(node.id) && node.data?.isParent)
.map(node => node.id);
const collapsedNodes = get()
.nodes.filter(
node => !parentNodesIds.includes(node.id) && !secondDegreeNodesIds.includes(node.id)
)
.map(node => node.id);
const closestParentToRoot = Math.min(...collapsedParents.map(n => +n));
const focusNodeId = `g[id*='node-${closestParentToRoot}']`;
const rootNode = document.querySelector(focusNodeId);
set({
collapsedParents,
collapsedNodes,
collapsedEdges: get()
.edges.filter(edge => !parentNodesIds.includes(edge.from))
.map(edge => edge.id),
graphCollapsed: true,
});
if (rootNode) {
get().viewPort?.camera?.centerFitElementIntoView(rootNode as HTMLElement, {
elementExtraMarginForZoom: 300,
});
}
},
expandGraph: () => {
set({
collapsedNodes: [],
collapsedEdges: [],
collapsedParents: [],
graphCollapsed: false,
});
},
focusFirstNode: () => {
const rootNode = document.querySelector("g[id*='node-1']");
get().viewPort?.camera?.centerFitElementIntoView(rootNode as HTMLElement, {
elementExtraMarginForZoom: 100,
});
},
setZoomFactor: zoomFactor => {
const viewPort = get().viewPort;
viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, zoomFactor);
},
zoomIn: () => {
const viewPort = get().viewPort;
viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor + 0.1);
},
zoomOut: () => {
const viewPort = get().viewPort;
viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor - 0.1);
},
centerView: () => {
const viewPort = get().viewPort;
viewPort?.updateContainerSize();
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement | null;
if (canvas) {
viewPort?.camera?.centerFitElementIntoView(canvas);
}
},
toggleFold: foldNodes => {
set({ foldNodes });
get().setGraph();
},
toggleFullscreen: fullscreen => set({ fullscreen }),
setViewPort: viewPort => set({ viewPort }),
}));
export default useGraph;