123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- import { BorderSize, TopicType } from "@/enum";
- import { addTopic, buildTopic } from "@/utils/mindmap";
- import { HierarchyResult, MindMapProjectInfo, TopicItem } from "@/types";
- import { Cell, Graph, Node, Edge } from "@antv/x6";
- import { message } from "antd";
- import { cloneDeep } from "lodash-es";
- import { uuid } from "@repo/utils";
- import { ContextMenuTool } from "./contentMenu";
- import { MutableRefObject } from "react";
- import TopicBorder from "@/components/mindMap/Border";
- import SummaryBorder from "@/components/mindMap/SummaryBorder";
- import { openInsertImageModal } from "@/components/ImageModal";
- import { BatchDeleteMindMapElement } from "@/api/systemDesigner";
- export const selectTopic = (graph: Graph, topic?: TopicItem) => {
- if (topic?.id) {
- setTimeout(() => {
- graph.resetSelection(topic.id);
- const node = graph.getCellById(topic?.id);
- node?.isNode() && graph.createTransformWidget(node);
- }, 100);
- }
- };
- export const selectTopics = (graph: Graph, topics?: TopicItem[]) => {
- setTimeout(() => {
- graph.resetSelection(topics?.map((item) => item.id));
- topics?.forEach((item) => {
- const node = graph.getCellById(item.id);
- node?.isNode() && graph.createTransformWidget(node);
- });
- }, 100);
- };
- /**
- * 添加同级主题
- * @param node
- * @param graph
- */
- export const addPeerTopic = (
- node: Cell,
- graph: Graph,
- setMindProjectInfo?: (info: MindMapProjectInfo) => void
- ) => {
- if (!setMindProjectInfo) return;
- const parentNode =
- node.data.type === TopicType.main || !node.data?.parentId
- ? node
- : graph.getCellById(node.data.parentId);
- const type =
- node.data.type === TopicType.main ? TopicType.branch : node.data.type;
- if (parentNode?.isNode()) {
- const topic = addTopic(type, setMindProjectInfo, graph, parentNode);
- selectTopic(graph, topic);
- }
- };
- /**
- * 添加子主题
- * @param node
- * @param setMindProjectInfo
- * @param graph
- */
- export const addChildrenTopic = (
- node: Node,
- graph: Graph,
- setMindProjectInfo?: (info: MindMapProjectInfo) => void,
- ) => {
- if (!setMindProjectInfo) return;
- const type =
- node.data?.type === TopicType.main ? TopicType.branch : TopicType.sub;
- const topic = addTopic(type, setMindProjectInfo, graph, node);
- graph && selectTopic(graph, topic);
- };
- /**
- * 添加父主题
- * @param node
- * @param setMindProjectInfo
- * @param graph
- */
- export const addParentTopic = (
- node: Node,
- setMindProjectInfo: (info: MindMapProjectInfo) => void,
- graph: Graph
- ) => {
- if (!setMindProjectInfo || !node.data?.parentId) return;
- const type =
- node.data?.type === TopicType.branch ? TopicType.branch : TopicType.sub;
- const parentNode = graph?.getCellById(node.data.parentId);
- // 删除原来的数据
- deleteTopics([node.data.id], graph, setMindProjectInfo);
- // 加入新的父主题
- const topic = addTopic(type, setMindProjectInfo, graph, parentNode as Node, {
- children: [
- {
- ...node.data,
- },
- ],
- });
- graph && selectTopic(graph, topic);
- };
- /**
- * 删除子主题
- * @param ids
- * @param setMindProjectInfo
- */
- export const deleteTopics = (
- ids: string[],
- graph: Graph,
- setMindProjectInfo?: (info: MindMapProjectInfo) => void
- ) => {
- // @ts-ignore
- const mindProjectInfo: MindMapProjectInfo = graph.extendAttr.getMindProjectInfo();
- if (!mindProjectInfo || !setMindProjectInfo) return;
- const topics = cloneDeep(mindProjectInfo.topics);
- const deleteIds: string[] = [];
- const filterTopics = (list: TopicItem[]): TopicItem[] => {
- const result: TopicItem[] = [];
- for (const item of list) {
- if(ids.includes(item.id) && item.type === TopicType.main) {
- item.children = [];
- }
- if (!ids.includes(item.id) || item.type === TopicType.main) {
- if (item.children?.length) {
- item.children = filterTopics(item.children);
- }
- result.push(item);
- } else {
- if(!item.parentId && !item.isSummary) {
- // 删除自由主题
- deleteIds.push(item.id);
- }
- }
- }
- return result;
- };
- mindProjectInfo.topics = filterTopics(topics);
- if(deleteIds.length) {
- BatchDeleteMindMapElement({ids: deleteIds});
- }
- setMindProjectInfo(mindProjectInfo);
- localStorage.setItem("minMapProjectInfo", JSON.stringify(mindProjectInfo));
- };
- /**
- * 删除当前主题
- * @param graph
- * @param nodes
- */
- export const handleDeleteCurrentTopic = (graph: Graph, nodes: Node[]) => {
- // @ts-ignore
- const mindProjectInfo: MindMapProjectInfo = graph.extendAttr.getMindProjectInfo();
- if (!mindProjectInfo) return;
- nodes.forEach((node) => {
- if (node.data.parentId) {
- traverseNode(mindProjectInfo.topics, (topic) => {
- if (topic.id === node.data.parentId) {
- const index = topic.children?.findIndex(
- (item) => item.id === node.id
- );
- if (typeof index === "number" && index >= 0) {
- const newChildren = (node.data.children || []).map(
- (childNode: TopicItem) => {
- return {
- ...childNode,
- type:
- topic.type === TopicType.main
- ? TopicType.branch
- : TopicType.sub,
- parentId: topic.id,
- };
- }
- );
- (topic.children || []).splice(index, 1, ...newChildren);
- }
- }
- });
- }
- // @ts-ignore
- graph?.extendAttr?.setMindProjectInfo?.(mindProjectInfo);
- localStorage.setItem("minMapProjectInfo", JSON.stringify(mindProjectInfo));
- });
- };
- /**
- * 执行粘贴
- * @param graph
- * @param setMindProjectInfo
- */
- export const handleMindmapPaste = (
- graph: Graph,
- setMindProjectInfo: (info: MindMapProjectInfo) => void
- ) => {
- // 读取剪切板数据
- navigator.clipboard.read().then((items) => {
- console.log("剪切板内容:", items);
- const currentNode = graph.getSelectedCells().find((cell) => cell.isNode());
- if (!currentNode) {
- message.warning("请先选择一个主题");
- return;
- }
- const item = items?.[0];
- if (item) {
- /**读取图片数据 */
- if (item.types[0] === "image/png") {
- item.getType("image/png").then((blob) => {
- const reader = new FileReader();
- reader.readAsDataURL(blob);
- reader.onload = function (event) {
- const dataUrl = event.target?.result as string;
- // 获取图片大小
- const img = new Image();
- img.src = dataUrl;
- img.onload = function () {
- const width = img.width;
- const height = img.height;
- // 插入图片
- currentNode.setData({
- extraModules: {
- type: "image",
- data: {
- imageUrl: dataUrl,
- width,
- height,
- },
- },
- });
- };
- };
- });
- }
- /**读取文本数据 */
- if (item.types[0] === "text/plain") {
- item.getType("text/plain").then((blob) => {
- const reader = new FileReader();
- reader.readAsText(blob);
- reader.onload = function (event) {
- const text = event.target?.result as string;
- // 内部复制方法
- if (text === " ") {
- const nodes = localStorage.getItem("mindmap-copy-data");
- if (nodes) {
- JSON.parse(nodes)?.forEach((node: Node) => {
- const data = node.data;
- // 修改新的数据嵌套
- data.id = uuid();
- data.parentId = currentNode.id;
- if (data.children?.length) {
- data.children = traverseCopyData(data.children, data.id);
- }
- addTopic(
- currentNode.data?.type === TopicType.main
- ? TopicType.branch
- : TopicType.sub,
- setMindProjectInfo,
- graph,
- currentNode,
- { ...data }
- );
- });
- }
- } else {
- const topic = addTopic(
- currentNode.data?.type === TopicType.main
- ? TopicType.branch
- : TopicType.sub,
- setMindProjectInfo,
- graph,
- currentNode,
- { label: text }
- );
- selectTopic(graph, topic);
- }
- };
- });
- }
- }
- });
- };
- const traverseCopyData = (list: TopicItem[], parentId: string): TopicItem[] => {
- return list.map((item) => {
- item.id = uuid();
- item.parentId = parentId;
- if (item.children?.length) {
- item.children = traverseCopyData(item.children, item.id);
- }
- return item;
- });
- };
- /**
- * 遍历主题树
- * @param topics
- * @param callback
- * @returns
- */
- export const traverseNode = (
- topics: TopicItem[],
- callback: (topic: TopicItem, index: number) => void
- ): TopicItem[] => {
- return topics.map((topic, index) => {
- callback && callback(topic, index);
- if (topic.children?.length) {
- topic.children = traverseNode(topic.children, callback);
- }
- // 遍历概要
- if (topic?.summary?.topic) {
- topic.summary.topic = traverseNode([topic.summary.topic], callback)[0];
- }
- return topic;
- });
- };
- // 关联线
- const handleCorrelation = (
- e: MouseEvent,
- correlationEdgeRef: MutableRefObject<Edge | undefined>,
- graph: Graph
- ) => {
- if (correlationEdgeRef.current) {
- const point = graph?.clientToLocal(e.x, e.y);
- point && correlationEdgeRef.current?.setTarget(point);
- }
- };
- /**
- * 添加关联线
- * @param graph
- * @param correlationEdgeRef
- * @param sourceNode
- */
- export const handleCreateCorrelationEdge = (
- graph: Graph,
- correlationEdgeRef: MutableRefObject<Edge | undefined>,
- sourceNode?: Node
- ) => {
- if (sourceNode) {
- correlationEdgeRef.current = graph?.addEdge({
- source: { cell: sourceNode },
- target: {
- x: sourceNode.position().x,
- y: sourceNode.position().y,
- },
- connector: "normal",
- attrs: {
- line: {
- stroke: "#71cb2d",
- strokeWidth: 2,
- sourceMarker: {
- name: "",
- },
- targetMarker: {
- name: "",
- },
- style: {
- opacity: 0.6,
- },
- },
- },
- data: {
- ignoreDrag: true,
- },
- zIndex: 0,
- });
- document.body.addEventListener("mousemove", (e) =>
- handleCorrelation(e, correlationEdgeRef, graph)
- );
- } else {
- document.body.removeEventListener("mousemove", (e) =>
- handleCorrelation(e, correlationEdgeRef, graph)
- );
- if (correlationEdgeRef.current) {
- graph?.removeCell(correlationEdgeRef.current);
- correlationEdgeRef.current = undefined;
- }
- }
- };
- export const addBorder = (nodes: Node[]) => {
- // 判断节点是否在当前存在父级以上节点
- nodes.forEach((node) => {
- let hasParent = false;
- traverseNode(node.data.children || [], (child) => {
- if (child.id === node.id) {
- hasParent = true;
- }
- });
- // 添加边框数据
- if (!hasParent && !node.data.border) {
- node.setData({
- border: {
- ...TopicBorder.data,
- },
- });
- }
- });
- };
- /**
- * 计算当前节点总大小
- * @param topItem
- * @param children
- * @returns
- */
- export const cacluculateExtremeValue = (topItem: HierarchyResult, children: HierarchyResult[]) => {
- let minX = topItem.x;
- let minY = topItem.y;
- let maxX = minX + topItem.data.width;
- let maxY = minY + topItem.data.height;
- children.forEach((child) => {
- const childXY = cacluculateExtremeValue(child, child.children);
- minX = Math.min(minX, child.x, childXY.minX);
- minY = Math.min(minY, child.y, childXY.minY);
- maxX = Math.max(maxX, child.x + child.data.width, childXY.maxX);
- maxY = Math.max(maxY, child.y + child.data.height, childXY.maxY);
- });
- return {
- minX,
- minY,
- maxX,
- maxY
- }
- };
- /**
- * 获取边框位置及大小
- * @param hierarchyItem
- * @returns
- */
- export const getBorderPositionAndSize = (hierarchyItem: HierarchyResult) => {
- const firstChild = hierarchyItem?.children?.[0];
- let totalHeigth = hierarchyItem?.totalHeight || 0;
- let totalWidth = hierarchyItem?.totalWidth || 0;
- let x = hierarchyItem?.x || 0;
- let y = hierarchyItem?.y || 0;
- // 是否存在子节点
- if (firstChild) {
- const position = cacluculateExtremeValue(hierarchyItem, hierarchyItem.children || []);
- x = position.minX;
- y = position.minY;
- totalHeigth = position.maxY - position.minY;
- totalWidth = position.maxX - position.minX;
- } else {
- totalWidth = hierarchyItem.data.width;
- totalHeigth = hierarchyItem.data.height;
- }
- return {
- x: x - 10,
- y: y - 10,
- width: totalWidth + 20,
- height: totalHeigth + 20,
- };
- };
- /**
- * 添加概要
- */
- export const addSummary = (nodes: Node[], graph: Graph) => {
- // 判断节点是否在当前存在父级以上节点
- nodes.forEach((node) => {
- let hasParent = false;
- traverseNode(node.data.children || [], (child) => {
- if (child.id === node.id) {
- hasParent = true;
- }
- });
- // 添加边框数据
- if (!hasParent && !node.data.summary) {
- const root = buildTopic(node.data.type, {
- setMindProjectInfo: node.data.setMindProjectInfo,
- type: TopicType.branch,
- label: "概要",
- borderSize: BorderSize.medium,
- isSummary: true,
- summarySource: node.id,
- }, graph);
- node.setData({
- summary: {
- topic: root,
- range: [node.id],
- border: {
- ...SummaryBorder.data,
- origin: root.id,
- summarySource: node.id,
- }
- }
- }, {
- deep: false
- });
- }
- });
- }
- /**
- * 插入图片
- */
- export const insertImage = (node?: Node) => {
- if(!node) return;
- openInsertImageModal((url) => {
- console.log('图片地址:', url);
- node.setData({
- extraModules: {
- type: "image",
- data: {
- imageUrl: url,
- width: 300,
- height: 300
- }
- }
- })
- })
- }
- /**
- * 右键菜单处理方法
- */
- export const mindmapMenuHander = {
- addTopic(tool: ContextMenuTool) {
- const node = tool.cell;
- if (node.isNode())
- addChildrenTopic(node, tool.graph, node.data.setMindProjectInfo,);
- },
- addPeerTopic(tool: ContextMenuTool) {
- const node = tool.cell;
- if (node.isNode())
- addPeerTopic(node, tool.graph, node.data.setMindProjectInfo);
- },
- addParentTopic(tool: ContextMenuTool) {
- const node = tool.cell;
- if (node.isNode())
- addParentTopic(node, node.data.setMindProjectInfo, tool.graph);
- },
- addCorrelationEdge(tool: ContextMenuTool) {
- if (tool.cell.isNode()) {
- // @ts-ignore
- const correlationEdgeRef = tool.graph?.extendAttr?.correlationEdgeRef;
- handleCreateCorrelationEdge(tool.graph, correlationEdgeRef, tool.cell);
- }
- },
- addRemark(tool: ContextMenuTool) {
- // @ts-ignore
- tool.graph?.extendAttr?.setRightToolbarActive("remark");
- selectTopic(tool.graph, tool.cell.data);
- },
- addHref(tool: ContextMenuTool) {
- // @ts-ignore
- tool.cell?.extendAttr?.showHrefConfig?.();
- },
- addTopicLink(tool: ContextMenuTool) {
- // todo
- },
- addImage(tool: ContextMenuTool) {
- const cell = tool.cell;
- cell.isNode() && insertImage(cell);
- },
- addTag(tool: ContextMenuTool) {
- // @ts-ignore
- tool.graph?.extendAttr?.setRightToolbarActive("tag");
- selectTopic(tool.graph, tool.cell.data);
- },
- addIcon(tool: ContextMenuTool) {
- // @ts-ignore
- tool.graph?.extendAttr?.setRightToolbarActive("icon");
- selectTopic(tool.graph, tool.cell.data);
- },
- addCode(tool: ContextMenuTool) {
- tool.cell.setData({
- extraModules: {
- type: "code",
- data: {
- code: "",
- language: "javascript",
- },
- },
- });
- },
- chooseSameLevel(tool: ContextMenuTool) {
- const parentId = tool.cell.data?.parentId;
- if (!parentId) return;
- const parent = tool.graph.getCellById(parentId);
- selectTopics(tool.graph, parent.data?.children);
- },
- chooseAllSameLevel(tool: ContextMenuTool) {
- // todo
- },
- copy(tool: ContextMenuTool) {
- localStorage.setItem("mindmap-copy-data", JSON.stringify([tool.cell]));
- navigator.clipboard.writeText(" ");
- },
- cut(tool: ContextMenuTool) {
- tool.graph.cut([tool.cell]);
- },
- paste(tool: ContextMenuTool) {
- handleMindmapPaste(tool.graph, tool.cell.data.setMindProjectInfo);
- },
- delete(tool: ContextMenuTool) {
- deleteTopics([tool.cell.id], tool.cell.data.setMindProjectInfo);
- },
- deleteCurrent(tool: ContextMenuTool) {
- tool.cell.isNode() && handleDeleteCurrentTopic(tool.graph, [tool.cell]);
- },
- exportImage(tool: ContextMenuTool) {
- tool.graph.exportPNG("", {
- quality: 1,
- copyStyles: false,
- });
- },
- copyImage(tool: ContextMenuTool) {
- // TODO复制为图片
- },
- };
|