From f1626acb61691a7a4cf0f19db333d764484cc069 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:01:46 +0000 Subject: [PATCH] refactor: Unify app builder & app widget lists (#9829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Currently app builder & app mode use slightly different rendering paths for the widgets, giving a different preview to what you actually get, this changes it to use the same path for both. ## Changes - **What**: - Extract LinearControls widget rendering - Replace app builder arrange step with this - Add ability to rename/remove widgets during app mode ## Screenshots (if applicable) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9829-refactor-Unify-app-builder-app-widget-lists-3216d73d36508157a888f08dbe3655ea) by [Unito](https://www.unito.io) --- src/components/builder/AppBuilder.vue | 46 +--- src/components/builder/AppModeWidgetList.vue | 212 ++++++++++++++++++ .../extensions/linearMode/LinearControls.vue | 133 +---------- src/stores/appModeStore.ts | 14 ++ 4 files changed, 237 insertions(+), 168 deletions(-) create mode 100644 src/components/builder/AppModeWidgetList.vue diff --git a/src/components/builder/AppBuilder.vue b/src/components/builder/AppBuilder.vue index bd2610d22b..73ed7a80e4 100644 --- a/src/components/builder/AppBuilder.vue +++ b/src/components/builder/AppBuilder.vue @@ -1,13 +1,13 @@ - + {{ isArrangeMode ? t('nodeHelpPage.inputs') : t('linearMode.builder.title') }} @@ -209,34 +199,10 @@ const renderedInputs = computed<[string, MaybeRef | undefined][]>( - - - - - - {{ widgetName }} - - ({{ t('linearMode.builder.unknownWidget') }}) - - - + +import { useEventListener } from '@vueuse/core' +import { computed, provide, shallowRef } from 'vue' +import { useI18n } from 'vue-i18n' + +import Popover from '@/components/ui/Popover.vue' +import Button from '@/components/ui/button/Button.vue' +import { extractVueNodeData } from '@/composables/graph/useGraphNodeManager' +import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import { LGraphEventMode } from '@/lib/litegraph/src/types/globalEnums' +import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' +import { appendCloudResParam } from '@/platform/distribution/cloudPreviewUtil' +import DropZone from '@/renderer/extensions/linearMode/DropZone.vue' +import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue' +import { api } from '@/scripts/api' +import { app } from '@/scripts/app' +import { useExecutionErrorStore } from '@/stores/executionErrorStore' +import { useAppModeStore } from '@/stores/appModeStore' +import { resolveNodeWidget } from '@/utils/litegraphUtil' +import { cn } from '@/utils/tailwindUtil' +import { HideLayoutFieldKey } from '@/types/widgetTypes' +import { promptRenameWidget } from '@/utils/widgetUtil' + +interface WidgetEntry { + key: string + nodeData: ReturnType & { + widgets: NonNullable['widgets']> + } + action: { widget: IBaseWidget; node: LGraphNode } +} + +const { mobile = false, builderMode = false } = defineProps<{ + mobile?: boolean + builderMode?: boolean +}>() + +const { t } = useI18n() +const executionErrorStore = useExecutionErrorStore() +const appModeStore = useAppModeStore() + +provide(HideLayoutFieldKey, true) + +const graphNodes = shallowRef(app.rootGraph.nodes) +useEventListener( + app.rootGraph.events, + 'configured', + () => (graphNodes.value = app.rootGraph.nodes) +) + +const mappedSelections = computed((): WidgetEntry[] => { + void graphNodes.value + const nodeDataByNode = new Map< + LGraphNode, + ReturnType + >() + + return appModeStore.selectedInputs.flatMap(([nodeId, widgetName]) => { + const [node, widget] = resolveNodeWidget(nodeId, widgetName) + if (!widget || !node || node.mode !== LGraphEventMode.ALWAYS) return [] + + if (!nodeDataByNode.has(node)) { + nodeDataByNode.set(node, nodeToNodeData(node)) + } + const fullNodeData = nodeDataByNode.get(node)! + + const matchingWidget = fullNodeData.widgets?.find((vueWidget) => { + if (vueWidget.slotMetadata?.linked) return false + + if (!node.isSubgraphNode()) return vueWidget.name === widget.name + + const storeNodeId = vueWidget.storeNodeId?.split(':')?.[1] ?? '' + return ( + isPromotedWidgetView(widget) && + widget.sourceNodeId == storeNodeId && + widget.sourceWidgetName === vueWidget.storeName + ) + }) + if (!matchingWidget) return [] + + matchingWidget.slotMetadata = undefined + matchingWidget.nodeId = String(node.id) + + return [ + { + key: `${nodeId}:${widgetName}`, + nodeData: { + ...fullNodeData, + widgets: [matchingWidget] + }, + action: { widget, node } + } + ] + }) +}) + +function getDropIndicator(node: LGraphNode) { + if (node.type !== 'LoadImage') return undefined + + const filename = node.widgets?.[0]?.value + const resultItem = { type: 'input', filename: `${filename}` } + + const buildImageUrl = () => { + if (!filename) return undefined + const params = new URLSearchParams(resultItem) + appendCloudResParam(params, resultItem.filename) + return api.apiURL(`/view?${params}${app.getPreviewFormatParam()}`) + } + + return { + iconClass: 'icon-[lucide--image]', + imageUrl: buildImageUrl(), + label: mobile ? undefined : t('linearMode.dragAndDropImage'), + onClick: () => node.widgets?.[1]?.callback?.(undefined) + } +} + +function nodeToNodeData(node: LGraphNode) { + const dropIndicator = getDropIndicator(node) + const nodeData = extractVueNodeData(node) + + return { + ...nodeData, + hasErrors: !!executionErrorStore.lastNodeErrors?.[node.id], + dropIndicator, + onDragDrop: node.onDragDrop, + onDragOver: node.onDragOver + } +} + + + + + + {{ action.widget.label || action.widget.name }} + + + {{ action.node.title }} + + + + + + + + + + + + + + + + + diff --git a/src/renderer/extensions/linearMode/LinearControls.vue b/src/renderer/extensions/linearMode/LinearControls.vue index d79d40cd91..6d8adf0da2 100644 --- a/src/renderer/extensions/linearMode/LinearControls.vue +++ b/src/renderer/extensions/linearMode/LinearControls.vue @@ -1,39 +1,26 @@
- ({{ t('linearMode.builder.unknownWidget') }}) -