feature: tree navigation
This commit is contained in:
27
src/App.tsx
27
src/App.tsx
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useEffectEvent, useState } from 'react';
|
import React, { useEffectEvent, useRef, useState } from 'react';
|
||||||
import { Layout, theme, Breadcrumb, Button, Space, Tree } from 'antd';
|
import { Layout, theme, Breadcrumb, Button, Space, Tree } from 'antd';
|
||||||
import Graph from './components/Graph';
|
import Graph, { type GraphHandle } from './components/Graph';
|
||||||
import type { BreadcrumbItemType } from 'antd/es/breadcrumb/Breadcrumb';
|
import type { BreadcrumbItemType } from 'antd/es/breadcrumb/Breadcrumb';
|
||||||
import { useKeysdownStore } from './stores/ArrayStore';
|
import { useKeysdownStore } from './stores/ArrayStore';
|
||||||
import {
|
import {
|
||||||
@@ -36,10 +36,20 @@ const App: React.FC = () => {
|
|||||||
onKeyUp(ev.key);
|
onKeyUp(ev.key);
|
||||||
});
|
});
|
||||||
const treeData = useGraphLayersTreeStore(store => store.tree);
|
const treeData = useGraphLayersTreeStore(store => store.tree);
|
||||||
|
const nodesFlatById = useGraphLayersTreeStore(store => store.nodesFlatById);
|
||||||
|
const parentIdByChildId = useGraphLayersTreeStore(store => store.parentIdByChildId);
|
||||||
|
const graphRef = useRef<GraphHandle>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
function buildPathToNode(nodeId: string) {
|
||||||
console.info(treeData);
|
const path: Array<{ id: string; name: string | undefined }> = [];
|
||||||
}, [treeData])
|
let current: string | undefined = nodeId;
|
||||||
|
while (current && nodesFlatById.has(current)) {
|
||||||
|
const node = nodesFlatById.get(current)!;
|
||||||
|
path.unshift({ id: current, name: node.title as string | undefined });
|
||||||
|
current = parentIdByChildId.get(current);
|
||||||
|
}
|
||||||
|
return [{ id: 'main', name: 'Main' }, ...path];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
@@ -54,6 +64,11 @@ const App: React.FC = () => {
|
|||||||
style={{
|
style={{
|
||||||
borderRadius: 0
|
borderRadius: 0
|
||||||
}}
|
}}
|
||||||
|
onSelect={(keys) => {
|
||||||
|
const nodeId = keys[0] as string;
|
||||||
|
if (!nodeId) return;
|
||||||
|
graphRef.current?.navigateTo(nodeId, buildPathToNode(nodeId));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Layout>
|
<Layout>
|
||||||
@@ -110,7 +125,7 @@ const App: React.FC = () => {
|
|||||||
borderRadius: '6px'
|
borderRadius: '6px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Graph setGraphPath={setGraphLevel} />
|
<Graph ref={graphRef} setGraphPath={setGraphLevel} />
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createContext, useEffect, useRef, useState } from "react";
|
import { createContext, forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||||
import Viz from 'viz.js';
|
import Viz from 'viz.js';
|
||||||
import { Module, render } from 'viz.js/full.render.js';
|
import { Module, render } from 'viz.js/full.render.js';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
@@ -63,7 +63,11 @@ export interface OpenNodeContext {
|
|||||||
const viz = new Viz({ Module, render });
|
const viz = new Viz({ Module, render });
|
||||||
export const graphContext = createContext<GraphContext | null>(null);
|
export const graphContext = createContext<GraphContext | null>(null);
|
||||||
|
|
||||||
export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<React.SetStateAction<BreadcrumbItemType[]>> }) {
|
export interface GraphHandle {
|
||||||
|
navigateTo: (nodeId: string, path: Array<{ id: string; name: string | undefined }>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Graph = forwardRef<GraphHandle, { setGraphPath: React.Dispatch<React.SetStateAction<BreadcrumbItemType[]>> }>(function Graph({ setGraphPath }, ref) {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const [graph, setGraph] = useState(defaultGraph());
|
const [graph, setGraph] = useState(defaultGraph());
|
||||||
const [contextMenuOpened, openContextMenu] = useState(false);
|
const [contextMenuOpened, openContextMenu] = useState(false);
|
||||||
@@ -242,6 +246,14 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
|
|||||||
} as BreadcrumbItemType;
|
} as BreadcrumbItemType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
navigateTo(nodeId, path) {
|
||||||
|
const newPath = path.map(p => createPathSegment(p.id, p.name));
|
||||||
|
setGraphsPath(newPath);
|
||||||
|
selectGraphId(nodeId);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 p-4">
|
<div className="flex-1 p-4">
|
||||||
<div ref={containerRef} className="w-full h-full bg-white rounded shadow" style={{ minHeight: '600px', overflow: 'auto' }}>
|
<div ref={containerRef} className="w-full h-full bg-white rounded shadow" style={{ minHeight: '600px', overflow: 'auto' }}>
|
||||||
@@ -262,7 +274,9 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
|
|||||||
</graphContext.Provider>
|
</graphContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default Graph;
|
||||||
|
|
||||||
export function defaultGraph(): GraphModel {
|
export function defaultGraph(): GraphModel {
|
||||||
const start = crypto.randomUUID();
|
const start = crypto.randomUUID();
|
||||||
|
|||||||
Reference in New Issue
Block a user