Loads of styling
This commit is contained in:
@@ -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",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -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<HTMLDivElement>("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<HTMLDivElement>("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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Response> {
|
||||
return fetch("/interrupt", { method: "POST" });
|
||||
return fetch(this.getBackendUrl() + "/interrupt", { method: "POST" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<QueueEntry[]> | null = null;
|
||||
let queueRunning: Writable<QueueEntry[]> | null = null;
|
||||
let queueCompleted: Writable<CompletedQueueEntry[]> | 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 @@
|
||||
</script>
|
||||
|
||||
<div class="queue">
|
||||
<div class="queue-entries">
|
||||
{#each _entries as entry}
|
||||
<div class="queue-entry {entry.status}">
|
||||
{#if entry.images.length > 0}
|
||||
<img class="queue-entry-image" src={entry.images[0]} alt="thumbnail" />
|
||||
{:else}
|
||||
<!-- <div class="queue-entry-image-placeholder" /> -->
|
||||
{/if}
|
||||
<div class="queue-entry-details">
|
||||
<div class="queue-entry-message">
|
||||
{entry.message}
|
||||
<div class="queue-entries" bind:this={queueList}>
|
||||
{#if _entries.length > 0}
|
||||
{#each _entries as entry}
|
||||
<div class="queue-entry {entry.status}">
|
||||
{#if entry.images.length > 0}
|
||||
<div class="queue-entry-images" on:click={(e) => showLightbox(entry, e)}>
|
||||
<img class="queue-entry-image" src={entry.images[0]} alt="thumbnail" />
|
||||
</div>
|
||||
{:else}
|
||||
<!-- <div class="queue-entry-image-placeholder" /> -->
|
||||
{/if}
|
||||
<div class="queue-entry-details">
|
||||
<div class="queue-entry-message">
|
||||
{entry.message}
|
||||
</div>
|
||||
<div class="queue-entry-submessage">
|
||||
{entry.submessage}
|
||||
</div>
|
||||
</div>
|
||||
<div class="queue-entry-submessage">
|
||||
{entry.submessage}
|
||||
<div class="queue-entry-rest">
|
||||
{#if entry.date != null}
|
||||
<span class="queue-entry-queued-at">
|
||||
{entry.date}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="queue-entry-rest">
|
||||
{#if entry.date != null}
|
||||
<span class="queue-entry-queued-at">
|
||||
{entry.date}
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="queue-empty">
|
||||
<div class="queue-empty-container">
|
||||
<div class="queue-empty-icon">
|
||||
<List size="120rem" />
|
||||
</div>
|
||||
<div class="queue-empty-message">
|
||||
(No entries)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mode-buttons">
|
||||
<div class="mode-button"
|
||||
<div class="mode-button secondary"
|
||||
on:click={() => switchMode("queue")}
|
||||
class:mode-selected={mode === "queue"}>
|
||||
Queue
|
||||
</div>
|
||||
<div class="mode-button"
|
||||
<div class="mode-button secondary"
|
||||
on:click={() => switchMode("history")}
|
||||
class:mode-selected={mode === "history"}>
|
||||
History
|
||||
@@ -159,6 +210,11 @@
|
||||
<div>
|
||||
<ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} />
|
||||
</div>
|
||||
<div class="queue-action-buttons">
|
||||
<Button variant="secondary" on:click={interrupt} style={{ full_width: true }}>
|
||||
Interrupt
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
<style>
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
background: var(--comfy-progress-bar-background);
|
||||
padding: 0px;
|
||||
|
||||
@@ -161,7 +161,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
}
|
||||
|
||||
private triggerChangeEvent(value: any) {
|
||||
console.debug("[Widget] trigger changed", this, value)
|
||||
// console.debug("[Widget] trigger changed", this, value)
|
||||
const changedOutput = this.outputs[this.changedIndex]
|
||||
if (changedOutput.type === BuiltInSlotType.EVENT)
|
||||
this.triggerSlot(this.changedIndex, value)
|
||||
@@ -271,7 +271,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
console.debug("Property copy", input, this.properties)
|
||||
// console.debug("Property copy", input, this.properties)
|
||||
|
||||
this.setValue(get(this.value))
|
||||
|
||||
@@ -288,7 +288,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
if (layoutEntry && layoutEntry.parent) {
|
||||
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
||||
}
|
||||
console.debug("propsChanged", this)
|
||||
// console.debug("propsChanged", this)
|
||||
this.propsChanged.set(get(this.propsChanged) + 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -122,6 +122,34 @@ function statusUpdated(status: ComfyAPIStatusResponse | null) {
|
||||
})
|
||||
}
|
||||
|
||||
function findEntryInPending(promptID: PromptID): [number, QueueEntry, Writable<QueueEntry[]>] | null {
|
||||
const state = get(store);
|
||||
let index = get(state.queuePending).findIndex(e => e.promptID === promptID)
|
||||
if (index)
|
||||
return [index, get(state.queuePending)[index], state.queuePending]
|
||||
|
||||
index = get(state.queueRunning).findIndex(e => e.promptID === promptID)
|
||||
if (index)
|
||||
return [index, get(state.queueRunning)[index], state.queueRunning]
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: QueueEntryStatus, error?: string) {
|
||||
const state = get(store)
|
||||
|
||||
const entry = get(queue)[index];
|
||||
entry.finishedAt = new Date() // Now
|
||||
queue.update(qp => { qp.splice(index, 1); return qp });
|
||||
state.queueCompleted.update(qc => {
|
||||
const completed: CompletedQueueEntry = { entry, status, error }
|
||||
qc.push(completed)
|
||||
return qc
|
||||
})
|
||||
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | null) {
|
||||
console.debug("[queueState] executingUpdated", promptID, runningNodeID)
|
||||
store.update((s) => {
|
||||
@@ -131,20 +159,12 @@ function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | nul
|
||||
}
|
||||
else if (promptID != null) {
|
||||
// Prompt finished executing.
|
||||
const queuePending = get(s.queuePending)
|
||||
const index = queuePending.findIndex(e => e.promptID === promptID)
|
||||
if (index) {
|
||||
const entry = queuePending[index]
|
||||
entry.finishedAt = new Date() // Now
|
||||
s.queuePending.update(qp => { qp.splice(index, 1); return qp });
|
||||
s.queueCompleted.update(qc => {
|
||||
const completed: CompletedQueueEntry = {
|
||||
entry,
|
||||
status: "success",
|
||||
}
|
||||
qc.push(completed)
|
||||
return qc
|
||||
})
|
||||
const [index, entry, queue] = findEntryInPending(promptID);
|
||||
if (entry) {
|
||||
moveToCompleted(index, queue, "success")
|
||||
}
|
||||
else {
|
||||
console.error("[queueState] Could not find in pending! (executingUpdated)", promptID)
|
||||
}
|
||||
s.progress = null;
|
||||
s.runningNodeID = null;
|
||||
@@ -156,24 +176,15 @@ function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | nul
|
||||
function executionCached(promptID: PromptID, nodes: NodeID[]) {
|
||||
console.debug("[queueState] executionCached", promptID, nodes)
|
||||
store.update(s => {
|
||||
const queuePending = get(s.queuePending)
|
||||
const index = queuePending.findIndex(e => e.promptID === promptID)
|
||||
if (index) {
|
||||
const entry = queuePending[index]
|
||||
|
||||
if (nodes.length >= Object.keys(entry.prompt.output).length) {
|
||||
entry.finishedAt = new Date() // Now
|
||||
s.queuePending.update(qp => { qp.splice(index, 1); return qp });
|
||||
s.queueCompleted.update(qc => {
|
||||
const completed: CompletedQueueEntry = {
|
||||
entry,
|
||||
status: "all_cached",
|
||||
}
|
||||
qc.push(completed)
|
||||
return qc
|
||||
})
|
||||
const [index, entry, queue] = findEntryInPending(promptID);
|
||||
if (entry) {
|
||||
if (nodes.length >= Object.keys(entry.prompt).length) {
|
||||
moveToCompleted(index, queue, "all_cached");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("[queueState] Could not find in pending! (executionCached)", promptID)
|
||||
}
|
||||
s.progress = null;
|
||||
s.runningNodeID = null;
|
||||
return s
|
||||
@@ -183,21 +194,12 @@ function executionCached(promptID: PromptID, nodes: NodeID[]) {
|
||||
function executionError(promptID: PromptID, message: string) {
|
||||
console.debug("[queueState] executionError", promptID, message)
|
||||
store.update(s => {
|
||||
const queuePending = get(s.queuePending)
|
||||
const index = queuePending.findIndex(e => e.promptID === promptID)
|
||||
if (index) {
|
||||
const entry = s.queuePending[index]
|
||||
entry.finishedAt = new Date() // Now
|
||||
s.queuePending.update(qp => { qp.splice(index, 1); return qp });
|
||||
s.queueCompleted.update(qc => {
|
||||
const completed: CompletedQueueEntry = {
|
||||
entry,
|
||||
status: "error",
|
||||
error: message
|
||||
}
|
||||
qc.push(completed)
|
||||
return qc
|
||||
})
|
||||
const [index, entry, queue] = findEntryInPending(promptID);
|
||||
if (entry) {
|
||||
moveToCompleted(index, queue, "error", message)
|
||||
}
|
||||
else {
|
||||
console.error("[queueState] Could not find in pending! (executionError)", promptID)
|
||||
}
|
||||
s.progress = null;
|
||||
s.runningNodeID = null;
|
||||
@@ -206,7 +208,7 @@ function executionError(promptID: PromptID, message: string) {
|
||||
}
|
||||
|
||||
function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) {
|
||||
console.debug("[queueState] afterQueued", promptID, Object.keys(prompt.nodes))
|
||||
console.debug("[queueState] afterQueued", promptID, Object.keys(prompt))
|
||||
store.update(s => {
|
||||
const entry: QueueEntry = {
|
||||
number,
|
||||
@@ -226,11 +228,13 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp
|
||||
function onExecuted(promptID: PromptID, nodeID: NodeID, output: GalleryOutput) {
|
||||
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
||||
store.update(s => {
|
||||
const queuePending = get(s.queuePending)
|
||||
const entry = queuePending.find(e => e.promptID === promptID)
|
||||
const [index, entry, queue] = findEntryInPending(promptID)
|
||||
if (entry) {
|
||||
entry.outputs[nodeID] = output;
|
||||
s.queuePending.update(qp => { qp.push(entry); return qp; })
|
||||
queue.set(get(queue))
|
||||
}
|
||||
else {
|
||||
console.error("[queueState] Could not find in pending! (onExecuted)", promptID)
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
@@ -13,6 +13,10 @@ export function clamp(n: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(n, min), max)
|
||||
}
|
||||
|
||||
export function negmod(n: number, m: number): number {
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
|
||||
export function range(size: number, startAt: number = 0): ReadonlyArray<number> {
|
||||
return [...Array(size).keys()].map(i => i + startAt);
|
||||
}
|
||||
|
||||
@@ -122,14 +122,14 @@
|
||||
// the event might fire too early
|
||||
|
||||
const callback = isMobile ? setupImageForMobileLightbox
|
||||
: ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance)
|
||||
: ImageViewer.instance.setupGalleryImageForLightbox.bind(ImageViewer.instance)
|
||||
|
||||
setTimeout(() => {
|
||||
const images = element.querySelectorAll<HTMLImageElement>('div.block div > img')
|
||||
if (images != null) {
|
||||
images.forEach(callback);
|
||||
}
|
||||
ImageViewer.instance.updateOnBackgroundChange();
|
||||
ImageViewer.instance.refreshImages();
|
||||
}, 200)
|
||||
|
||||
// Update index
|
||||
|
||||
@@ -14,6 +14,7 @@ body {
|
||||
:root {
|
||||
--color-blue-500: #3985f5;
|
||||
|
||||
--comfy-accent-soft: var(--neutral-400);
|
||||
--comfy-splitpanes-background-fill: var(--secondary-100);
|
||||
--comfy-splitpanes-background-fill-hover: var(--secondary-300);
|
||||
--comfy-splitpanes-background-fill-active: var(--secondary-400);
|
||||
@@ -21,8 +22,8 @@ body {
|
||||
--comfy-dropdown-item-background-hover: var(--neutral-400);
|
||||
--comfy-dropdown-item-color-active: var(--neutral-100);
|
||||
--comfy-dropdown-item-background-active: var(--secondary-600);
|
||||
--comfy-progress-bar-background: var(--secondary-300);
|
||||
--comfy-progress-bar-foreground: #var(--body-text-color);
|
||||
--comfy-progress-bar-background: var(--neutral-300);
|
||||
--comfy-progress-bar-foreground: var(--secondary-300);
|
||||
--comfy-node-name-background: var(--color-red-300);
|
||||
--comfy-node-name-foreground: var(--body-text-color);
|
||||
--comfy-spinner-main-color: var(--neutral-400);
|
||||
@@ -32,6 +33,7 @@ body {
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
|
||||
--comfy-accent-soft: var(--neutral-600);
|
||||
--comfy-splitpanes-background-fill: var(--panel-border-color);
|
||||
--comfy-splitpanes-background-fill-hover: var(--secondary-500);
|
||||
--comfy-splitpanes-background-fill-active: var(--secondary-300);
|
||||
@@ -40,9 +42,9 @@ body {
|
||||
--comfy-dropdown-item-background-hover: var(--neutral-600);
|
||||
--comfy-dropdown-item-background-active: var(--secondary-600);
|
||||
--comfy-dropdown-border-color: var(--neutral-600);
|
||||
--comfy-progress-bar-background: var(--secondary-400);
|
||||
--comfy-progress-bar-foreground: #var(--body-text-color);
|
||||
--comfy-node-name-background: var(--primary-500);
|
||||
--comfy-progress-bar-background: var(--neutral-500);
|
||||
--comfy-progress-bar-foreground: var(--secondary-400);
|
||||
--comfy-node-name-background: var(--neutral-700);
|
||||
--comfy-node-name-foreground: var(--body-text-color);
|
||||
--comfy-spinner-main-color: var(--neutral-600);
|
||||
--comfy-spinner-accent-color: var(--secondary-600);
|
||||
@@ -96,6 +98,30 @@ select {
|
||||
// }
|
||||
}
|
||||
|
||||
// button {
|
||||
// filter: none;
|
||||
// &.primary:active {
|
||||
// filter: brightness(80%)
|
||||
// }
|
||||
// &.secondary:active {
|
||||
// filter: brightness(80%)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
button {
|
||||
&.primary:active {
|
||||
border-color: var(--button-primary-border-color-active) !important;
|
||||
background: var(--button-primary-background-fill-active) !important;
|
||||
color: var(--button-primary-text-color-active) !important;
|
||||
}
|
||||
&.secondary:active {
|
||||
border-color: var(--button-secondary-border-color-active) !important;
|
||||
background: var(--button-secondary-background-fill-active) !important;
|
||||
color: var(--button-secondary-text-color-active) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.widget {
|
||||
// padding: var(--ae-outside-gap-size);
|
||||
// border: 1px solid var(--ae-panel-border-color);
|
||||
|
||||
@@ -180,16 +180,22 @@
|
||||
--button-large-text-weight: 600;
|
||||
--button-primary-background-fill: linear-gradient(to bottom right, var(--primary-100), var(--primary-300));
|
||||
--button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-100), var(--primary-200));
|
||||
--button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-200), var(--primary-400));
|
||||
--button-primary-border-color: var(--primary-200);
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-border-color-active: var(--button-primary-border-color);
|
||||
--button-primary-text-color: var(--primary-600);
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-primary-text-color-active: var(--button-primary-text-color);
|
||||
--button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-200));
|
||||
--button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-100));
|
||||
--button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-200), var(--neutral-300));
|
||||
--button-secondary-border-color: var(--neutral-200);
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-border-color-active: var(--button-secondary-border-color);
|
||||
--button-secondary-text-color: var(--neutral-700);
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
--button-secondary-text-color-active: var(--neutral-800);
|
||||
--button-shadow: var(--shadow-drop);
|
||||
--button-shadow-active: var(--shadow-inset);
|
||||
--button-shadow-hover: var(--shadow-drop-lg);
|
||||
@@ -277,16 +283,22 @@
|
||||
--button-cancel-text-color-hover: var(--button-cancel-text-color);
|
||||
--button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-600));
|
||||
--button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-500));
|
||||
--button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-600), var(--primary-700));
|
||||
--button-primary-border-color: var(--primary-500);
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-border-color-active: var(--button-primary-border-color);
|
||||
--button-primary-text-color: white;
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-primary-text-color-active: var(--neutral-300);
|
||||
--button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-700));
|
||||
--button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-600));
|
||||
--button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-700), var(--neutral-800));
|
||||
--button-secondary-border-color: var(--neutral-600);
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-border-color-active: var(--button-secondary-border-color);
|
||||
--button-secondary-text-color: white;
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
--button-secondary-text-color-active: var(--neutral-300);
|
||||
--name: default;
|
||||
--primary-50: #fff7ed;
|
||||
--primary-100: #ffedd5;
|
||||
|
||||
Reference in New Issue
Block a user