From 1234e1c56d66084866ce7257db5cea2d04137fee Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Thu, 16 Oct 2025 18:15:23 -0700 Subject: [PATCH] Support Right Click -> Save for animated webp (#6095) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow for Right Click -> Save Image to work for the "SaveAnimatedWEBP" node. Fixing this revealed other existing issues: - Attempting to resize the node causes runaway scaling - Right clicking on the image directly causes a browser context menu without a save option. Significant rewriting has been performed to resolve both of these issues. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6095-Support-Right-Click-Save-for-animated-webp-28e6d73d3650818e85a2ec58c38c2aae) by [Unito](https://www.unito.io) --- src/composables/node/useNodeAnimatedImage.ts | 55 ++++++++++---------- src/scripts/ui/imagePreview.ts | 10 ++-- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/composables/node/useNodeAnimatedImage.ts b/src/composables/node/useNodeAnimatedImage.ts index ac2e9b161..a785e5cbf 100644 --- a/src/composables/node/useNodeAnimatedImage.ts +++ b/src/composables/node/useNodeAnimatedImage.ts @@ -1,8 +1,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { IWidget } from '@/lib/litegraph/src/types/widgets' +import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' import { ANIM_PREVIEW_WIDGET } from '@/scripts/app' -import { createImageHost } from '@/scripts/ui/imagePreview' -import { fitDimensionsToNodeWidth } from '@/utils/imageUtil' +import { isDOMWidget } from '@/scripts/domWidget' /** * Composable for handling animated image previews in nodes @@ -20,38 +19,38 @@ export function useNodeAnimatedImage() { (w) => w.name === ANIM_PREVIEW_WIDGET ) + node.imgs[0].classList.value = 'block size-full object-contain' if (widgetIdx > -1) { // Replace content in existing widget - const widget = node.widgets[widgetIdx] as IWidget & { - options: { host: ReturnType } - } - widget.options.host.updateImages(node.imgs) + const widget = node.widgets[widgetIdx] + if (!isDOMWidget(widget)) return + widget.element.replaceChildren(node.imgs[0]) } else { // Create new widget - const host = createImageHost(node) - // @ts-expect-error host is not a standard DOM widget option. - const widget = node.addDOMWidget(ANIM_PREVIEW_WIDGET, 'img', host.el, { - host, - // @ts-expect-error `getHeight` of image host returns void instead of number. - getHeight: host.getHeight, - onDraw: host.onDraw, + const element = document.createElement('div') + element.appendChild(node.imgs[0]) + const widget = node.addDOMWidget(ANIM_PREVIEW_WIDGET, 'img', element, { hideOnZoom: false - }) as IWidget & { - options: { host: ReturnType } - } + }) + node.overIndex = 0 + + // Add event listeners for canvas interactions + const { handleWheel, handlePointer, forwardEventToCanvas } = + useCanvasInteractions() + node.imgs[0].style.pointerEvents = 'none' + element.addEventListener('wheel', handleWheel) + element.addEventListener('pointermove', handlePointer) + element.addEventListener('pointerup', handlePointer) + element.addEventListener( + 'pointerdown', + (e) => { + return e.button !== 2 ? handlePointer(e) : forwardEventToCanvas(e) + }, + true + ) + widget.serialize = false widget.serializeValue = () => undefined - widget.options.host.updateImages(node.imgs) - widget.computeLayoutSize = () => { - const img = widget.options.host.getCurrentImage() - if (!img) return { minHeight: 0, minWidth: 0 } - - return fitDimensionsToNodeWidth( - img.naturalWidth, - img.naturalHeight, - node.size?.[0] || 0 - ) - } } } diff --git a/src/scripts/ui/imagePreview.ts b/src/scripts/ui/imagePreview.ts index 0842d5cf8..da8516283 100644 --- a/src/scripts/ui/imagePreview.ts +++ b/src/scripts/ui/imagePreview.ts @@ -1,3 +1,5 @@ +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' + import { app } from '../app' import { $el } from '../ui' @@ -48,8 +50,8 @@ export function calculateImageGrid( return { cellWidth, cellHeight, cols, rows, shiftX } } -// @ts-expect-error fixme ts strict error -export function createImageHost(node) { +/** @knipIgnoreUnusedButUsedByCustomNodes */ +export function createImageHost(node: LGraphNode) { const el = $el('div.comfy-img-preview') // @ts-expect-error fixme ts strict error let currentImgs @@ -108,8 +110,8 @@ export function createImageHost(node) { } el.replaceChildren(...imgs) currentImgs = imgs - node.onResize(node.size) - node.graph.setDirtyCanvas(true, true) + node.onResize?.(node.size) + node.graph?.setDirtyCanvas(true, true) } }, getHeight() {