layers tree I
This commit is contained in:
@@ -40,3 +40,7 @@
|
|||||||
.read-the-docs {
|
.read-the-docs {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-tree-list-holder {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
57
src/App.tsx
57
src/App.tsx
@@ -1,16 +1,22 @@
|
|||||||
import React, { createContext, useEffectEvent, useState } from 'react';
|
import React, { createContext, useEffect, useEffectEvent, useState } from 'react';
|
||||||
import { Layout, theme, Breadcrumb } from 'antd';
|
import { Layout, theme, Breadcrumb, Button, Space, Tree, type TreeDataNode } from 'antd';
|
||||||
import Graph, { GraphModel } from './components/Graph';
|
import Graph, { GraphModel } 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 {
|
||||||
|
MenuFoldOutlined,
|
||||||
|
MenuUnfoldOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useGraphLayersTreeStore } from './stores/TreeStore';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Header, Content, Sider } = Layout;
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
token: { colorBgContainer, borderRadiusLG },
|
token: { colorBgContainer },
|
||||||
} = theme.useToken();
|
} = theme.useToken();
|
||||||
const [graphLevel, setGraphLevel] = useState<BreadcrumbItemType[]>([])
|
const [graphLevel, setGraphLevel] = useState<BreadcrumbItemType[]>([])
|
||||||
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const addKey = useKeysdownStore(state => state.add);
|
const addKey = useKeysdownStore(state => state.add);
|
||||||
const removeKey = useKeysdownStore(state => state.remove);
|
const removeKey = useKeysdownStore(state => state.remove);
|
||||||
const onKeydown = useEffectEvent((key: string) => {
|
const onKeydown = useEffectEvent((key: string) => {
|
||||||
@@ -22,21 +28,56 @@ const App: React.FC = () => {
|
|||||||
document.addEventListener('keydown', (ev) => {
|
document.addEventListener('keydown', (ev) => {
|
||||||
onKeydown(ev.key)
|
onKeydown(ev.key)
|
||||||
});
|
});
|
||||||
document.addEventListener('keyup', (ev) => {
|
document.addEventListener('keyup', (ev) => {
|
||||||
onKeyUp(ev.key);
|
onKeyUp(ev.key);
|
||||||
})
|
});
|
||||||
|
const treeData = useGraphLayersTreeStore(store => store.tree);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.info(treeData);
|
||||||
|
}, [treeData])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
<Sider trigger={null} collapsible collapsed={collapsed} collapsedWidth={0} style={{
|
||||||
|
background: colorBgContainer,
|
||||||
|
}}>
|
||||||
|
<Header style={{ padding: 0 }}></Header>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
treeData={treeData}
|
||||||
|
defaultExpandAll={true}
|
||||||
|
style={{
|
||||||
|
borderRadius: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Sider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Breadcrumb style={{ margin: '8px 0 0 16px' }} items={graphLevel} />
|
<Header style={{ padding: 0, background: colorBgContainer }}>
|
||||||
|
<Space>
|
||||||
|
<div style={{ background: '#001529' }}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
color: colorBgContainer,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Breadcrumb items={graphLevel} />
|
||||||
|
</Space>
|
||||||
|
</Header>
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
margin: '8px 16px',
|
margin: '8px 16px',
|
||||||
padding: 24,
|
padding: 24,
|
||||||
minHeight: 280,
|
minHeight: 280,
|
||||||
background: colorBgContainer,
|
background: colorBgContainer,
|
||||||
borderRadius: borderRadiusLG,
|
borderRadius: '6px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Graph setGraphPath={setGraphLevel} />
|
<Graph setGraphPath={setGraphLevel} />
|
||||||
|
|||||||
@@ -190,12 +190,7 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRandomWords(count: number) {
|
function getRandomWords(count: number) {
|
||||||
const wordList = [
|
const wordList = ['Apple', 'Sun', 'Flame', 'Earth', 'Forest', 'Dream', 'Sky', 'Shadow', 'Flower', 'Ocean', 'River', 'Path', 'Sand', 'Night', 'Star', 'Rain', 'Light', 'Tree', 'Wave', 'Storm', 'Stone', 'Snow', 'Cloud', 'Heart', 'Mountain', 'Leaf', 'Bird', 'Wind', 'Fire', 'Wolf'];
|
||||||
"apple", "river", "mountain", "sky", "storm", "ocean", "forest", "dream",
|
|
||||||
"stone", "flame", "shadow", "cloud", "leaf", "wind", "fire", "earth",
|
|
||||||
"flower", "bird", "light", "night", "sun", "rain", "snow", "tree",
|
|
||||||
"wolf", "star", "sand", "wave", "heart", "path"
|
|
||||||
];
|
|
||||||
|
|
||||||
return wordList
|
return wordList
|
||||||
.sort(() => Math.random() - 0.5)
|
.sort(() => Math.random() - 0.5)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { defaultGraph, graphContext, type EdgeModel, type NodeContext } from "./
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useGraphsStore } from "../stores/GraphsStore";
|
import { useGraphsStore } from "../stores/GraphsStore";
|
||||||
|
import { useGraphLayersTreeStore } from "../stores/TreeStore";
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
@@ -39,6 +40,7 @@ export default function NodeContextMenu({
|
|||||||
|
|
||||||
const graphContextValue = useContext(graphContext)!;
|
const graphContextValue = useContext(graphContext)!;
|
||||||
const graphsById = useGraphsStore((s) => s.graphsById);
|
const graphsById = useGraphsStore((s) => s.graphsById);
|
||||||
|
const addTreeNode = useGraphLayersTreeStore(store => store.add);
|
||||||
|
|
||||||
function contextMenuOpenChange(open: boolean) {
|
function contextMenuOpenChange(open: boolean) {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
@@ -64,9 +66,10 @@ export default function NodeContextMenu({
|
|||||||
if (!selectedGraph) {
|
if (!selectedGraph) {
|
||||||
selectedGraph = defaultGraph();
|
selectedGraph = defaultGraph();
|
||||||
graphsById.set(nodeContext.nodeId, selectedGraph);
|
graphsById.set(nodeContext.nodeId, selectedGraph);
|
||||||
|
const parenNodeId = graphContextValue.graphId === 'main' ? undefined : graphContextValue.graphId;
|
||||||
|
addTreeNode(nodeContext, parenNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info(graphsById)
|
|
||||||
graphContextValue.setGraph(selectedGraph);
|
graphContextValue.setGraph(selectedGraph);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
115
src/stores/TreeStore.tsx
Normal file
115
src/stores/TreeStore.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import type { TreeDataNode } from "antd";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import React from "react";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import type { NodeContext } from "../components/Graph";
|
||||||
|
|
||||||
|
export interface TreeStore {
|
||||||
|
nodesFlatById: Map<React.Key, TreeDataNode>,
|
||||||
|
parentIdByChildId: Map<React.Key, string>;
|
||||||
|
rootNodes: TreeDataNode[];
|
||||||
|
tree: TreeDataNode[];
|
||||||
|
add: (childNode: NodeContext, parentNodeId: string | undefined) => void;
|
||||||
|
remove: (nodeId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGraphLayersTreeStore = create<TreeStore>()((set, get) => ({
|
||||||
|
nodesFlatById: new Map<React.Key, TreeDataNode>(),
|
||||||
|
parentIdByChildId: new Map<React.Key, string>(),
|
||||||
|
rootNodes: [],
|
||||||
|
tree: [],
|
||||||
|
add: (childNodeContext, parentNodeId) => set((state) => {
|
||||||
|
const childNode = nodeContextToTreeNode(childNodeContext);
|
||||||
|
if (parentNodeId) {
|
||||||
|
const parentNode = state.nodesFlatById.get(parentNodeId);
|
||||||
|
if (parentNode) {
|
||||||
|
parentNode.children = parentNode.children ? [...parentNode.children, childNode] : [childNode];
|
||||||
|
const parentIdByChildId = new Map(state.parentIdByChildId);
|
||||||
|
parentIdByChildId.set(childNode.key, parentNodeId);
|
||||||
|
const nodesFlatById = new Map(state.nodesFlatById);
|
||||||
|
nodesFlatById.set(childNode.key, childNode);
|
||||||
|
|
||||||
|
const newState = {
|
||||||
|
nodesFlatById: nodesFlatById,
|
||||||
|
parentIdByChildId: parentIdByChildId,
|
||||||
|
rootNodes: [...state.rootNodes],
|
||||||
|
tree: createTree([...state.rootNodes], nodesFlatById)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
} else {
|
||||||
|
throw Error(`There is no parent node with id: ${parentNodeId}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nodesFlatById = new Map(state.nodesFlatById);
|
||||||
|
nodesFlatById.set(childNode.key, childNode);
|
||||||
|
const newState = {
|
||||||
|
nodesFlatById: nodesFlatById,
|
||||||
|
parentIdByChildId: state.parentIdByChildId,
|
||||||
|
rootNodes: [...state.rootNodes, childNode],
|
||||||
|
tree: createTree([...state.rootNodes], nodesFlatById)
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
remove: (nodeId) => set((state) => {
|
||||||
|
const node = state.nodesFlatById.get(nodeId);
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodesFlatById = new Map(state.nodesFlatById);
|
||||||
|
nodesFlatById.delete(nodeId);
|
||||||
|
const parentNodeId = state.parentIdByChildId.get(nodeId);
|
||||||
|
|
||||||
|
if (parentNodeId) {
|
||||||
|
const parentNode = state.nodesFlatById.get(parentNodeId);
|
||||||
|
|
||||||
|
if (parentNode) {
|
||||||
|
parentNode.children = parentNode.children
|
||||||
|
? [...parentNode.children?.filter(n => n.key !== nodeId)]
|
||||||
|
: parentNode.children;
|
||||||
|
const parentIdByChildId = new Map(state.parentIdByChildId);
|
||||||
|
parentIdByChildId.delete(nodeId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rootNodes: [...state.rootNodes],
|
||||||
|
nodesFlatById: nodesFlatById,
|
||||||
|
parentIdByChildId: parentIdByChildId,
|
||||||
|
tree: createTree([...state.rootNodes], nodesFlatById)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rootNodes: [...state.rootNodes.filter(n => n.key !== nodeId)],
|
||||||
|
nodesFlatById: nodesFlatById,
|
||||||
|
parentIdByChildId: state.parentIdByChildId,
|
||||||
|
tree: createTree([...state.rootNodes], nodesFlatById)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function createTree(nodes: TreeDataNode[], nodesFlatById: Map<React.Key, TreeDataNode>): TreeDataNode[] {
|
||||||
|
const n = [...nodes];
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (const node of n) {
|
||||||
|
const stateNode = nodesFlatById.get(node.key);
|
||||||
|
|
||||||
|
if (stateNode) {
|
||||||
|
stateNode.children = createTree(stateNode?.children ?? [], nodesFlatById);
|
||||||
|
result.push(stateNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nodeContextToTreeNode(nodeContext: NodeContext): TreeDataNode {
|
||||||
|
return {
|
||||||
|
key: nodeContext.nodeId,
|
||||||
|
title: nodeContext.nodeName,
|
||||||
|
} as TreeDataNode;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user