mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 15:10:06 +00:00
Replace \t with spaces (#80)
This commit is contained in:
@@ -1,40 +1,40 @@
|
||||
import { $el } from "../ui";
|
||||
|
||||
export class ComfyDialog<T extends HTMLElement = HTMLElement> extends EventTarget {
|
||||
element: T;
|
||||
textElement: HTMLElement;
|
||||
#buttons: HTMLButtonElement[] | null;
|
||||
element: T;
|
||||
textElement: HTMLElement;
|
||||
#buttons: HTMLButtonElement[] | null;
|
||||
|
||||
constructor(type = "div", buttons = null) {
|
||||
super();
|
||||
this.#buttons = buttons;
|
||||
this.element = $el(type + ".comfy-modal", { parent: document.body }, [
|
||||
$el("div.comfy-modal-content", [$el("p", { $: (p) => (this.textElement = p) }), ...this.createButtons()]),
|
||||
]) as T;
|
||||
}
|
||||
constructor(type = "div", buttons = null) {
|
||||
super();
|
||||
this.#buttons = buttons;
|
||||
this.element = $el(type + ".comfy-modal", { parent: document.body }, [
|
||||
$el("div.comfy-modal-content", [$el("p", { $: (p) => (this.textElement = p) }), ...this.createButtons()]),
|
||||
]) as T;
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
return (
|
||||
this.#buttons ?? [
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Close",
|
||||
onclick: () => this.close(),
|
||||
}),
|
||||
]
|
||||
);
|
||||
}
|
||||
createButtons() {
|
||||
return (
|
||||
this.#buttons ?? [
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Close",
|
||||
onclick: () => this.close(),
|
||||
}),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.element.style.display = "none";
|
||||
}
|
||||
close() {
|
||||
this.element.style.display = "none";
|
||||
}
|
||||
|
||||
show(html) {
|
||||
if (typeof html === "string") {
|
||||
this.textElement.innerHTML = html;
|
||||
} else {
|
||||
this.textElement.replaceChildren(...(html instanceof Array ? html : [html]));
|
||||
}
|
||||
this.element.style.display = "flex";
|
||||
}
|
||||
show(html) {
|
||||
if (typeof html === "string") {
|
||||
this.textElement.innerHTML = html;
|
||||
} else {
|
||||
this.textElement.replaceChildren(...(html instanceof Array ? html : [html]));
|
||||
}
|
||||
this.element.style.display = "flex";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Original implementation:
|
||||
Original implementation:
|
||||
https://github.com/TahaSh/drag-to-reorder
|
||||
MIT License
|
||||
|
||||
@@ -44,243 +44,243 @@ $el("style", {
|
||||
});
|
||||
|
||||
export class DraggableList extends EventTarget {
|
||||
listContainer;
|
||||
draggableItem;
|
||||
pointerStartX;
|
||||
pointerStartY;
|
||||
scrollYMax;
|
||||
itemsGap = 0;
|
||||
items = [];
|
||||
itemSelector;
|
||||
handleClass = "drag-handle";
|
||||
off = [];
|
||||
offDrag = [];
|
||||
listContainer;
|
||||
draggableItem;
|
||||
pointerStartX;
|
||||
pointerStartY;
|
||||
scrollYMax;
|
||||
itemsGap = 0;
|
||||
items = [];
|
||||
itemSelector;
|
||||
handleClass = "drag-handle";
|
||||
off = [];
|
||||
offDrag = [];
|
||||
|
||||
constructor(element, itemSelector) {
|
||||
constructor(element, itemSelector) {
|
||||
super();
|
||||
this.listContainer = element;
|
||||
this.listContainer = element;
|
||||
this.itemSelector = itemSelector;
|
||||
|
||||
if (!this.listContainer) return;
|
||||
if (!this.listContainer) return;
|
||||
|
||||
this.off.push(this.on(this.listContainer, "mousedown", this.dragStart));
|
||||
this.off.push(this.on(this.listContainer, "touchstart", this.dragStart));
|
||||
this.off.push(this.on(document, "mouseup", this.dragEnd));
|
||||
this.off.push(this.on(document, "touchend", this.dragEnd));
|
||||
}
|
||||
this.off.push(this.on(this.listContainer, "mousedown", this.dragStart));
|
||||
this.off.push(this.on(this.listContainer, "touchstart", this.dragStart));
|
||||
this.off.push(this.on(document, "mouseup", this.dragEnd));
|
||||
this.off.push(this.on(document, "touchend", this.dragEnd));
|
||||
}
|
||||
|
||||
getAllItems() {
|
||||
if (!this.items?.length) {
|
||||
this.items = Array.from(this.listContainer.querySelectorAll(this.itemSelector));
|
||||
this.items.forEach((element) => {
|
||||
element.classList.add("is-idle");
|
||||
});
|
||||
}
|
||||
return this.items;
|
||||
}
|
||||
getAllItems() {
|
||||
if (!this.items?.length) {
|
||||
this.items = Array.from(this.listContainer.querySelectorAll(this.itemSelector));
|
||||
this.items.forEach((element) => {
|
||||
element.classList.add("is-idle");
|
||||
});
|
||||
}
|
||||
return this.items;
|
||||
}
|
||||
|
||||
getIdleItems() {
|
||||
return this.getAllItems().filter((item) => item.classList.contains("is-idle"));
|
||||
}
|
||||
getIdleItems() {
|
||||
return this.getAllItems().filter((item) => item.classList.contains("is-idle"));
|
||||
}
|
||||
|
||||
isItemAbove(item) {
|
||||
return item.hasAttribute("data-is-above");
|
||||
}
|
||||
isItemAbove(item) {
|
||||
return item.hasAttribute("data-is-above");
|
||||
}
|
||||
|
||||
isItemToggled(item) {
|
||||
return item.hasAttribute("data-is-toggled");
|
||||
}
|
||||
isItemToggled(item) {
|
||||
return item.hasAttribute("data-is-toggled");
|
||||
}
|
||||
|
||||
on(source, event, listener, options?) {
|
||||
listener = listener.bind(this);
|
||||
source.addEventListener(event, listener, options);
|
||||
return () => source.removeEventListener(event, listener);
|
||||
}
|
||||
on(source, event, listener, options?) {
|
||||
listener = listener.bind(this);
|
||||
source.addEventListener(event, listener, options);
|
||||
return () => source.removeEventListener(event, listener);
|
||||
}
|
||||
|
||||
dragStart(e) {
|
||||
if (e.target.classList.contains(this.handleClass)) {
|
||||
this.draggableItem = e.target.closest(this.itemSelector);
|
||||
}
|
||||
dragStart(e) {
|
||||
if (e.target.classList.contains(this.handleClass)) {
|
||||
this.draggableItem = e.target.closest(this.itemSelector);
|
||||
}
|
||||
|
||||
if (!this.draggableItem) return;
|
||||
if (!this.draggableItem) return;
|
||||
|
||||
this.pointerStartX = e.clientX || e.touches[0].clientX;
|
||||
this.pointerStartY = e.clientY || e.touches[0].clientY;
|
||||
this.scrollYMax = this.listContainer.scrollHeight - this.listContainer.clientHeight;
|
||||
this.pointerStartX = e.clientX || e.touches[0].clientX;
|
||||
this.pointerStartY = e.clientY || e.touches[0].clientY;
|
||||
this.scrollYMax = this.listContainer.scrollHeight - this.listContainer.clientHeight;
|
||||
|
||||
this.setItemsGap();
|
||||
this.initDraggableItem();
|
||||
this.initItemsState();
|
||||
this.setItemsGap();
|
||||
this.initDraggableItem();
|
||||
this.initItemsState();
|
||||
|
||||
this.offDrag.push(this.on(document, "mousemove", this.drag));
|
||||
this.offDrag.push(this.on(document, "touchmove", this.drag, { passive: false }));
|
||||
this.offDrag.push(this.on(document, "mousemove", this.drag));
|
||||
this.offDrag.push(this.on(document, "touchmove", this.drag, { passive: false }));
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("dragstart", {
|
||||
detail: { element: this.draggableItem, position: this.getAllItems().indexOf(this.draggableItem) },
|
||||
})
|
||||
);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("dragstart", {
|
||||
detail: { element: this.draggableItem, position: this.getAllItems().indexOf(this.draggableItem) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setItemsGap() {
|
||||
if (this.getIdleItems().length <= 1) {
|
||||
this.itemsGap = 0;
|
||||
return;
|
||||
}
|
||||
setItemsGap() {
|
||||
if (this.getIdleItems().length <= 1) {
|
||||
this.itemsGap = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const item1 = this.getIdleItems()[0];
|
||||
const item2 = this.getIdleItems()[1];
|
||||
const item1 = this.getIdleItems()[0];
|
||||
const item2 = this.getIdleItems()[1];
|
||||
|
||||
const item1Rect = item1.getBoundingClientRect();
|
||||
const item2Rect = item2.getBoundingClientRect();
|
||||
const item1Rect = item1.getBoundingClientRect();
|
||||
const item2Rect = item2.getBoundingClientRect();
|
||||
|
||||
this.itemsGap = Math.abs(item1Rect.bottom - item2Rect.top);
|
||||
}
|
||||
this.itemsGap = Math.abs(item1Rect.bottom - item2Rect.top);
|
||||
}
|
||||
|
||||
initItemsState() {
|
||||
this.getIdleItems().forEach((item, i) => {
|
||||
if (this.getAllItems().indexOf(this.draggableItem) > i) {
|
||||
item.dataset.isAbove = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
initItemsState() {
|
||||
this.getIdleItems().forEach((item, i) => {
|
||||
if (this.getAllItems().indexOf(this.draggableItem) > i) {
|
||||
item.dataset.isAbove = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initDraggableItem() {
|
||||
this.draggableItem.classList.remove("is-idle");
|
||||
this.draggableItem.classList.add("is-draggable");
|
||||
}
|
||||
initDraggableItem() {
|
||||
this.draggableItem.classList.remove("is-idle");
|
||||
this.draggableItem.classList.add("is-draggable");
|
||||
}
|
||||
|
||||
drag(e) {
|
||||
if (!this.draggableItem) return;
|
||||
drag(e) {
|
||||
if (!this.draggableItem) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
const clientX = e.clientX || e.touches[0].clientX;
|
||||
const clientY = e.clientY || e.touches[0].clientY;
|
||||
const clientX = e.clientX || e.touches[0].clientX;
|
||||
const clientY = e.clientY || e.touches[0].clientY;
|
||||
|
||||
const listRect = this.listContainer.getBoundingClientRect();
|
||||
const listRect = this.listContainer.getBoundingClientRect();
|
||||
|
||||
if (clientY > listRect.bottom) {
|
||||
if (this.listContainer.scrollTop < this.scrollYMax) {
|
||||
this.listContainer.scrollBy(0, 10);
|
||||
this.pointerStartY -= 10;
|
||||
}
|
||||
} else if (clientY < listRect.top && this.listContainer.scrollTop > 0) {
|
||||
this.pointerStartY += 10;
|
||||
this.listContainer.scrollBy(0, -10);
|
||||
}
|
||||
if (clientY > listRect.bottom) {
|
||||
if (this.listContainer.scrollTop < this.scrollYMax) {
|
||||
this.listContainer.scrollBy(0, 10);
|
||||
this.pointerStartY -= 10;
|
||||
}
|
||||
} else if (clientY < listRect.top && this.listContainer.scrollTop > 0) {
|
||||
this.pointerStartY += 10;
|
||||
this.listContainer.scrollBy(0, -10);
|
||||
}
|
||||
|
||||
const pointerOffsetX = clientX - this.pointerStartX;
|
||||
const pointerOffsetY = clientY - this.pointerStartY;
|
||||
const pointerOffsetX = clientX - this.pointerStartX;
|
||||
const pointerOffsetY = clientY - this.pointerStartY;
|
||||
|
||||
this.updateIdleItemsStateAndPosition();
|
||||
this.draggableItem.style.transform = `translate(${pointerOffsetX}px, ${pointerOffsetY}px)`;
|
||||
}
|
||||
this.updateIdleItemsStateAndPosition();
|
||||
this.draggableItem.style.transform = `translate(${pointerOffsetX}px, ${pointerOffsetY}px)`;
|
||||
}
|
||||
|
||||
updateIdleItemsStateAndPosition() {
|
||||
const draggableItemRect = this.draggableItem.getBoundingClientRect();
|
||||
const draggableItemY = draggableItemRect.top + draggableItemRect.height / 2;
|
||||
updateIdleItemsStateAndPosition() {
|
||||
const draggableItemRect = this.draggableItem.getBoundingClientRect();
|
||||
const draggableItemY = draggableItemRect.top + draggableItemRect.height / 2;
|
||||
|
||||
// Update state
|
||||
this.getIdleItems().forEach((item) => {
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
const itemY = itemRect.top + itemRect.height / 2;
|
||||
if (this.isItemAbove(item)) {
|
||||
if (draggableItemY <= itemY) {
|
||||
item.dataset.isToggled = "";
|
||||
} else {
|
||||
delete item.dataset.isToggled;
|
||||
}
|
||||
} else {
|
||||
if (draggableItemY >= itemY) {
|
||||
item.dataset.isToggled = "";
|
||||
} else {
|
||||
delete item.dataset.isToggled;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Update state
|
||||
this.getIdleItems().forEach((item) => {
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
const itemY = itemRect.top + itemRect.height / 2;
|
||||
if (this.isItemAbove(item)) {
|
||||
if (draggableItemY <= itemY) {
|
||||
item.dataset.isToggled = "";
|
||||
} else {
|
||||
delete item.dataset.isToggled;
|
||||
}
|
||||
} else {
|
||||
if (draggableItemY >= itemY) {
|
||||
item.dataset.isToggled = "";
|
||||
} else {
|
||||
delete item.dataset.isToggled;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update position
|
||||
this.getIdleItems().forEach((item) => {
|
||||
if (this.isItemToggled(item)) {
|
||||
const direction = this.isItemAbove(item) ? 1 : -1;
|
||||
item.style.transform = `translateY(${direction * (draggableItemRect.height + this.itemsGap)}px)`;
|
||||
} else {
|
||||
item.style.transform = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
// Update position
|
||||
this.getIdleItems().forEach((item) => {
|
||||
if (this.isItemToggled(item)) {
|
||||
const direction = this.isItemAbove(item) ? 1 : -1;
|
||||
item.style.transform = `translateY(${direction * (draggableItemRect.height + this.itemsGap)}px)`;
|
||||
} else {
|
||||
item.style.transform = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dragEnd() {
|
||||
if (!this.draggableItem) return;
|
||||
dragEnd() {
|
||||
if (!this.draggableItem) return;
|
||||
|
||||
this.applyNewItemsOrder();
|
||||
this.cleanup();
|
||||
}
|
||||
this.applyNewItemsOrder();
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
applyNewItemsOrder() {
|
||||
const reorderedItems = [];
|
||||
applyNewItemsOrder() {
|
||||
const reorderedItems = [];
|
||||
|
||||
let oldPosition = -1;
|
||||
this.getAllItems().forEach((item, index) => {
|
||||
if (item === this.draggableItem) {
|
||||
oldPosition = index;
|
||||
return;
|
||||
}
|
||||
if (!this.isItemToggled(item)) {
|
||||
reorderedItems[index] = item;
|
||||
return;
|
||||
}
|
||||
const newIndex = this.isItemAbove(item) ? index + 1 : index - 1;
|
||||
reorderedItems[newIndex] = item;
|
||||
});
|
||||
let oldPosition = -1;
|
||||
this.getAllItems().forEach((item, index) => {
|
||||
if (item === this.draggableItem) {
|
||||
oldPosition = index;
|
||||
return;
|
||||
}
|
||||
if (!this.isItemToggled(item)) {
|
||||
reorderedItems[index] = item;
|
||||
return;
|
||||
}
|
||||
const newIndex = this.isItemAbove(item) ? index + 1 : index - 1;
|
||||
reorderedItems[newIndex] = item;
|
||||
});
|
||||
|
||||
for (let index = 0; index < this.getAllItems().length; index++) {
|
||||
const item = reorderedItems[index];
|
||||
if (typeof item === "undefined") {
|
||||
reorderedItems[index] = this.draggableItem;
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < this.getAllItems().length; index++) {
|
||||
const item = reorderedItems[index];
|
||||
if (typeof item === "undefined") {
|
||||
reorderedItems[index] = this.draggableItem;
|
||||
}
|
||||
}
|
||||
|
||||
reorderedItems.forEach((item) => {
|
||||
this.listContainer.appendChild(item);
|
||||
});
|
||||
reorderedItems.forEach((item) => {
|
||||
this.listContainer.appendChild(item);
|
||||
});
|
||||
|
||||
this.items = reorderedItems;
|
||||
this.items = reorderedItems;
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("dragend", {
|
||||
detail: { element: this.draggableItem, oldPosition, newPosition: reorderedItems.indexOf(this.draggableItem) },
|
||||
})
|
||||
);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("dragend", {
|
||||
detail: { element: this.draggableItem, oldPosition, newPosition: reorderedItems.indexOf(this.draggableItem) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.itemsGap = 0;
|
||||
this.items = [];
|
||||
this.unsetDraggableItem();
|
||||
this.unsetItemState();
|
||||
cleanup() {
|
||||
this.itemsGap = 0;
|
||||
this.items = [];
|
||||
this.unsetDraggableItem();
|
||||
this.unsetItemState();
|
||||
|
||||
this.offDrag.forEach((f) => f());
|
||||
this.offDrag = [];
|
||||
}
|
||||
this.offDrag.forEach((f) => f());
|
||||
this.offDrag = [];
|
||||
}
|
||||
|
||||
unsetDraggableItem() {
|
||||
this.draggableItem.style = null;
|
||||
this.draggableItem.classList.remove("is-draggable");
|
||||
this.draggableItem.classList.add("is-idle");
|
||||
this.draggableItem = null;
|
||||
}
|
||||
unsetDraggableItem() {
|
||||
this.draggableItem.style = null;
|
||||
this.draggableItem.classList.remove("is-draggable");
|
||||
this.draggableItem.classList.add("is-idle");
|
||||
this.draggableItem = null;
|
||||
}
|
||||
|
||||
unsetItemState() {
|
||||
this.getIdleItems().forEach((item, i) => {
|
||||
delete item.dataset.isAbove;
|
||||
delete item.dataset.isToggled;
|
||||
item.style.transform = "";
|
||||
});
|
||||
}
|
||||
unsetItemState() {
|
||||
this.getIdleItems().forEach((item, i) => {
|
||||
delete item.dataset.isAbove;
|
||||
delete item.dataset.isToggled;
|
||||
item.style.transform = "";
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.off.forEach((f) => f());
|
||||
}
|
||||
dispose() {
|
||||
this.off.forEach((f) => f());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,97 +2,97 @@ import { app } from "../app";
|
||||
import { $el } from "../ui";
|
||||
|
||||
export function calculateImageGrid(imgs, dw, dh) {
|
||||
let best = 0;
|
||||
let w = imgs[0].naturalWidth;
|
||||
let h = imgs[0].naturalHeight;
|
||||
const numImages = imgs.length;
|
||||
let best = 0;
|
||||
let w = imgs[0].naturalWidth;
|
||||
let h = imgs[0].naturalHeight;
|
||||
const numImages = imgs.length;
|
||||
|
||||
let cellWidth, cellHeight, cols, rows, shiftX;
|
||||
// compact style
|
||||
for (let c = 1; c <= numImages; c++) {
|
||||
const r = Math.ceil(numImages / c);
|
||||
const cW = dw / c;
|
||||
const cH = dh / r;
|
||||
const scaleX = cW / w;
|
||||
const scaleY = cH / h;
|
||||
let cellWidth, cellHeight, cols, rows, shiftX;
|
||||
// compact style
|
||||
for (let c = 1; c <= numImages; c++) {
|
||||
const r = Math.ceil(numImages / c);
|
||||
const cW = dw / c;
|
||||
const cH = dh / r;
|
||||
const scaleX = cW / w;
|
||||
const scaleY = cH / h;
|
||||
|
||||
const scale = Math.min(scaleX, scaleY, 1);
|
||||
const imageW = w * scale;
|
||||
const imageH = h * scale;
|
||||
const area = imageW * imageH * numImages;
|
||||
const scale = Math.min(scaleX, scaleY, 1);
|
||||
const imageW = w * scale;
|
||||
const imageH = h * scale;
|
||||
const area = imageW * imageH * numImages;
|
||||
|
||||
if (area > best) {
|
||||
best = area;
|
||||
cellWidth = imageW;
|
||||
cellHeight = imageH;
|
||||
cols = c;
|
||||
rows = r;
|
||||
shiftX = c * ((cW - imageW) / 2);
|
||||
}
|
||||
}
|
||||
if (area > best) {
|
||||
best = area;
|
||||
cellWidth = imageW;
|
||||
cellHeight = imageH;
|
||||
cols = c;
|
||||
rows = r;
|
||||
shiftX = c * ((cW - imageW) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
return { cellWidth, cellHeight, cols, rows, shiftX };
|
||||
return { cellWidth, cellHeight, cols, rows, shiftX };
|
||||
}
|
||||
|
||||
export function createImageHost(node) {
|
||||
const el = $el("div.comfy-img-preview");
|
||||
let currentImgs;
|
||||
let first = true;
|
||||
const el = $el("div.comfy-img-preview");
|
||||
let currentImgs;
|
||||
let first = true;
|
||||
|
||||
function updateSize() {
|
||||
let w = null;
|
||||
let h = null;
|
||||
function updateSize() {
|
||||
let w = null;
|
||||
let h = null;
|
||||
|
||||
if (currentImgs) {
|
||||
let elH = el.clientHeight;
|
||||
if (first) {
|
||||
first = false;
|
||||
// On first run, if we are small then grow a bit
|
||||
if (elH < 190) {
|
||||
elH = 190;
|
||||
}
|
||||
el.style.setProperty("--comfy-widget-min-height", elH.toString());
|
||||
} else {
|
||||
el.style.setProperty("--comfy-widget-min-height", null);
|
||||
}
|
||||
if (currentImgs) {
|
||||
let elH = el.clientHeight;
|
||||
if (first) {
|
||||
first = false;
|
||||
// On first run, if we are small then grow a bit
|
||||
if (elH < 190) {
|
||||
elH = 190;
|
||||
}
|
||||
el.style.setProperty("--comfy-widget-min-height", elH.toString());
|
||||
} else {
|
||||
el.style.setProperty("--comfy-widget-min-height", null);
|
||||
}
|
||||
|
||||
const nw = node.size[0];
|
||||
({ cellWidth: w, cellHeight: h } = calculateImageGrid(currentImgs, nw - 20, elH));
|
||||
w += "px";
|
||||
h += "px";
|
||||
const nw = node.size[0];
|
||||
({ cellWidth: w, cellHeight: h } = calculateImageGrid(currentImgs, nw - 20, elH));
|
||||
w += "px";
|
||||
h += "px";
|
||||
|
||||
el.style.setProperty("--comfy-img-preview-width", w);
|
||||
el.style.setProperty("--comfy-img-preview-height", h);
|
||||
}
|
||||
}
|
||||
return {
|
||||
el,
|
||||
updateImages(imgs) {
|
||||
if (imgs !== currentImgs) {
|
||||
if (currentImgs == null) {
|
||||
requestAnimationFrame(() => {
|
||||
updateSize();
|
||||
});
|
||||
}
|
||||
el.replaceChildren(...imgs);
|
||||
currentImgs = imgs;
|
||||
node.onResize(node.size);
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
},
|
||||
getHeight() {
|
||||
updateSize();
|
||||
},
|
||||
onDraw() {
|
||||
// Element from point uses a hittest find elements so we need to toggle pointer events
|
||||
el.style.pointerEvents = "all";
|
||||
const over = document.elementFromPoint(app.canvas.mouse[0], app.canvas.mouse[1]);
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.setProperty("--comfy-img-preview-width", w);
|
||||
el.style.setProperty("--comfy-img-preview-height", h);
|
||||
}
|
||||
}
|
||||
return {
|
||||
el,
|
||||
updateImages(imgs) {
|
||||
if (imgs !== currentImgs) {
|
||||
if (currentImgs == null) {
|
||||
requestAnimationFrame(() => {
|
||||
updateSize();
|
||||
});
|
||||
}
|
||||
el.replaceChildren(...imgs);
|
||||
currentImgs = imgs;
|
||||
node.onResize(node.size);
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
},
|
||||
getHeight() {
|
||||
updateSize();
|
||||
},
|
||||
onDraw() {
|
||||
// Element from point uses a hittest find elements so we need to toggle pointer events
|
||||
el.style.pointerEvents = "all";
|
||||
const over = document.elementFromPoint(app.canvas.mouse[0], app.canvas.mouse[1]);
|
||||
el.style.pointerEvents = "none";
|
||||
|
||||
if(!over) return;
|
||||
// Set the overIndex so Open Image etc work
|
||||
const idx = currentImgs.indexOf(over);
|
||||
node.overIndex = idx;
|
||||
},
|
||||
};
|
||||
if(!over) return;
|
||||
// Set the overIndex so Open Image etc work
|
||||
const idx = currentImgs.indexOf(over);
|
||||
node.overIndex = idx;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,291 +13,291 @@ import { getInteruptButton } from "./interruptButton";
|
||||
import "./menu.css";
|
||||
|
||||
const collapseOnMobile = (t) => {
|
||||
(t.element ?? t).classList.add("comfyui-menu-mobile-collapse");
|
||||
return t;
|
||||
(t.element ?? t).classList.add("comfyui-menu-mobile-collapse");
|
||||
return t;
|
||||
};
|
||||
const showOnMobile = (t) => {
|
||||
(t.element ?? t).classList.add("lt-lg-show");
|
||||
return t;
|
||||
(t.element ?? t).classList.add("lt-lg-show");
|
||||
return t;
|
||||
};
|
||||
|
||||
export class ComfyAppMenu {
|
||||
#sizeBreak = "lg";
|
||||
#lastSizeBreaks = {
|
||||
lg: null,
|
||||
md: null,
|
||||
sm: null,
|
||||
xs: null,
|
||||
};
|
||||
#sizeBreaks = Object.keys(this.#lastSizeBreaks);
|
||||
#cachedInnerSize = null;
|
||||
#cacheTimeout = null;
|
||||
#sizeBreak = "lg";
|
||||
#lastSizeBreaks = {
|
||||
lg: null,
|
||||
md: null,
|
||||
sm: null,
|
||||
xs: null,
|
||||
};
|
||||
#sizeBreaks = Object.keys(this.#lastSizeBreaks);
|
||||
#cachedInnerSize = null;
|
||||
#cacheTimeout = null;
|
||||
|
||||
/**
|
||||
* @param { import("../../app").ComfyApp } app
|
||||
*/
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
/**
|
||||
* @param { import("../../app").ComfyApp } app
|
||||
*/
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
this.workflows = new ComfyWorkflowsMenu(app);
|
||||
const getSaveButton = (t) =>
|
||||
new ComfyButton({
|
||||
icon: "content-save",
|
||||
tooltip: "Save the current workflow",
|
||||
action: () => app.workflowManager.activeWorkflow.save(),
|
||||
content: t,
|
||||
});
|
||||
this.workflows = new ComfyWorkflowsMenu(app);
|
||||
const getSaveButton = (t) =>
|
||||
new ComfyButton({
|
||||
icon: "content-save",
|
||||
tooltip: "Save the current workflow",
|
||||
action: () => app.workflowManager.activeWorkflow.save(),
|
||||
content: t,
|
||||
});
|
||||
|
||||
this.logo = $el("h1.comfyui-logo.nlg-hide", { title: "ComfyUI" }, "ComfyUI");
|
||||
this.saveButton = new ComfySplitButton(
|
||||
{
|
||||
primary: getSaveButton(),
|
||||
mode: "hover",
|
||||
position: "absolute",
|
||||
},
|
||||
getSaveButton("Save"),
|
||||
new ComfyButton({
|
||||
icon: "content-save-edit",
|
||||
content: "Save As",
|
||||
tooltip: "Save the current graph as a new workflow",
|
||||
action: () => app.workflowManager.activeWorkflow.save(true),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "download",
|
||||
content: "Export",
|
||||
tooltip: "Export the current workflow as JSON",
|
||||
action: () => this.exportWorkflow("workflow", "workflow"),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "api",
|
||||
content: "Export (API Format)",
|
||||
tooltip: "Export the current workflow as JSON for use with the ComfyUI API",
|
||||
action: () => this.exportWorkflow("workflow_api", "output"),
|
||||
visibilitySetting: { id: "Comfy.DevMode", showValue: true },
|
||||
app,
|
||||
})
|
||||
);
|
||||
this.actionsGroup = new ComfyButtonGroup(
|
||||
new ComfyButton({
|
||||
icon: "refresh",
|
||||
content: "Refresh",
|
||||
tooltip: "Refresh widgets in nodes to find new models or files",
|
||||
action: () => app.refreshComboInNodes(),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "clipboard-edit-outline",
|
||||
content: "Clipspace",
|
||||
tooltip: "Open Clipspace window",
|
||||
action: () => app["openClipspace"](),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "fit-to-page-outline",
|
||||
content: "Reset View",
|
||||
tooltip: "Reset the canvas view",
|
||||
action: () => app.resetView(),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "cancel",
|
||||
content: "Clear",
|
||||
tooltip: "Clears current workflow",
|
||||
action: () => {
|
||||
if (!app.ui.settings.getSettingValue("Comfy.ConfirmClear", true) || confirm("Clear workflow?")) {
|
||||
app.clean();
|
||||
app.graph.clear();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
this.settingsGroup = new ComfyButtonGroup(
|
||||
new ComfyButton({
|
||||
icon: "cog",
|
||||
content: "Settings",
|
||||
tooltip: "Open settings",
|
||||
action: () => {
|
||||
app.ui.settings.show();
|
||||
},
|
||||
})
|
||||
);
|
||||
this.viewGroup = new ComfyButtonGroup(
|
||||
new ComfyViewHistoryButton(app).element,
|
||||
new ComfyViewQueueButton(app).element,
|
||||
getInteruptButton("nlg-hide").element
|
||||
);
|
||||
this.mobileMenuButton = new ComfyButton({
|
||||
icon: "menu",
|
||||
action: (_, btn) => {
|
||||
btn.icon = this.element.classList.toggle("expanded") ? "menu-open" : "menu";
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
classList: "comfyui-button comfyui-menu-button",
|
||||
});
|
||||
this.logo = $el("h1.comfyui-logo.nlg-hide", { title: "ComfyUI" }, "ComfyUI");
|
||||
this.saveButton = new ComfySplitButton(
|
||||
{
|
||||
primary: getSaveButton(),
|
||||
mode: "hover",
|
||||
position: "absolute",
|
||||
},
|
||||
getSaveButton("Save"),
|
||||
new ComfyButton({
|
||||
icon: "content-save-edit",
|
||||
content: "Save As",
|
||||
tooltip: "Save the current graph as a new workflow",
|
||||
action: () => app.workflowManager.activeWorkflow.save(true),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "download",
|
||||
content: "Export",
|
||||
tooltip: "Export the current workflow as JSON",
|
||||
action: () => this.exportWorkflow("workflow", "workflow"),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "api",
|
||||
content: "Export (API Format)",
|
||||
tooltip: "Export the current workflow as JSON for use with the ComfyUI API",
|
||||
action: () => this.exportWorkflow("workflow_api", "output"),
|
||||
visibilitySetting: { id: "Comfy.DevMode", showValue: true },
|
||||
app,
|
||||
})
|
||||
);
|
||||
this.actionsGroup = new ComfyButtonGroup(
|
||||
new ComfyButton({
|
||||
icon: "refresh",
|
||||
content: "Refresh",
|
||||
tooltip: "Refresh widgets in nodes to find new models or files",
|
||||
action: () => app.refreshComboInNodes(),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "clipboard-edit-outline",
|
||||
content: "Clipspace",
|
||||
tooltip: "Open Clipspace window",
|
||||
action: () => app["openClipspace"](),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "fit-to-page-outline",
|
||||
content: "Reset View",
|
||||
tooltip: "Reset the canvas view",
|
||||
action: () => app.resetView(),
|
||||
}),
|
||||
new ComfyButton({
|
||||
icon: "cancel",
|
||||
content: "Clear",
|
||||
tooltip: "Clears current workflow",
|
||||
action: () => {
|
||||
if (!app.ui.settings.getSettingValue("Comfy.ConfirmClear", true) || confirm("Clear workflow?")) {
|
||||
app.clean();
|
||||
app.graph.clear();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
this.settingsGroup = new ComfyButtonGroup(
|
||||
new ComfyButton({
|
||||
icon: "cog",
|
||||
content: "Settings",
|
||||
tooltip: "Open settings",
|
||||
action: () => {
|
||||
app.ui.settings.show();
|
||||
},
|
||||
})
|
||||
);
|
||||
this.viewGroup = new ComfyButtonGroup(
|
||||
new ComfyViewHistoryButton(app).element,
|
||||
new ComfyViewQueueButton(app).element,
|
||||
getInteruptButton("nlg-hide").element
|
||||
);
|
||||
this.mobileMenuButton = new ComfyButton({
|
||||
icon: "menu",
|
||||
action: (_, btn) => {
|
||||
btn.icon = this.element.classList.toggle("expanded") ? "menu-open" : "menu";
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
classList: "comfyui-button comfyui-menu-button",
|
||||
});
|
||||
|
||||
this.element = $el("nav.comfyui-menu.lg", { style: { display: "none" } }, [
|
||||
this.logo,
|
||||
this.workflows.element,
|
||||
this.saveButton.element,
|
||||
collapseOnMobile(this.actionsGroup).element,
|
||||
$el("section.comfyui-menu-push"),
|
||||
collapseOnMobile(this.settingsGroup).element,
|
||||
collapseOnMobile(this.viewGroup).element,
|
||||
this.element = $el("nav.comfyui-menu.lg", { style: { display: "none" } }, [
|
||||
this.logo,
|
||||
this.workflows.element,
|
||||
this.saveButton.element,
|
||||
collapseOnMobile(this.actionsGroup).element,
|
||||
$el("section.comfyui-menu-push"),
|
||||
collapseOnMobile(this.settingsGroup).element,
|
||||
collapseOnMobile(this.viewGroup).element,
|
||||
|
||||
getInteruptButton("lt-lg-show").element,
|
||||
new ComfyQueueButton(app).element,
|
||||
showOnMobile(this.mobileMenuButton).element,
|
||||
]);
|
||||
getInteruptButton("lt-lg-show").element,
|
||||
new ComfyQueueButton(app).element,
|
||||
showOnMobile(this.mobileMenuButton).element,
|
||||
]);
|
||||
|
||||
let resizeHandler;
|
||||
this.menuPositionSetting = app.ui.settings.addSetting({
|
||||
id: "Comfy.UseNewMenu",
|
||||
defaultValue: "Disabled",
|
||||
name: "[Beta] Use new menu and workflow management. Note: On small screens the menu will always be at the top.",
|
||||
type: "combo",
|
||||
options: ["Disabled", "Top", "Bottom"],
|
||||
onChange: async (v) => {
|
||||
if (v && v !== "Disabled") {
|
||||
if (!resizeHandler) {
|
||||
resizeHandler = () => {
|
||||
this.calculateSizeBreak();
|
||||
};
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
}
|
||||
this.updatePosition(v);
|
||||
} else {
|
||||
if (resizeHandler) {
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
resizeHandler = null;
|
||||
}
|
||||
document.body.style.removeProperty("display");
|
||||
app.ui.menuContainer.style.removeProperty("display");
|
||||
this.element.style.display = "none";
|
||||
app.ui.restoreMenuPosition();
|
||||
}
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
});
|
||||
}
|
||||
let resizeHandler;
|
||||
this.menuPositionSetting = app.ui.settings.addSetting({
|
||||
id: "Comfy.UseNewMenu",
|
||||
defaultValue: "Disabled",
|
||||
name: "[Beta] Use new menu and workflow management. Note: On small screens the menu will always be at the top.",
|
||||
type: "combo",
|
||||
options: ["Disabled", "Top", "Bottom"],
|
||||
onChange: async (v) => {
|
||||
if (v && v !== "Disabled") {
|
||||
if (!resizeHandler) {
|
||||
resizeHandler = () => {
|
||||
this.calculateSizeBreak();
|
||||
};
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
}
|
||||
this.updatePosition(v);
|
||||
} else {
|
||||
if (resizeHandler) {
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
resizeHandler = null;
|
||||
}
|
||||
document.body.style.removeProperty("display");
|
||||
app.ui.menuContainer.style.removeProperty("display");
|
||||
this.element.style.display = "none";
|
||||
app.ui.restoreMenuPosition();
|
||||
}
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updatePosition(v) {
|
||||
document.body.style.display = "grid";
|
||||
this.app.ui.menuContainer.style.display = "none";
|
||||
this.element.style.removeProperty("display");
|
||||
this.position = v;
|
||||
if (v === "Bottom") {
|
||||
this.app.bodyBottom.append(this.element);
|
||||
} else {
|
||||
this.app.bodyTop.prepend(this.element);
|
||||
}
|
||||
this.calculateSizeBreak();
|
||||
}
|
||||
updatePosition(v) {
|
||||
document.body.style.display = "grid";
|
||||
this.app.ui.menuContainer.style.display = "none";
|
||||
this.element.style.removeProperty("display");
|
||||
this.position = v;
|
||||
if (v === "Bottom") {
|
||||
this.app.bodyBottom.append(this.element);
|
||||
} else {
|
||||
this.app.bodyTop.prepend(this.element);
|
||||
}
|
||||
this.calculateSizeBreak();
|
||||
}
|
||||
|
||||
updateSizeBreak(idx, prevIdx, direction) {
|
||||
const newSize = this.#sizeBreaks[idx];
|
||||
if (newSize === this.#sizeBreak) return;
|
||||
this.#cachedInnerSize = null;
|
||||
clearTimeout(this.#cacheTimeout);
|
||||
updateSizeBreak(idx, prevIdx, direction) {
|
||||
const newSize = this.#sizeBreaks[idx];
|
||||
if (newSize === this.#sizeBreak) return;
|
||||
this.#cachedInnerSize = null;
|
||||
clearTimeout(this.#cacheTimeout);
|
||||
|
||||
this.#sizeBreak = this.#sizeBreaks[idx];
|
||||
for (let i = 0; i < this.#sizeBreaks.length; i++) {
|
||||
const sz = this.#sizeBreaks[i];
|
||||
if (sz === this.#sizeBreak) {
|
||||
this.element.classList.add(sz);
|
||||
} else {
|
||||
this.element.classList.remove(sz);
|
||||
}
|
||||
if (i < idx) {
|
||||
this.element.classList.add("lt-" + sz);
|
||||
} else {
|
||||
this.element.classList.remove("lt-" + sz);
|
||||
}
|
||||
}
|
||||
this.#sizeBreak = this.#sizeBreaks[idx];
|
||||
for (let i = 0; i < this.#sizeBreaks.length; i++) {
|
||||
const sz = this.#sizeBreaks[i];
|
||||
if (sz === this.#sizeBreak) {
|
||||
this.element.classList.add(sz);
|
||||
} else {
|
||||
this.element.classList.remove(sz);
|
||||
}
|
||||
if (i < idx) {
|
||||
this.element.classList.add("lt-" + sz);
|
||||
} else {
|
||||
this.element.classList.remove("lt-" + sz);
|
||||
}
|
||||
}
|
||||
|
||||
if (idx) {
|
||||
// We're on a small screen, force the menu at the top
|
||||
if (this.position !== "Top") {
|
||||
this.updatePosition("Top");
|
||||
}
|
||||
} else if (this.position != this.menuPositionSetting.value) {
|
||||
// Restore user position
|
||||
this.updatePosition(this.menuPositionSetting.value);
|
||||
}
|
||||
if (idx) {
|
||||
// We're on a small screen, force the menu at the top
|
||||
if (this.position !== "Top") {
|
||||
this.updatePosition("Top");
|
||||
}
|
||||
} else if (this.position != this.menuPositionSetting.value) {
|
||||
// Restore user position
|
||||
this.updatePosition(this.menuPositionSetting.value);
|
||||
}
|
||||
|
||||
// Allow multiple updates, but prevent bouncing
|
||||
if (!direction) {
|
||||
direction = prevIdx - idx;
|
||||
} else if (direction != prevIdx - idx) {
|
||||
return;
|
||||
}
|
||||
this.calculateSizeBreak(direction);
|
||||
}
|
||||
// Allow multiple updates, but prevent bouncing
|
||||
if (!direction) {
|
||||
direction = prevIdx - idx;
|
||||
} else if (direction != prevIdx - idx) {
|
||||
return;
|
||||
}
|
||||
this.calculateSizeBreak(direction);
|
||||
}
|
||||
|
||||
calculateSizeBreak(direction = 0) {
|
||||
let idx = this.#sizeBreaks.indexOf(this.#sizeBreak);
|
||||
const currIdx = idx;
|
||||
const innerSize = this.calculateInnerSize(idx);
|
||||
if (window.innerWidth >= this.#lastSizeBreaks[this.#sizeBreaks[idx - 1]]) {
|
||||
if (idx > 0) {
|
||||
idx--;
|
||||
}
|
||||
} else if (innerSize > this.element.clientWidth) {
|
||||
this.#lastSizeBreaks[this.#sizeBreak] = Math.max(window.innerWidth, innerSize);
|
||||
// We need to shrink
|
||||
if (idx < this.#sizeBreaks.length - 1) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
calculateSizeBreak(direction = 0) {
|
||||
let idx = this.#sizeBreaks.indexOf(this.#sizeBreak);
|
||||
const currIdx = idx;
|
||||
const innerSize = this.calculateInnerSize(idx);
|
||||
if (window.innerWidth >= this.#lastSizeBreaks[this.#sizeBreaks[idx - 1]]) {
|
||||
if (idx > 0) {
|
||||
idx--;
|
||||
}
|
||||
} else if (innerSize > this.element.clientWidth) {
|
||||
this.#lastSizeBreaks[this.#sizeBreak] = Math.max(window.innerWidth, innerSize);
|
||||
// We need to shrink
|
||||
if (idx < this.#sizeBreaks.length - 1) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSizeBreak(idx, currIdx, direction);
|
||||
}
|
||||
this.updateSizeBreak(idx, currIdx, direction);
|
||||
}
|
||||
|
||||
calculateInnerSize(idx) {
|
||||
// Cache the inner size to prevent too much calculation when resizing the window
|
||||
clearTimeout(this.#cacheTimeout);
|
||||
if (this.#cachedInnerSize) {
|
||||
// Extend cache time
|
||||
this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100);
|
||||
} else {
|
||||
let innerSize = 0;
|
||||
let count = 1;
|
||||
for (const c of this.element.children) {
|
||||
if (c.classList.contains("comfyui-menu-push")) continue; // ignore right push
|
||||
if (idx && c.classList.contains("comfyui-menu-mobile-collapse")) continue; // ignore collapse items
|
||||
innerSize += c.clientWidth;
|
||||
count++;
|
||||
}
|
||||
innerSize += 8 * count;
|
||||
this.#cachedInnerSize = innerSize;
|
||||
this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100);
|
||||
}
|
||||
return this.#cachedInnerSize;
|
||||
}
|
||||
calculateInnerSize(idx) {
|
||||
// Cache the inner size to prevent too much calculation when resizing the window
|
||||
clearTimeout(this.#cacheTimeout);
|
||||
if (this.#cachedInnerSize) {
|
||||
// Extend cache time
|
||||
this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100);
|
||||
} else {
|
||||
let innerSize = 0;
|
||||
let count = 1;
|
||||
for (const c of this.element.children) {
|
||||
if (c.classList.contains("comfyui-menu-push")) continue; // ignore right push
|
||||
if (idx && c.classList.contains("comfyui-menu-mobile-collapse")) continue; // ignore collapse items
|
||||
innerSize += c.clientWidth;
|
||||
count++;
|
||||
}
|
||||
innerSize += 8 * count;
|
||||
this.#cachedInnerSize = innerSize;
|
||||
this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100);
|
||||
}
|
||||
return this.#cachedInnerSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} defaultName
|
||||
*/
|
||||
getFilename(defaultName) {
|
||||
if (this.app.ui.settings.getSettingValue("Comfy.PromptFilename", true)) {
|
||||
defaultName = prompt("Save workflow as:", defaultName);
|
||||
if (!defaultName) return;
|
||||
if (!defaultName.toLowerCase().endsWith(".json")) {
|
||||
defaultName += ".json";
|
||||
}
|
||||
}
|
||||
return defaultName;
|
||||
}
|
||||
/**
|
||||
* @param {string} defaultName
|
||||
*/
|
||||
getFilename(defaultName) {
|
||||
if (this.app.ui.settings.getSettingValue("Comfy.PromptFilename", true)) {
|
||||
defaultName = prompt("Save workflow as:", defaultName);
|
||||
if (!defaultName) return;
|
||||
if (!defaultName.toLowerCase().endsWith(".json")) {
|
||||
defaultName += ".json";
|
||||
}
|
||||
}
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [filename]
|
||||
* @param { "workflow" | "output" } [promptProperty]
|
||||
*/
|
||||
async exportWorkflow(filename, promptProperty) {
|
||||
if (this.app.workflowManager.activeWorkflow?.path) {
|
||||
filename = this.app.workflowManager.activeWorkflow.name;
|
||||
}
|
||||
const p = await this.app.graphToPrompt();
|
||||
const json = JSON.stringify(p[promptProperty], null, 2);
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const file = this.getFilename(filename);
|
||||
if (!file) return;
|
||||
downloadBlob(file, blob);
|
||||
}
|
||||
/**
|
||||
* @param {string} [filename]
|
||||
* @param { "workflow" | "output" } [promptProperty]
|
||||
*/
|
||||
async exportWorkflow(filename, promptProperty) {
|
||||
if (this.app.workflowManager.activeWorkflow?.path) {
|
||||
filename = this.app.workflowManager.activeWorkflow.name;
|
||||
}
|
||||
const p = await this.app.graphToPrompt();
|
||||
const json = JSON.stringify(p[promptProperty], null, 2);
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const file = this.getFilename(filename);
|
||||
if (!file) return;
|
||||
downloadBlob(file, blob);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,20 @@ import { api } from "../../api";
|
||||
import { ComfyButton } from "../components/button";
|
||||
|
||||
export function getInteruptButton(visibility) {
|
||||
const btn = new ComfyButton({
|
||||
icon: "close",
|
||||
tooltip: "Cancel current generation",
|
||||
enabled: false,
|
||||
action: () => {
|
||||
api.interrupt();
|
||||
},
|
||||
classList: ["comfyui-button", "comfyui-interrupt-button", visibility],
|
||||
});
|
||||
const btn = new ComfyButton({
|
||||
icon: "close",
|
||||
tooltip: "Cancel current generation",
|
||||
enabled: false,
|
||||
action: () => {
|
||||
api.interrupt();
|
||||
},
|
||||
classList: ["comfyui-button", "comfyui-interrupt-button", visibility],
|
||||
});
|
||||
|
||||
api.addEventListener("status", ({ detail }) => {
|
||||
const sz = detail?.exec_info?.queue_remaining;
|
||||
btn.enabled = sz > 0;
|
||||
});
|
||||
api.addEventListener("status", ({ detail }) => {
|
||||
const sz = detail?.exec_info?.queue_remaining;
|
||||
btn.enabled = sz > 0;
|
||||
});
|
||||
|
||||
return btn;
|
||||
return btn;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,86 +8,86 @@ import { ComfyQueueOptions } from "./queueOptions";
|
||||
import { prop } from "../../utils";
|
||||
|
||||
export class ComfyQueueButton {
|
||||
element = $el("div.comfyui-queue-button");
|
||||
#internalQueueSize = 0;
|
||||
element = $el("div.comfyui-queue-button");
|
||||
#internalQueueSize = 0;
|
||||
|
||||
queuePrompt = async (e) => {
|
||||
this.#internalQueueSize += this.queueOptions.batchCount;
|
||||
// Hold shift to queue front, event is undefined when auto-queue is enabled
|
||||
await this.app.queuePrompt(e?.shiftKey ? -1 : 0, this.queueOptions.batchCount);
|
||||
};
|
||||
queuePrompt = async (e) => {
|
||||
this.#internalQueueSize += this.queueOptions.batchCount;
|
||||
// Hold shift to queue front, event is undefined when auto-queue is enabled
|
||||
await this.app.queuePrompt(e?.shiftKey ? -1 : 0, this.queueOptions.batchCount);
|
||||
};
|
||||
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.queueSizeElement = $el("span.comfyui-queue-count", {
|
||||
textContent: "?",
|
||||
});
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
this.queueSizeElement = $el("span.comfyui-queue-count", {
|
||||
textContent: "?",
|
||||
});
|
||||
|
||||
const queue = new ComfyButton({
|
||||
content: $el("div", [
|
||||
$el("span", {
|
||||
textContent: "Queue",
|
||||
}),
|
||||
this.queueSizeElement,
|
||||
]),
|
||||
icon: "play",
|
||||
classList: "comfyui-button",
|
||||
action: this.queuePrompt,
|
||||
});
|
||||
const queue = new ComfyButton({
|
||||
content: $el("div", [
|
||||
$el("span", {
|
||||
textContent: "Queue",
|
||||
}),
|
||||
this.queueSizeElement,
|
||||
]),
|
||||
icon: "play",
|
||||
classList: "comfyui-button",
|
||||
action: this.queuePrompt,
|
||||
});
|
||||
|
||||
this.queueOptions = new ComfyQueueOptions(app);
|
||||
this.queueOptions = new ComfyQueueOptions(app);
|
||||
|
||||
const btn = new ComfySplitButton(
|
||||
{
|
||||
primary: queue,
|
||||
mode: "click",
|
||||
position: "absolute",
|
||||
horizontal: "right",
|
||||
},
|
||||
this.queueOptions.element
|
||||
);
|
||||
btn.element.classList.add("primary");
|
||||
this.element.append(btn.element);
|
||||
const btn = new ComfySplitButton(
|
||||
{
|
||||
primary: queue,
|
||||
mode: "click",
|
||||
position: "absolute",
|
||||
horizontal: "right",
|
||||
},
|
||||
this.queueOptions.element
|
||||
);
|
||||
btn.element.classList.add("primary");
|
||||
this.element.append(btn.element);
|
||||
|
||||
this.autoQueueMode = prop(this, "autoQueueMode", "", () => {
|
||||
switch (this.autoQueueMode) {
|
||||
case "instant":
|
||||
queue.icon = "infinity";
|
||||
break;
|
||||
case "change":
|
||||
queue.icon = "auto-mode";
|
||||
break;
|
||||
default:
|
||||
queue.icon = "play";
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.autoQueueMode = prop(this, "autoQueueMode", "", () => {
|
||||
switch (this.autoQueueMode) {
|
||||
case "instant":
|
||||
queue.icon = "infinity";
|
||||
break;
|
||||
case "change":
|
||||
queue.icon = "auto-mode";
|
||||
break;
|
||||
default:
|
||||
queue.icon = "play";
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.queueOptions.addEventListener("autoQueueMode", (e) => (this.autoQueueMode = e["detail"]));
|
||||
this.queueOptions.addEventListener("autoQueueMode", (e) => (this.autoQueueMode = e["detail"]));
|
||||
|
||||
api.addEventListener("graphChanged", () => {
|
||||
if (this.autoQueueMode === "change") {
|
||||
if (this.#internalQueueSize) {
|
||||
this.graphHasChanged = true;
|
||||
} else {
|
||||
this.graphHasChanged = false;
|
||||
this.queuePrompt();
|
||||
}
|
||||
}
|
||||
});
|
||||
api.addEventListener("graphChanged", () => {
|
||||
if (this.autoQueueMode === "change") {
|
||||
if (this.#internalQueueSize) {
|
||||
this.graphHasChanged = true;
|
||||
} else {
|
||||
this.graphHasChanged = false;
|
||||
this.queuePrompt();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.addEventListener("status", ({ detail }) => {
|
||||
this.#internalQueueSize = detail?.exec_info?.queue_remaining;
|
||||
if (this.#internalQueueSize != null) {
|
||||
this.queueSizeElement.textContent = this.#internalQueueSize > 99 ? "99+" : this.#internalQueueSize + "";
|
||||
this.queueSizeElement.title = `${this.#internalQueueSize} prompts in queue`;
|
||||
if (!this.#internalQueueSize && !app.lastExecutionError) {
|
||||
if (this.autoQueueMode === "instant" || (this.autoQueueMode === "change" && this.graphHasChanged)) {
|
||||
this.graphHasChanged = false;
|
||||
this.queuePrompt();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
api.addEventListener("status", ({ detail }) => {
|
||||
this.#internalQueueSize = detail?.exec_info?.queue_remaining;
|
||||
if (this.#internalQueueSize != null) {
|
||||
this.queueSizeElement.textContent = this.#internalQueueSize > 99 ? "99+" : this.#internalQueueSize + "";
|
||||
this.queueSizeElement.title = `${this.#internalQueueSize} prompts in queue`;
|
||||
if (!this.#internalQueueSize && !app.lastExecutionError) {
|
||||
if (this.autoQueueMode === "instant" || (this.autoQueueMode === "change" && this.graphHasChanged)) {
|
||||
this.graphHasChanged = false;
|
||||
this.queuePrompt();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,74 +4,74 @@ import { $el } from "../../ui";
|
||||
import { prop } from "../../utils";
|
||||
|
||||
export class ComfyQueueOptions extends EventTarget {
|
||||
element = $el("div.comfyui-queue-options");
|
||||
element = $el("div.comfyui-queue-options");
|
||||
|
||||
constructor(app) {
|
||||
super();
|
||||
this.app = app;
|
||||
constructor(app) {
|
||||
super();
|
||||
this.app = app;
|
||||
|
||||
this.batchCountInput = $el("input", {
|
||||
className: "comfyui-queue-batch-value",
|
||||
type: "number",
|
||||
min: "1",
|
||||
value: "1",
|
||||
oninput: () => (this.batchCount = +this.batchCountInput.value),
|
||||
});
|
||||
this.batchCountInput = $el("input", {
|
||||
className: "comfyui-queue-batch-value",
|
||||
type: "number",
|
||||
min: "1",
|
||||
value: "1",
|
||||
oninput: () => (this.batchCount = +this.batchCountInput.value),
|
||||
});
|
||||
|
||||
this.batchCountRange = $el("input", {
|
||||
type: "range",
|
||||
min: "1",
|
||||
max: "100",
|
||||
value: "1",
|
||||
oninput: () => (this.batchCount = +this.batchCountRange.value),
|
||||
});
|
||||
this.batchCountRange = $el("input", {
|
||||
type: "range",
|
||||
min: "1",
|
||||
max: "100",
|
||||
value: "1",
|
||||
oninput: () => (this.batchCount = +this.batchCountRange.value),
|
||||
});
|
||||
|
||||
this.element.append(
|
||||
$el("div.comfyui-queue-batch", [
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
textContent: "Batch count: ",
|
||||
},
|
||||
this.batchCountInput
|
||||
),
|
||||
this.batchCountRange,
|
||||
])
|
||||
);
|
||||
this.element.append(
|
||||
$el("div.comfyui-queue-batch", [
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
textContent: "Batch count: ",
|
||||
},
|
||||
this.batchCountInput
|
||||
),
|
||||
this.batchCountRange,
|
||||
])
|
||||
);
|
||||
|
||||
const createOption = (text, value, checked = false) =>
|
||||
$el(
|
||||
"label",
|
||||
{ textContent: text },
|
||||
$el("input", {
|
||||
type: "radio",
|
||||
name: "AutoQueueMode",
|
||||
checked,
|
||||
value,
|
||||
oninput: (e) => (this.autoQueueMode = e.target["value"]),
|
||||
})
|
||||
);
|
||||
const createOption = (text, value, checked = false) =>
|
||||
$el(
|
||||
"label",
|
||||
{ textContent: text },
|
||||
$el("input", {
|
||||
type: "radio",
|
||||
name: "AutoQueueMode",
|
||||
checked,
|
||||
value,
|
||||
oninput: (e) => (this.autoQueueMode = e.target["value"]),
|
||||
})
|
||||
);
|
||||
|
||||
this.autoQueueEl = $el("div.comfyui-queue-mode", [
|
||||
$el("span", "Auto Queue:"),
|
||||
createOption("Disabled", "", true),
|
||||
createOption("Instant", "instant"),
|
||||
createOption("On Change", "change"),
|
||||
]);
|
||||
this.autoQueueEl = $el("div.comfyui-queue-mode", [
|
||||
$el("span", "Auto Queue:"),
|
||||
createOption("Disabled", "", true),
|
||||
createOption("Instant", "instant"),
|
||||
createOption("On Change", "change"),
|
||||
]);
|
||||
|
||||
this.element.append(this.autoQueueEl);
|
||||
this.element.append(this.autoQueueEl);
|
||||
|
||||
this.batchCount = prop(this, "batchCount", 1, () => {
|
||||
this.batchCountInput.value = this.batchCount + "";
|
||||
this.batchCountRange.value = this.batchCount + "";
|
||||
});
|
||||
this.batchCount = prop(this, "batchCount", 1, () => {
|
||||
this.batchCountInput.value = this.batchCount + "";
|
||||
this.batchCountRange.value = this.batchCount + "";
|
||||
});
|
||||
|
||||
this.autoQueueMode = prop(this, "autoQueueMode", "Disabled", () => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("autoQueueMode", {
|
||||
detail: this.autoQueueMode,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
this.autoQueueMode = prop(this, "autoQueueMode", "Disabled", () => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("autoQueueMode", {
|
||||
detail: this.autoQueueMode,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,24 @@ import { ComfyButton } from "../components/button";
|
||||
import { ComfyViewList, ComfyViewListButton } from "./viewList";
|
||||
|
||||
export class ComfyViewHistoryButton extends ComfyViewListButton {
|
||||
constructor(app) {
|
||||
super(app, {
|
||||
button: new ComfyButton({
|
||||
content: "View History",
|
||||
icon: "history",
|
||||
tooltip: "View history",
|
||||
classList: "comfyui-button comfyui-history-button",
|
||||
}),
|
||||
list: ComfyViewHistoryList,
|
||||
mode: "History",
|
||||
});
|
||||
}
|
||||
constructor(app) {
|
||||
super(app, {
|
||||
button: new ComfyButton({
|
||||
content: "View History",
|
||||
icon: "history",
|
||||
tooltip: "View history",
|
||||
classList: "comfyui-button comfyui-history-button",
|
||||
}),
|
||||
list: ComfyViewHistoryList,
|
||||
mode: "History",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfyViewHistoryList extends ComfyViewList {
|
||||
async loadItems() {
|
||||
const items = await super.loadItems();
|
||||
items["History"].reverse();
|
||||
return items;
|
||||
}
|
||||
async loadItems() {
|
||||
const items = await super.loadItems();
|
||||
items["History"].reverse();
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,198 +6,198 @@ import { api } from "../../api";
|
||||
import { ComfyPopup } from "../components/popup";
|
||||
|
||||
export class ComfyViewListButton {
|
||||
get open() {
|
||||
return this.popup.open;
|
||||
}
|
||||
get open() {
|
||||
return this.popup.open;
|
||||
}
|
||||
|
||||
set open(open) {
|
||||
this.popup.open = open;
|
||||
}
|
||||
set open(open) {
|
||||
this.popup.open = open;
|
||||
}
|
||||
|
||||
constructor(app, { button, list, mode }) {
|
||||
this.app = app;
|
||||
this.button = button;
|
||||
this.element = $el("div.comfyui-button-wrapper", this.button.element);
|
||||
this.popup = new ComfyPopup({
|
||||
target: this.element,
|
||||
container: this.element,
|
||||
horizontal: "right",
|
||||
});
|
||||
this.list = new (list ?? ComfyViewList)(app, mode, this.popup);
|
||||
this.popup.children = [this.list.element];
|
||||
this.popup.addEventListener("open", () => {
|
||||
this.list.update();
|
||||
});
|
||||
this.popup.addEventListener("close", () => {
|
||||
this.list.close();
|
||||
});
|
||||
this.button.withPopup(this.popup);
|
||||
constructor(app, { button, list, mode }) {
|
||||
this.app = app;
|
||||
this.button = button;
|
||||
this.element = $el("div.comfyui-button-wrapper", this.button.element);
|
||||
this.popup = new ComfyPopup({
|
||||
target: this.element,
|
||||
container: this.element,
|
||||
horizontal: "right",
|
||||
});
|
||||
this.list = new (list ?? ComfyViewList)(app, mode, this.popup);
|
||||
this.popup.children = [this.list.element];
|
||||
this.popup.addEventListener("open", () => {
|
||||
this.list.update();
|
||||
});
|
||||
this.popup.addEventListener("close", () => {
|
||||
this.list.close();
|
||||
});
|
||||
this.button.withPopup(this.popup);
|
||||
|
||||
api.addEventListener("status", () => {
|
||||
if (this.popup.open) {
|
||||
this.popup.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
api.addEventListener("status", () => {
|
||||
if (this.popup.open) {
|
||||
this.popup.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfyViewList {
|
||||
popup;
|
||||
popup;
|
||||
|
||||
constructor(app, mode, popup) {
|
||||
this.app = app;
|
||||
this.mode = mode;
|
||||
this.popup = popup;
|
||||
this.type = mode.toLowerCase();
|
||||
constructor(app, mode, popup) {
|
||||
this.app = app;
|
||||
this.mode = mode;
|
||||
this.popup = popup;
|
||||
this.type = mode.toLowerCase();
|
||||
|
||||
this.items = $el(`div.comfyui-${this.type}-items.comfyui-view-list-items`);
|
||||
this.clear = new ComfyButton({
|
||||
icon: "cancel",
|
||||
content: "Clear",
|
||||
action: async () => {
|
||||
this.showSpinner(false);
|
||||
await api.clearItems(this.type);
|
||||
await this.update();
|
||||
},
|
||||
});
|
||||
this.items = $el(`div.comfyui-${this.type}-items.comfyui-view-list-items`);
|
||||
this.clear = new ComfyButton({
|
||||
icon: "cancel",
|
||||
content: "Clear",
|
||||
action: async () => {
|
||||
this.showSpinner(false);
|
||||
await api.clearItems(this.type);
|
||||
await this.update();
|
||||
},
|
||||
});
|
||||
|
||||
this.refresh = new ComfyButton({
|
||||
icon: "refresh",
|
||||
content: "Refresh",
|
||||
action: async () => {
|
||||
await this.update(false);
|
||||
},
|
||||
});
|
||||
this.refresh = new ComfyButton({
|
||||
icon: "refresh",
|
||||
content: "Refresh",
|
||||
action: async () => {
|
||||
await this.update(false);
|
||||
},
|
||||
});
|
||||
|
||||
this.element = $el(`div.comfyui-${this.type}-popup.comfyui-view-list-popup`, [
|
||||
$el("h3", mode),
|
||||
$el("header", [this.clear.element, this.refresh.element]),
|
||||
this.items,
|
||||
]);
|
||||
this.element = $el(`div.comfyui-${this.type}-popup.comfyui-view-list-popup`, [
|
||||
$el("h3", mode),
|
||||
$el("header", [this.clear.element, this.refresh.element]),
|
||||
this.items,
|
||||
]);
|
||||
|
||||
api.addEventListener("status", () => {
|
||||
if (this.popup.open) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
api.addEventListener("status", () => {
|
||||
if (this.popup.open) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.items.replaceChildren();
|
||||
}
|
||||
async close() {
|
||||
this.items.replaceChildren();
|
||||
}
|
||||
|
||||
async update(resize = true) {
|
||||
this.showSpinner(resize);
|
||||
const res = await this.loadItems();
|
||||
let any = false;
|
||||
async update(resize = true) {
|
||||
this.showSpinner(resize);
|
||||
const res = await this.loadItems();
|
||||
let any = false;
|
||||
|
||||
const names = Object.keys(res);
|
||||
const sections = names
|
||||
.map((section) => {
|
||||
const items = res[section];
|
||||
if (items?.length) {
|
||||
any = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const names = Object.keys(res);
|
||||
const sections = names
|
||||
.map((section) => {
|
||||
const items = res[section];
|
||||
if (items?.length) {
|
||||
any = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
if (names.length > 1) {
|
||||
rows.push($el("h5", section));
|
||||
}
|
||||
rows.push(...items.flatMap((item) => this.createRow(item, section)));
|
||||
return $el("section", rows);
|
||||
})
|
||||
.filter(Boolean);
|
||||
const rows = [];
|
||||
if (names.length > 1) {
|
||||
rows.push($el("h5", section));
|
||||
}
|
||||
rows.push(...items.flatMap((item) => this.createRow(item, section)));
|
||||
return $el("section", rows);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (any) {
|
||||
this.items.replaceChildren(...sections);
|
||||
} else {
|
||||
this.items.replaceChildren($el("h5", "None"));
|
||||
}
|
||||
if (any) {
|
||||
this.items.replaceChildren(...sections);
|
||||
} else {
|
||||
this.items.replaceChildren($el("h5", "None"));
|
||||
}
|
||||
|
||||
this.popup.update();
|
||||
this.clear.enabled = this.refresh.enabled = true;
|
||||
this.element.style.removeProperty("height");
|
||||
}
|
||||
this.popup.update();
|
||||
this.clear.enabled = this.refresh.enabled = true;
|
||||
this.element.style.removeProperty("height");
|
||||
}
|
||||
|
||||
showSpinner(resize = true) {
|
||||
// if (!this.spinner) {
|
||||
// this.spinner = createSpinner();
|
||||
// }
|
||||
// if (!resize) {
|
||||
// this.element.style.height = this.element.clientHeight + "px";
|
||||
// }
|
||||
// this.clear.enabled = this.refresh.enabled = false;
|
||||
// this.items.replaceChildren(
|
||||
// $el(
|
||||
// "div",
|
||||
// {
|
||||
// style: {
|
||||
// fontSize: "18px",
|
||||
// },
|
||||
// },
|
||||
// this.spinner
|
||||
// )
|
||||
// );
|
||||
// this.popup.update();
|
||||
}
|
||||
showSpinner(resize = true) {
|
||||
// if (!this.spinner) {
|
||||
// this.spinner = createSpinner();
|
||||
// }
|
||||
// if (!resize) {
|
||||
// this.element.style.height = this.element.clientHeight + "px";
|
||||
// }
|
||||
// this.clear.enabled = this.refresh.enabled = false;
|
||||
// this.items.replaceChildren(
|
||||
// $el(
|
||||
// "div",
|
||||
// {
|
||||
// style: {
|
||||
// fontSize: "18px",
|
||||
// },
|
||||
// },
|
||||
// this.spinner
|
||||
// )
|
||||
// );
|
||||
// this.popup.update();
|
||||
}
|
||||
|
||||
async loadItems() {
|
||||
return await api.getItems(this.type);
|
||||
}
|
||||
async loadItems() {
|
||||
return await api.getItems(this.type);
|
||||
}
|
||||
|
||||
getRow(item, section) {
|
||||
return {
|
||||
text: item.prompt[0] + "",
|
||||
actions: [
|
||||
{
|
||||
text: "Load",
|
||||
action: async () => {
|
||||
try {
|
||||
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
|
||||
if (item.outputs) {
|
||||
this.app.nodeOutputs = item.outputs;
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error loading workflow: " + error.message);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
action: async () => {
|
||||
try {
|
||||
await api.deleteItem(this.type, item.prompt[1]);
|
||||
this.update();
|
||||
} catch (error) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
getRow(item, section) {
|
||||
return {
|
||||
text: item.prompt[0] + "",
|
||||
actions: [
|
||||
{
|
||||
text: "Load",
|
||||
action: async () => {
|
||||
try {
|
||||
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
|
||||
if (item.outputs) {
|
||||
this.app.nodeOutputs = item.outputs;
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error loading workflow: " + error.message);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
action: async () => {
|
||||
try {
|
||||
await api.deleteItem(this.type, item.prompt[1]);
|
||||
this.update();
|
||||
} catch (error) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
createRow = (item, section) => {
|
||||
const row = this.getRow(item, section);
|
||||
return [
|
||||
$el("span", row.text),
|
||||
...row.actions.map(
|
||||
(a) =>
|
||||
new ComfyButton({
|
||||
content: a.text,
|
||||
action: async (e, btn) => {
|
||||
btn.enabled = false;
|
||||
try {
|
||||
await a.action();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
btn.enabled = true;
|
||||
}
|
||||
},
|
||||
}).element
|
||||
),
|
||||
];
|
||||
};
|
||||
createRow = (item, section) => {
|
||||
const row = this.getRow(item, section);
|
||||
return [
|
||||
$el("span", row.text),
|
||||
...row.actions.map(
|
||||
(a) =>
|
||||
new ComfyButton({
|
||||
content: a.text,
|
||||
action: async (e, btn) => {
|
||||
btn.enabled = false;
|
||||
try {
|
||||
await a.action();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
btn.enabled = true;
|
||||
}
|
||||
},
|
||||
}).element
|
||||
),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,51 +5,51 @@ import { ComfyViewList, ComfyViewListButton } from "./viewList";
|
||||
import { api } from "../../api";
|
||||
|
||||
export class ComfyViewQueueButton extends ComfyViewListButton {
|
||||
constructor(app) {
|
||||
super(app, {
|
||||
button: new ComfyButton({
|
||||
content: "View Queue",
|
||||
icon: "format-list-numbered",
|
||||
tooltip: "View queue",
|
||||
classList: "comfyui-button comfyui-queue-button",
|
||||
}),
|
||||
list: ComfyViewQueueList,
|
||||
mode: "Queue",
|
||||
});
|
||||
}
|
||||
constructor(app) {
|
||||
super(app, {
|
||||
button: new ComfyButton({
|
||||
content: "View Queue",
|
||||
icon: "format-list-numbered",
|
||||
tooltip: "View queue",
|
||||
classList: "comfyui-button comfyui-queue-button",
|
||||
}),
|
||||
list: ComfyViewQueueList,
|
||||
mode: "Queue",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfyViewQueueList extends ComfyViewList {
|
||||
getRow = (item, section) => {
|
||||
if (section !== "Running") {
|
||||
return super.getRow(item, section);
|
||||
}
|
||||
return {
|
||||
text: item.prompt[0] + "",
|
||||
actions: [
|
||||
{
|
||||
text: "Load",
|
||||
action: async () => {
|
||||
try {
|
||||
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
|
||||
if (item.outputs) {
|
||||
this.app.nodeOutputs = item.outputs;
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error loading workflow: " + error.message);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
action: async () => {
|
||||
try {
|
||||
await api.interrupt();
|
||||
} catch (error) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
getRow = (item, section) => {
|
||||
if (section !== "Running") {
|
||||
return super.getRow(item, section);
|
||||
}
|
||||
return {
|
||||
text: item.prompt[0] + "",
|
||||
actions: [
|
||||
{
|
||||
text: "Load",
|
||||
action: async () => {
|
||||
try {
|
||||
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
|
||||
if (item.outputs) {
|
||||
this.app.nodeOutputs = item.outputs;
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error loading workflow: " + error.message);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
action: async () => {
|
||||
try {
|
||||
await api.interrupt();
|
||||
} catch (error) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,359 +5,359 @@ import type { ComfyApp } from "../app";
|
||||
|
||||
/* The Setting entry stored in `ComfySettingsDialog` */
|
||||
interface Setting {
|
||||
id: string;
|
||||
onChange?: (value: any, oldValue?: any) => void;
|
||||
name: string;
|
||||
render: () => HTMLElement;
|
||||
id: string;
|
||||
onChange?: (value: any, oldValue?: any) => void;
|
||||
name: string;
|
||||
render: () => HTMLElement;
|
||||
}
|
||||
|
||||
interface SettingOption {
|
||||
text: string;
|
||||
value?: string;
|
||||
text: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface SettingParams {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string | ((name: string, setter: (v: any) => void, value: any, attrs: any) => HTMLElement);
|
||||
defaultValue: any;
|
||||
onChange?: (newValue: any, oldValue?: any) => void;
|
||||
attrs?: any;
|
||||
tooltip?: string;
|
||||
options?: SettingOption[] | ((value: any) => SettingOption[]);
|
||||
id: string;
|
||||
name: string;
|
||||
type: string | ((name: string, setter: (v: any) => void, value: any, attrs: any) => HTMLElement);
|
||||
defaultValue: any;
|
||||
onChange?: (newValue: any, oldValue?: any) => void;
|
||||
attrs?: any;
|
||||
tooltip?: string;
|
||||
options?: SettingOption[] | ((value: any) => SettingOption[]);
|
||||
}
|
||||
|
||||
export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
app: ComfyApp;
|
||||
settingsValues: any;
|
||||
settingsLookup: Record<string, Setting>;
|
||||
app: ComfyApp;
|
||||
settingsValues: any;
|
||||
settingsLookup: Record<string, Setting>;
|
||||
|
||||
constructor(app) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.settingsValues = {};
|
||||
this.settingsLookup = {};
|
||||
this.element = $el(
|
||||
"dialog",
|
||||
{
|
||||
id: "comfy-settings-dialog",
|
||||
parent: document.body,
|
||||
},
|
||||
[
|
||||
$el("table.comfy-modal-content.comfy-table", [
|
||||
$el(
|
||||
"caption",
|
||||
{ textContent: "Settings" },
|
||||
$el("button.comfy-btn", {
|
||||
type: "button",
|
||||
textContent: "\u00d7",
|
||||
onclick: () => {
|
||||
this.element.close();
|
||||
},
|
||||
})
|
||||
),
|
||||
$el("tbody", { $: (tbody) => (this.textElement = tbody) }),
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Close",
|
||||
style: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
onclick: () => {
|
||||
this.element.close();
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]
|
||||
) as HTMLDialogElement;
|
||||
}
|
||||
constructor(app) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.settingsValues = {};
|
||||
this.settingsLookup = {};
|
||||
this.element = $el(
|
||||
"dialog",
|
||||
{
|
||||
id: "comfy-settings-dialog",
|
||||
parent: document.body,
|
||||
},
|
||||
[
|
||||
$el("table.comfy-modal-content.comfy-table", [
|
||||
$el(
|
||||
"caption",
|
||||
{ textContent: "Settings" },
|
||||
$el("button.comfy-btn", {
|
||||
type: "button",
|
||||
textContent: "\u00d7",
|
||||
onclick: () => {
|
||||
this.element.close();
|
||||
},
|
||||
})
|
||||
),
|
||||
$el("tbody", { $: (tbody) => (this.textElement = tbody) }),
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Close",
|
||||
style: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
onclick: () => {
|
||||
this.element.close();
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]
|
||||
) as HTMLDialogElement;
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return Object.values(this.settingsLookup);
|
||||
}
|
||||
get settings() {
|
||||
return Object.values(this.settingsLookup);
|
||||
}
|
||||
|
||||
#dispatchChange(id, value, oldValue?) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(id + ".change", {
|
||||
detail: {
|
||||
value,
|
||||
oldValue
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
#dispatchChange(id, value, oldValue?) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(id + ".change", {
|
||||
detail: {
|
||||
value,
|
||||
oldValue
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
this.settingsValues = localStorage;
|
||||
} else {
|
||||
this.settingsValues = await api.getSettings();
|
||||
}
|
||||
async load() {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
this.settingsValues = localStorage;
|
||||
} else {
|
||||
this.settingsValues = await api.getSettings();
|
||||
}
|
||||
|
||||
// Trigger onChange for any settings added before load
|
||||
for (const id in this.settingsLookup) {
|
||||
const value = this.settingsValues[this.getId(id)];
|
||||
this.settingsLookup[id].onChange?.(value);
|
||||
this.#dispatchChange(id, value);
|
||||
}
|
||||
}
|
||||
// Trigger onChange for any settings added before load
|
||||
for (const id in this.settingsLookup) {
|
||||
const value = this.settingsValues[this.getId(id)];
|
||||
this.settingsLookup[id].onChange?.(value);
|
||||
this.#dispatchChange(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
getId(id) {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
id = "Comfy.Settings." + id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
getId(id) {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
id = "Comfy.Settings." + id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
getSettingValue(id, defaultValue?) {
|
||||
let value = this.settingsValues[this.getId(id)];
|
||||
if (value != null) {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return value ?? defaultValue;
|
||||
}
|
||||
getSettingValue(id, defaultValue?) {
|
||||
let value = this.settingsValues[this.getId(id)];
|
||||
if (value != null) {
|
||||
if (this.app.storageLocation === "browser") {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return value ?? defaultValue;
|
||||
}
|
||||
|
||||
async setSettingValueAsync(id, value) {
|
||||
const json = JSON.stringify(value);
|
||||
localStorage["Comfy.Settings." + id] = json; // backwards compatibility for extensions keep setting in storage
|
||||
async setSettingValueAsync(id, value) {
|
||||
const json = JSON.stringify(value);
|
||||
localStorage["Comfy.Settings." + id] = json; // backwards compatibility for extensions keep setting in storage
|
||||
|
||||
let oldValue = this.getSettingValue(id, undefined);
|
||||
this.settingsValues[this.getId(id)] = value;
|
||||
let oldValue = this.getSettingValue(id, undefined);
|
||||
this.settingsValues[this.getId(id)] = value;
|
||||
|
||||
if (id in this.settingsLookup) {
|
||||
this.settingsLookup[id].onChange?.(value, oldValue);
|
||||
}
|
||||
this.#dispatchChange(id, value, oldValue);
|
||||
if (id in this.settingsLookup) {
|
||||
this.settingsLookup[id].onChange?.(value, oldValue);
|
||||
}
|
||||
this.#dispatchChange(id, value, oldValue);
|
||||
|
||||
await api.storeSetting(id, value);
|
||||
}
|
||||
await api.storeSetting(id, value);
|
||||
}
|
||||
|
||||
setSettingValue(id, value) {
|
||||
this.setSettingValueAsync(id, value).catch((err) => {
|
||||
alert(`Error saving setting '${id}'`);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
setSettingValue(id, value) {
|
||||
this.setSettingValueAsync(id, value).catch((err) => {
|
||||
alert(`Error saving setting '${id}'`);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
addSetting(params: SettingParams) {
|
||||
const { id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined } = params;
|
||||
if (!id) {
|
||||
throw new Error("Settings must have an ID");
|
||||
}
|
||||
addSetting(params: SettingParams) {
|
||||
const { id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined } = params;
|
||||
if (!id) {
|
||||
throw new Error("Settings must have an ID");
|
||||
}
|
||||
|
||||
if (id in this.settingsLookup) {
|
||||
throw new Error(`Setting ${id} of type ${type} must have a unique ID.`);
|
||||
}
|
||||
if (id in this.settingsLookup) {
|
||||
throw new Error(`Setting ${id} of type ${type} must have a unique ID.`);
|
||||
}
|
||||
|
||||
let skipOnChange = false;
|
||||
let value = this.getSettingValue(id);
|
||||
if (value == null) {
|
||||
if (this.app.isNewUserSession) {
|
||||
// Check if we have a localStorage value but not a setting value and we are a new user
|
||||
const localValue = localStorage["Comfy.Settings." + id];
|
||||
if (localValue) {
|
||||
value = JSON.parse(localValue);
|
||||
this.setSettingValue(id, value); // Store on the server
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
value = defaultValue;
|
||||
}
|
||||
}
|
||||
let skipOnChange = false;
|
||||
let value = this.getSettingValue(id);
|
||||
if (value == null) {
|
||||
if (this.app.isNewUserSession) {
|
||||
// Check if we have a localStorage value but not a setting value and we are a new user
|
||||
const localValue = localStorage["Comfy.Settings." + id];
|
||||
if (localValue) {
|
||||
value = JSON.parse(localValue);
|
||||
this.setSettingValue(id, value); // Store on the server
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
value = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger initial setting of value
|
||||
if (!skipOnChange) {
|
||||
onChange?.(value, undefined);
|
||||
}
|
||||
// Trigger initial setting of value
|
||||
if (!skipOnChange) {
|
||||
onChange?.(value, undefined);
|
||||
}
|
||||
|
||||
this.settingsLookup[id] = {
|
||||
id,
|
||||
onChange,
|
||||
name,
|
||||
render: () => {
|
||||
if (type === "hidden") return;
|
||||
this.settingsLookup[id] = {
|
||||
id,
|
||||
onChange,
|
||||
name,
|
||||
render: () => {
|
||||
if (type === "hidden") return;
|
||||
|
||||
const setter = (v) => {
|
||||
if (onChange) {
|
||||
onChange(v, value);
|
||||
}
|
||||
const setter = (v) => {
|
||||
if (onChange) {
|
||||
onChange(v, value);
|
||||
}
|
||||
|
||||
this.setSettingValue(id, v);
|
||||
value = v;
|
||||
};
|
||||
value = this.getSettingValue(id, defaultValue);
|
||||
this.setSettingValue(id, v);
|
||||
value = v;
|
||||
};
|
||||
value = this.getSettingValue(id, defaultValue);
|
||||
|
||||
let element;
|
||||
const htmlID = id.replaceAll(".", "-");
|
||||
let element;
|
||||
const htmlID = id.replaceAll(".", "-");
|
||||
|
||||
const labelCell = $el("td", [
|
||||
$el("label", {
|
||||
for: htmlID,
|
||||
classList: [tooltip !== "" ? "comfy-tooltip-indicator" : ""],
|
||||
textContent: name,
|
||||
}),
|
||||
]);
|
||||
const labelCell = $el("td", [
|
||||
$el("label", {
|
||||
for: htmlID,
|
||||
classList: [tooltip !== "" ? "comfy-tooltip-indicator" : ""],
|
||||
textContent: name,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (typeof type === "function") {
|
||||
element = type(name, setter, value, attrs);
|
||||
} else {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
id: htmlID,
|
||||
type: "checkbox",
|
||||
checked: value,
|
||||
onchange: (event) => {
|
||||
const isChecked = event.target.checked;
|
||||
if (onChange !== undefined) {
|
||||
onChange(isChecked);
|
||||
}
|
||||
this.setSettingValue(id, isChecked);
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "number":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
type,
|
||||
value,
|
||||
id: htmlID,
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
...attrs,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "slider":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "grid",
|
||||
gridAutoFlow: "column",
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
...attrs,
|
||||
value,
|
||||
type: "range",
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
e.target.nextElementSibling.value = e.target.value;
|
||||
},
|
||||
}),
|
||||
$el("input", {
|
||||
...attrs,
|
||||
value,
|
||||
id: htmlID,
|
||||
type: "number",
|
||||
style: { maxWidth: "4rem" },
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
e.target.previousElementSibling.value = e.target.value;
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "combo":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el(
|
||||
"select",
|
||||
{
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
},
|
||||
(typeof options === "function" ? options(value) : options || []).map((opt) => {
|
||||
if (typeof opt === "string") {
|
||||
opt = { text: opt };
|
||||
}
|
||||
const v = opt.value ?? opt.text;
|
||||
return $el("option", {
|
||||
value: v,
|
||||
textContent: opt.text,
|
||||
selected: value + "" === v + "",
|
||||
});
|
||||
})
|
||||
),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "text":
|
||||
default:
|
||||
if (type !== "text") {
|
||||
console.warn(`Unsupported setting type '${type}, defaulting to text`);
|
||||
}
|
||||
if (typeof type === "function") {
|
||||
element = type(name, setter, value, attrs);
|
||||
} else {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
id: htmlID,
|
||||
type: "checkbox",
|
||||
checked: value,
|
||||
onchange: (event) => {
|
||||
const isChecked = event.target.checked;
|
||||
if (onChange !== undefined) {
|
||||
onChange(isChecked);
|
||||
}
|
||||
this.setSettingValue(id, isChecked);
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "number":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
type,
|
||||
value,
|
||||
id: htmlID,
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
...attrs,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "slider":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "grid",
|
||||
gridAutoFlow: "column",
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
...attrs,
|
||||
value,
|
||||
type: "range",
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
e.target.nextElementSibling.value = e.target.value;
|
||||
},
|
||||
}),
|
||||
$el("input", {
|
||||
...attrs,
|
||||
value,
|
||||
id: htmlID,
|
||||
type: "number",
|
||||
style: { maxWidth: "4rem" },
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
e.target.previousElementSibling.value = e.target.value;
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "combo":
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el(
|
||||
"select",
|
||||
{
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
},
|
||||
(typeof options === "function" ? options(value) : options || []).map((opt) => {
|
||||
if (typeof opt === "string") {
|
||||
opt = { text: opt };
|
||||
}
|
||||
const v = opt.value ?? opt.text;
|
||||
return $el("option", {
|
||||
value: v,
|
||||
textContent: opt.text,
|
||||
selected: value + "" === v + "",
|
||||
});
|
||||
})
|
||||
),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
case "text":
|
||||
default:
|
||||
if (type !== "text") {
|
||||
console.warn(`Unsupported setting type '${type}, defaulting to text`);
|
||||
}
|
||||
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
value,
|
||||
id: htmlID,
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
...attrs,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tooltip) {
|
||||
element.title = tooltip;
|
||||
}
|
||||
element = $el("tr", [
|
||||
labelCell,
|
||||
$el("td", [
|
||||
$el("input", {
|
||||
value,
|
||||
id: htmlID,
|
||||
oninput: (e) => {
|
||||
setter(e.target.value);
|
||||
},
|
||||
...attrs,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tooltip) {
|
||||
element.title = tooltip;
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
} as Setting;
|
||||
return element;
|
||||
},
|
||||
} as Setting;
|
||||
|
||||
const self = this;
|
||||
return {
|
||||
get value() {
|
||||
return self.getSettingValue(id, defaultValue);
|
||||
},
|
||||
set value(v) {
|
||||
self.setSettingValue(id, v);
|
||||
},
|
||||
};
|
||||
}
|
||||
const self = this;
|
||||
return {
|
||||
get value() {
|
||||
return self.getSettingValue(id, defaultValue);
|
||||
},
|
||||
set value(v) {
|
||||
self.setSettingValue(id, v);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
show() {
|
||||
this.textElement.replaceChildren(
|
||||
$el(
|
||||
"tr",
|
||||
{
|
||||
style: { display: "none" },
|
||||
},
|
||||
[$el("th"), $el("th", { style: { width: "33%" } })]
|
||||
),
|
||||
...this.settings.sort((a, b) => a.name.localeCompare(b.name)).map((s) => s.render()).filter(Boolean)
|
||||
);
|
||||
this.element.showModal();
|
||||
}
|
||||
show() {
|
||||
this.textElement.replaceChildren(
|
||||
$el(
|
||||
"tr",
|
||||
{
|
||||
style: { display: "none" },
|
||||
},
|
||||
[$el("th"), $el("th", { style: { width: "33%" } })]
|
||||
),
|
||||
...this.settings.sort((a, b) => a.name.localeCompare(b.name)).map((s) => s.render()).filter(Boolean)
|
||||
);
|
||||
this.element.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0.15em solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0.15em solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.lds-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes lds-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import "./spinner.css";
|
||||
|
||||
|
||||
export function createSpinner() {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `<div class="lds-ring"><div></div><div></div><div></div><div></div></div>`;
|
||||
return div.firstElementChild;
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `<div class="lds-ring"><div></div><div></div><div></div><div></div></div>`;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
|
||||
@@ -11,52 +11,52 @@ import { $el } from "../ui";
|
||||
* @param { (e: { item: ToggleSwitchItem, prev?: ToggleSwitchItem }) => void } [opts.onChange]
|
||||
*/
|
||||
export function toggleSwitch(name, items, e?) {
|
||||
const onChange = e?.onChange;
|
||||
const onChange = e?.onChange;
|
||||
|
||||
let selectedIndex;
|
||||
let elements;
|
||||
let selectedIndex;
|
||||
let elements;
|
||||
|
||||
function updateSelected(index) {
|
||||
if (selectedIndex != null) {
|
||||
elements[selectedIndex].classList.remove("comfy-toggle-selected");
|
||||
}
|
||||
onChange?.({ item: items[index], prev: selectedIndex == null ? undefined : items[selectedIndex] });
|
||||
selectedIndex = index;
|
||||
elements[selectedIndex].classList.add("comfy-toggle-selected");
|
||||
}
|
||||
function updateSelected(index) {
|
||||
if (selectedIndex != null) {
|
||||
elements[selectedIndex].classList.remove("comfy-toggle-selected");
|
||||
}
|
||||
onChange?.({ item: items[index], prev: selectedIndex == null ? undefined : items[selectedIndex] });
|
||||
selectedIndex = index;
|
||||
elements[selectedIndex].classList.add("comfy-toggle-selected");
|
||||
}
|
||||
|
||||
elements = items.map((item, i) => {
|
||||
if (typeof item === "string") item = { text: item };
|
||||
if (!item.value) item.value = item.text;
|
||||
elements = items.map((item, i) => {
|
||||
if (typeof item === "string") item = { text: item };
|
||||
if (!item.value) item.value = item.text;
|
||||
|
||||
const toggle = $el(
|
||||
"label",
|
||||
{
|
||||
textContent: item.text,
|
||||
title: item.tooltip ?? "",
|
||||
},
|
||||
$el("input", {
|
||||
name,
|
||||
type: "radio",
|
||||
value: item.value ?? item.text,
|
||||
checked: item.selected,
|
||||
onchange: () => {
|
||||
updateSelected(i);
|
||||
},
|
||||
})
|
||||
);
|
||||
if (item.selected) {
|
||||
updateSelected(i);
|
||||
}
|
||||
return toggle;
|
||||
});
|
||||
const toggle = $el(
|
||||
"label",
|
||||
{
|
||||
textContent: item.text,
|
||||
title: item.tooltip ?? "",
|
||||
},
|
||||
$el("input", {
|
||||
name,
|
||||
type: "radio",
|
||||
value: item.value ?? item.text,
|
||||
checked: item.selected,
|
||||
onchange: () => {
|
||||
updateSelected(i);
|
||||
},
|
||||
})
|
||||
);
|
||||
if (item.selected) {
|
||||
updateSelected(i);
|
||||
}
|
||||
return toggle;
|
||||
});
|
||||
|
||||
const container = $el("div.comfy-toggle-switch", elements);
|
||||
const container = $el("div.comfy-toggle-switch", elements);
|
||||
|
||||
if (selectedIndex == null) {
|
||||
elements[0].children[0].checked = true;
|
||||
updateSelected(0);
|
||||
}
|
||||
if (selectedIndex == null) {
|
||||
elements[0].children[0].checked = true;
|
||||
updateSelected(0);
|
||||
}
|
||||
|
||||
return container;
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -5,122 +5,122 @@ import "./userSelection.css";
|
||||
|
||||
|
||||
interface SelectedUser {
|
||||
username: string;
|
||||
userId: string;
|
||||
created: boolean;
|
||||
username: string;
|
||||
userId: string;
|
||||
created: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class UserSelectionScreen {
|
||||
async show(users, user): Promise<SelectedUser>{
|
||||
const userSelection = document.getElementById("comfy-user-selection");
|
||||
userSelection.style.display = "";
|
||||
return new Promise((resolve) => {
|
||||
const input = userSelection.getElementsByTagName("input")[0];
|
||||
const select = userSelection.getElementsByTagName("select")[0];
|
||||
const inputSection = input.closest("section");
|
||||
const selectSection = select.closest("section");
|
||||
const form = userSelection.getElementsByTagName("form")[0];
|
||||
const error = userSelection.getElementsByClassName("comfy-user-error")[0];
|
||||
const button = userSelection.getElementsByClassName("comfy-user-button-next")[0];
|
||||
async show(users, user): Promise<SelectedUser>{
|
||||
const userSelection = document.getElementById("comfy-user-selection");
|
||||
userSelection.style.display = "";
|
||||
return new Promise((resolve) => {
|
||||
const input = userSelection.getElementsByTagName("input")[0];
|
||||
const select = userSelection.getElementsByTagName("select")[0];
|
||||
const inputSection = input.closest("section");
|
||||
const selectSection = select.closest("section");
|
||||
const form = userSelection.getElementsByTagName("form")[0];
|
||||
const error = userSelection.getElementsByClassName("comfy-user-error")[0];
|
||||
const button = userSelection.getElementsByClassName("comfy-user-button-next")[0];
|
||||
|
||||
let inputActive = null;
|
||||
input.addEventListener("focus", () => {
|
||||
inputSection.classList.add("selected");
|
||||
selectSection.classList.remove("selected");
|
||||
inputActive = true;
|
||||
});
|
||||
select.addEventListener("focus", () => {
|
||||
inputSection.classList.remove("selected");
|
||||
selectSection.classList.add("selected");
|
||||
inputActive = false;
|
||||
select.style.color = "";
|
||||
});
|
||||
select.addEventListener("blur", () => {
|
||||
if (!select.value) {
|
||||
select.style.color = "var(--descrip-text)";
|
||||
}
|
||||
});
|
||||
let inputActive = null;
|
||||
input.addEventListener("focus", () => {
|
||||
inputSection.classList.add("selected");
|
||||
selectSection.classList.remove("selected");
|
||||
inputActive = true;
|
||||
});
|
||||
select.addEventListener("focus", () => {
|
||||
inputSection.classList.remove("selected");
|
||||
selectSection.classList.add("selected");
|
||||
inputActive = false;
|
||||
select.style.color = "";
|
||||
});
|
||||
select.addEventListener("blur", () => {
|
||||
if (!select.value) {
|
||||
select.style.color = "var(--descrip-text)";
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
if (inputActive == null) {
|
||||
error.textContent = "Please enter a username or select an existing user.";
|
||||
} else if (inputActive) {
|
||||
const username = input.value.trim();
|
||||
if (!username) {
|
||||
error.textContent = "Please enter a username.";
|
||||
return;
|
||||
}
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
if (inputActive == null) {
|
||||
error.textContent = "Please enter a username or select an existing user.";
|
||||
} else if (inputActive) {
|
||||
const username = input.value.trim();
|
||||
if (!username) {
|
||||
error.textContent = "Please enter a username.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new user
|
||||
// @ts-ignore
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled = select.disabled = input.readonly = select.readonly = true;
|
||||
const spinner = createSpinner();
|
||||
button.prepend(spinner);
|
||||
try {
|
||||
const resp = await api.createUser(username);
|
||||
if (resp.status >= 300) {
|
||||
let message = "Error creating user: " + resp.status + " " + resp.statusText;
|
||||
try {
|
||||
const res = await resp.json();
|
||||
if(res.error) {
|
||||
message = res.error;
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
// Create new user
|
||||
// @ts-ignore
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled = select.disabled = input.readonly = select.readonly = true;
|
||||
const spinner = createSpinner();
|
||||
button.prepend(spinner);
|
||||
try {
|
||||
const resp = await api.createUser(username);
|
||||
if (resp.status >= 300) {
|
||||
let message = "Error creating user: " + resp.status + " " + resp.statusText;
|
||||
try {
|
||||
const res = await resp.json();
|
||||
if(res.error) {
|
||||
message = res.error;
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
resolve({ username, userId: await resp.json(), created: true });
|
||||
} catch (err) {
|
||||
spinner.remove();
|
||||
error.textContent = err.message ?? err.statusText ?? err ?? "An unknown error occurred.";
|
||||
// @ts-ignore
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled = select.disabled = input.readonly = select.readonly = false;
|
||||
return;
|
||||
}
|
||||
} else if (!select.value) {
|
||||
error.textContent = "Please select an existing user.";
|
||||
return;
|
||||
} else {
|
||||
resolve({ username: users[select.value], userId: select.value, created: false });
|
||||
}
|
||||
});
|
||||
resolve({ username, userId: await resp.json(), created: true });
|
||||
} catch (err) {
|
||||
spinner.remove();
|
||||
error.textContent = err.message ?? err.statusText ?? err ?? "An unknown error occurred.";
|
||||
// @ts-ignore
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled = select.disabled = input.readonly = select.readonly = false;
|
||||
return;
|
||||
}
|
||||
} else if (!select.value) {
|
||||
error.textContent = "Please select an existing user.";
|
||||
return;
|
||||
} else {
|
||||
resolve({ username: users[select.value], userId: select.value, created: false });
|
||||
}
|
||||
});
|
||||
|
||||
if (user) {
|
||||
const name = localStorage["Comfy.userName"];
|
||||
if (name) {
|
||||
input.value = name;
|
||||
}
|
||||
}
|
||||
if (input.value) {
|
||||
// Focus the input, do this separately as sometimes browsers like to fill in the value
|
||||
input.focus();
|
||||
}
|
||||
if (user) {
|
||||
const name = localStorage["Comfy.userName"];
|
||||
if (name) {
|
||||
input.value = name;
|
||||
}
|
||||
}
|
||||
if (input.value) {
|
||||
// Focus the input, do this separately as sometimes browsers like to fill in the value
|
||||
input.focus();
|
||||
}
|
||||
|
||||
const userIds = Object.keys(users ?? {});
|
||||
if (userIds.length) {
|
||||
for (const u of userIds) {
|
||||
$el("option", { textContent: users[u], value: u, parent: select });
|
||||
}
|
||||
select.style.color = "var(--descrip-text)";
|
||||
const userIds = Object.keys(users ?? {});
|
||||
if (userIds.length) {
|
||||
for (const u of userIds) {
|
||||
$el("option", { textContent: users[u], value: u, parent: select });
|
||||
}
|
||||
select.style.color = "var(--descrip-text)";
|
||||
|
||||
if (select.value) {
|
||||
// Focus the select, do this separately as sometimes browsers like to fill in the value
|
||||
select.focus();
|
||||
}
|
||||
} else {
|
||||
userSelection.classList.add("no-users");
|
||||
input.focus();
|
||||
}
|
||||
}).then((r: SelectedUser) => {
|
||||
userSelection.remove();
|
||||
return r;
|
||||
});
|
||||
}
|
||||
if (select.value) {
|
||||
// Focus the select, do this separately as sometimes browsers like to fill in the value
|
||||
select.focus();
|
||||
}
|
||||
} else {
|
||||
userSelection.classList.add("no-users");
|
||||
input.focus();
|
||||
}
|
||||
}).then((r: SelectedUser) => {
|
||||
userSelection.remove();
|
||||
return r;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,25 +8,25 @@
|
||||
* @param { string[] } requiredClasses
|
||||
*/
|
||||
export function applyClasses(element, classList, ...requiredClasses) {
|
||||
classList ??= "";
|
||||
classList ??= "";
|
||||
|
||||
let str;
|
||||
if (typeof classList === "string") {
|
||||
str = classList;
|
||||
} else if (classList instanceof Array) {
|
||||
str = classList.join(" ");
|
||||
} else {
|
||||
str = Object.entries(classList).reduce((p, c) => {
|
||||
if (c[1]) {
|
||||
p += (p.length ? " " : "") + c[0];
|
||||
}
|
||||
return p;
|
||||
}, "");
|
||||
}
|
||||
element.className = str;
|
||||
if (requiredClasses) {
|
||||
element.classList.add(...requiredClasses);
|
||||
}
|
||||
let str;
|
||||
if (typeof classList === "string") {
|
||||
str = classList;
|
||||
} else if (classList instanceof Array) {
|
||||
str = classList.join(" ");
|
||||
} else {
|
||||
str = Object.entries(classList).reduce((p, c) => {
|
||||
if (c[1]) {
|
||||
p += (p.length ? " " : "") + c[0];
|
||||
}
|
||||
return p;
|
||||
}, "");
|
||||
}
|
||||
element.className = str;
|
||||
if (requiredClasses) {
|
||||
element.classList.add(...requiredClasses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,22 +35,22 @@ export function applyClasses(element, classList, ...requiredClasses) {
|
||||
* @returns
|
||||
*/
|
||||
export function toggleElement(element, { onHide, onShow } = {}) {
|
||||
let placeholder;
|
||||
let hidden;
|
||||
return (value) => {
|
||||
if (value) {
|
||||
if (hidden) {
|
||||
hidden = false;
|
||||
placeholder.replaceWith(element);
|
||||
}
|
||||
onShow?.(element, value);
|
||||
} else {
|
||||
if (!placeholder) {
|
||||
placeholder = document.createComment("");
|
||||
}
|
||||
hidden = true;
|
||||
element.replaceWith(placeholder);
|
||||
onHide?.(element);
|
||||
}
|
||||
};
|
||||
let placeholder;
|
||||
let hidden;
|
||||
return (value) => {
|
||||
if (value) {
|
||||
if (hidden) {
|
||||
hidden = false;
|
||||
placeholder.replaceWith(element);
|
||||
}
|
||||
onShow?.(element, value);
|
||||
} else {
|
||||
if (!placeholder) {
|
||||
placeholder = document.createComment("");
|
||||
}
|
||||
hidden = true;
|
||||
element.replaceWith(placeholder);
|
||||
onHide?.(element);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user