refactor context menu to separate component

This commit is contained in:
2025-11-09 23:21:33 +01:00
parent 33141ce865
commit 80f9044729
4 changed files with 75 additions and 72 deletions

View File

@@ -1,70 +0,0 @@
import { useEffect, useState } from "react";
import { renderToStaticMarkup } from "react-dom/server";
export class NodeContextMenu {
contextMenu: ContextMenuInput | null;
setContextMenu: React.Dispatch<React.SetStateAction<ContextMenuInput | null>>;
constructor(containerRef: React.RefObject<null>) {
[this.contextMenu, this.setContextMenu] = useState(null);
useEffect(() => {
if(!this.contextMenu){
return;
}
const container = containerRef.current;
if (container) {
const menu = renderToStaticMarkup(this.render());
renderTo
(container as HTMLElement).append
}
}, [this.contextMenu]);
useEffect(() => {
const close = () => this.setContextMenu(null);
document.addEventListener('click', close);
return () => document.removeEventListener('click', close);
}, []);
}
render() {
if (this.contextMenu)
return (
<div
className="absolute bg-white border rounded shadow-lg z-50 p-2 text-sm"
style={{ left: this.contextMenu.x, top: this.contextMenu.y }}
onClick={e => e.stopPropagation()}
>
<div className="font-bold mb-1">Node: {this.contextMenu.nodeId}</div>
<button
className="block w-full text-left hover:bg-gray-100 px-2 py-1"
onClick={() => {
this.setContextMenu(null);
}}
>
Edit subgraph
</button>
<button
className="block w-full text-left hover:bg-gray-100 px-2 py-1"
onClick={() => {
this.setContextMenu(null);
}}
>
Delete node
</button>
</div>
)
}
}
export class ContextMenuInput {
x: number;
y: number;
nodeId: string;
constructor(x: number, y: number, nodeId: string) {
this.x = x;
this.y = y;
this.nodeId = nodeId;
}
}

3
src/TODO Normal file
View File

@@ -0,0 +1,3 @@
- add not connected node in selected rank
- connect node
- remove connection

View File

@@ -8,6 +8,7 @@ import { Modal } from "antd";
import { Input } from "antd"; import { Input } from "antd";
import type { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb"; import type { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import NodeContextMenu from "./NodeContextMenu";
export class GraphModel { export class GraphModel {
nodes: NodeModel[] = []; nodes: NodeModel[] = [];
@@ -224,7 +225,10 @@ export default function Graph({ setGraphPath }) {
<div ref={containerRef} className="w-full h-full bg-white rounded shadow" style={{ minHeight: '600px', overflow: 'auto' }}> <div ref={containerRef} className="w-full h-full bg-white rounded shadow" style={{ minHeight: '600px', overflow: 'auto' }}>
</div> </div>
<Dropdown menu={{ items, onClick: onMenuClick }} trigger={['contextMenu']} open={contextMenuOpened} onOpenChange={contextMenuOpenChange} getPopupContainer={() => document.body} <NodeContextMenu coords={coords} openContextMenu={openContextMenu} contextMenuOpened={contextMenuOpened}>
</NodeContextMenu>
{/* <Dropdown menu={{ items, onClick: onMenuClick }} trigger={['contextMenu']} open={contextMenuOpened} onOpenChange={contextMenuOpenChange} getPopupContainer={() => document.body}
// 👇 Key part: manually position the dropdown // 👇 Key part: manually position the dropdown
overlayStyle={{ overlayStyle={{
position: "absolute", position: "absolute",
@@ -232,7 +236,7 @@ export default function Graph({ setGraphPath }) {
top: coords.y, top: coords.y,
}}> }}>
</Dropdown> </Dropdown> */}
<Modal <Modal
title="Rename" title="Rename"
open={renameModalOpened} open={renameModalOpened}

View File

@@ -0,0 +1,66 @@
import { Dropdown, type MenuProps } from "antd";
import { useState } from "react";
const items: MenuProps['items'] = [
{
key: 'rename',
label: 'Rename',
extra: 'ctrl + n',
},
{
key: 'subgraph',
label: 'Subgraph',
extra: 'ctrl + s',
},
{
key: 'remove',
label: 'Remove',
extra: 'ctrl + r'
}
]
export default function NodeContextMenu({
coords,
openContextMenu,
contextMenuOpened } :
{
coords: {x:number, y:number},
openContextMenu: React.Dispatch<React.SetStateAction<boolean>>,
contextMenuOpened: boolean
}) {
function contextMenuOpenChange(open: boolean) {
if (!open) {
openContextMenu(false)
}
}
const onMenuClick: MenuProps['onClick'] = ({ key }) => {
switch (key) {
case 'rename': {
//openRenameModal(true);
break;
}
case 'remove': {
//removeNode(nodeId);
break;
}
case 'subgraph': {
break;
}
}
};
return (
<Dropdown menu={{ items, onClick: onMenuClick }} trigger={['contextMenu']} open={contextMenuOpened} onOpenChange={contextMenuOpenChange} getPopupContainer={() => document.body}
// 👇 Key part: manually position the dropdown
overlayStyle={{
position: "absolute",
left: coords.x,
top: coords.y,
}}>
</Dropdown>
)
}