123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- 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<string>();
- // 格式刷启用
- const [enableFormatBrush, setEnableFormatBrush] = useState(false);
- // 格式刷样式
- const formatBrushStyle = useRef<cellStyle>();
- const graphRef = useRef<Graph>();
- const dndRef = useRef<Dnd>();
- const [graph, setGraph] = useState<Graph>();
- const [canRedo, setCanRedo] = useState(false);
- const [canUndo, setCanUndo] = useState(false);
- const [selectedCell, setSelectedCell] = useState<Cell[]>([]);
- const correlationEdgeRef = useRef<Edge>();
- const [mindProjectInfo, setProjectInfo] = useState<MindMapProjectInfo>();
- const projectInfoRef = useRef<MindMapProjectInfo>();
- const timer = useRef<any>();
- 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<MindMapProjectInfo["pageSetting"]>();
- 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,
- };
- }
|