refactor context menu to separate component
This commit is contained in:
@@ -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
3
src/TODO
Normal file
@@ -0,0 +1,3 @@
|
||||
- add not connected node in selected rank
|
||||
- connect node
|
||||
- remove connection
|
||||
@@ -8,6 +8,7 @@ import { Modal } from "antd";
|
||||
import { Input } from "antd";
|
||||
import type { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb";
|
||||
import { cloneDeep } from "lodash";
|
||||
import NodeContextMenu from "./NodeContextMenu";
|
||||
|
||||
export class GraphModel {
|
||||
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>
|
||||
<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
|
||||
overlayStyle={{
|
||||
position: "absolute",
|
||||
@@ -232,7 +236,7 @@ export default function Graph({ setGraphPath }) {
|
||||
top: coords.y,
|
||||
}}>
|
||||
|
||||
</Dropdown>
|
||||
</Dropdown> */}
|
||||
<Modal
|
||||
title="Rename"
|
||||
open={renameModalOpened}
|
||||
|
||||
66
src/components/NodeContextMenu.tsx
Normal file
66
src/components/NodeContextMenu.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user