Format all code / Add pre-commit format hook (#81)

* Add format-guard

* Format code
This commit is contained in:
Chenlei Hu
2024-07-02 13:22:37 -04:00
committed by GitHub
parent 4fb7fa9db1
commit acdaa6a594
62 changed files with 28225 additions and 25406 deletions

View File

@@ -2,63 +2,63 @@ import { ComfyDialog } from "../dialog";
import { $el } from "../../ui";
export class ComfyAsyncDialog extends ComfyDialog {
#resolve;
#resolve;
constructor(actions) {
super(
"dialog.comfy-dialog.comfyui-dialog",
actions?.map((opt) => {
if (typeof opt === "string") {
opt = { text: opt };
}
return $el("button.comfyui-button", {
type: "button",
textContent: opt.text,
onclick: () => this.close(opt.value ?? opt.text),
});
})
);
}
constructor(actions) {
super(
"dialog.comfy-dialog.comfyui-dialog",
actions?.map((opt) => {
if (typeof opt === "string") {
opt = { text: opt };
}
return $el("button.comfyui-button", {
type: "button",
textContent: opt.text,
onclick: () => this.close(opt.value ?? opt.text),
});
})
);
}
show(html) {
this.element.addEventListener("close", () => {
this.close();
});
show(html) {
this.element.addEventListener("close", () => {
this.close();
});
super.show(html);
super.show(html);
return new Promise((resolve) => {
this.#resolve = resolve;
});
}
return new Promise((resolve) => {
this.#resolve = resolve;
});
}
showModal(html) {
this.element.addEventListener("close", () => {
this.close();
});
showModal(html) {
this.element.addEventListener("close", () => {
this.close();
});
super.show(html);
this.element.showModal();
super.show(html);
this.element.showModal();
return new Promise((resolve) => {
this.#resolve = resolve;
});
}
return new Promise((resolve) => {
this.#resolve = resolve;
});
}
close(result = null) {
this.#resolve(result);
this.element.close();
super.close();
}
close(result = null) {
this.#resolve(result);
this.element.close();
super.close();
}
static async prompt({ title = null, message, actions }) {
const dialog = new ComfyAsyncDialog(actions);
const content = [$el("span", message)];
if (title) {
content.unshift($el("h3", title));
}
const res = await dialog.showModal(content);
dialog.element.remove();
return res;
}
static async prompt({ title = null, message, actions }) {
const dialog = new ComfyAsyncDialog(actions);
const content = [$el("span", message)];
if (title) {
content.unshift($el("h3", title));
}
const res = await dialog.showModal(content);
dialog.element.remove();
return res;
}
}

View File

@@ -19,145 +19,159 @@ import { prop } from "../../utils";
* }} ComfyButtonProps
*/
export class ComfyButton {
#over = 0;
#popupOpen = false;
isOver = false;
iconElement = $el("i.mdi");
contentElement = $el("span");
/**
* @type {import("./popup").ComfyPopup}
*/
popup;
#over = 0;
#popupOpen = false;
isOver = false;
iconElement = $el("i.mdi");
contentElement = $el("span");
/**
* @type {import("./popup").ComfyPopup}
*/
popup;
/**
* @param {ComfyButtonProps} opts
*/
constructor({
icon,
overIcon,
iconSize,
content,
tooltip,
action,
classList = "comfyui-button",
visibilitySetting,
app,
enabled = true,
}) {
this.element = $el("button", {
onmouseenter: () => {
this.isOver = true;
if(this.overIcon) {
this.updateIcon();
}
},
onmouseleave: () => {
this.isOver = false;
if(this.overIcon) {
this.updateIcon();
}
}
/**
* @param {ComfyButtonProps} opts
*/
constructor({
icon,
overIcon,
iconSize,
content,
tooltip,
action,
classList = "comfyui-button",
visibilitySetting,
app,
enabled = true,
}) {
this.element = $el(
"button",
{
onmouseenter: () => {
this.isOver = true;
if (this.overIcon) {
this.updateIcon();
}
},
onmouseleave: () => {
this.isOver = false;
if (this.overIcon) {
this.updateIcon();
}
},
},
[this.iconElement, this.contentElement]
);
}, [this.iconElement, this.contentElement]);
this.icon = prop(
this,
"icon",
icon,
toggleElement(this.iconElement, { onShow: this.updateIcon })
);
this.overIcon = prop(this, "overIcon", overIcon, () => {
if (this.isOver) {
this.updateIcon();
}
});
this.iconSize = prop(this, "iconSize", iconSize, this.updateIcon);
this.content = prop(
this,
"content",
content,
toggleElement(this.contentElement, {
onShow: (el, v) => {
if (typeof v === "string") {
el.textContent = v;
} else {
el.replaceChildren(v);
}
},
})
);
this.icon = prop(this, "icon", icon, toggleElement(this.iconElement, { onShow: this.updateIcon }));
this.overIcon = prop(this, "overIcon", overIcon, () => {
if(this.isOver) {
this.updateIcon();
}
});
this.iconSize = prop(this, "iconSize", iconSize, this.updateIcon);
this.content = prop(
this,
"content",
content,
toggleElement(this.contentElement, {
onShow: (el, v) => {
if (typeof v === "string") {
el.textContent = v;
} else {
el.replaceChildren(v);
}
},
})
);
this.tooltip = prop(this, "tooltip", tooltip, (v) => {
if (v) {
this.element.title = v;
} else {
this.element.removeAttribute("title");
}
});
this.classList = prop(this, "classList", classList, this.updateClasses);
this.hidden = prop(this, "hidden", false, this.updateClasses);
this.enabled = prop(this, "enabled", enabled, () => {
this.updateClasses();
this.element.disabled = !this.enabled;
});
this.action = prop(this, "action", action);
this.element.addEventListener("click", (e) => {
if (this.popup) {
// we are either a touch device or triggered by click not hover
if (!this.#over) {
this.popup.toggle();
}
}
this.action?.(e, this);
});
this.tooltip = prop(this, "tooltip", tooltip, (v) => {
if (v) {
this.element.title = v;
} else {
this.element.removeAttribute("title");
}
});
this.classList = prop(this, "classList", classList, this.updateClasses);
this.hidden = prop(this, "hidden", false, this.updateClasses);
this.enabled = prop(this, "enabled", enabled, () => {
this.updateClasses();
this.element.disabled = !this.enabled;
});
this.action = prop(this, "action", action);
this.element.addEventListener("click", (e) => {
if (this.popup) {
// we are either a touch device or triggered by click not hover
if (!this.#over) {
this.popup.toggle();
}
}
this.action?.(e, this);
});
if (visibilitySetting?.id) {
const settingUpdated = () => {
this.hidden =
app.ui.settings.getSettingValue(visibilitySetting.id) !==
visibilitySetting.showValue;
};
app.ui.settings.addEventListener(
visibilitySetting.id + ".change",
settingUpdated
);
settingUpdated();
}
}
if (visibilitySetting?.id) {
const settingUpdated = () => {
this.hidden = app.ui.settings.getSettingValue(visibilitySetting.id) !== visibilitySetting.showValue;
};
app.ui.settings.addEventListener(visibilitySetting.id + ".change", settingUpdated);
settingUpdated();
}
}
updateIcon = () =>
(this.iconElement.className = `mdi mdi-${(this.isOver && this.overIcon) || this.icon}${this.iconSize ? " mdi-" + this.iconSize + "px" : ""}`);
updateClasses = () => {
const internalClasses = [];
if (this.hidden) {
internalClasses.push("hidden");
}
if (!this.enabled) {
internalClasses.push("disabled");
}
if (this.popup) {
if (this.#popupOpen) {
internalClasses.push("popup-open");
} else {
internalClasses.push("popup-closed");
}
}
applyClasses(this.element, this.classList, ...internalClasses);
};
updateIcon = () => (this.iconElement.className = `mdi mdi-${(this.isOver && this.overIcon) || this.icon}${this.iconSize ? " mdi-" + this.iconSize + "px" : ""}`);
updateClasses = () => {
const internalClasses = [];
if (this.hidden) {
internalClasses.push("hidden");
}
if (!this.enabled) {
internalClasses.push("disabled");
}
if (this.popup) {
if (this.#popupOpen) {
internalClasses.push("popup-open");
} else {
internalClasses.push("popup-closed");
}
}
applyClasses(this.element, this.classList, ...internalClasses);
};
/**
*
* @param { import("./popup").ComfyPopup } popup
* @param { "click" | "hover" } mode
*/
withPopup(popup, mode = "click") {
this.popup = popup;
/**
*
* @param { import("./popup").ComfyPopup } popup
* @param { "click" | "hover" } mode
*/
withPopup(popup, mode = "click") {
this.popup = popup;
if (mode === "hover") {
for (const el of [this.element, this.popup.element]) {
el.addEventListener("mouseenter", () => {
this.popup.open = !!++this.#over;
});
el.addEventListener("mouseleave", () => {
this.popup.open = !!--this.#over;
});
}
}
if (mode === "hover") {
for (const el of [this.element, this.popup.element]) {
el.addEventListener("mouseenter", () => {
this.popup.open = !!++this.#over;
});
el.addEventListener("mouseleave", () => {
this.popup.open = !!--this.#over;
});
}
}
popup.addEventListener("change", () => {
this.#popupOpen = popup.open;
this.updateClasses();
});
popup.addEventListener("change", () => {
this.#popupOpen = popup.open;
this.updateClasses();
});
return this;
}
return this;
}
}

View File

@@ -5,41 +5,41 @@ import { ComfyButton } from "./button";
import { prop } from "../../utils";
export class ComfyButtonGroup {
element = $el("div.comfyui-button-group");
element = $el("div.comfyui-button-group");
/** @param {Array<ComfyButton | HTMLElement>} buttons */
constructor(...buttons) {
this.buttons = prop(this, "buttons", buttons, () => this.update());
}
/** @param {Array<ComfyButton | HTMLElement>} buttons */
constructor(...buttons) {
this.buttons = prop(this, "buttons", buttons, () => this.update());
}
/**
* @param {ComfyButton} button
* @param {number} index
*/
insert(button, index) {
this.buttons.splice(index, 0, button);
this.update();
}
/**
* @param {ComfyButton} button
* @param {number} index
*/
insert(button, index) {
this.buttons.splice(index, 0, button);
this.update();
}
/** @param {ComfyButton} button */
append(button) {
this.buttons.push(button);
this.update();
}
/** @param {ComfyButton} button */
append(button) {
this.buttons.push(button);
this.update();
}
/** @param {ComfyButton|number} indexOrButton */
remove(indexOrButton) {
if (typeof indexOrButton !== "number") {
indexOrButton = this.buttons.indexOf(indexOrButton);
}
if (indexOrButton > -1) {
const r = this.buttons.splice(indexOrButton, 1);
this.update();
return r;
}
}
/** @param {ComfyButton|number} indexOrButton */
remove(indexOrButton) {
if (typeof indexOrButton !== "number") {
indexOrButton = this.buttons.indexOf(indexOrButton);
}
if (indexOrButton > -1) {
const r = this.buttons.splice(indexOrButton, 1);
this.update();
return r;
}
}
update() {
this.element.replaceChildren(...this.buttons.map((b) => b["element"] ?? b));
}
update() {
this.element.replaceChildren(...this.buttons.map((b) => b["element"] ?? b));
}
}

View File

@@ -5,124 +5,133 @@ import { $el } from "../../ui";
import { applyClasses } from "../utils";
export class ComfyPopup extends EventTarget {
element = $el("div.comfyui-popup");
element = $el("div.comfyui-popup");
/**
* @param {{
* target: HTMLElement,
* container?: HTMLElement,
* classList?: import("../utils").ClassList,
* ignoreTarget?: boolean,
* closeOnEscape?: boolean,
* position?: "absolute" | "relative",
* horizontal?: "left" | "right"
* }} param0
* @param {...HTMLElement} children
*/
constructor(
{
target,
container = document.body,
classList = "",
ignoreTarget = true,
closeOnEscape = true,
position = "absolute",
horizontal = "left",
},
...children
) {
super();
this.target = target;
this.ignoreTarget = ignoreTarget;
this.container = container;
this.position = position;
this.closeOnEscape = closeOnEscape;
this.horizontal = horizontal;
/**
* @param {{
* target: HTMLElement,
* container?: HTMLElement,
* classList?: import("../utils").ClassList,
* ignoreTarget?: boolean,
* closeOnEscape?: boolean,
* position?: "absolute" | "relative",
* horizontal?: "left" | "right"
* }} param0
* @param {...HTMLElement} children
*/
constructor(
{
target,
container = document.body,
classList = "",
ignoreTarget = true,
closeOnEscape = true,
position = "absolute",
horizontal = "left",
},
...children
) {
super();
this.target = target;
this.ignoreTarget = ignoreTarget;
this.container = container;
this.position = position;
this.closeOnEscape = closeOnEscape;
this.horizontal = horizontal;
container.append(this.element);
container.append(this.element);
this.children = prop(this, "children", children, () => {
this.element.replaceChildren(...this.children);
this.update();
});
this.classList = prop(this, "classList", classList, () => applyClasses(this.element, this.classList, "comfyui-popup", horizontal));
this.open = prop(this, "open", false, (v, o) => {
if (v === o) return;
if (v) {
this.#show();
} else {
this.#hide();
}
});
}
this.children = prop(this, "children", children, () => {
this.element.replaceChildren(...this.children);
this.update();
});
this.classList = prop(this, "classList", classList, () =>
applyClasses(this.element, this.classList, "comfyui-popup", horizontal)
);
this.open = prop(this, "open", false, (v, o) => {
if (v === o) return;
if (v) {
this.#show();
} else {
this.#hide();
}
});
}
toggle() {
this.open = !this.open;
}
toggle() {
this.open = !this.open;
}
#hide() {
this.element.classList.remove("open");
window.removeEventListener("resize", this.update);
window.removeEventListener("click", this.#clickHandler, { capture: true });
window.removeEventListener("keydown", this.#escHandler, { capture: true });
#hide() {
this.element.classList.remove("open");
window.removeEventListener("resize", this.update);
window.removeEventListener("click", this.#clickHandler, { capture: true });
window.removeEventListener("keydown", this.#escHandler, { capture: true });
this.dispatchEvent(new CustomEvent("close"));
this.dispatchEvent(new CustomEvent("change"));
}
this.dispatchEvent(new CustomEvent("close"));
this.dispatchEvent(new CustomEvent("change"));
}
#show() {
this.element.classList.add("open");
this.update();
#show() {
this.element.classList.add("open");
this.update();
window.addEventListener("resize", this.update);
window.addEventListener("click", this.#clickHandler, { capture: true });
if (this.closeOnEscape) {
window.addEventListener("keydown", this.#escHandler, { capture: true });
}
window.addEventListener("resize", this.update);
window.addEventListener("click", this.#clickHandler, { capture: true });
if (this.closeOnEscape) {
window.addEventListener("keydown", this.#escHandler, { capture: true });
}
this.dispatchEvent(new CustomEvent("open"));
this.dispatchEvent(new CustomEvent("change"));
}
this.dispatchEvent(new CustomEvent("open"));
this.dispatchEvent(new CustomEvent("change"));
}
#escHandler = (e) => {
if (e.key === "Escape") {
this.open = false;
e.preventDefault();
e.stopImmediatePropagation();
}
};
#escHandler = (e) => {
if (e.key === "Escape") {
this.open = false;
e.preventDefault();
e.stopImmediatePropagation();
}
};
#clickHandler = (e) => {
/** @type {any} */
const target = e.target;
if (!this.element.contains(target) && this.ignoreTarget && !this.target.contains(target)) {
this.open = false;
}
};
#clickHandler = (e) => {
/** @type {any} */
const target = e.target;
if (
!this.element.contains(target) &&
this.ignoreTarget &&
!this.target.contains(target)
) {
this.open = false;
}
};
update = () => {
const rect = this.target.getBoundingClientRect();
this.element.style.setProperty("--bottom", "unset");
if (this.position === "absolute") {
if (this.horizontal === "left") {
this.element.style.setProperty("--left", rect.left + "px");
} else {
this.element.style.setProperty("--left", rect.right - this.element.clientWidth + "px");
}
this.element.style.setProperty("--top", rect.bottom + "px");
this.element.style.setProperty("--limit", rect.bottom + "px");
} else {
this.element.style.setProperty("--left", 0 + "px");
this.element.style.setProperty("--top", rect.height + "px");
this.element.style.setProperty("--limit", rect.height + "px");
}
update = () => {
const rect = this.target.getBoundingClientRect();
this.element.style.setProperty("--bottom", "unset");
if (this.position === "absolute") {
if (this.horizontal === "left") {
this.element.style.setProperty("--left", rect.left + "px");
} else {
this.element.style.setProperty(
"--left",
rect.right - this.element.clientWidth + "px"
);
}
this.element.style.setProperty("--top", rect.bottom + "px");
this.element.style.setProperty("--limit", rect.bottom + "px");
} else {
this.element.style.setProperty("--left", 0 + "px");
this.element.style.setProperty("--top", rect.height + "px");
this.element.style.setProperty("--limit", rect.height + "px");
}
const thisRect = this.element.getBoundingClientRect();
if (thisRect.height < 30) {
// Move up instead
this.element.style.setProperty("--top", "unset");
this.element.style.setProperty("--bottom", rect.height + 5 + "px");
this.element.style.setProperty("--limit", rect.height + 5 + "px");
}
};
const thisRect = this.element.getBoundingClientRect();
if (thisRect.height < 30) {
// Move up instead
this.element.style.setProperty("--top", "unset");
this.element.style.setProperty("--bottom", rect.height + 5 + "px");
this.element.style.setProperty("--limit", rect.height + 5 + "px");
}
};
}

View File

@@ -6,38 +6,47 @@ import { prop } from "../../utils";
import { ComfyPopup } from "./popup";
export class ComfySplitButton {
/**
* @param {{
* primary: ComfyButton,
* mode?: "hover" | "click",
* horizontal?: "left" | "right",
* position?: "relative" | "absolute"
* }} param0
* @param {Array<ComfyButton> | Array<HTMLElement>} items
*/
constructor({ primary, mode, horizontal = "left", position = "relative" }, ...items) {
this.arrow = new ComfyButton({
icon: "chevron-down",
});
this.element = $el("div.comfyui-split-button" + (mode === "hover" ? ".hover" : ""), [
$el("div.comfyui-split-primary", primary.element),
$el("div.comfyui-split-arrow", this.arrow.element),
]);
this.popup = new ComfyPopup({
target: this.element,
container: position === "relative" ? this.element : document.body,
classList: "comfyui-split-button-popup" + (mode === "hover" ? " hover" : ""),
closeOnEscape: mode === "click",
position,
horizontal,
});
/**
* @param {{
* primary: ComfyButton,
* mode?: "hover" | "click",
* horizontal?: "left" | "right",
* position?: "relative" | "absolute"
* }} param0
* @param {Array<ComfyButton> | Array<HTMLElement>} items
*/
constructor(
{ primary, mode, horizontal = "left", position = "relative" },
...items
) {
this.arrow = new ComfyButton({
icon: "chevron-down",
});
this.element = $el(
"div.comfyui-split-button" + (mode === "hover" ? ".hover" : ""),
[
$el("div.comfyui-split-primary", primary.element),
$el("div.comfyui-split-arrow", this.arrow.element),
]
);
this.popup = new ComfyPopup({
target: this.element,
container: position === "relative" ? this.element : document.body,
classList:
"comfyui-split-button-popup" + (mode === "hover" ? " hover" : ""),
closeOnEscape: mode === "click",
position,
horizontal,
});
this.arrow.withPopup(this.popup, mode);
this.arrow.withPopup(this.popup, mode);
this.items = prop(this, "items", items, () => this.update());
}
this.items = prop(this, "items", items, () => this.update());
}
update() {
this.popup.element.replaceChildren(...this.items.map((b) => b.element ?? b));
}
update() {
this.popup.element.replaceChildren(
...this.items.map((b) => b.element ?? b)
);
}
}

View File

@@ -1,6 +1,8 @@
import { $el } from "../ui";
export class ComfyDialog<T extends HTMLElement = HTMLElement> extends EventTarget {
export class ComfyDialog<
T extends HTMLElement = HTMLElement,
> extends EventTarget {
element: T;
textElement: HTMLElement;
#buttons: HTMLButtonElement[] | null;
@@ -9,7 +11,10 @@ export class ComfyDialog<T extends HTMLElement = HTMLElement> extends EventTarge
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()]),
$el("div.comfy-modal-content", [
$el("p", { $: (p) => (this.textElement = p) }),
...this.createButtons(),
]),
]) as T;
}
@@ -33,7 +38,9 @@ export class ComfyDialog<T extends HTMLElement = HTMLElement> extends EventTarge
if (typeof html === "string") {
this.textElement.innerHTML = html;
} else {
this.textElement.replaceChildren(...(html instanceof Array ? html : [html]));
this.textElement.replaceChildren(
...(html instanceof Array ? html : [html])
);
}
this.element.style.display = "flex";
}

View File

@@ -27,8 +27,8 @@
import { $el } from "../ui";
$el("style", {
parent: document.head,
textContent: `
parent: document.head,
textContent: `
.draggable-item {
position: relative;
will-change: transform;
@@ -40,7 +40,7 @@ $el("style", {
.draggable-item.is-draggable {
z-index: 10;
}
`
`,
});
export class DraggableList extends EventTarget {
@@ -57,9 +57,9 @@ export class DraggableList extends EventTarget {
offDrag = [];
constructor(element, itemSelector) {
super();
super();
this.listContainer = element;
this.itemSelector = itemSelector;
this.itemSelector = itemSelector;
if (!this.listContainer) return;
@@ -71,7 +71,9 @@ export class DraggableList extends EventTarget {
getAllItems() {
if (!this.items?.length) {
this.items = Array.from(this.listContainer.querySelectorAll(this.itemSelector));
this.items = Array.from(
this.listContainer.querySelectorAll(this.itemSelector)
);
this.items.forEach((element) => {
element.classList.add("is-idle");
});
@@ -80,7 +82,9 @@ export class DraggableList extends EventTarget {
}
getIdleItems() {
return this.getAllItems().filter((item) => item.classList.contains("is-idle"));
return this.getAllItems().filter((item) =>
item.classList.contains("is-idle")
);
}
isItemAbove(item) {
@@ -106,18 +110,24 @@ export class DraggableList extends EventTarget {
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.scrollYMax =
this.listContainer.scrollHeight - this.listContainer.clientHeight;
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, "touchmove", this.drag, { passive: false })
);
this.dispatchEvent(
new CustomEvent("dragstart", {
detail: { element: this.draggableItem, position: this.getAllItems().indexOf(this.draggableItem) },
detail: {
element: this.draggableItem,
position: this.getAllItems().indexOf(this.draggableItem),
},
})
);
}
@@ -250,7 +260,11 @@ export class DraggableList extends EventTarget {
this.dispatchEvent(
new CustomEvent("dragend", {
detail: { element: this.draggableItem, oldPosition, newPosition: reorderedItems.indexOf(this.draggableItem) },
detail: {
element: this.draggableItem,
oldPosition,
newPosition: reorderedItems.indexOf(this.draggableItem),
},
})
);
}

View File

@@ -57,7 +57,11 @@ export function createImageHost(node) {
}
const nw = node.size[0];
({ cellWidth: w, cellHeight: h } = calculateImageGrid(currentImgs, nw - 20, elH));
({ cellWidth: w, cellHeight: h } = calculateImageGrid(
currentImgs,
nw - 20,
elH
));
w += "px";
h += "px";
@@ -86,10 +90,13 @@ export function createImageHost(node) {
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]);
const over = document.elementFromPoint(
app.canvas.mouse[0],
app.canvas.mouse[1]
);
el.style.pointerEvents = "none";
if(!over) return;
if (!over) return;
// Set the overIndex so Open Image etc work
const idx = currentImgs.indexOf(over);
node.overIndex = idx;

View File

@@ -48,7 +48,11 @@ export class ComfyAppMenu {
content: t,
});
this.logo = $el("h1.comfyui-logo.nlg-hide", { title: "ComfyUI" }, "ComfyUI");
this.logo = $el(
"h1.comfyui-logo.nlg-hide",
{ title: "ComfyUI" },
"ComfyUI"
);
this.saveButton = new ComfySplitButton(
{
primary: getSaveButton(),
@@ -71,7 +75,8 @@ export class ComfyAppMenu {
new ComfyButton({
icon: "api",
content: "Export (API Format)",
tooltip: "Export the current workflow as JSON for use with the ComfyUI API",
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,
@@ -101,7 +106,10 @@ export class ComfyAppMenu {
content: "Clear",
tooltip: "Clears current workflow",
action: () => {
if (!app.ui.settings.getSettingValue("Comfy.ConfirmClear", true) || confirm("Clear workflow?")) {
if (
!app.ui.settings.getSettingValue("Comfy.ConfirmClear", true) ||
confirm("Clear workflow?")
) {
app.clean();
app.graph.clear();
}
@@ -126,7 +134,9 @@ export class ComfyAppMenu {
this.mobileMenuButton = new ComfyButton({
icon: "menu",
action: (_, btn) => {
btn.icon = this.element.classList.toggle("expanded") ? "menu-open" : "menu";
btn.icon = this.element.classList.toggle("expanded")
? "menu-open"
: "menu";
window.dispatchEvent(new Event("resize"));
},
classList: "comfyui-button comfyui-menu-button",
@@ -239,7 +249,10 @@ export class ComfyAppMenu {
idx--;
}
} else if (innerSize > this.element.clientWidth) {
this.#lastSizeBreaks[this.#sizeBreak] = Math.max(window.innerWidth, innerSize);
this.#lastSizeBreaks[this.#sizeBreak] = Math.max(
window.innerWidth,
innerSize
);
// We need to shrink
if (idx < this.#sizeBreaks.length - 1) {
idx++;
@@ -254,19 +267,26 @@ export class ComfyAppMenu {
clearTimeout(this.#cacheTimeout);
if (this.#cachedInnerSize) {
// Extend cache time
this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100);
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
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);
this.#cacheTimeout = setTimeout(
() => (this.#cachedInnerSize = null),
100
);
}
return this.#cachedInnerSize;
}

View File

@@ -14,7 +14,10 @@ export class ComfyQueueButton {
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);
await this.app.queuePrompt(
e?.shiftKey ? -1 : 0,
this.queueOptions.batchCount
);
};
constructor(app) {
@@ -63,7 +66,10 @@ export class ComfyQueueButton {
}
});
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") {
@@ -79,10 +85,14 @@ export class ComfyQueueButton {
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.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)) {
if (
this.autoQueueMode === "instant" ||
(this.autoQueueMode === "change" && this.graphHasChanged)
) {
this.graphHasChanged = false;
this.queuePrompt();
}

View File

@@ -69,11 +69,14 @@ export class ComfyViewList {
},
});
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) {
@@ -155,7 +158,9 @@ export class ComfyViewList {
text: "Load",
action: async () => {
try {
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
await this.app.loadGraphData(
item.prompt[3].extra_pnginfo.workflow
);
if (item.outputs) {
this.app.nodeOutputs = item.outputs;
}

View File

@@ -31,7 +31,9 @@ export class ComfyViewQueueList extends ComfyViewList {
text: "Load",
action: async () => {
try {
await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow);
await this.app.loadGraphData(
item.prompt[3].extra_pnginfo.workflow
);
if (item.outputs) {
this.app.nodeOutputs = item.outputs;
}
@@ -51,5 +53,5 @@ export class ComfyViewQueueList extends ComfyViewList {
},
],
};
}
};
}

View File

@@ -37,14 +37,21 @@ export class ComfyWorkflowsMenu {
this.buttonProgress = $el("div.comfyui-workflows-button-progress");
this.workflowLabel = $el("span.comfyui-workflows-label", "");
this.button = new ComfyButton({
content: $el("div.comfyui-workflows-button-inner", [$el("i.mdi.mdi-graph"), this.workflowLabel, this.buttonProgress]),
content: $el("div.comfyui-workflows-button-inner", [
$el("i.mdi.mdi-graph"),
this.workflowLabel,
this.buttonProgress,
]),
icon: "chevron-down",
classList,
});
this.element.append(this.button.element);
this.popup = new ComfyPopup({ target: this.element, classList: "comfyui-workflows-popup" });
this.popup = new ComfyPopup({
target: this.element,
classList: "comfyui-workflows-popup",
});
this.content = new ComfyWorkflowsContent(app, this.popup);
this.popup.children = [this.content.element];
this.popup.addEventListener("change", () => {
@@ -85,7 +92,10 @@ export class ComfyWorkflowsMenu {
};
#bindEvents() {
this.app.workflowManager.addEventListener("changeWorkflow", this.#updateActive);
this.app.workflowManager.addEventListener(
"changeWorkflow",
this.#updateActive
);
this.app.workflowManager.addEventListener("rename", this.#updateActive);
this.app.workflowManager.addEventListener("delete", this.#updateActive);
@@ -157,10 +167,15 @@ export class ComfyWorkflowsMenu {
name: "Comfy.Workflows",
async beforeRegisterNodeDef(nodeType) {
function getImageWidget(node) {
const inputs = { ...node.constructor?.nodeData?.input?.required, ...node.constructor?.nodeData?.input?.optional };
const inputs = {
...node.constructor?.nodeData?.input?.required,
...node.constructor?.nodeData?.input?.optional,
};
for (const input in inputs) {
if (inputs[input][0] === "IMAGEUPLOAD") {
const imageWidget = node.widgets.find((w) => w.name === (inputs[input]?.[1]?.widget ?? "image"));
const imageWidget = node.widgets.find(
(w) => w.name === (inputs[input]?.[1]?.widget ?? "image")
);
if (imageWidget) return imageWidget;
}
}
@@ -213,8 +228,13 @@ export class ComfyWorkflowsMenu {
const getExtraMenuOptions = nodeType.prototype["getExtraMenuOptions"];
nodeType.prototype["getExtraMenuOptions"] = function (_, options) {
const r = getExtraMenuOptions?.apply?.(this, arguments);
if (app.ui.settings.getSettingValue("Comfy.UseNewMenu", false) === true) {
const t = /** @type { {imageIndex?: number, overIndex?: number, imgs: string[]} } */ /** @type {any} */ (this);
if (
app.ui.settings.getSettingValue("Comfy.UseNewMenu", false) === true
) {
const t =
/** @type { {imageIndex?: number, overIndex?: number, imgs: string[]} } */ /** @type {any} */ (
this
);
let img;
if (t.imageIndex != null) {
// An image is selected so select that
@@ -238,10 +258,13 @@ export class ComfyWorkflowsMenu {
submenu: {
options: [
{
callback: () => sendToWorkflow(img, app.workflowManager.activeWorkflow),
callback: () =>
sendToWorkflow(img, app.workflowManager.activeWorkflow),
title: "[Current workflow]",
},
...self.#getFavoriteMenuOptions(sendToWorkflow.bind(null, img)),
...self.#getFavoriteMenuOptions(
sendToWorkflow.bind(null, img)
),
null,
...self.#getMenuOptions(sendToWorkflow.bind(null, img)),
],
@@ -315,7 +338,9 @@ export class ComfyWorkflowsContent {
this.element.replaceChildren(this.actions, this.spinner);
this.popup.addEventListener("open", () => this.load());
this.popup.addEventListener("close", () => this.element.replaceChildren(this.actions, this.spinner));
this.popup.addEventListener("close", () =>
this.element.replaceChildren(this.actions, this.spinner)
);
this.app.workflowManager.addEventListener("favorite", (e) => {
const workflow = e["detail"];
@@ -331,7 +356,9 @@ export class ComfyWorkflowsContent {
app.workflowManager.addEventListener(e, () => this.updateOpen());
}
this.app.workflowManager.addEventListener("rename", () => this.load());
this.app.workflowManager.addEventListener("execute", (e) => this.#updateActive());
this.app.workflowManager.addEventListener("execute", (e) =>
this.#updateActive()
);
}
async load() {
@@ -339,7 +366,12 @@ export class ComfyWorkflowsContent {
this.updateTree();
this.updateFavorites();
this.updateOpen();
this.element.replaceChildren(this.actions, this.openElement, this.favoritesElement, this.treeElement);
this.element.replaceChildren(
this.actions,
this.openElement,
this.favoritesElement,
this.treeElement
);
}
updateOpen() {
@@ -368,7 +400,7 @@ export class ComfyWorkflowsContent {
if (w.unsaved) {
wrapper.element.classList.add("unsaved");
}
if(w === this.app.workflowManager.activeWorkflow) {
if (w === this.app.workflowManager.activeWorkflow) {
wrapper.element.classList.add("active");
}
@@ -383,7 +415,9 @@ export class ComfyWorkflowsContent {
updateFavorites() {
const current = this.favoritesElement;
const favorites = [...this.app.workflowManager.workflows.filter((w) => w.isFavorite)];
const favorites = [
...this.app.workflowManager.workflows.filter((w) => w.isFavorite),
];
this.favoritesElement = $el("div.comfyui-workflows-favorites", [
$el("h3", "Favorites"),
@@ -437,7 +471,10 @@ export class ComfyWorkflowsContent {
hideTreeParents(element) {
// Hide all parents if no children are visible
if (element.parentElement?.classList.contains("comfyui-workflows-tree") === false) {
if (
element.parentElement?.classList.contains("comfyui-workflows-tree") ===
false
) {
for (let i = 1; i < element.parentElement.children.length; i++) {
const c = element.parentElement.children[i];
if (c.style.display !== "none") {
@@ -450,7 +487,10 @@ export class ComfyWorkflowsContent {
}
showTreeParents(element) {
if (element.parentElement?.classList.contains("comfyui-workflows-tree") === false) {
if (
element.parentElement?.classList.contains("comfyui-workflows-tree") ===
false
) {
element.parentElement.style.removeProperty("display");
this.showTreeParents(element.parentElement);
}
@@ -490,7 +530,9 @@ export class ComfyWorkflowsContent {
for (let i = 0; i < workflow.pathParts.length; i++) {
currentPath += (currentPath ? "\\" : "") + workflow.pathParts[i];
const parentNode = nodes[currentPath] ?? this.#createNode(currentPath, workflow, i, currentRoot);
const parentNode =
nodes[currentPath] ??
this.#createNode(currentPath, workflow, i, currentRoot);
nodes[currentPath] = parentNode;
currentRoot = parentNode;
@@ -559,7 +601,9 @@ export class ComfyWorkflowsContent {
/** @param {ComfyWorkflow} workflow */
#getFavoriteTooltip(workflow) {
return workflow.isFavorite ? "Remove this workflow from your favorites" : "Add this workflow to your favorites";
return workflow.isFavorite
? "Remove this workflow from your favorites"
: "Add this workflow to your favorites";
}
/** @param {ComfyWorkflow} workflow */
@@ -568,7 +612,9 @@ export class ComfyWorkflowsContent {
icon: this.#getFavoriteIcon(workflow),
overIcon: this.#getFavoriteOverIcon(workflow),
iconSize: 18,
classList: "comfyui-button comfyui-workflows-file-action-favorite" + (primary ? " comfyui-workflows-file-action-primary" : ""),
classList:
"comfyui-button comfyui-workflows-file-action-favorite" +
(primary ? " comfyui-workflows-file-action-primary" : ""),
tooltip: this.#getFavoriteTooltip(workflow),
action: (e) => {
e.stopImmediatePropagation();
@@ -628,7 +674,9 @@ export class ComfyWorkflowsContent {
#getRenameButton(workflow) {
return new ComfyButton({
icon: "pencil",
tooltip: workflow.path ? "Rename this workflow" : "This workflow can't be renamed as it hasn't been saved.",
tooltip: workflow.path
? "Rename this workflow"
: "This workflow can't be renamed as it hasn't been saved.",
classList: "comfyui-button comfyui-workflows-file-action",
iconSize: 18,
enabled: !!workflow.path,
@@ -646,7 +694,11 @@ export class ComfyWorkflowsContent {
#getWorkflowElement(workflow) {
return new WorkflowElement(this, workflow, {
primary: this.#getFavoriteButton(workflow, true),
buttons: [this.#getInsertButton(workflow), this.#getRenameButton(workflow), this.#getDeleteButton(workflow)],
buttons: [
this.#getInsertButton(workflow),
this.#getRenameButton(workflow),
this.#getDeleteButton(workflow),
],
});
}
@@ -660,14 +712,17 @@ export class ComfyWorkflowsContent {
#createNode(currentPath, workflow, i, currentRoot) {
const part = workflow.pathParts[i];
const parentNode = $el("ul" + (this.treeState[currentPath] ? "" : ".closed"), {
$: (el) => {
el.onclick = (e) => {
this.#expandNode(el, workflow, currentPath, i);
e.stopImmediatePropagation();
};
},
});
const parentNode = $el(
"ul" + (this.treeState[currentPath] ? "" : ".closed"),
{
$: (el) => {
el.onclick = (e) => {
this.#expandNode(el, workflow, currentPath, i);
e.stopImmediatePropagation();
};
},
}
);
currentRoot.append(parentNode);
// Create a node for the current part and an inner UL for its children if it isnt a leaf node
@@ -676,7 +731,10 @@ export class ComfyWorkflowsContent {
if (leaf) {
nodeElement = this.#createLeafNode(workflow).element;
} else {
nodeElement = $el("li", [$el("i.mdi.mdi-18px.mdi-folder"), $el("span", part)]);
nodeElement = $el("li", [
$el("i.mdi.mdi-18px.mdi-folder"),
$el("span", part),
]);
}
parentNode.append(nodeElement);
return parentNode;
@@ -703,7 +761,11 @@ class WorkflowElement {
},
title: this.workflow.path,
},
[this.primary?.element, $el("span", workflow.name), ...buttons.map((b) => b.element)]
[
this.primary?.element,
$el("span", workflow.name),
...buttons.map((b) => b.element),
]
);
}
}
@@ -732,7 +794,11 @@ class WidgetSelectionDialog extends ComfyAsyncDialog {
"section",
this.#options.map((opt) => {
return $el("div.comfy-widget-selection-item", [
$el("span", { dataset: { id: opt.node.id } }, `${opt.node.title ?? opt.node.type} ${opt.widget.name}`),
$el(
"span",
{ dataset: { id: opt.node.id } },
`${opt.node.title ?? opt.node.type} ${opt.widget.name}`
),
$el(
"button.comfyui-button",
{
@@ -760,4 +826,4 @@ class WidgetSelectionDialog extends ComfyAsyncDialog {
])
);
}
}
}

View File

@@ -19,7 +19,14 @@ interface SettingOption {
interface SettingParams {
id: string;
name: string;
type: string | ((name: string, setter: (v: any) => void, value: any, attrs: any) => HTMLElement);
type:
| string
| ((
name: string,
setter: (v: any) => void,
value: any,
attrs: any
) => HTMLElement);
defaultValue: any;
onChange?: (newValue: any, oldValue?: any) => void;
attrs?: any;
@@ -81,7 +88,7 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
new CustomEvent(id + ".change", {
detail: {
value,
oldValue
oldValue,
},
})
);
@@ -115,8 +122,7 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
if (this.app.storageLocation === "browser") {
try {
value = JSON.parse(value);
} catch (error) {
}
} catch (error) {}
}
}
return value ?? defaultValue;
@@ -145,7 +151,16 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
}
addSetting(params: SettingParams) {
const { id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined } = params;
const {
id,
name,
type,
defaultValue,
onChange,
attrs = {},
tooltip = "",
options = undefined,
} = params;
if (!id) {
throw new Error("Settings must have an ID");
}
@@ -272,7 +287,8 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
style: { maxWidth: "4rem" },
oninput: (e) => {
setter(e.target.value);
e.target.previousElementSibling.value = e.target.value;
e.target.previousElementSibling.value =
e.target.value;
},
}),
]
@@ -291,7 +307,10 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
setter(e.target.value);
},
},
(typeof options === "function" ? options(value) : options || []).map((opt) => {
(typeof options === "function"
? options(value)
: options || []
).map((opt) => {
if (typeof opt === "string") {
opt = { text: opt };
}
@@ -309,7 +328,9 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
case "text":
default:
if (type !== "text") {
console.warn(`Unsupported setting type '${type}, defaulting to text`);
console.warn(
`Unsupported setting type '${type}, defaulting to text`
);
}
element = $el("tr", [
@@ -356,7 +377,10 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
},
[$el("th"), $el("th", { style: { width: "33%" } })]
),
...this.settings.sort((a, b) => a.name.localeCompare(b.name)).map((s) => s.render()).filter(Boolean)
...this.settings
.sort((a, b) => a.name.localeCompare(b.name))
.map((s) => s.render())
.filter(Boolean)
);
this.element.showModal();
}

View File

@@ -1,6 +1,5 @@
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>`;

View File

@@ -20,7 +20,10 @@ export function toggleSwitch(name, items, e?) {
if (selectedIndex != null) {
elements[selectedIndex].classList.remove("comfy-toggle-selected");
}
onChange?.({ item: items[index], prev: selectedIndex == null ? undefined : items[selectedIndex] });
onChange?.({
item: items[index],
prev: selectedIndex == null ? undefined : items[selectedIndex],
});
selectedIndex = index;
elements[selectedIndex].classList.add("comfy-toggle-selected");
}

View File

@@ -3,16 +3,14 @@ import { $el } from "../ui";
import { createSpinner } from "./spinner";
import "./userSelection.css";
interface SelectedUser {
username: string;
userId: string;
created: boolean;
}
export class UserSelectionScreen {
async show(users, user): Promise<SelectedUser>{
async show(users, user): Promise<SelectedUser> {
const userSelection = document.getElementById("comfy-user-selection");
userSelection.style.display = "";
return new Promise((resolve) => {
@@ -22,7 +20,9 @@ export class UserSelectionScreen {
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];
const button = userSelection.getElementsByClassName(
"comfy-user-button-next"
)[0];
let inputActive = null;
input.addEventListener("focus", () => {
@@ -45,7 +45,8 @@ export class UserSelectionScreen {
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (inputActive == null) {
error.textContent = "Please enter a username or select an existing user.";
error.textContent =
"Please enter a username or select an existing user.";
} else if (inputActive) {
const username = input.value.trim();
if (!username) {
@@ -54,41 +55,59 @@ export class UserSelectionScreen {
}
// 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;
input.disabled =
select.disabled =
// @ts-ignore
input.readonly =
// @ts-ignore
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;
let message =
"Error creating user: " + resp.status + " " + resp.statusText;
try {
const res = await resp.json();
if(res.error) {
if (res.error) {
message = res.error;
}
} catch (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
error.textContent =
err.message ??
err.statusText ??
err ??
"An unknown error occurred.";
// 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;
input.disabled =
select.disabled =
// @ts-ignore
input.readonly =
// @ts-ignore
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: users[select.value],
userId: select.value,
created: false,
});
}
});