contentMenu.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import React from "react";
  2. import { createRoot, Root } from "react-dom/client";
  3. import { Dropdown } from "antd";
  4. import { Graph, ToolsView, EdgeView } from "@antv/x6";
  5. import type { MenuProps } from "antd";
  6. import { menuHander } from "./hander";
  7. export class ContextMenuTool extends ToolsView.ToolItem<
  8. EdgeView,
  9. ContextMenuToolOptions
  10. > {
  11. private timer: number | null = null;
  12. private root: Root | null = null;
  13. private toggleContextMenu(visible: boolean, e?: MouseEvent) {
  14. this.root?.unmount();
  15. document.removeEventListener("mousedown", this.onMouseDown);
  16. if (visible && e) {
  17. const { sx, sy } = this.graph.scale();
  18. let offsetX = e.offsetX * sx,
  19. offsetY = e.offsetY * sy;
  20. // 非页面节点需要获取当前节点位置 + 节点本身偏移位置
  21. if (this.cell.isNode() && !this.cell.getData()?.isPage) {
  22. const { x, y } = this.cell.getPosition();
  23. offsetX = x * sx + e.offsetX * sx;
  24. offsetY = y * sy + e.offsetY * sy;
  25. }
  26. this.root = createRoot(this.container);
  27. const items = this.options.menu?.map((item: any) => {
  28. if (!item) return item;
  29. return {
  30. ...item,
  31. onClick: () => {
  32. setTimeout(() => {
  33. item?.onClick?.call(this, this, e);
  34. }, 200);
  35. },
  36. };
  37. });
  38. this.root.render(
  39. <Dropdown
  40. open={true}
  41. trigger={["contextMenu"]}
  42. menu={{ items }}
  43. align={{ offset: [offsetX, offsetY] }}
  44. >
  45. <span />
  46. </Dropdown>
  47. );
  48. document.addEventListener("mousedown", this.onMouseDown);
  49. }
  50. }
  51. private onMouseDown = () => {
  52. this.timer = window.setTimeout(() => {
  53. this.toggleContextMenu(false);
  54. }, 200);
  55. };
  56. private onContextMenu({ e }: { e: MouseEvent }) {
  57. if (this.timer) {
  58. clearTimeout(this.timer);
  59. this.timer = 0;
  60. }
  61. console.log(e, this);
  62. this.toggleContextMenu(true, e);
  63. }
  64. delegateEvents() {
  65. this.cellView.on("cell:contextmenu", this.onContextMenu, this);
  66. this.graph.on("blank:contextmenu", this.onContextMenu, this);
  67. return super.delegateEvents();
  68. }
  69. protected onRemove() {
  70. this.cellView.off("cell:contextmenu", this.onContextMenu, this);
  71. }
  72. }
  73. ContextMenuTool.config({
  74. tagName: "div",
  75. isSVGElement: false,
  76. });
  77. export interface ContextMenuToolOptions extends ToolsView.ToolItem.Options {
  78. menu: MenuProps["items"];
  79. }
  80. Graph.registerEdgeTool("contextmenu", ContextMenuTool, true);
  81. Graph.registerNodeTool("contextmenu", ContextMenuTool, true);
  82. interface MenuItem {
  83. key?: string;
  84. label?: string;
  85. type?: "divider";
  86. icon?: string;
  87. fastKey?: string;
  88. handler?: (tool: ContextMenuTool, e: MouseEvent) => void;
  89. }
  90. // [复制、剪切、粘贴、复用、删除、设为默认样式],[置于顶层、置于底层、上移一层、下移一层],[锁定],[全选],[导出所选图形为PNG、复制所选图形为图片]
  91. const commonMenuData: MenuItem[] = [
  92. { key: "copy", label: "复制", fastKey: "Ctrl+C", handler: menuHander.copy },
  93. { key: "cut", label: "剪切", fastKey: "Ctrl+X", handler: menuHander.cut },
  94. { key: "paste", label: "粘贴", fastKey: "Ctrl+V", handler: menuHander.paste },
  95. {
  96. key: "duplicate",
  97. label: "复用",
  98. fastKey: "Ctrl+D",
  99. handler: menuHander.duplicate,
  100. },
  101. {
  102. key: "delete",
  103. label: "删除",
  104. fastKey: "Delete/Backspace",
  105. handler: menuHander.delete,
  106. },
  107. {
  108. key: "setDefaultStyle",
  109. label: "设为默认样式",
  110. handler: menuHander.defaultStyle,
  111. },
  112. { type: "divider" },
  113. {
  114. key: "top",
  115. label: "置于顶层",
  116. fastKey: "Ctrl+]",
  117. icon: "icon-zhiding1",
  118. handler: menuHander.top,
  119. },
  120. {
  121. key: "bottom",
  122. label: "置于底层",
  123. fastKey: "Ctrl+[",
  124. icon: "icon-zhidi1",
  125. handler: menuHander.bottom,
  126. },
  127. {
  128. key: "up",
  129. label: "上移一层",
  130. fastKey: "Ctrl+Shift+]",
  131. icon: "icon-shangyiyiceng1",
  132. handler: menuHander.up,
  133. },
  134. {
  135. key: "down",
  136. label: "下移一层",
  137. fastKey: "Ctrl+Shift+[",
  138. icon: "icon-xiayiyiceng1",
  139. handler: menuHander.down,
  140. },
  141. { type: "divider" },
  142. {
  143. key: "lock",
  144. label: "锁定",
  145. fastKey: "Ctrl+L",
  146. icon: "icon-lock",
  147. handler: menuHander.lock,
  148. },
  149. {
  150. key: "unlock",
  151. label: "解锁",
  152. fastKey: "Ctrl+Shift+L",
  153. icon: "icon-unlock",
  154. handler: menuHander.unlock,
  155. },
  156. { type: "divider" },
  157. {
  158. key: "selectAll",
  159. label: "全选",
  160. fastKey: "A",
  161. handler: menuHander.selectAll,
  162. },
  163. { type: "divider" },
  164. {
  165. key: "export",
  166. label: "导出所选图形为PNG",
  167. icon: "icon-tupian",
  168. handler: menuHander.exportImage,
  169. },
  170. {
  171. key: "copyAsImage",
  172. label: "复制所选图形为图片",
  173. handler: menuHander.copyAsImage,
  174. },
  175. ];
  176. const edgeMenuData: MenuItem[] = [
  177. ...commonMenuData.toSpliced(6, 0, {
  178. key: "resetDefaultStyle",
  179. label: "恢复默认样式",
  180. handler: menuHander.resetStyle,
  181. }),
  182. ];
  183. const nodeMenuData: MenuItem[] = [...commonMenuData];
  184. const lockMenuData: MenuItem[] = [
  185. { key: "paste", label: "粘贴", fastKey: "Ctrl+V", handler: menuHander.paste },
  186. { type: "divider" },
  187. {
  188. key: "unlock",
  189. label: "解锁",
  190. fastKey: "Ctrl+Shift+L",
  191. icon: "icon-lock",
  192. handler: menuHander.unlock,
  193. },
  194. { type: "divider" },
  195. {
  196. key: "selectAll",
  197. label: "全选",
  198. fastKey: "A",
  199. handler: menuHander.selectAll,
  200. },
  201. ];
  202. const pageMenuData: MenuItem[] = [
  203. {
  204. key: "paste",
  205. label: "粘贴",
  206. fastKey: "Ctrl+V",
  207. handler: menuHander.paste,
  208. },
  209. { key: "1", type: "divider" },
  210. {
  211. key: "zoomIn",
  212. label: "放大",
  213. fastKey: "Ctrl+(+)",
  214. icon: "icon-fangda",
  215. handler: menuHander.zoomIn,
  216. },
  217. {
  218. key: "zoomOut",
  219. label: "缩小",
  220. fastKey: "Ctrl+(-)",
  221. icon: "icon-suoxiao",
  222. handler: menuHander.zoomOut,
  223. },
  224. {
  225. key: "resetView",
  226. label: "重置视图缩放",
  227. handler: menuHander.resetView,
  228. },
  229. { key: "2", type: "divider" },
  230. {
  231. key: "selectAll",
  232. label: "全选",
  233. fastKey: "A",
  234. handler: menuHander.selectAll,
  235. },
  236. { key: "3", type: "divider" },
  237. {
  238. key: "createLine",
  239. label: "创建连线",
  240. fastKey: "L",
  241. handler: menuHander.createLine,
  242. },
  243. {
  244. key: "insertImage",
  245. label: "插入图片",
  246. fastKey: "I",
  247. handler: menuHander.insertImage,
  248. },
  249. ];
  250. const LabelComponent = ({ item }: { item: MenuItem }) => {
  251. return (
  252. <div className="w-150px flex items-center justify-between">
  253. <span>
  254. <span className="inline-block w-20px">
  255. {item.icon && <i className={`iconfont mr-8px ${item.icon}`} />}
  256. </span>
  257. {item.label}
  258. </span>
  259. <span className="text-12px color-#a6b9cd">{item.fastKey}</span>
  260. </div>
  261. );
  262. };
  263. const getMenuData = (menuData: MenuItem[]) => {
  264. return menuData.map((item) => {
  265. if (item.type === "divider") return item;
  266. return {
  267. key: item.key,
  268. label: <LabelComponent item={item} />,
  269. onClick: item.handler,
  270. };
  271. });
  272. };
  273. // 节点右键菜单
  274. export const nodeMenu = getMenuData(nodeMenuData);
  275. // 边线右键菜单
  276. export const edgeMenu = getMenuData(edgeMenuData);
  277. // 页面右键菜单
  278. export const pageMenu = getMenuData(pageMenuData);
  279. // 上锁节点菜单
  280. export const lockMenu = getMenuData(lockMenuData);