diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 24b2b1a64f..815d987cd1 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -78,10 +78,64 @@ export interface GraphNodeManager { cleanup(): void } +export function safeWidgetMapper( + node: LGraphNode, + slotMetadata: Map +): (widget: IBaseWidget) => SafeWidgetData { + const nodeDefStore = useNodeDefStore() + return function (widget) { + try { + // TODO: Use widget.getReactiveData() once TypeScript types are updated + let value = widget.value + + // For combo widgets, if value is undefined, use the first option as default + if ( + value === undefined && + widget.type === 'combo' && + widget.options?.values && + Array.isArray(widget.options.values) && + widget.options.values.length > 0 + ) { + value = widget.options.values[0] + } + const spec = nodeDefStore.getInputSpecForWidget(node, widget.name) + const slotInfo = slotMetadata.get(widget.name) + + return { + name: widget.name, + type: widget.type, + value: value, + label: widget.label, + options: widget.options ? { ...widget.options } : undefined, + callback: widget.callback, + spec, + slotMetadata: slotInfo, + isDOMWidget: isDOMWidget(widget) + } + } catch (error) { + return { + name: widget.name || 'unknown', + type: widget.type || 'text', + value: undefined + } + } + } +} + +export function isValidWidgetValue(value: unknown): value is WidgetValue { + return ( + value === null || + value === undefined || + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'object' + ) +} + export function useGraphNodeManager(graph: LGraph): GraphNodeManager { // Get layout mutations composable const { createNode, deleteNode, setSource } = useLayoutMutations() - const nodeDefStore = useNodeDefStore() // Safe reactive data extracted from LiteGraph nodes const vueNodeData = reactive(new Map()) @@ -147,45 +201,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { linked: input.link != null }) }) - return ( - node.widgets?.map((widget) => { - try { - // TODO: Use widget.getReactiveData() once TypeScript types are updated - let value = widget.value - - // For combo widgets, if value is undefined, use the first option as default - if ( - value === undefined && - widget.type === 'combo' && - widget.options?.values && - Array.isArray(widget.options.values) && - widget.options.values.length > 0 - ) { - value = widget.options.values[0] - } - const spec = nodeDefStore.getInputSpecForWidget(node, widget.name) - const slotInfo = slotMetadata.get(widget.name) - - return { - name: widget.name, - type: widget.type, - value: value, - label: widget.label, - options: widget.options ? { ...widget.options } : undefined, - callback: widget.callback, - spec, - slotMetadata: slotInfo, - isDOMWidget: isDOMWidget(widget) - } - } catch (error) { - return { - name: widget.name || 'unknown', - type: widget.type || 'text', - value: undefined - } - } - }) ?? [] - ) + return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] }) const nodeType = diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 7662fd0a62..fa4761c462 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -1219,6 +1219,12 @@ export function useCoreCommands(): ComfyCommand[] { await settingStore.set('Comfy.Assets.UseAssetAPI', !current) await useWorkflowService().reloadCurrentWorkflow() // ensure changes take effect immediately } + }, + { + id: 'Comfy.ToggleLinear', + icon: 'pi pi-database', + label: 'toggle linear mode', + function: () => (canvasStore.linearMode = !canvasStore.linearMode) } ] diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index 9037bf5328..584f496753 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -13,6 +13,7 @@ import type { ComfyWorkflowJSON, NodeId } from '@/platform/workflow/validation/schemas/workflowSchema' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail' import { api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' @@ -329,6 +330,7 @@ export const useWorkflowStore = defineStore('workflow', () => { tabActivationHistory.value.shift() } + useCanvasStore().linearMode = !!loadedWorkflow.activeState.extra?.linearMode return loadedWorkflow } diff --git a/src/renderer/core/canvas/canvasStore.ts b/src/renderer/core/canvas/canvasStore.ts index 42740ec22c..bfc54c1db5 100644 --- a/src/renderer/core/canvas/canvasStore.ts +++ b/src/renderer/core/canvas/canvasStore.ts @@ -40,6 +40,8 @@ export const useCanvasStore = defineStore('canvas', () => { // Reactive scale percentage that syncs with app.canvas.ds.scale const appScalePercentage = ref(100) + const linearMode = ref(false) + // Set up scale synchronization when canvas is available let originalOnChanged: ((scale: number, offset: Point) => void) | undefined = undefined @@ -138,6 +140,7 @@ export const useCanvasStore = defineStore('canvas', () => { groupSelected, rerouteSelected, appScalePercentage, + linearMode, updateSelectedItems, getCanvas, setAppZoomFromPercentage, diff --git a/src/stores/imagePreviewStore.ts b/src/stores/imagePreviewStore.ts index 300c532c97..ee8ae759bd 100644 --- a/src/stores/imagePreviewStore.ts +++ b/src/stores/imagePreviewStore.ts @@ -40,6 +40,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { const { nodeIdToNodeLocatorId, nodeToNodeLocatorId } = useWorkflowStore() const { executionIdToNodeLocatorId } = useExecutionStore() const scheduledRevoke: Record void }> = {} + const latestOutput = ref([]) function scheduleRevoke(locator: NodeLocatorId, cb: () => void) { scheduledRevoke[locator]?.stop() @@ -146,6 +147,13 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { } } + //TODO:Preview params and deduplication + latestOutput.value = + (outputs as ExecutedWsMessage['output'])?.images?.map((image) => { + const imgUrlPart = new URLSearchParams(image) + const rand = app.getRandParam() + return api.apiURL(`/view?${imgUrlPart}${rand}`) + }) ?? [] app.nodeOutputs[nodeLocatorId] = outputs nodeOutputs.value[nodeLocatorId] = outputs } @@ -213,6 +221,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { scheduledRevoke[nodeLocatorId].stop() delete scheduledRevoke[nodeLocatorId] } + latestOutput.value = previewImages app.nodePreviewImages[nodeLocatorId] = previewImages nodePreviewImages.value[nodeLocatorId] = previewImages } @@ -381,6 +390,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { // State nodeOutputs, - nodePreviewImages + nodePreviewImages, + latestOutput } }) diff --git a/src/views/GraphView.vue b/src/views/GraphView.vue index 4af4710f82..5603984218 100644 --- a/src/views/GraphView.vue +++ b/src/views/GraphView.vue @@ -5,12 +5,14 @@
+
@@ -22,6 +24,7 @@ +