Show sampling previews on Vue nodes (#5579)

* refactor: simplify preview state provider

- Remove unnecessary event listeners and manual syncing
- Use computed() to directly reference app.nodePreviewImages
- Eliminate data duplication and any types
- Rely on Vue's reactivity for automatic updates
- Follow established patterns from execution state provider

* feat: optimize Vue node preview image display with reactive store

- Move preview display logic from inline ternaries to computed properties
- Add useNodePreviewState composable for preview state management
- Implement reactive store approach using Pinia storeToRefs
- Use VueUse useTimeoutFn for modern timeout management instead of window.setTimeout
- Add v-memo optimization for preview image template rendering
- Maintain proper sync between app.nodePreviewImages and reactive store state

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: update props usage for Vue 3.5 destructured props syntax

* [refactor] improve code style and architecture based on review feedback

- Replace inject pattern with direct store access in useNodePreviewState
- Use optional chaining for more concise conditional checks
- Use modern Array.at(-1) for accessing last element
- Remove provide/inject for nodePreviewImages in favor of direct store refs
- Update preview image styling: remove rounded borders, use flexible height
- Simplify scheduleRevoke function with optional chaining

Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>

* [cleanup] remove unused NodePreviewImagesKey injection key

Addresses knip unused export warning after switching from provide/inject
to direct store access pattern.

* [test] add mock for useNodePreviewState in LGraphNode test

Fixes test failure after adding preview functionality to LGraphNode component.

* [fix] update workflowStore import path after rebase

Updates import to new location: @/platform/workflow/management/stores/workflowStore

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
This commit is contained in:
Christian Byrne
2025-09-16 17:34:04 -07:00
committed by GitHub
parent 15cffe9d9e
commit 0483630f82
4 changed files with 117 additions and 4 deletions

View File

@@ -1,3 +1,4 @@
import { useTimeoutFn } from '@vueuse/core'
import { defineStore } from 'pinia'
import { ref } from 'vue'
@@ -19,6 +20,8 @@ import type { NodeLocatorId } from '@/types/nodeIdentification'
import { parseFilePath } from '@/utils/formatUtil'
import { isVideoNode } from '@/utils/litegraphUtil'
const PREVIEW_REVOKE_DELAY_MS = 400
const createOutputs = (
filenames: string[],
type: ResultItemType,
@@ -40,9 +43,26 @@ interface SetOutputOptions {
export const useNodeOutputStore = defineStore('nodeOutput', () => {
const { nodeIdToNodeLocatorId } = useWorkflowStore()
const { executionIdToNodeLocatorId } = useExecutionStore()
const scheduledRevoke: Record<NodeLocatorId, { stop: () => void }> = {}
function scheduleRevoke(locator: NodeLocatorId, cb: () => void) {
scheduledRevoke[locator]?.stop()
const { stop } = useTimeoutFn(() => {
delete scheduledRevoke[locator]
cb()
}, PREVIEW_REVOKE_DELAY_MS)
scheduledRevoke[locator] = { stop }
}
const nodeOutputs = ref<Record<string, ExecutedWsMessage['output']>>({})
// Reactive state for node preview images - mirrors app.nodePreviewImages
const nodePreviewImages = ref<Record<string, string[]>>(
app.nodePreviewImages || {}
)
function getNodeOutputs(
node: LGraphNode
): ExecutedWsMessage['output'] | undefined {
@@ -196,8 +216,12 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
) {
const nodeLocatorId = executionIdToNodeLocatorId(executionId)
if (!nodeLocatorId) return
if (scheduledRevoke[nodeLocatorId]) {
scheduledRevoke[nodeLocatorId].stop()
delete scheduledRevoke[nodeLocatorId]
}
app.nodePreviewImages[nodeLocatorId] = previewImages
nodePreviewImages.value[nodeLocatorId] = previewImages
}
/**
@@ -212,7 +236,12 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
previewImages: string[]
) {
const nodeLocatorId = nodeIdToNodeLocatorId(nodeId)
if (scheduledRevoke[nodeLocatorId]) {
scheduledRevoke[nodeLocatorId].stop()
delete scheduledRevoke[nodeLocatorId]
}
app.nodePreviewImages[nodeLocatorId] = previewImages
nodePreviewImages.value[nodeLocatorId] = previewImages
}
/**
@@ -224,8 +253,9 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
function revokePreviewsByExecutionId(executionId: string) {
const nodeLocatorId = executionIdToNodeLocatorId(executionId)
if (!nodeLocatorId) return
revokePreviewsByLocatorId(nodeLocatorId)
scheduleRevoke(nodeLocatorId, () =>
revokePreviewsByLocatorId(nodeLocatorId)
)
}
/**
@@ -243,6 +273,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
}
delete app.nodePreviewImages[nodeLocatorId]
delete nodePreviewImages.value[nodeLocatorId]
}
/**
@@ -259,6 +290,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
}
}
app.nodePreviewImages = {}
nodePreviewImages.value = {}
}
/**
@@ -293,6 +325,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
// Clear preview images
if (app.nodePreviewImages[nodeLocatorId]) {
delete app.nodePreviewImages[nodeLocatorId]
delete nodePreviewImages.value[nodeLocatorId]
}
return hadOutputs
@@ -318,6 +351,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
removeNodeOutputs,
// State
nodeOutputs
nodeOutputs,
nodePreviewImages
}
})