From 4f241c17ab79a727aa509babd0eb909ddae2fa5f Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 12 May 2023 15:52:10 -0500 Subject: [PATCH] Loads of styling --- package.json | 1 + pnpm-lock.yaml | 9 ++ src/lib/ImageViewer.ts | 127 +++++++-------- src/lib/api.ts | 4 +- src/lib/components/ComfyApp.ts | 3 +- src/lib/components/ComfyQueue.svelte | 212 ++++++++++++++++++++------ src/lib/components/ProgressBar.svelte | 2 +- src/lib/nodes/ComfyWidgetNodes.ts | 6 +- src/lib/stores/queueState.ts | 102 +++++++------ src/lib/utils.ts | 4 + src/lib/widgets/GalleryWidget.svelte | 4 +- src/scss/global.scss | 36 ++++- src/scss/gradio.scss | 12 ++ 13 files changed, 349 insertions(+), 173 deletions(-) diff --git a/package.json b/package.json index 8318efb..34d7b65 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "img-comparison-slider": "^8.0.0", "pollen-css": "^4.6.2", "radix-icons-svelte": "^1.2.1", + "svelte-feather-icons": "^4.0.0", "svelte-preprocess": "^5.0.3", "svelte-select": "^5.5.3", "svelte-splitpanes": "^0.7.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1b4366..42c00ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,9 @@ importers: radix-icons-svelte: specifier: ^1.2.1 version: 1.2.1 + svelte-feather-icons: + specifier: ^4.0.0 + version: 4.0.0 svelte-preprocess: specifier: ^5.0.3 version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3) @@ -6097,6 +6100,12 @@ packages: svelte: 3.58.0 dev: true + /svelte-feather-icons@4.0.0: + resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==} + dependencies: + svelte: 3.58.0 + dev: false + /svelte-floating-ui@1.2.8: resolution: {integrity: sha512-8Ifi5CD2Ui7FX7NjJRmutFtXjrB8T/FMNoS2H8P81t5LHK4I9G4NIs007rLWG/nRl7y+zJUXa3tWuTjYXw/O5A==} dependencies: diff --git a/src/lib/ImageViewer.ts b/src/lib/ImageViewer.ts index 66a7467..eeb68db 100644 --- a/src/lib/ImageViewer.ts +++ b/src/lib/ImageViewer.ts @@ -1,6 +1,10 @@ +import { clamp, negmod } from "./utils"; + export class ImageViewer { root: HTMLDivElement; lightboxModal: HTMLDivElement; + currentImages: string[] = [] + selectedIndex: number = -1; currentGallery: HTMLDivElement | null = null; private static _instance: ImageViewer; @@ -22,6 +26,8 @@ export class ImageViewer { // A full size 'lightbox' preview modal shown when left clicking on gallery previews closeModal() { this.lightboxModal.style.display = "none"; + this.currentImages = [] + this.selectedIndex = -1; this.currentGallery = null; } @@ -36,30 +42,24 @@ export class ImageViewer { return visibleGalleryButtons; } - static selected_gallery_button(gallery: HTMLDivElement): HTMLButtonElement | null { + static selected_gallery_button(gallery: HTMLDivElement): [HTMLButtonElement | null, number] { var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small.selected'); var visibleCurrentButton = null; - allCurrentButtons.forEach((elem) => { + let index = -1; + allCurrentButtons.forEach((elem, i) => { if (elem.parentElement.offsetParent) { visibleCurrentButton = elem; + index = i; } }) - return visibleCurrentButton; + return [visibleCurrentButton, index]; } - showModal(event: Event) { - const source = (event.target || event.srcElement) as HTMLImageElement; - const galleryElem = source.closest("div.block") - console.debug("[ImageViewer] showModal", event, source, galleryElem); - if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) { - console.error("No buttons found on gallery element!", galleryElem) - return; - } + showModal(imageUrls: string[], index: number, galleryElem?: HTMLDivElement) { + this.currentImages = imageUrls + this.selectedIndex = index this.currentGallery = galleryElem; - this.modalImage.src = source.src - if (this.modalImage.style.display === 'none') { - this.lightboxModal.style.setProperty('background-image', 'url(' + source.src + ')'); - } + this.setModalImageSrc(imageUrls[index]) this.lightboxModal.style.display = "flex"; setTimeout(() => { this.modalImage.focus() @@ -68,52 +68,52 @@ export class ImageViewer { event.stopPropagation() } - static negmod(n: number, m: number) { - return ((n % m) + m) % m; + static get_gallery_urls(galleryElem: HTMLDivElement): string[] { + return ImageViewer.all_gallery_buttons(galleryElem) + .map(b => (b.children[0] as HTMLImageElement).src) } - updateOnBackgroundChange() { - const modalImage = this.modalImage - if (modalImage && modalImage.offsetParent && this.currentGallery) { - let currentButton = ImageViewer.selected_gallery_button(this.currentGallery); + refreshImages() { + if (this.currentGallery) { + this.currentImages = ImageViewer.get_gallery_urls(this.currentGallery) + let [_currentButton, index] = ImageViewer.selected_gallery_button(this.currentGallery); + this.selectedIndex = index; + } - 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})`) - } - } + const selectedImageUrl = this.currentImages[this.selectedIndex]; + this.setModalImageSrc(selectedImageUrl) + } + + private setModalImageSrc(src: string, isTiling: boolean = false) { + const modalImage = this.modalImage + const modal = this.lightboxModal + modalImage.src = src; + if (isTiling) { + modalImage.style.display = 'none'; + modal.style.setProperty('background-image', `url(${modalImage.src})`) + } else { + modalImage.style.display = 'block'; + modal.style.setProperty('background-image', 'none') } } modalImageSwitch(offset: number) { - if (!this.currentGallery) - return + this.selectedIndex = negmod(this.selectedIndex + offset, this.currentImages.length - 1); + const selectedImageUrl = this.currentImages[this.selectedIndex]; - var galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); + this.setModalImageSrc(selectedImageUrl) - if (galleryButtons.length > 1) { - var currentButton = ImageViewer.selected_gallery_button(this.currentGallery); + if (this.currentGallery) { + var galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); + var [_currentButton, index] = 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)] + if (index != -1) { + const nextButton = galleryButtons[negmod((index + 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) } } + + setTimeout(() => { this.lightboxModal.focus() }, 10) } modalNextImage(event) { @@ -140,7 +140,7 @@ export class ImageViewer { } } - setupImageForLightbox(e: HTMLImageElement) { + setupGalleryImageForLightbox(e: HTMLImageElement) { if (e.dataset.modded === "true") return; @@ -161,7 +161,21 @@ export class ImageViewer { const initiallyZoomed = true this.modalZoomSet(this.modalImage, initiallyZoomed) evt.preventDefault() - this.showModal(evt) + + const source = evt.target as HTMLImageElement; + + const galleryElem = source.closest("div.block") + console.debug("[ImageViewer] showModal", event, source, galleryElem); + if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) { + console.error("No buttons found on gallery element!", galleryElem) + return; + } + + let urls = ImageViewer.get_gallery_urls(galleryElem) + const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem) + + this.showModal(urls, index, galleryElem) + evt.stopPropagation(); }, true); } @@ -181,17 +195,8 @@ export class ImageViewer { } 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})`) - } - + const isTiling = this.modalImage.style.display === 'none'; + this.setModalImageSrc(this.modalImage.src, isTiling) event.stopPropagation() } } diff --git a/src/lib/api.ts b/src/lib/api.ts index 78bd989..0780443 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -298,7 +298,7 @@ export default class ComfyAPI { return Promise.reject(error) } - return fetch("/" + type, { + return fetch(this.getBackendUrl() + "/" + type, { method: "POST", headers: { "Content-Type": "application/json", @@ -328,6 +328,6 @@ export default class ComfyAPI { * Interrupts the execution of the running prompt */ async interrupt(): Promise { - return fetch("/interrupt", { method: "POST" }); + return fetch(this.getBackendUrl() + "/interrupt", { method: "POST" }); } } diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 80e2991..600726b 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -452,8 +452,6 @@ export default class ComfyApp { this.lGraph.start(); this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true)) - - uiState.update(s => { s.uiUnlocked = this.lGraph._nodes.length === 0; return s; }) } async initDefaultGraph() { @@ -468,6 +466,7 @@ export default class ComfyApp { state = structuredClone(blankGraph) } await this.deserialize(state) + uiState.update(s => { s.uiUnlocked = true; return s; }) } /** diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index 5911fbe..4540ef8 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -2,13 +2,19 @@ import queueState, { type CompletedQueueEntry, type QueueEntry, type QueueEntryStatus } from "$lib/stores/queueState"; import ProgressBar from "./ProgressBar.svelte"; import Spinner from "./Spinner.svelte"; + import { ListIcon as List } from "svelte-feather-icons"; import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo } from "$lib/utils" import type { Writable } from "svelte/store"; import type { QueueItemType } from "$lib/api"; + import { ImageViewer } from "$lib/ImageViewer"; + import { Button } from "@gradio/button"; + import type ComfyApp from "./ComfyApp"; + import { tick } from "svelte"; let queuePending: Writable | null = null; let queueRunning: Writable | null = null; let queueCompleted: Writable | null = null; + let queueList: HTMLDivElement | null = null; type QueueUIEntry = { message: string, @@ -28,7 +34,6 @@ let mode: QueueItemType = "queue"; function switchMode(newMode: QueueItemType) { - console.warn("SwitchMode", newMode) const changed = mode !== newMode mode = newMode if (changed) @@ -66,6 +71,9 @@ message = `Prompt: ${entry.extraData.subgraphs.join(', ')}` let submessage = `Nodes: ${Object.keys(entry.prompt).length}` + if (Object.keys(entry.outputs).length > 0) { + submessage = `Images: ${Object.keys(entry.outputs).length}` + } return { message, @@ -80,19 +88,47 @@ const result = convertEntry(entry.entry); result.status = entry.status; result.error = entry.error; + + if (result.status === "all_cached") + result.submessage = "(Execution was cached)" + return result; } - function updateFromQueue() { - _entries = $queuePending.map(convertEntry); + async function updateFromQueue() { + _entries = $queuePending.map(convertEntry).reverse(); // newest entries appear at the top + if (queueList) { + await tick(); // Wait for list size to be recalculated + queueList.scroll({ top: queueList.scrollHeight }) + } console.warn("[ComfyQueue] BUILDQUEUE", _entries, $queuePending) } - function updateFromHistory() { + async function updateFromHistory() { _entries = $queueCompleted.map(convertCompletedEntry); + if (queueList) { + await tick(); // Wait for list size to be recalculated + queueList.scroll({ top: queueList.scrollHeight }) + } console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted) } + function showLightbox(entry: QueueUIEntry, e: Event) { + e.preventDefault() + if (!entry.images) + return + + ImageViewer.instance.showModal(entry.images, 0) + } + + async function interrupt() { + const app = (window as any).app as ComfyApp; + if (!app || !app.api) + return; + + await app.api.interrupt(); + } + let queued = false $: queued = Boolean($queueState.runningNodeID || $queueState.progress); @@ -101,39 +137,54 @@
-
- {#each _entries as entry} -
- {#if entry.images.length > 0} - thumbnail - {:else} - - {/if} -
-
- {entry.message} +
+ {#if _entries.length > 0} + {#each _entries as entry} +
+ {#if entry.images.length > 0} +
showLightbox(entry, e)}> + thumbnail +
+ {:else} + + {/if} +
+
+ {entry.message} +
+
+ {entry.submessage} +
-
- {entry.submessage} +
+ {#if entry.date != null} + + {entry.date} + + {/if}
-
- {#if entry.date != null} - - {entry.date} - - {/if} + {/each} + {:else} +
+
+
+ +
+
+ (No entries) +
- {/each} + {/if}
-
switchMode("queue")} class:mode-selected={mode === "queue"}> Queue
-
switchMode("history")} class:mode-selected={mode === "history"}> History @@ -159,6 +210,11 @@
+
+ +
{/if}
@@ -177,13 +233,37 @@ overflow-y: scroll; height: $queue-height; max-height: $queue-height; + + > .queue-empty { + display: flex; + color: var(--comfy-accent-soft); + flex-direction: row; + margin: auto; + height: 100%; + + > .queue-empty-container { + margin: auto; + display: flex; + flex-direction: column; + + > .queue-empty-icon { + margin: auto; + } + > .queue-empty-message { + margin: auto; + font-size: 32px; + font-weight: bolder; + } + } + } } .queue-entry { padding: 1.0rem; display: flex; flex-direction: row; - border-bottom: 1px solid var(--panel-border-color); + border-bottom: 1px solid var(--block-border-color); + border-top: 1px solid var(--table-border-color); background: var(--panel-background-fill); &.success { @@ -193,7 +273,9 @@ background: red; } &.all_cached { - background: grey; + filter: brightness(80%); + background: var(--neutral-600); + color: var(--neutral-300); } &.running { /* background: lightblue; */ @@ -203,8 +285,17 @@ } } - .queue-entry-image { - width: var(--size-20); + .queue-entry-images { + height: 100%; + aspect-ratio: 1/1; + margin: auto; + + > .queue-entry-image { + filter: none; + &:hover { + filter: brightness(120%) contrast(120%); + } + } } .queue-entry-image-placeholder { @@ -240,49 +331,65 @@ font-size: 10px; position:absolute; right: 0px; - bottom:0px; + bottom: 0px; padding: 0.0rem 0.4rem; color: var(--body-text-color); } .mode-buttons { - height: calc($mode-buttons-height); display: flex; flex-direction: row; - text-align: center; justify-content: center; + height: 100%; - .mode-button { - padding: 0.2rem; + > .mode-button { width: 100%; - height: 100%; - border: 1px solid var(--panel-border-color); - font-weight: bold; - background: var(--button-secondary-background-fill); + } + } + .mode-button { + height: calc($mode-buttons-height); + padding: 0.2rem; + border: 1px solid var(--panel-border-color); + font-weight: bold; + text-align: center; + margin: auto; + + &.primary { + background: var(--button-primary-background-fill); + &:hover { + background: var(--button-primary-background-fill-hover); + } + } + + &.secondary { + background: var(--button-secondary-background-fill); &:hover { background: var(--button-secondary-background-fill-hover); - filter: brightness(120%); - } - &:active { - filter: brightness(50%) - } - &.mode-selected { - filter: brightness(150%) } } + + &:hover { + filter: brightness(120%); + } + &:active { + filter: brightness(50%) + } + &.mode-selected { + filter: brightness(150%) + } } .bottom { width: 100%; height: calc($pending-height); position: absolute; - border: 2px solid var(--panel-border-color); .node-name { background-color: var(--comfy-node-name-background); color: var(--comfy-node-name-foreground); padding: 0.2em; + margin: 5px; display: flex; justify-content: center; align-items: center; @@ -302,7 +409,16 @@ } &.queued { - height: calc($pending-height - $bottom-bar-height); + height: calc($pending-height - $mode-buttons-height - $bottom-bar-height - 16px); + } + } + + .queue-action-buttons { + margin: 5px; + height: 20px; + + :global(button) { + border-radius: 0px !important; } } } diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte index 8d56126..7b5682f 100644 --- a/src/lib/components/ProgressBar.svelte +++ b/src/lib/components/ProgressBar.svelte @@ -23,8 +23,8 @@