From a0606e473180aafe79131774926aa281417720f6 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Mon, 15 Jul 2024 16:56:13 +0900 Subject: [PATCH 1/5] add attach node from canvas context menu --- src/commands/CommandIDs.tsx | 1 + src/commands/NodeActionCommands.tsx | 23 +++++++++++++++++++++++ src/components/port/CustomPortModel.ts | 11 +++++++++++ src/context-menu/CanvasContextMenu.tsx | 14 ++++++++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/commands/CommandIDs.tsx b/src/commands/CommandIDs.tsx index bcee2810..45ba8e7c 100644 --- a/src/commands/CommandIDs.tsx +++ b/src/commands/CommandIDs.tsx @@ -16,6 +16,7 @@ export const commandIDs = { cutNode: "Xircuit-editor:cut-node", copyNode: "Xircuit-editor:copy-node", pasteNode: "Xircuit-editor:paste-node", + attachNode: "Xircuit-editor:attach-node", triggerLoadingAnimation: "Xircuit-editor:trigger-loading-animation", reloadNode: "Xircuit-editor:reload-node", reloadAllNodes: "Xircuit-editor:reload-all-nodes", diff --git a/src/commands/NodeActionCommands.tsx b/src/commands/NodeActionCommands.tsx index a4701b15..e352595c 100644 --- a/src/commands/NodeActionCommands.tsx +++ b/src/commands/NodeActionCommands.tsx @@ -928,4 +928,27 @@ export function addNodeActionCommands( } } + + // Add command to attach selected node + commands.addCommand(commandIDs.attachNode, { + execute: async () => { + + await fetchComponents(); + + const widget = tracker.currentWidget?.content as XircuitsPanel; + const model = widget.xircuitsApp.getDiagramEngine().getModel(); + const selected_entities = model.getSelectedEntities(); + const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; + selected_nodes.forEach(node => { + node.getOptions().selected = false + node.getOptions().extras.attached = true; + let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; + let connectedNodes = parameterOutPort.getTargetNodes(); + connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + }); + widget.xircuitsApp.getDiagramEngine().repaintCanvas(); + }, + label: trans.__('attach node') + }); + } \ No newline at end of file diff --git a/src/components/port/CustomPortModel.ts b/src/components/port/CustomPortModel.ts index 8f5a7201..267542e6 100644 --- a/src/components/port/CustomPortModel.ts +++ b/src/components/port/CustomPortModel.ts @@ -1,6 +1,7 @@ import { DefaultPortModel, DefaultPortModelOptions } from "@projectstorm/react-diagrams"; import { DeserializeEvent} from '@projectstorm/react-canvas-core'; import {PortModel} from "@projectstorm/react-diagrams-core"; +import { CustomLinkModel } from "../link/CustomLinkModel"; /** * @author wenfeng xu @@ -365,6 +366,16 @@ export class CustomPortModel extends DefaultPortModel { } + getTargetPorts = () => { + let port: any = this; + return Object.values(port.getLinks()).map((link:CustomLinkModel) => link.getTargetPort()); + } + + getTargetNodes = () => { + let port: any = this; + return Object.values(port.getLinks()).map((link:CustomLinkModel) => link.getTargetPort().getNode()); + } + getCustomProps() { const { name, varName, portType, dataType } = this; const id = this.getID(); diff --git a/src/context-menu/CanvasContextMenu.tsx b/src/context-menu/CanvasContextMenu.tsx index 8106200e..6346ee98 100644 --- a/src/context-menu/CanvasContextMenu.tsx +++ b/src/context-menu/CanvasContextMenu.tsx @@ -30,6 +30,11 @@ export class CanvasContextMenu extends React.Component { }); }; + const handleAttachNode = async () => { + this.props.app.commands.execute(commandIDs.attachNode); + await this.props.app.commands.execute(commandIDs.reloadNode); + }; + return (
{visibility.showCutCopyPaste && ( @@ -45,6 +50,9 @@ export class CanvasContextMenu extends React.Component { {visibility.showEdit && (
this.props.app.commands.execute(commandIDs.editNode)}>Edit
)} + {visibility.showAttachNode && ( +
Attach
+ )} {visibility.showOpenScript && (
this.props.app.commands.execute(commandIDs.openScript)}>Open Script
)} @@ -93,7 +101,8 @@ export function getMenuOptionsVisibility(models) { let isSingleParameterNodeSelected = parameterNodes.length === 1; let isSingleComponentNodeSelected = componentNodes.length === 1; let showReloadNode = isNodeSelected && componentNodes.length > 0; - let showopenXircuitsWorkflow = isSingleComponentNodeSelected && models.some(model => isXircuitsWorkflow(model)) + let showopenXircuitsWorkflow = isSingleComponentNodeSelected && models.some(model => isXircuitsWorkflow(model)); + let showAttachNode = isNodeSelected && parameterNodes.length > 0; return { showCutCopyPaste: !models.length || isNodeSelected || isLinkSelected, @@ -103,7 +112,8 @@ export function getMenuOptionsVisibility(models) { showopenXircuitsWorkflow: showopenXircuitsWorkflow, showDelete: isNodeSelected || isLinkSelected || parameterNodes.length > 0, showUndoRedo: !models.length, - showAddComment: !models.length + showAddComment: !models.length, + showAttachNode: showAttachNode }; } From 92ed81d9692e2885f2568cfc6eaf816b815015c8 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Mon, 15 Jul 2024 23:17:30 +0900 Subject: [PATCH 2/5] add detach all node context option --- src/commands/CommandIDs.tsx | 1 + src/commands/NodeActionCommands.tsx | 37 ++++++++++++++++++++++++-- src/components/port/CustomPortModel.ts | 10 +++++++ src/context-menu/CanvasContextMenu.tsx | 27 ++++++++++++++++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/commands/CommandIDs.tsx b/src/commands/CommandIDs.tsx index 45ba8e7c..5c9a32ec 100644 --- a/src/commands/CommandIDs.tsx +++ b/src/commands/CommandIDs.tsx @@ -17,6 +17,7 @@ export const commandIDs = { copyNode: "Xircuit-editor:copy-node", pasteNode: "Xircuit-editor:paste-node", attachNode: "Xircuit-editor:attach-node", + detachNode: "Xircuit-editor:detach-node", triggerLoadingAnimation: "Xircuit-editor:trigger-loading-animation", reloadNode: "Xircuit-editor:reload-node", reloadAllNodes: "Xircuit-editor:reload-all-nodes", diff --git a/src/commands/NodeActionCommands.tsx b/src/commands/NodeActionCommands.tsx index e352595c..aba0c89e 100644 --- a/src/commands/NodeActionCommands.tsx +++ b/src/commands/NodeActionCommands.tsx @@ -933,8 +933,6 @@ export function addNodeActionCommands( commands.addCommand(commandIDs.attachNode, { execute: async () => { - await fetchComponents(); - const widget = tracker.currentWidget?.content as XircuitsPanel; const model = widget.xircuitsApp.getDiagramEngine().getModel(); const selected_entities = model.getSelectedEntities(); @@ -951,4 +949,39 @@ export function addNodeActionCommands( label: trans.__('attach node') }); + + // Add command to detach parameter node + commands.addCommand(commandIDs.detachNode, { + execute: async () => { + + const widget = tracker.currentWidget?.content as XircuitsPanel; + const model = widget.xircuitsApp.getDiagramEngine().getModel(); + const selected_entities = model.getSelectedEntities(); + + const literal_nodes = []; + const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; + selected_nodes.forEach(node => { + node.getOptions().selected = false; + let inPorts = node.getInPorts(); + Object.values(inPorts).forEach((port: CustomPortModel) => { + let sourceNode = port.getSourceNodes()[0] as CustomNodeModel; + if (sourceNode && sourceNode['name'].startsWith('Literal ') && sourceNode['extras']['attached']) { + sourceNode.getOptions().extras.attached = false; + sourceNode.getOptions().selected = true; + literal_nodes.push(sourceNode); + } + }) + }); + + literal_nodes.forEach(node => { + let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; + let connectedNodes = parameterOutPort.getTargetNodes(); + connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + }); + + widget.xircuitsApp.getDiagramEngine().repaintCanvas(); + }, + label: trans.__('detach node') + }); + } \ No newline at end of file diff --git a/src/components/port/CustomPortModel.ts b/src/components/port/CustomPortModel.ts index 267542e6..dc839d01 100644 --- a/src/components/port/CustomPortModel.ts +++ b/src/components/port/CustomPortModel.ts @@ -376,6 +376,16 @@ export class CustomPortModel extends DefaultPortModel { return Object.values(port.getLinks()).map((link:CustomLinkModel) => link.getTargetPort().getNode()); } + getSourcePorts = () => { + let port: any = this; + return Object.values(port.getLinks()).map((link:CustomLinkModel) => link.getSourcePort()); + } + + getSourceNodes = () => { + let port: any = this; + return Object.values(port.getLinks()).map((link:CustomLinkModel) => link.getSourcePort().getNode()); + } + getCustomProps() { const { name, varName, portType, dataType } = this; const id = this.getID(); diff --git a/src/context-menu/CanvasContextMenu.tsx b/src/context-menu/CanvasContextMenu.tsx index 6346ee98..073dfb0e 100644 --- a/src/context-menu/CanvasContextMenu.tsx +++ b/src/context-menu/CanvasContextMenu.tsx @@ -5,6 +5,7 @@ import { DiagramEngine, NodeModel, LinkModel } from '@projectstorm/react-diagram import '../../style/ContextMenu.css' import { commandIDs } from "../commands/CommandIDs"; +import { CustomPortModel } from '../components/port/CustomPortModel'; export interface CanvasContextMenuProps { app: JupyterFrontEnd; @@ -35,6 +36,11 @@ export class CanvasContextMenu extends React.Component { await this.props.app.commands.execute(commandIDs.reloadNode); }; + const handleDetachNode = async () => { + this.props.app.commands.execute(commandIDs.detachNode); + await this.props.app.commands.execute(commandIDs.reloadNode); + }; + return (
{visibility.showCutCopyPaste && ( @@ -44,15 +50,18 @@ export class CanvasContextMenu extends React.Component {
this.props.app.commands.execute(commandIDs.pasteNode)}>Paste
)} + {visibility.showAttachNode && ( +
Attach
+ )} + {visibility.showDetachNode && ( +
Detach
+ )} {visibility.showReloadNode && (
Reload Node
)} {visibility.showEdit && (
this.props.app.commands.execute(commandIDs.editNode)}>Edit
)} - {visibility.showAttachNode && ( -
Attach
- )} {visibility.showOpenScript && (
this.props.app.commands.execute(commandIDs.openScript)}>Open Script
)} @@ -90,6 +99,14 @@ export function getMenuOptionsVisibility(models) { return !isLiteralNode(node) && !isArgumentNode(node); } + function canDetachNode(node) { + let ports = node.getInPorts(); + return ports.some((port) => { + let sourceNode = port.getSourceNodes()[0]; + return sourceNode?.getOptions()?.extras?.attached === true; + }); + } + function isXircuitsWorkflow(node) { return node.getOptions()?.extras?.type == 'xircuits_workflow' ?? false; } @@ -103,6 +120,7 @@ export function getMenuOptionsVisibility(models) { let showReloadNode = isNodeSelected && componentNodes.length > 0; let showopenXircuitsWorkflow = isSingleComponentNodeSelected && models.some(model => isXircuitsWorkflow(model)); let showAttachNode = isNodeSelected && parameterNodes.length > 0; + let showDetachNode = componentNodes.some(model => canDetachNode(model)); return { showCutCopyPaste: !models.length || isNodeSelected || isLinkSelected, @@ -113,7 +131,8 @@ export function getMenuOptionsVisibility(models) { showDelete: isNodeSelected || isLinkSelected || parameterNodes.length > 0, showUndoRedo: !models.length, showAddComment: !models.length, - showAttachNode: showAttachNode + showAttachNode: showAttachNode, + showDetachNode: showDetachNode }; } From 2c450b6a6a14319823bd916de0123db3ce83c87c Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Tue, 16 Jul 2024 02:03:07 +0900 Subject: [PATCH 3/5] add attach all node context option --- src/commands/CommandIDs.tsx | 3 +- src/commands/NodeActionCommands.tsx | 41 +++++++++++++++++++++++--- src/context-menu/CanvasContextMenu.tsx | 32 +++++++++++++++----- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/commands/CommandIDs.tsx b/src/commands/CommandIDs.tsx index 5c9a32ec..f7181e1b 100644 --- a/src/commands/CommandIDs.tsx +++ b/src/commands/CommandIDs.tsx @@ -17,7 +17,8 @@ export const commandIDs = { copyNode: "Xircuit-editor:copy-node", pasteNode: "Xircuit-editor:paste-node", attachNode: "Xircuit-editor:attach-node", - detachNode: "Xircuit-editor:detach-node", + attachAllNodes: "Xircuit-editor:attach-all-nodes", + detachAllNodes: "Xircuit-editor:detach-all-nodes", triggerLoadingAnimation: "Xircuit-editor:trigger-loading-animation", reloadNode: "Xircuit-editor:reload-node", reloadAllNodes: "Xircuit-editor:reload-all-nodes", diff --git a/src/commands/NodeActionCommands.tsx b/src/commands/NodeActionCommands.tsx index aba0c89e..d73b5692 100644 --- a/src/commands/NodeActionCommands.tsx +++ b/src/commands/NodeActionCommands.tsx @@ -948,10 +948,43 @@ export function addNodeActionCommands( }, label: trans.__('attach node') }); - - // Add command to detach parameter node - commands.addCommand(commandIDs.detachNode, { + // Add command to attach all parameter nodes + commands.addCommand(commandIDs.attachAllNodes, { + execute: async () => { + + const widget = tracker.currentWidget?.content as XircuitsPanel; + const model = widget.xircuitsApp.getDiagramEngine().getModel(); + const selected_entities = model.getSelectedEntities(); + + const literal_nodes = []; + const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; + selected_nodes.forEach(node => { + node.getOptions().selected = false; + let inPorts = node.getInPorts(); + Object.values(inPorts).forEach((port: CustomPortModel) => { + let sourceNode = port.getSourceNodes()[0] as CustomNodeModel; + if (sourceNode && sourceNode['name'].startsWith('Literal ') && !sourceNode['extras']['attached']) { + sourceNode.getOptions().extras.attached = true; + sourceNode.getOptions().selected = true; + literal_nodes.push(sourceNode); + } + }) + }); + + literal_nodes.forEach(node => { + let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; + let connectedNodes = parameterOutPort.getTargetNodes(); + connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + }); + + widget.xircuitsApp.getDiagramEngine().repaintCanvas(); + }, + label: trans.__('attach all nodes') + }); + + // Add command to detach all parameter nodes + commands.addCommand(commandIDs.detachAllNodes, { execute: async () => { const widget = tracker.currentWidget?.content as XircuitsPanel; @@ -981,7 +1014,7 @@ export function addNodeActionCommands( widget.xircuitsApp.getDiagramEngine().repaintCanvas(); }, - label: trans.__('detach node') + label: trans.__('detach all nodes') }); } \ No newline at end of file diff --git a/src/context-menu/CanvasContextMenu.tsx b/src/context-menu/CanvasContextMenu.tsx index 073dfb0e..24fb14bd 100644 --- a/src/context-menu/CanvasContextMenu.tsx +++ b/src/context-menu/CanvasContextMenu.tsx @@ -36,8 +36,13 @@ export class CanvasContextMenu extends React.Component { await this.props.app.commands.execute(commandIDs.reloadNode); }; - const handleDetachNode = async () => { - this.props.app.commands.execute(commandIDs.detachNode); + const handleAllAttachNodes = async () => { + this.props.app.commands.execute(commandIDs.attachAllNodes); + await this.props.app.commands.execute(commandIDs.reloadNode); + }; + + const handleDetachAllNodes = async () => { + this.props.app.commands.execute(commandIDs.detachAllNodes); await this.props.app.commands.execute(commandIDs.reloadNode); }; @@ -53,8 +58,11 @@ export class CanvasContextMenu extends React.Component { {visibility.showAttachNode && (
Attach
)} - {visibility.showDetachNode && ( -
Detach
+ {visibility.showAttachAllNodes && ( +
Attach Literals
+ )} + {visibility.showDetachAllNodes && ( +
Detach Literals
)} {visibility.showReloadNode && (
Reload Node
@@ -99,7 +107,15 @@ export function getMenuOptionsVisibility(models) { return !isLiteralNode(node) && !isArgumentNode(node); } - function canDetachNode(node) { + function canAttachAllNodes(node) { + let ports = node.getInPorts(); + return ports.some((port) => { + let sourceNode = port.getSourceNodes()[0]; + return sourceNode?.getOptions()?.extras?.attached === false; + }); + } + + function canDetachAllNodes(node) { let ports = node.getInPorts(); return ports.some((port) => { let sourceNode = port.getSourceNodes()[0]; @@ -120,7 +136,8 @@ export function getMenuOptionsVisibility(models) { let showReloadNode = isNodeSelected && componentNodes.length > 0; let showopenXircuitsWorkflow = isSingleComponentNodeSelected && models.some(model => isXircuitsWorkflow(model)); let showAttachNode = isNodeSelected && parameterNodes.length > 0; - let showDetachNode = componentNodes.some(model => canDetachNode(model)); + let showAttachAllNodes = componentNodes.some(model => canAttachAllNodes(model)); + let showDetachAllNodes = componentNodes.some(model => canDetachAllNodes(model)); return { showCutCopyPaste: !models.length || isNodeSelected || isLinkSelected, @@ -132,7 +149,8 @@ export function getMenuOptionsVisibility(models) { showUndoRedo: !models.length, showAddComment: !models.length, showAttachNode: showAttachNode, - showDetachNode: showDetachNode + showAttachAllNodes: showAttachAllNodes, + showDetachAllNodes: showDetachAllNodes }; } From d472625e338f03cc05ac57aa3266b04d73fb4ac2 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Tue, 16 Jul 2024 17:29:43 +0900 Subject: [PATCH 4/5] type check ctx display for literals --- src/commands/NodeActionCommands.tsx | 23 +++++++++++++---------- src/context-menu/CanvasContextMenu.tsx | 8 ++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/commands/NodeActionCommands.tsx b/src/commands/NodeActionCommands.tsx index d73b5692..bf202f30 100644 --- a/src/commands/NodeActionCommands.tsx +++ b/src/commands/NodeActionCommands.tsx @@ -936,13 +936,16 @@ export function addNodeActionCommands( const widget = tracker.currentWidget?.content as XircuitsPanel; const model = widget.xircuitsApp.getDiagramEngine().getModel(); const selected_entities = model.getSelectedEntities(); - const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; - selected_nodes.forEach(node => { - node.getOptions().selected = false + const selected_literals = selected_entities.filter((entity): entity is CustomNodeModel => { + return entity instanceof NodeModel && entity.getOptions().name.startsWith("Literal "); + }); + + selected_literals.forEach(node => { + node.setSelected(false); node.getOptions().extras.attached = true; let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; let connectedNodes = parameterOutPort.getTargetNodes(); - connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + connectedNodes.forEach((node: CustomNodeModel) => node.setSelected(true)) }); widget.xircuitsApp.getDiagramEngine().repaintCanvas(); }, @@ -960,13 +963,13 @@ export function addNodeActionCommands( const literal_nodes = []; const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; selected_nodes.forEach(node => { - node.getOptions().selected = false; + node.setSelected(false); let inPorts = node.getInPorts(); Object.values(inPorts).forEach((port: CustomPortModel) => { let sourceNode = port.getSourceNodes()[0] as CustomNodeModel; if (sourceNode && sourceNode['name'].startsWith('Literal ') && !sourceNode['extras']['attached']) { sourceNode.getOptions().extras.attached = true; - sourceNode.getOptions().selected = true; + sourceNode.setSelected(true); literal_nodes.push(sourceNode); } }) @@ -975,7 +978,7 @@ export function addNodeActionCommands( literal_nodes.forEach(node => { let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; let connectedNodes = parameterOutPort.getTargetNodes(); - connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + connectedNodes.forEach((node: CustomNodeModel) => node.setSelected(true)) }); widget.xircuitsApp.getDiagramEngine().repaintCanvas(); @@ -994,13 +997,13 @@ export function addNodeActionCommands( const literal_nodes = []; const selected_nodes = selected_entities.filter(entity => entity instanceof NodeModel) as CustomNodeModel[]; selected_nodes.forEach(node => { - node.getOptions().selected = false; + node.setSelected(false); let inPorts = node.getInPorts(); Object.values(inPorts).forEach((port: CustomPortModel) => { let sourceNode = port.getSourceNodes()[0] as CustomNodeModel; if (sourceNode && sourceNode['name'].startsWith('Literal ') && sourceNode['extras']['attached']) { sourceNode.getOptions().extras.attached = false; - sourceNode.getOptions().selected = true; + sourceNode.setSelected(true); literal_nodes.push(sourceNode); } }) @@ -1009,7 +1012,7 @@ export function addNodeActionCommands( literal_nodes.forEach(node => { let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; let connectedNodes = parameterOutPort.getTargetNodes(); - connectedNodes.forEach((node: CustomNodeModel) => node.getOptions().selected = true) + connectedNodes.forEach((node: CustomNodeModel) => node.setSelected(true)) }); widget.xircuitsApp.getDiagramEngine().repaintCanvas(); diff --git a/src/context-menu/CanvasContextMenu.tsx b/src/context-menu/CanvasContextMenu.tsx index 24fb14bd..e4969c0a 100644 --- a/src/context-menu/CanvasContextMenu.tsx +++ b/src/context-menu/CanvasContextMenu.tsx @@ -32,17 +32,17 @@ export class CanvasContextMenu extends React.Component { }; const handleAttachNode = async () => { - this.props.app.commands.execute(commandIDs.attachNode); + await this.props.app.commands.execute(commandIDs.attachNode); await this.props.app.commands.execute(commandIDs.reloadNode); }; const handleAllAttachNodes = async () => { - this.props.app.commands.execute(commandIDs.attachAllNodes); + await this.props.app.commands.execute(commandIDs.attachAllNodes); await this.props.app.commands.execute(commandIDs.reloadNode); }; const handleDetachAllNodes = async () => { - this.props.app.commands.execute(commandIDs.detachAllNodes); + await this.props.app.commands.execute(commandIDs.detachAllNodes); await this.props.app.commands.execute(commandIDs.reloadNode); }; @@ -104,7 +104,7 @@ export function getMenuOptionsVisibility(models) { } function isComponentNode(node) { - return !isLiteralNode(node) && !isArgumentNode(node); + return node instanceof NodeModel && !isLiteralNode(node) && !isArgumentNode(node); } function canAttachAllNodes(node) { From 519f834e952ddc74039868c0ab53b32916a97861 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Tue, 16 Jul 2024 17:46:36 +0900 Subject: [PATCH 5/5] prevent attach if not connected, update canvas context menu spawn logic --- src/commands/NodeActionCommands.tsx | 8 +++++--- src/context-menu/CanvasContextMenu.tsx | 10 +++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/commands/NodeActionCommands.tsx b/src/commands/NodeActionCommands.tsx index bf202f30..5f7c5986 100644 --- a/src/commands/NodeActionCommands.tsx +++ b/src/commands/NodeActionCommands.tsx @@ -936,11 +936,13 @@ export function addNodeActionCommands( const widget = tracker.currentWidget?.content as XircuitsPanel; const model = widget.xircuitsApp.getDiagramEngine().getModel(); const selected_entities = model.getSelectedEntities(); - const selected_literals = selected_entities.filter((entity): entity is CustomNodeModel => { - return entity instanceof NodeModel && entity.getOptions().name.startsWith("Literal "); + const connected_literals = selected_entities.filter((entity): entity is CustomNodeModel => { + return entity instanceof CustomNodeModel && + entity.getOptions().name.startsWith("Literal ") && + Object.keys(entity.getOutPorts()[0].getLinks()).length > 0; }); - selected_literals.forEach(node => { + connected_literals.forEach(node => { node.setSelected(false); node.getOptions().extras.attached = true; let parameterOutPort = node.getOutPorts()[0] as CustomPortModel; diff --git a/src/context-menu/CanvasContextMenu.tsx b/src/context-menu/CanvasContextMenu.tsx index e4969c0a..2b9749ae 100644 --- a/src/context-menu/CanvasContextMenu.tsx +++ b/src/context-menu/CanvasContextMenu.tsx @@ -107,6 +107,13 @@ export function getMenuOptionsVisibility(models) { return node instanceof NodeModel && !isLiteralNode(node) && !isArgumentNode(node); } + function isConnected(node): boolean { + let outPorts = node.getOutPorts(); + let inPorts = node.getInPorts(); + return outPorts.some(port => Object.keys(port.getLinks()).length > 0) || + inPorts.some(port => Object.keys(port.getLinks()).length > 0); + } + function canAttachAllNodes(node) { let ports = node.getInPorts(); return ports.some((port) => { @@ -129,13 +136,14 @@ export function getMenuOptionsVisibility(models) { let isNodeSelected = models.some(model => model instanceof NodeModel); let isLinkSelected = models.some(model => model instanceof LinkModel); + let literalNodes = models.filter(model => isLiteralNode(model)); let parameterNodes = models.filter(model => !isComponentNode(model)); let componentNodes = models.filter(model => isComponentNode(model)); let isSingleParameterNodeSelected = parameterNodes.length === 1; let isSingleComponentNodeSelected = componentNodes.length === 1; let showReloadNode = isNodeSelected && componentNodes.length > 0; let showopenXircuitsWorkflow = isSingleComponentNodeSelected && models.some(model => isXircuitsWorkflow(model)); - let showAttachNode = isNodeSelected && parameterNodes.length > 0; + let showAttachNode = literalNodes.length > 0 && literalNodes.some(model => isConnected(model)); let showAttachAllNodes = componentNodes.some(model => canAttachAllNodes(model)); let showDetachAllNodes = componentNodes.some(model => canDetachAllNodes(model));