diff --git a/src/lib/ImageViewer.ts b/src/lib/ImageViewer.ts new file mode 100644 index 0000000..0965f7e --- /dev/null +++ b/src/lib/ImageViewer.ts @@ -0,0 +1,196 @@ +export class ImageViewer { + root: HTMLDivElement; + lightboxModal: HTMLDivElement; + currentGallery: HTMLDivElement | null = null; + private static _instance: ImageViewer; + + static get instance(): ImageViewer { + if (!ImageViewer._instance) + ImageViewer._instance = new ImageViewer((window as any).app.rootEl) + return ImageViewer._instance + } + + get modalImage(): HTMLImageElement | null { + return this.root.querySelector("#modalImage"); + } + + constructor(root: HTMLDivElement) { + this.root = root; + this.lightboxModal = this.root.querySelector("#lightboxModal"); + } + + // A full size 'lightbox' preview modal shown when left clicking on gallery previews + closeModal() { + this.lightboxModal.style.display = "none"; + this.currentGallery = null; + } + + static all_gallery_buttons(gallery: HTMLDivElement): HTMLButtonElement[] { + var allGalleryButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small'); + var visibleGalleryButtons = []; + allGalleryButtons.forEach((elem) => { + if (elem.parentElement.offsetParent) { + visibleGalleryButtons.push(elem); + } + }) + return visibleGalleryButtons; + } + + static selected_gallery_button(gallery: HTMLDivElement): HTMLButtonElement | null { + var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small.selected'); + var visibleCurrentButton = null; + allCurrentButtons.forEach((elem) => { + if (elem.parentElement.offsetParent) { + visibleCurrentButton = elem; + } + }) + return visibleCurrentButton; + } + + showModal(event: Event) { + const source = (event.target || event.srcElement) as HTMLImageElement; + const galleryElem = source.closest("div.block") + if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) { + console.error("No buttons found on gallery element!", galleryElem) + return; + } + this.currentGallery = galleryElem; + this.modalImage.src = source.src + if (this.modalImage.style.display === 'none') { + this.lightboxModal.style.setProperty('background-image', 'url(' + source.src + ')'); + } + this.lightboxModal.style.display = "flex"; + setTimeout(() => { + this.modalImage.focus() + }, 200) + + event.stopPropagation() + } + + static negmod(n: number, m: number) { + return ((n % m) + m) % m; + } + + updateOnBackgroundChange() { + const modalImage = this.modalImage + if (modalImage && modalImage.offsetParent && this.currentGallery) { + let currentButton = ImageViewer.selected_gallery_button(this.currentGallery); + + if (currentButton?.children?.length > 0 && modalImage.src != currentButton.children[0].src) { + modalImage.src = currentButton.children[0].src; + if (modalImage.style.display === 'none') { + this.lightboxModal.style.setProperty('background-image', `url(${modalImage.src})`) + } + } + } + } + + modalImageSwitch(offset: number) { + if (!this.currentGallery) + return + + var galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); + + if (galleryButtons.length > 1) { + var currentButton = ImageViewer.selected_gallery_button(this.currentGallery); + + var result = -1 + galleryButtons.forEach((v, i) => { + if (v == currentButton) { + result = i + } + }) + + if (result != -1) { + const nextButton = galleryButtons[ImageViewer.negmod((result + offset), galleryButtons.length)] + nextButton.click() + const modalImage = this.modalImage; + const modal = this.lightboxModal + modalImage.src = nextButton.children[0].src; + if (modalImage.style.display === 'none') { + modal.style.setProperty('background-image', `url(${modalImage.src})`) + } + setTimeout(() => { modal.focus() }, 10) + } + } + } + + modalNextImage(event) { + this.modalImageSwitch(1) + event.stopPropagation() + } + + modalPrevImage(event) { + this.modalImageSwitch(-1) + event.stopPropagation() + } + + modalKeyHandler(event) { + switch (event.key) { + case "ArrowLeft": + this.modalPrevImage(event) + break; + case "ArrowRight": + this.modalNextImage(event) + break; + case "Escape": + this.closeModal(); + break; + } + } + + setupImageForLightbox(e: HTMLImageElement) { + if (e.dataset.modded === "true") + return; + + e.dataset.modded = "true"; + e.style.cursor = 'pointer' + e.style.userSelect = 'none' + + var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 + + // For Firefox, listening on click first switched to next image then shows the lightbox. + // If you know how to fix this without switching to mousedown event, please. + // For other browsers the event is click to make it possiblr to drag picture. + var event = isFirefox ? 'mousedown' : 'click' + + e.addEventListener(event, (evt) => { + // if (!opts.js_modal_lightbox || evt.button != 0) return; + + const initiallyZoomed = true + this.modalZoomSet(this.modalImage, initiallyZoomed) + evt.preventDefault() + this.showModal(evt) + }, true); + + } + + modalZoomSet(modalImage: HTMLImageElement, enable: boolean) { + if (enable) { + modalImage.classList.add('modalImageFullscreen'); + } else { + modalImage.classList.remove('modalImageFullscreen'); + } + } + + modalZoomToggle(event: Event) { + const modalImage = this.modalImage; + this.modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen')) + event.stopPropagation() + } + + modalTileImageToggle(event: Event) { + const modalImage = this.modalImage + const modal = this.lightboxModal + const isTiling = modalImage.style.display === 'none'; + if (isTiling) { + modalImage.style.display = 'block'; + modal.style.setProperty('background-image', 'none') + } else { + modalImage.style.display = 'none'; + modal.style.setProperty('background-image', `url(${modalImage.src})`) + } + + event.stopPropagation() + } +} diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index 48e3e86..777030f 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -6,15 +6,19 @@ import ComfyUIPane from "./ComfyUIPane.svelte"; import ComfyApp, { type SerializedAppState } from "./ComfyApp"; import widgetState from "$lib/stores/widgetState"; + import { ImageViewer } from "$lib/ImageViewer"; import { LGraph, LGraphNode } from "@litegraph-ts/core"; + import LightboxModal from "./LightboxModal.svelte"; let app: ComfyApp = undefined; + let imageViewer: ImageViewer; let uiPane: ComfyUIPane = undefined; + let mainElem: HTMLDivElement; let containerElem: HTMLDivElement; let resizeTimeout: typeof Timer = -1; - function refreshView(event) { + function refreshView(event?: Event) { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250); } @@ -87,9 +91,11 @@ (window as any).app = app; (window as any).appPane = uiPane; - let wrappers = containerElem.querySelectorAll(".pane-wrapper") + imageViewer = new ImageViewer(app.rootEl); + + let wrappers = containerElem.querySelectorAll(".pane-wrapper") for (const wrapper of wrappers) { - const paneNode = wrapper.parentNode; // get the node inside the + const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the paneNode.ontransitionend = () => { app.resizeCanvas() } @@ -97,38 +103,41 @@ }) -
-
- - - - - - - -
- -
-
-
-
- - - -
-
-
- - - +
+
+
+ + + + + + + +
+ +
+
+
+
+ + + +
+
+
+ + + +
+
diff --git a/src/lib/nodes/ComfySaveImageNode.ts b/src/lib/nodes/ComfySaveImageNode.ts index 3ba41c4..0beeb97 100644 --- a/src/lib/nodes/ComfySaveImageNode.ts +++ b/src/lib/nodes/ComfySaveImageNode.ts @@ -24,7 +24,9 @@ export default class ComfySaveImageNode extends ComfyGraphNode { this._imageResults = Array.from(output.images); // TODO append? const galleryItems = this._imageResults.map(r => { // TODO - let entry: ComfyGalleryEntry = ["http://localhost:8188/" + r.type + "/" + r.filename, null] + const url = "http://localhost:8188/view?" + const params = new URLSearchParams(r) + let entry: ComfyGalleryEntry = [url + params, null] return entry }); this._galleryWidget.setValue(galleryItems) diff --git a/src/lib/widgets.ts b/src/lib/widgets.ts index 8596142..991e355 100644 --- a/src/lib/widgets.ts +++ b/src/lib/widgets.ts @@ -22,7 +22,7 @@ function getNumberDefaults(inputData: any, defaultStep: number): NumberDefaults if (max == undefined) max = 2048; if (step == undefined) step = defaultStep; - return { val: defaultVal, config: { min, max, step: 10.0 * step, precision: 0 } }; + return { val: defaultVal, config: { min, max, step: step, precision: 0 } }; } @@ -40,7 +40,7 @@ const INT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any) inputName, val, function(v) { - const s = this.options.step / 10; + const s = this.options.step; this.value = Math.round(v / s) * s; }, config diff --git a/src/lib/widgets/ComfyGalleryWidget.svelte b/src/lib/widgets/ComfyGalleryWidget.svelte index f36fdb5..36432ae 100644 --- a/src/lib/widgets/ComfyGalleryWidget.svelte +++ b/src/lib/widgets/ComfyGalleryWidget.svelte @@ -1,20 +1,43 @@ -