Files
drawNET_test/static/js/modules/ui/context_menu/handlers.js
T
2026-04-22 12:05:03 +09:00

192 lines
6.7 KiB
JavaScript

import { state } from '../../state.js';
import { toggleSidebar } from '../../properties_sidebar/index.js';
/**
* handleMenuAction - Executes the logic for a clicked context menu item
* Direct X6 API version (Replaces legacy DSL-based handler)
*/
export function handleMenuAction(action, cellId) {
if (!state.graph) return;
// Recursive Selection Focus
if (action === 'select-cell' && cellId) {
const target = state.graph.getCellById(cellId);
if (target) {
state.graph.cleanSelection();
state.graph.select(target);
toggleSidebar(true);
}
return;
}
const selectedCells = state.graph.getSelectedCells();
const selectedNodes = selectedCells.filter(c => c.isNode());
if (action === 'properties') {
if (cellId) {
const target = state.graph.getCellById(cellId);
if (target) {
state.graph.cleanSelection();
state.graph.select(target);
}
}
toggleSidebar(true);
return;
}
// Alignment & Distribution Actions
const isAlignmentAction = action.startsWith('align') || action.startsWith('distribute');
const isZOrderAction = action === 'to-front' || action === 'to-back';
if (isAlignmentAction || isZOrderAction) {
if (action === 'to-front') { selectedCells.forEach(c => c.toFront({ deep: true })); return; }
if (action === 'to-back') { selectedCells.forEach(c => c.toBack({ deep: true })); return; }
import('/static/js/modules/graph/alignment.js').then(m => {
const alignMap = {
'alignLeft': 'left', 'align-left': 'left',
'alignRight': 'right', 'align-right': 'right',
'alignTop': 'top', 'align-top': 'top',
'alignBottom': 'bottom', 'align-bottom': 'bottom',
'alignCenter': 'center', 'align-center': 'center',
'alignMiddle': 'middle', 'align-middle': 'middle'
};
const distMap = {
'distributeHorizontal': 'horizontal', 'distribute-h': 'horizontal',
'distributeVertical': 'vertical', 'distribute-v': 'vertical'
};
if (alignMap[action]) m.alignNodes(alignMap[action]);
else if (distMap[action]) m.distributeNodes(distMap[action]);
});
return;
}
// --- Group Management (X6 API directly) ---
if (action === 'group') {
if (selectedNodes.length < 2) return;
const groupLabel = 'group_' + Math.random().toString(36).substr(2, 4);
// Calculate BBox of selected nodes
const bbox = state.graph.getCellsBBox(selectedNodes);
const padding = 40;
const groupNode = state.graph.addNode({
shape: 'drawnet-node',
x: bbox.x - padding,
y: bbox.y - padding,
width: bbox.width + padding * 2,
height: bbox.height + padding * 2,
zIndex: 1,
data: {
label: groupLabel,
is_group: true,
background: '#e0f2fe',
description: "",
asset_tag: "",
tags: []
},
attrs: {
label: { text: groupLabel },
body: { fill: '#e0f2fe', stroke: '#3b82f6' }
}
});
// Set child nodes
selectedNodes.forEach(n => groupNode.addChild(n));
state.graph.cleanSelection();
state.graph.select(groupNode);
import('/static/js/modules/persistence.js').then(m => m.markDirty());
return;
}
if (action === 'ungroup') {
if (selectedNodes.length === 0) return;
const group = selectedNodes[0];
if (!group.getData()?.is_group) return;
// Separate child nodes
const children = group.getChildren() || [];
children.forEach(child => group.removeChild(child));
state.graph.removeNode(group);
import('/static/js/modules/persistence.js').then(m => m.markDirty());
return;
}
// --- Disconnect (X6 API directly) ---
if (action === 'disconnect') {
if (selectedNodes.length < 2) return;
const nodeIds = new Set(selectedNodes.map(n => n.id));
const edgesToRemove = state.graph.getEdges().filter(e => {
return nodeIds.has(e.getSourceCellId()) && nodeIds.has(e.getTargetCellId());
});
edgesToRemove.forEach(e => state.graph.removeEdge(e));
import('/static/js/modules/persistence.js').then(m => m.markDirty());
return;
}
// --- Connect / Line Style Update actions ---
const selectedEdges = selectedCells.filter(c => c.isEdge());
const styleMap = {
'connect-solid': { routing: 'manhattan' },
'connect-straight': { routing: 'straight' },
'connect-dashed': { style: 'dashed' }
};
if (selectedEdges.length > 0 && styleMap[action]) {
const edgeData = styleMap[action];
selectedEdges.forEach(async (edge) => {
const currentData = edge.getData() || {};
const newData = { ...currentData, ...edgeData };
const { handleEdgeUpdate } = await import('../../properties_sidebar/handlers/edge.js');
await handleEdgeUpdate(edge, newData);
});
return;
}
const connectableNodes = selectedCells.filter(c => c.isNode());
if (connectableNodes.length < 2) return;
// Sort by selection order if available
const sortedNodes = [...connectableNodes].sort((a, b) => {
const idxA = state.selectionOrder.indexOf(a.id);
const idxB = state.selectionOrder.indexOf(b.id);
if (idxA === -1 || idxB === -1) return 0;
return idxA - idxB;
});
const edgeData = styleMap[action] || {};
// Chain connection logic (1 -> 2, 2 -> 3, ...)
for (let i = 0; i < sortedNodes.length - 1; i++) {
const src = sortedNodes[i];
const dst = sortedNodes[i+1];
// Prevent duplicate connections (bi-directional check)
const exists = state.graph.getEdges().some(e =>
(e.getSourceCellId() === src.id && e.getTargetCellId() === dst.id) ||
(e.getSourceCellId() === dst.id && e.getTargetCellId() === src.id)
);
if (exists) continue;
import('/static/js/modules/graph/styles.js').then(({ getX6EdgeConfig }) => {
const config = getX6EdgeConfig({
id: `e_${src.id}_${dst.id}_${Date.now()}`,
source: src.id,
target: dst.id,
description: "",
asset_tag: "",
routing_offset: 20,
...edgeData
});
state.graph.addEdge(config);
});
}
import('/static/js/modules/persistence.js').then(m => m.markDirty());
}