123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import React from "react";
- import { createRoot, Root } from "react-dom/client";
- import { Dropdown } from "antd";
- import { Graph, ToolsView, EdgeView } from "@antv/x6";
- import type { MenuProps } from "antd";
- import { menuHander } from "./hander";
- export class ContextMenuTool extends ToolsView.ToolItem<
- EdgeView,
- ContextMenuToolOptions
- > {
- private timer: number | null = null;
- private root: Root | null = null;
- private toggleContextMenu(visible: boolean, e?: MouseEvent) {
- this.root?.unmount();
- document.removeEventListener("mousedown", this.onMouseDown);
- if (visible && e) {
- const { sx, sy } = this.graph.scale();
- let offsetX = e.offsetX * sx,
- offsetY = e.offsetY * sy;
- // 非页面节点需要获取当前节点位置 + 节点本身偏移位置
- if (this.cell.isNode() && !this.cell.getData()?.isPage) {
- const { x, y } = this.cell.getPosition();
- offsetX = x * sx + e.offsetX * sx;
- offsetY = y * sy + e.offsetY * sy;
- }
- this.root = createRoot(this.container);
- const items = this.options.menu?.map((item: any) => {
- if (!item) return item;
- return {
- ...item,
- onClick: () => {
- setTimeout(() => {
- item?.onClick?.call(this, this, e);
- }, 200);
- },
- };
- });
- this.root.render(
- <Dropdown
- open={true}
- trigger={["contextMenu"]}
- menu={{ items }}
- align={{ offset: [offsetX, offsetY] }}
- >
- <span />
- </Dropdown>
- );
- document.addEventListener("mousedown", this.onMouseDown);
- }
- }
- private onMouseDown = () => {
- this.timer = window.setTimeout(() => {
- this.toggleContextMenu(false);
- }, 200);
- };
- private onContextMenu({ e }: { e: MouseEvent }) {
- if (this.timer) {
- clearTimeout(this.timer);
- this.timer = 0;
- }
- console.log(e, this);
- this.toggleContextMenu(true, e);
- }
- delegateEvents() {
- this.cellView.on("cell:contextmenu", this.onContextMenu, this);
- this.graph.on("blank:contextmenu", this.onContextMenu, this);
- return super.delegateEvents();
- }
- protected onRemove() {
- this.cellView.off("cell:contextmenu", this.onContextMenu, this);
- }
- }
- ContextMenuTool.config({
- tagName: "div",
- isSVGElement: false,
- });
- export interface ContextMenuToolOptions extends ToolsView.ToolItem.Options {
- menu: MenuProps["items"];
- }
- Graph.registerEdgeTool("contextmenu", ContextMenuTool, true);
- Graph.registerNodeTool("contextmenu", ContextMenuTool, true);
- interface MenuItem {
- key?: string;
- label?: string;
- type?: "divider";
- icon?: string;
- fastKey?: string;
- handler?: (tool: ContextMenuTool, e: MouseEvent) => void;
- }
- // [复制、剪切、粘贴、复用、删除、设为默认样式],[置于顶层、置于底层、上移一层、下移一层],[锁定],[全选],[导出所选图形为PNG、复制所选图形为图片]
- const commonMenuData: MenuItem[] = [
- { key: "copy", label: "复制", fastKey: "Ctrl+C", handler: menuHander.copy },
- { key: "cut", label: "剪切", fastKey: "Ctrl+X", handler: menuHander.cut },
- { key: "paste", label: "粘贴", fastKey: "Ctrl+V", handler: menuHander.paste },
- {
- key: "duplicate",
- label: "复用",
- fastKey: "Ctrl+D",
- handler: menuHander.duplicate,
- },
- {
- key: "delete",
- label: "删除",
- fastKey: "Delete/Backspace",
- handler: menuHander.delete,
- },
- {
- key: "setDefaultStyle",
- label: "设为默认样式",
- handler: menuHander.defaultStyle,
- },
- { type: "divider" },
- {
- key: "top",
- label: "置于顶层",
- fastKey: "Ctrl+]",
- icon: "icon-zhiding1",
- handler: menuHander.top,
- },
- {
- key: "bottom",
- label: "置于底层",
- fastKey: "Ctrl+[",
- icon: "icon-zhidi1",
- handler: menuHander.bottom,
- },
- {
- key: "up",
- label: "上移一层",
- fastKey: "Ctrl+Shift+]",
- icon: "icon-shangyiyiceng1",
- handler: menuHander.up,
- },
- {
- key: "down",
- label: "下移一层",
- fastKey: "Ctrl+Shift+[",
- icon: "icon-xiayiyiceng1",
- handler: menuHander.down,
- },
- { type: "divider" },
- {
- key: "lock",
- label: "锁定",
- fastKey: "Ctrl+L",
- icon: "icon-lock",
- handler: menuHander.lock,
- },
- {
- key: "unlock",
- label: "解锁",
- fastKey: "Ctrl+Shift+L",
- icon: "icon-unlock",
- handler: menuHander.unlock,
- },
- { type: "divider" },
- {
- key: "selectAll",
- label: "全选",
- fastKey: "A",
- handler: menuHander.selectAll,
- },
- { type: "divider" },
- {
- key: "export",
- label: "导出所选图形为PNG",
- icon: "icon-tupian",
- handler: menuHander.exportImage,
- },
- {
- key: "copyAsImage",
- label: "复制所选图形为图片",
- handler: menuHander.copyAsImage,
- },
- ];
- const edgeMenuData: MenuItem[] = [
- ...commonMenuData.toSpliced(6, 0, {
- key: "resetDefaultStyle",
- label: "恢复默认样式",
- handler: menuHander.resetStyle,
- }),
- ];
- const nodeMenuData: MenuItem[] = [...commonMenuData];
- const lockMenuData: MenuItem[] = [
- { key: "paste", label: "粘贴", fastKey: "Ctrl+V", handler: menuHander.paste },
- { type: "divider" },
- {
- key: "unlock",
- label: "解锁",
- fastKey: "Ctrl+Shift+L",
- icon: "icon-lock",
- handler: menuHander.unlock,
- },
- { type: "divider" },
- {
- key: "selectAll",
- label: "全选",
- fastKey: "A",
- handler: menuHander.selectAll,
- },
- ];
- const pageMenuData: MenuItem[] = [
- {
- key: "paste",
- label: "粘贴",
- fastKey: "Ctrl+V",
- handler: menuHander.paste,
- },
- { key: "1", type: "divider" },
- {
- key: "zoomIn",
- label: "放大",
- fastKey: "Ctrl+(+)",
- icon: "icon-fangda",
- handler: menuHander.zoomIn,
- },
- {
- key: "zoomOut",
- label: "缩小",
- fastKey: "Ctrl+(-)",
- icon: "icon-suoxiao",
- handler: menuHander.zoomOut,
- },
- {
- key: "resetView",
- label: "重置视图缩放",
- handler: menuHander.resetView,
- },
- { key: "2", type: "divider" },
- {
- key: "selectAll",
- label: "全选",
- fastKey: "A",
- handler: menuHander.selectAll,
- },
- { key: "3", type: "divider" },
- {
- key: "createLine",
- label: "创建连线",
- fastKey: "L",
- handler: menuHander.createLine,
- },
- {
- key: "insertImage",
- label: "插入图片",
- fastKey: "I",
- handler: menuHander.insertImage,
- },
- ];
- const LabelComponent = ({ item }: { item: MenuItem }) => {
- return (
- <div className="w-150px flex items-center justify-between">
- <span>
- <span className="inline-block w-20px">
- {item.icon && <i className={`iconfont mr-8px ${item.icon}`} />}
- </span>
- {item.label}
- </span>
- <span className="text-12px color-#a6b9cd">{item.fastKey}</span>
- </div>
- );
- };
- const getMenuData = (menuData: MenuItem[]) => {
- return menuData.map((item) => {
- if (item.type === "divider") return item;
- return {
- key: item.key,
- label: <LabelComponent item={item} />,
- onClick: item.handler,
- };
- });
- };
- // 节点右键菜单
- export const nodeMenu = getMenuData(nodeMenuData);
- // 边线右键菜单
- export const edgeMenu = getMenuData(edgeMenuData);
- // 页面右键菜单
- export const pageMenu = getMenuData(pageMenuData);
- // 上锁节点菜单
- export const lockMenu = getMenuData(lockMenuData);
|