import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { EyeOutlined, EyeInvisibleOutlined, NodeIndexOutlined, DisconnectOutlined, TagOutlined, SearchOutlined, HighlightOutlined } from '@ant-design/icons'; const isBrowser = typeof window !== 'undefined'; const G6 = isBrowser ? require('../../dist/g6.min.js') : null; const insertCss = isBrowser ? require('insert-css') : null; const modifyCSS = isBrowser ? require('@antv/dom-util/lib/modify-css').default : null; if (isBrowser) { insertCss(` #canvas-menu { position: absolute; z-index: 2; left: 16px; top: 80px; width: fit-content; padding: 8px 16px; background-color: rgba(54, 59, 64, 0); border-radius: 24px; box-shadow: 0 5px 18px 0 rgba(0, 0, 0, 0); font-family: PingFangSC-Semibold; transition: all 0.2s linear; } #canvas-menu:hover { background-color: rgba(54, 59, 64, 1); box-shadow: 0 5px 18px 0 rgba(0, 0, 0, 0.6); } .icon-span { padding-left: 8px; padding-right: 8px; cursor: pointer; } #search-node-input { background-color: rgba(60, 60, 60, 0.95); border-radius: 21px; width: 100px; border-color: rgba(80, 80, 80, 0.95); border-style: solid; color: rgba(255, 255, 255, 0.85); } #submit-button { background-color: rgba(82, 115, 224, 0.2); border-radius: 21px; border-color: rgb(82, 115, 224); border-style: solid; color: rgba(152, 165, 254, 1); margin-left: 4px; } .menu-tip { position: absolute; right: calc(30% + 32px); width: fit-content; height: 40px; line-height: 40px; top: 80px; padding-left: 16px; padding-right: 16px; background-color: rgba(54, 59, 64, 0.5); color: rgba(255, 255, 255, 0.65); border-radius: 8px; transition: all 0.2s linear; font-family: PingFangSC-Semibold; } #g6-canavs-menu-item-tip { position: absolute; background-color: rgba(0,0,0, 0.65); padding: 10px; box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 10px; width: fit-content; color: #fff; border-radius: 8px; font-size: 12px; height: fit-content; font-family: PingFangSC-Semibold; transition: all 0.2s linear; } `); } let fishEye = null; const CanvasMenu: React.FC<{ graph: any; clickFisheyeIcon: (onlyDisable?: boolean) => void; clickLassoIcon: (onlyDisable?: boolean) => void; fisheyeEnabled: boolean; lassoEnabled: boolean; edgeLabelVisible: boolean; setEdgeLabelVisible: (vis: boolean) => void; searchNode: (id: string) => boolean; handleFindPath: () => void; stopLayout: () => void; }> = ({ graph, clickFisheyeIcon, clickLassoIcon, fisheyeEnabled, lassoEnabled, stopLayout, edgeLabelVisible, setEdgeLabelVisible, searchNode, handleFindPath }) => { const { t } = useTranslation(); // menu tip, 例如 “按下 ESC 退出鱼眼” const [menuTip, setMenuTip] = useState({ text: '', display: 'none', opacity: 0 }); // menu item tip const [menuItemTip, setMenuItemTip] = useState({ text: '', display: 'none', opacity: 0 }); const [enableSearch, setEnableSearch] = useState(false); const [enableSelectPathEnd, setEnableSelectPathEnd] = useState(false); const clickEdgeLabelController = () => { setEdgeLabelVisible(!edgeLabelVisible); } const handleEnableSearch = () => { // 关闭 lasso 框选 if (lassoEnabled) clickLassoIcon(true) // 关闭选择路径端点 if (enableSelectPathEnd) setEnableSelectPathEnd(false); // 关闭搜索节点框 if (enableSearch) setEnableSearch(false); // 关闭 fisheye if (fisheyeEnabled && fishEye) { graph.removePlugin(fishEye); clickFisheyeIcon(true); } setEnableSearch(old => { if (old) { // 设置 menuTip setMenuTip({ text: '', display: 'none', opacity: 0 }); return false; } // 设置 menuTip setMenuTip({ text: t('输入需要搜索的节点 ID,并点击 Submit 按钮'), display: 'block', opacity: 1 }); return true; }); } // 放大 const handleZoomOut = () => { if (!graph || graph.destroyed) return; const current = graph.getZoom(); const canvas = graph.get('canvas'); const point = canvas.getPointByClient(canvas.get('width') / 2, canvas.get('height') / 2); const pixelRatio = canvas.get('pixelRatio') || 1; const ratio = 1 + 0.05 * 5; if (ratio * current > 5) { return; } graph.zoom(ratio, { x: point.x / pixelRatio, y: point.y / pixelRatio }); }; // 缩小 const handleZoomIn = () => { if (!graph || graph.destroyed) return; const current = graph.getZoom(); const canvas = graph.get('canvas'); const point = canvas.getPointByClient(canvas.get('width') / 2, canvas.get('height') / 2); const pixelRatio = canvas.get('pixelRatio') || 1; const ratio = 1 - 0.05 * 5; if (ratio * current < 0.3) { return; } graph.zoom(ratio, { x: point.x / pixelRatio, y: point.y / pixelRatio }); }; const handleFitViw = () => { if (!graph || graph.destroyed) return; graph.fitView(16); } const handleSearchNode = () => { const value = (document.getElementById('search-node-input') as HTMLInputElement).value; const found = searchNode(value); if (!found) setMenuTip({ text: t('没有找到该节点'), display: 'block', opacity: 1 }); } const showItemTip = (e, text) => { const { clientX: x, clientY: y } = e; setMenuItemTip({ text, display: 'block', opacity: 1 }); const tipDom = document.getElementById('g6-canavs-menu-item-tip') modifyCSS(tipDom, { top: `${124}px`, left: `${x - 20}px`, zIndex: 100, }); }; const hideItemTip = () => { setMenuItemTip({ text: '', display: 'none', opacity: 0 }); const tipDom = document.getElementById('g6-canavs-menu-item-tip') modifyCSS(tipDom, { zIndex: -100, }); } /** * 打开或关闭鱼眼放大功能 */ const toggleFishEye = () => { if (!graph || graph.destroyed) return; // 设置鼠标样式为默认 graph.get('canvas').setCursor('default'); // 关闭 FishEye if (fisheyeEnabled && fishEye) { graph.removePlugin(fishEye); graph.setMode('default') // 设置 menuTip setMenuTip({ text: t('按下 Esc 键退出鱼眼放大镜'), display: 'none', opacity: 0 }); } else { // 停止布局 stopLayout(); // 关闭 lasso 框选 if (lassoEnabled) clickLassoIcon(true) // 关闭选择路径端点 if (enableSelectPathEnd) setEnableSelectPathEnd(false); // 关闭搜索节点框 if (enableSearch) setEnableSearch(false); // 设置 menuTip setMenuTip({ text: t('按下 Esc 键退出鱼眼放大镜'), display: 'block', opacity: 1 }); // 将交互模式切换到鱼眼只读模式,即不能缩放画布、框选节点、拉索选择节点等可能和鱼眼有冲突的操作 graph.setMode('fisheyeMode'); // 开启 FishEye fishEye = new G6.Fisheye({ r: 249, scaleRByWheel: true, minR: 100, maxR: 500 // showLabel: true, }); graph.addPlugin(fishEye); } clickFisheyeIcon(); }; // 开启 lasso select 功能 const enabledLassoSelect = () => { if (!graph || graph.destroyed) return; clickLassoIcon(); if (!lassoEnabled) { graph.setMode('lassoSelect'); // 设置鼠标样式为十字形 graph.get('canvas').setCursor('crosshair'); // 关闭 fisheye if (fisheyeEnabled && fishEye) { graph.removePlugin(fishEye); clickFisheyeIcon(true); } // 关闭选择路径端点 if (enableSelectPathEnd) setEnableSelectPathEnd(false); // 关闭搜索节点框 if (enableSearch) setEnableSearch(false); // 设置 menuTip setMenuTip({ text: t('按下 Esc 键退出拉索选择模式'), display: 'block', opacity: 1 }); } else { graph.setMode('default'); // 设置鼠标样式为默认 graph.get('canvas').setCursor('default'); // 设置 menuTip setMenuTip({ text: t('按下 Esc 键退出拉索选择模式'), display: 'none', opacity: 0 }); } } // 开启选择两个节点显示最短路径 const handleEnableSelectPathEnd = () => { setEnableSelectPathEnd(old => { if (!old) { graph.setMode('default'); // 关闭 fisheye if (fishEye) { graph.removePlugin(fishEye); clickFisheyeIcon(true); } // 关闭 lasso 框选 if (lassoEnabled) clickLassoIcon(true) // 关闭搜索节点框 if (enableSearch) setEnableSearch(false); // 设置 menuTip setMenuTip({ text: t('按住 SHIFT 键并点选两个节点作为路径起终点'), display: 'block', opacity: 1 }); return true; } // 设置 menuTip setMenuTip({ text: '', display: 'none', opacity: 0 }); return false; }) } const escListener = e => { if (!graph || graph.destroyed) return; if (e.key !== 'Escape') return; if (fishEye) { graph.removePlugin(fishEye); clickFisheyeIcon(true); } // 关闭 lasso 框选 graph.setMode('default'); // 关闭搜索节点框 setEnableSearch(false); // 关闭选择路径端点 setEnableSelectPathEnd(false); // 设置鼠标样式为默认 graph.get('canvas').setCursor('default'); clickLassoIcon(true); // 设置 menuTip setMenuTip({ text: t('按下 Esc 键退出当前模式'), display: 'none', opacity: 0 }); } useEffect(() => { if (typeof window !== 'undefined') { window.addEventListener("keydown", escListener.bind(this)); return window.removeEventListener("keydown", escListener.bind(this)); } }, []); const iconStyle = { disable: { color: 'rgba(255, 255, 255, 0.85)' }, enable: { color: 'rgba(82, 115, 224, 1)' }, }; return ( <>
showItemTip(e, edgeLabelVisible ? t('隐藏边标签') : t('显示边标签'))} onMouseLeave={hideItemTip} > {edgeLabelVisible ? : } showItemTip(e, fisheyeEnabled ? t('关闭鱼眼放大镜') : t('打开鱼眼放大镜'))} onMouseLeave={hideItemTip} > {fisheyeEnabled ? : } showItemTip(e, lassoEnabled ? t('关闭拉索选择模式') : t('打开拉索选择模式'))} onMouseLeave={hideItemTip} > showItemTip(e, t('搜索最短路径'))} onMouseLeave={hideItemTip} > showItemTip(e, t('缩小'))} onMouseLeave={hideItemTip} > - showItemTip(e, t('图内容适配容器,快捷键:ctrl + 1'))} onMouseLeave={hideItemTip} style={{ paddingLeft: '8px', paddingRight: '8px' }} > FIT showItemTip(e, t('放大'))} onMouseLeave={hideItemTip} > + showItemTip(e, t('输入 ID 搜索节点'))} onMouseLeave={hideItemTip} > {enableSearch && showItemTip(e, t('输入需要搜索的节点 ID,并点击 Submit 按钮'))} onMouseLeave={hideItemTip} > } {enableSelectPathEnd && showItemTip(e, t('选择有且仅有两个节点作为端点,并点击 Find Path 按钮'))} onMouseLeave={hideItemTip} > }
{menuTip.text}
{menuItemTip.text}
); }; export default CanvasMenu;