import { cellStyle } from "@/types"; import { Cell, Edge, EventArgs, Graph, Node } from "@antv/x6"; import { message } from "antd"; import { useEffect, useRef, useState } from "react"; import { Selection } from "@repo/x6-plugin-selection"; import { Keyboard } from "@antv/x6-plugin-keyboard"; import { History } from "@antv/x6-plugin-history"; import { Transform } from "@antv/x6-plugin-transform"; import { Scroller } from "@antv/x6-plugin-scroller"; import { Clipboard } from "@antv/x6-plugin-clipboard"; import { Export } from "@antv/x6-plugin-export"; import { MindMapProjectInfo } from "@/types"; import { bindMindMapEvents } from "@/events/mindMapEvent"; import { useSessionStorageState } from "ahooks"; import { renderMindMap } from "@/pages/mindmap/mindMap"; import { TopicType } from "@/enum"; import { isEqual, cloneDeep } from "lodash-es"; import { bindMindmapKeys } from "@/utils/fastKey"; import { handleCreateCorrelationEdge } from "@/utils/mindmapHander"; import { Dnd } from "@antv/x6-plugin-dnd"; import { EditGraph, BatchEditMindMapElement } from "@/api/systemDesigner"; export default function mindMapModel() { const [rightToobarActive, setRightToolbarActive] = useState(); // 格式刷启用 const [enableFormatBrush, setEnableFormatBrush] = useState(false); // 格式刷样式 const formatBrushStyle = useRef(); const graphRef = useRef(); const dndRef = useRef(); const [graph, setGraph] = useState(); const [canRedo, setCanRedo] = useState(false); const [canUndo, setCanUndo] = useState(false); const [selectedCell, setSelectedCell] = useState([]); const correlationEdgeRef = useRef(); const [mindProjectInfo, setProjectInfo] = useState(); const projectInfoRef = useRef(); const timer = useRef(); const setMindProjectInfo = ( info: MindMapProjectInfo, init?: boolean, isSetting?: boolean, ignoreRender?: boolean ) => { setProjectInfo(cloneDeep(info)); projectInfoRef.current = info; sessionStorage.setItem("mindMapProjectInfo", JSON.stringify(info)); if (!init && !isSetting) { const graphId = sessionStorage.getItem("projectId"); if (graphId && info?.topics) { // 清除定时器 clearTimeout(timer.current); timer.current = setTimeout(() => { BatchEditMindMapElement( info.topics.map((topic) => { return { ...topic, graphId, }; }) ); }, 500); } } // 配置更新 if (isSetting) { const pageSetting = info?.pageSetting; if (sessionStorage.getItem("projectId") && pageSetting) { EditGraph({ id: sessionStorage.getItem("projectId"), ...pageSetting, structure: info?.structure, theme: info?.theme, langNameList: [ { name: "zh-CN", value: info.name, }, ], }); } } if(!ignoreRender && graphRef.current) { renderMindMap({ graph: graphRef.current, setMindProjectInfo, pageSetting: info?.pageSetting, structure: info?.structure, theme: info?.theme, topics: info?.topics, }); } if(init) { graph?.centerContent(); } }; const pageSettingRef = useRef(); useEffect(() => { if (mindProjectInfo?.pageSetting && graph) { if (isEqual(pageSettingRef.current, mindProjectInfo?.pageSetting)) { return; } pageSettingRef.current = cloneDeep( mindProjectInfo?.pageSetting ) as MindMapProjectInfo["pageSetting"]; const pageSetting = pageSettingRef.current; if (pageSetting?.backgroundType === "color") { graph.drawBackground({ color: pageSetting?.backgroundColor, }); } else { graph.drawBackground({ image: pageSetting?.backgroundImage, repeat: "repeat", }); } // 设置水印 if (pageSetting.showWatermark && pageSetting.watermarkText) { const canvas = document.createElement("canvas"); canvas.width = pageSetting.watermarkText.length * 16; canvas.height = 100; const ctx = canvas.getContext("2d"); if (ctx) { ctx.fillStyle = "#aaa"; ctx.font = "16px Arial"; ctx.fillText(pageSetting.watermarkText, 1, 15); } const img = canvas.toDataURL(); graph.drawBackground({ image: img, repeat: "watermark", }); } } }, [graph, mindProjectInfo?.pageSetting]); // 初始化脑图 const initMindMap = (container: HTMLElement) => { const instance = new Graph({ container, width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, autoResize: true, async: false, mousewheel: { enabled: true, modifiers: "ctrl", minScale: 0.2, maxScale: 2, }, connecting: { connectionPoint: "anchor", }, interacting: { nodeMovable: (view) => { const data = view.cell.getData<{ ignoreDrag: boolean; lock: boolean; type: TopicType; parentId: string; shadow: boolean; isSummary: boolean; }>(); // 禁止拖拽或锁节点 if (data?.ignoreDrag || data?.lock) return false; // 影子节点 if (data?.shadow) return true; // 概要 if (data?.isSummary) return false; // 自由节点 return data?.type === TopicType.branch && !data?.parentId; }, }, }); instance.use(new Selection()); instance.use(new Keyboard()); instance.use(new Clipboard()); instance.use(new Export()); instance.use( new Scroller({ enabled: true, pannable: true, }) ); instance.use( new Transform({ resizing: { enabled: true, orthogonal: true, }, }) ); instance.use( new History({ enabled: true, // beforeAddCommand: (e, args) => { // // @ts-ignore // return !args.cell.isEdge() // }, }) ); instance.on("history:change", () => { setCanRedo(instance.canRedo()); setCanUndo(instance.canUndo()); }); graphRef.current = instance; dndRef.current = new Dnd({ target: instance, validateNode: () => { return false; }, }); // 绑定事件 bindMindMapEvents(instance, setMindProjectInfo, setSelectedCell, dndRef); // 绑定键盘 bindMindmapKeys(instance, mindProjectInfo, setMindProjectInfo); // 绑定实例方法 // @ts-ignore instance.extendAttr = { setRightToolbarActive, correlationEdgeRef, setMindProjectInfo, getMindProjectInfo: () => cloneDeep(projectInfoRef.current), }; setGraph(instance); }; const handleBrushClick = (args: EventArgs & { cell: Cell }) => { // 取消格式刷 if (!args?.cell || args?.cell?.data?.isPage) { formatBrushStyle.current = undefined; setEnableFormatBrush(false); graphRef.current?.off("cell:click", handleBrushClick); graphRef.current?.off("blank:click", handleBrushClick); } else { if (args.cell.data?.lock) return; // 应用格式刷 const data = args.cell.data; args.cell.setData({ text: formatBrushStyle.current?.text || data?.text, fill: formatBrushStyle.current?.fill || data?.fill, stroke: formatBrushStyle.current?.stroke || data?.stroke, opacity: formatBrushStyle.current?.opacity || data?.opacity, }); } }; // 开启格式刷 const toggleFormatBrush = (graph: Graph) => { graphRef.current = graph; const cell = graph?.getSelectedCells()?.find((item) => item.isNode()); setEnableFormatBrush((state) => { if (!state) { const data = cell?.getData(); formatBrushStyle.current = data; message.info("格式刷已开启"); graph.on("cell:click", handleBrushClick); graph.on("blank:click", handleBrushClick); } else { formatBrushStyle.current = undefined; graph.off("cell:click", handleBrushClick); graph.off("blank:click", handleBrushClick); } return !state; }); }; // 撤销 const onUndo = () => { graphRef.current?.undo(); }; // 重做 const onRedo = () => { graphRef.current?.redo(); }; // 设置右侧工具激活项 const rightToolbarActive = (type: string) => { setRightToolbarActive(rightToobarActive === type ? undefined : type); }; const setCorrelationEdgeInfo = (sourceNode?: Node) => { graph && handleCreateCorrelationEdge(graph, correlationEdgeRef, sourceNode); }; // 添加连接线 const handleAddCorrelation = (source: Cell, target: Cell) => { const link = graph ?.createEdge({ source: { cell: source.id, connectionPoint: "rect", anchor: "center", }, target: { cell: target.id, connectionPoint: "rect", anchor: "center", }, zIndex: 0, connector: "smooth", attrs: { line: { stroke: "#71cb2d", strokeWidth: 2, }, }, data: { isLink: true, }, tools: ["vertices", "edge-editor", "button-remove"], }) .toJSON(); if (link) { source.setData({ links: [...(source.data?.links || []), link], }); } }; useEffect(() => { if (graph) { graph.on("node:click", (args) => { if (correlationEdgeRef.current) { handleAddCorrelation( correlationEdgeRef.current.getSourceCell()!, args.node ); } }); graph.on("blank:click", () => { setTimeout(() => { setCorrelationEdgeInfo(undefined); }, 50); }); graph.on("cell:click", () => { setTimeout(() => { setCorrelationEdgeInfo(undefined); }, 50); }); } }, [graph]); return { graph, selectedCell, initMindMap, rightToobarActive, rightToolbarActive, mindProjectInfo, setMindProjectInfo, canUndo, canRedo, onUndo, onRedo, enableFormatBrush, toggleFormatBrush, setCorrelationEdgeInfo, }; }