diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index d19e6a4b8..8b65e56c3 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index f4624a45b..cc881f9ba 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d5bb80f89..95a5a7e4d 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -7,14 +7,20 @@ "comingSoon": "Coming Soon", "download": "Download", "downloadImage": "Download image", + "downloadVideo": "Download video", "editOrMaskImage": "Edit or mask image", "removeImage": "Remove image", + "removeVideo": "Remove video", "viewImageOfTotal": "View image {index} of {total}", + "viewVideoOfTotal": "View video {index} of {total}", "imagePreview": "Image preview - Use arrow keys to navigate between images", + "videoPreview": "Video preview - Use arrow keys to navigate between videos", "galleryImage": "Gallery image", "galleryThumbnail": "Gallery thumbnail", "errorLoadingImage": "Error loading image", + "errorLoadingVideo": "Error loading video", "failedToDownloadImage": "Failed to download image", + "failedToDownloadVideo": "Failed to download video", "calculatingDimensions": "Calculating dimensions", "import": "Import", "loadAllFolders": "Load All Folders", diff --git a/src/renderer/extensions/vueNodes/VideoPreview.vue b/src/renderer/extensions/vueNodes/VideoPreview.vue new file mode 100644 index 000000000..901876b59 --- /dev/null +++ b/src/renderer/extensions/vueNodes/VideoPreview.vue @@ -0,0 +1,257 @@ + + + diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 2911abbc0..b1a7f9acf 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -101,7 +101,7 @@
@@ -267,10 +267,10 @@ onMounted(() => { // Track collapsed state const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false) -// Check if node has custom content (like image outputs) +// Check if node has custom content (like image/video outputs) const hasCustomContent = computed(() => { - // Show custom content if node has image outputs - return nodeImageUrls.value.length > 0 + // Show custom content if node has media outputs + return !!nodeMedia.value && nodeMedia.value.urls.length > 0 }) // Computed classes and conditions for better reusability @@ -340,26 +340,29 @@ const nodeOutputs = useNodeOutputStore() const nodeOutputLocatorId = computed(() => nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id ) -const nodeImageUrls = computed(() => { - const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value] + +const lgraphNode = computed(() => { const locatorId = getLocatorIdFromNodeData(nodeData) - - // Use root graph for getNodeByLocatorId since it needs to traverse from root const rootGraph = app.graph?.rootGraph || app.graph - if (!rootGraph) { - return [] - } + if (!rootGraph) return null + return getNodeByLocatorId(rootGraph, locatorId) +}) - const node = getNodeByLocatorId(rootGraph, locatorId) +const nodeMedia = computed(() => { + const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value] + const node = lgraphNode.value + // Note: Despite the field name "images", videos are also included. + // The actual media type is determined by node.previewMediaType + // TODO: fix the backend to return videos using the vidoes key instead of the images key if (node && newOutputs?.images?.length) { const urls = nodeOutputs.getNodeImageUrls(node) - if (urls) { - return urls + if (urls && urls.length > 0) { + const type = node.previewMediaType === 'video' ? 'video' : 'image' + return { type, urls } as const } } - // Clear URLs if no outputs or no images - return [] + return undefined }) const nodeContainerRef = ref() diff --git a/src/renderer/extensions/vueNodes/components/NodeContent.vue b/src/renderer/extensions/vueNodes/components/NodeContent.vue index 0874c4013..0b5b00a47 100644 --- a/src/renderer/extensions/vueNodes/components/NodeContent.vue +++ b/src/renderer/extensions/vueNodes/components/NodeContent.vue @@ -5,9 +5,15 @@
+ @@ -20,24 +26,24 @@ import { computed, onErrorCaptured, ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import { useErrorHandling } from '@/composables/useErrorHandling' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import VideoPreview from '../VideoPreview.vue' import ImagePreview from './ImagePreview.vue' interface NodeContentProps { - node?: LGraphNode // For backwards compatibility - nodeData?: VueNodeData // New clean data structure - imageUrls?: string[] + nodeData?: VueNodeData + media?: { + type: 'image' | 'video' + urls: string[] + } } const props = defineProps() -const hasImages = computed(() => props.imageUrls && props.imageUrls.length > 0) +const hasMedia = computed(() => props.media && props.media.urls.length > 0) -// Get node ID from nodeData or node prop -const nodeId = computed(() => { - return props.nodeData?.id?.toString() || props.node?.id?.toString() -}) +// Get node ID from nodeData +const nodeId = computed(() => props.nodeData?.id?.toString()) // Error boundary implementation const renderError = ref(null) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue index c1c313890..dcdc4c9ce 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue @@ -83,6 +83,7 @@ const specDescriptor = computed<{ const allowUpload = image_upload === true || animated_image_upload === true || + video_upload === true || audio_upload === true return { kind, diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue index 83d6854eb..9c16f8373 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue @@ -1,5 +1,5 @@