From 1dd789fa543750ae01f7b348433baf9ce2e04432 Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Mon, 2 Mar 2026 09:49:21 -0800 Subject: [PATCH] Support selection of app inputs and outputs from vue mode (#9259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The input and output indicators are now plugged directly into the `LGraphNode.vue` template. Care was taken to make implementation to have low cost for performance and complexity when not in app mode setup. - Context menu event handlers are added to each widget in vue mode instead of resolving the target widget of an event - Swap the nodeId passed by `useGraphNodeManager` to not include the locator id. This id was never used and was incorrect since it didn't resolve across nested subgraphs. - Continued bug fixes for app mode as a whole. Known issue: There is disparity of nodeId between litegraph (which references the widget in the root graph) and vue (which promotes the original widget). Efforts to reconcile are ongoing. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9259-Support-selection-app-inputs-and-outputs-from-vue-mode-3136d73d365081ae8e56e35bf6322409) by [Unito](https://www.unito.io) --------- Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com> --- .../LiteGraphCanvasSplitterOverlay.vue | 6 +- src/components/builder/AppBuilder.vue | 47 +++++++++----- src/components/builder/IoItem.vue | 7 ++- .../layout/PropertiesAccordionItem.vue | 4 +- src/composables/graph/useMoreOptionsMenu.ts | 35 +++++++---- src/locales/en/main.json | 4 +- .../extensions/linearMode/AppInput.vue | 62 +++++++++++++++++++ .../extensions/linearMode/AppOutput.vue | 51 +++++++++++++++ .../extensions/linearMode/LinearControls.vue | 9 +-- .../vueNodes/components/LGraphNode.vue | 7 +++ .../vueNodes/components/NodeWidgets.vue | 59 +++++++++++++----- 11 files changed, 237 insertions(+), 54 deletions(-) create mode 100644 src/renderer/extensions/linearMode/AppInput.vue create mode 100644 src/renderer/extensions/linearMode/AppOutput.vue diff --git a/src/components/LiteGraphCanvasSplitterOverlay.vue b/src/components/LiteGraphCanvasSplitterOverlay.vue index 316fc258d3..004796fe1f 100644 --- a/src/components/LiteGraphCanvasSplitterOverlay.vue +++ b/src/components/LiteGraphCanvasSplitterOverlay.vue @@ -155,7 +155,7 @@ const unifiedWidth = computed(() => const { focusMode } = storeToRefs(workspaceStore) -const { isSelectMode } = useAppMode() +const { isSelectMode, isBuilderMode } = useAppMode() const { activeSidebarTabId, activeSidebarTab } = storeToRefs(sidebarTabStore) const { bottomPanelVisible } = storeToRefs(useBottomPanelStore()) const { isOpen: rightSidePanelVisible } = storeToRefs(rightSidePanelStore) @@ -163,7 +163,9 @@ const showOffsideSplitter = computed( () => rightSidePanelVisible.value || isSelectMode.value ) -const sidebarPanelVisible = computed(() => activeSidebarTab.value !== null) +const sidebarPanelVisible = computed( + () => activeSidebarTab.value !== null && !isBuilderMode.value +) const sidebarStateKey = computed(() => { return unifiedWidth.value diff --git a/src/components/builder/AppBuilder.vue b/src/components/builder/AppBuilder.vue index 40d2158f4a..c5f59052b0 100644 --- a/src/components/builder/AppBuilder.vue +++ b/src/components/builder/AppBuilder.vue @@ -38,20 +38,28 @@ const workflowStore = useWorkflowStore() const { t } = useI18n() const canvas: LGraphCanvas = canvasStore.getCanvas() -const { mode, isArrangeMode } = useAppMode() +const { isSelectMode, isArrangeMode } = useAppMode() const hoveringSelectable = ref(false) provide(HideLayoutFieldKey, true) workflowStore.activeWorkflow?.changeTracker?.reset() +function resolveNode(nodeId: NodeId) { + return ( + app.rootGraph.getNodeById(nodeId) ?? + [...app.rootGraph.subgraphs.values()] + .flatMap((sg) => sg.nodes) + .find((n) => n.id == nodeId) + ) +} + // Prune stale entries whose node/widget no longer exists, so the // DraggableList model always matches the rendered items. watchEffect(() => { - const valid = appModeStore.selectedInputs.filter(([nodeId, widgetName]) => { - const node = app.rootGraph.getNodeById(nodeId) - return node?.widgets?.some((w) => w.name === widgetName) - }) + const valid = appModeStore.selectedInputs.filter(([nodeId, widgetName]) => + resolveNode(nodeId)?.widgets?.some((w) => w.name === widgetName) + ) if (valid.length < appModeStore.selectedInputs.length) { appModeStore.selectedInputs = valid } @@ -60,7 +68,7 @@ watchEffect(() => { const arrangeInputs = computed(() => appModeStore.selectedInputs .map(([nodeId, widgetName]) => { - const node = app.rootGraph.getNodeById(nodeId) + const node = resolveNode(nodeId) const widget = node?.widgets?.find((w) => w.name === widgetName) if (!node || !widget) return null return { nodeId, widgetName, node, widget } @@ -70,7 +78,7 @@ const arrangeInputs = computed(() => const inputsWithState = computed(() => appModeStore.selectedInputs.map(([nodeId, widgetName]) => { - const node = app.rootGraph.getNodeById(nodeId) + const node = resolveNode(nodeId) const widget = node?.widgets?.find((w) => w.name === widgetName) if (!node || !widget) return { nodeId, widgetName } @@ -168,14 +176,14 @@ function handleClick(e: MouseEvent) { if (!widget) { if (!node.constructor.nodeData?.output_node) return canvasInteractions.forwardEventToCanvas(e) - const index = appModeStore.selectedOutputs.findIndex((id) => id === node.id) + const index = appModeStore.selectedOutputs.findIndex((id) => id == node.id) if (index === -1) appModeStore.selectedOutputs.push(node.id) else appModeStore.selectedOutputs.splice(index, 1) return } const index = appModeStore.selectedInputs.findIndex( - ([nodeId, widgetName]) => node.id === nodeId && widget.name === widgetName + ([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName ) if (index === -1) appModeStore.selectedInputs.push([node.id, widget.name]) else appModeStore.selectedInputs.splice(index, 1) @@ -239,6 +247,7 @@ const renderedInputs = computed<[string, MaybeRef | undefined][]>( :disabled="!appModeStore.selectedInputs.length" class="border-border-subtle border-b" :tooltip="`${t('linearMode.builder.inputsDesc')}\n${t('linearMode.builder.inputsExample')}`" + :tooltip-delay="100" >