Rename to ts (#92)

Rename

Remove ts-nocheck, fix errors

Update state when graph cleared via UI (#88)

Convert to ts, fix imports

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
This commit is contained in:
Chenlei Hu
2024-07-05 21:18:32 -04:00
committed by GitHub
parent 84c939cf71
commit e05a33cb17
21 changed files with 305 additions and 300 deletions

View File

@@ -20,9 +20,9 @@ import {
parseComfyWorkflow,
} from "../types/comfyWorkflow";
import { ComfyNodeDef } from "/types/apiTypes";
import { ComfyAppMenu } from "./ui/menu/index.js";
import { getStorageValue, setStorageValue } from "./utils.js";
import { ComfyWorkflowManager } from "./workflows.js";
import { ComfyAppMenu } from "./ui/menu/index";
import { getStorageValue } from "./utils";
import { ComfyWorkflowManager, ComfyWorkflow } from "./workflows";
import {
LGraphCanvas,
LGraph,
@@ -2179,16 +2179,11 @@ export class ComfyApp {
}
}
/**
* Populates the graph with the specified workflow data
* @param {*} graphData A serialized graph object
* @param { boolean } clean If the graph state, e.g. images, should be cleared
*/
async loadGraphData(
graphData?: ComfyWorkflowJSON,
clean: boolean = true,
restore_view: boolean = true,
workflow: string | null = null
workflow: string | null | ComfyWorkflow = null
) {
if (clean !== false) {
this.clean();

View File

@@ -1,27 +1,26 @@
// @ts-nocheck
import type { ComfyApp } from "./app";
import { api } from "./api";
import { clone } from "./utils";
import { LGraphCanvas, LiteGraph } from "@comfyorg/litegraph";
import { ComfyWorkflow } from "./workflows";
export class ChangeTracker {
static MAX_HISTORY = 50;
#app;
#app: ComfyApp;
undo = [];
redo = [];
activeState = null;
isOurLoad = false;
/** @type { import("./workflows").ComfyWorkflow | null } */
workflow;
workflow: ComfyWorkflow | null;
ds;
nodeOutputs;
ds: { scale: number; offset: [number, number]; };
nodeOutputs: any;
get app() {
return this.#app ?? this.workflow.manager.app;
}
constructor(workflow) {
constructor(workflow: ComfyWorkflow) {
this.workflow = workflow;
}
@@ -90,8 +89,7 @@ export class ChangeTracker {
}
}
/** @param { import("./app").ComfyApp } app */
static init(app) {
static init(app: ComfyApp) {
const changeTracker = () =>
app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker;
globalTracker.#setApp(app);
@@ -137,7 +135,7 @@ export class ChangeTracker {
if (await changeTracker().undoRedo(e)) return;
// If our active element is some type of input then handle changes after they're done
if (ChangeTracker.bindInput(activeEl)) return;
if (ChangeTracker.bindInput(app, activeEl)) return;
changeTracker().checkState();
});
},
@@ -277,4 +275,4 @@ export class ChangeTracker {
}
}
const globalTracker = new ChangeTracker({});
const globalTracker = new ChangeTracker({} as ComfyWorkflow);

View File

@@ -24,11 +24,15 @@ type Props = {
type Children = Element[] | Element | string | string[];
export function $el(
tag: string,
type ElementType<K extends string> = K extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[K]
: HTMLElement;
export function $el<TTag extends string>(
tag: TTag,
propsOrChildren?: Children | Props,
children?: Children
): HTMLElement {
): ElementType<TTag> {
const split = tag.split(".");
const element = document.createElement(split.shift() as string);
if (split.length > 0) {
@@ -78,10 +82,10 @@ export function $el(
}
}
}
return element;
return element as ElementType<TTag>;
}
function dragElement(dragEl, settings) {
function dragElement(dragEl, settings): () => void {
var posDiffX = 0,
posDiffY = 0,
posStartX = 0,
@@ -340,6 +344,8 @@ export class ComfyUI {
menuHamburger: HTMLDivElement;
menuContainer: HTMLDivElement;
queueSize: Element;
restoreMenuPosition: () => void;
loadFile: () => void;
constructor(app) {
this.app = app;
@@ -425,7 +431,6 @@ export class ComfyUI {
},
}) as HTMLInputElement;
// @ts-ignore
this.loadFile = () => fileInput.click();
const autoQueueModeEl = toggleSwitch(
@@ -740,7 +745,6 @@ export class ComfyUI {
},
});
// @ts-ignore
this.restoreMenuPosition = dragElement(this.menuContainer, this.settings);
this.setStatus({ exec_info: { queue_remaining: "X" } });

View File

@@ -1,10 +1,10 @@
import { ComfyDialog } from "../dialog";
import { $el } from "../../ui";
export class ComfyAsyncDialog extends ComfyDialog {
#resolve;
export class ComfyAsyncDialog extends ComfyDialog<HTMLDialogElement> {
#resolve: (value: any) => void;
constructor(actions) {
constructor(actions?: Array<string | { value?: any; text: string }>) {
super(
"dialog.comfy-dialog.comfyui-dialog",
actions?.map((opt) => {
@@ -20,7 +20,7 @@ export class ComfyAsyncDialog extends ComfyDialog {
);
}
show(html) {
show(html: string | HTMLElement | HTMLElement[]) {
this.element.addEventListener("close", () => {
this.close();
});
@@ -32,7 +32,7 @@ export class ComfyAsyncDialog extends ComfyDialog {
});
}
showModal(html) {
showModal(html: string | HTMLElement | HTMLElement[]) {
this.element.addEventListener("close", () => {
this.close();
});

View File

@@ -1,37 +1,41 @@
// @ts-nocheck
import { $el } from "../../ui";
import { applyClasses, toggleElement } from "../utils";
import { applyClasses, ClassList, toggleElement } from "../utils";
import { prop } from "../../utils";
import type { ComfyPopup } from "./popup";
import type { ComfyComponent } from ".";
import type { ComfyApp } from "scripts/app";
/**
* @typedef {{
* icon?: string;
* overIcon?: string;
* iconSize?: number;
* content?: string | HTMLElement;
* tooltip?: string;
* enabled?: boolean;
* action?: (e: Event, btn: ComfyButton) => void,
* classList?: import("../utils").ClassList,
* visibilitySetting?: { id: string, showValue: any },
* app?: import("../../app").ComfyApp
* }} ComfyButtonProps
*/
export class ComfyButton {
type ComfyButtonProps = {
icon?: string;
overIcon?: string;
iconSize?: number;
content?: string | HTMLElement;
tooltip?: string;
enabled?: boolean;
action?: (e: Event, btn: ComfyButton) => void;
classList?: ClassList;
visibilitySetting?: { id: string; showValue: any };
app?: ComfyApp;
};
export class ComfyButton implements ComfyComponent<HTMLElement> {
#over = 0;
#popupOpen = false;
isOver = false;
iconElement = $el("i.mdi");
contentElement = $el("span");
/**
* @type {import("./popup").ComfyPopup}
*/
popup;
popup: ComfyPopup;
element: HTMLElement;
overIcon: string;
iconSize: number;
content: string | HTMLElement;
icon: string;
tooltip: string;
classList: ClassList;
hidden: boolean;
enabled: boolean;
action: (e: Event, btn: ComfyButton) => void;
/**
* @param {ComfyButtonProps} opts
*/
constructor({
icon,
overIcon,
@@ -43,7 +47,7 @@ export class ComfyButton {
visibilitySetting,
app,
enabled = true,
}) {
}: ComfyButtonProps) {
this.element = $el(
"button",
{
@@ -101,7 +105,7 @@ export class ComfyButton {
this.hidden = prop(this, "hidden", false, this.updateClasses);
this.enabled = prop(this, "enabled", enabled, () => {
this.updateClasses();
this.element.disabled = !this.enabled;
(this.element as HTMLButtonElement).disabled = !this.enabled;
});
this.action = prop(this, "action", action);
this.element.addEventListener("click", (e) => {
@@ -148,12 +152,7 @@ export class ComfyButton {
applyClasses(this.element, this.classList, ...internalClasses);
};
/**
*
* @param { import("./popup").ComfyPopup } popup
* @param { "click" | "hover" } mode
*/
withPopup(popup, mode = "click") {
withPopup(popup: ComfyPopup, mode: "click" | "hover" = "click") {
this.popup = popup;
if (mode === "hover") {

View File

@@ -1,34 +1,26 @@
// @ts-nocheck
import { $el } from "../../ui";
import { ComfyButton } from "./button";
import { prop } from "../../utils";
export class ComfyButtonGroup {
element = $el("div.comfyui-button-group");
buttons: (HTMLElement | ComfyButton)[];
/** @param {Array<ComfyButton | HTMLElement>} buttons */
constructor(...buttons) {
constructor(...buttons: (HTMLElement | ComfyButton)[]) {
this.buttons = prop(this, "buttons", buttons, () => this.update());
}
/**
* @param {ComfyButton} button
* @param {number} index
*/
insert(button, index) {
insert(button: ComfyButton, index: number) {
this.buttons.splice(index, 0, button);
this.update();
}
/** @param {ComfyButton} button */
append(button) {
append(button: ComfyButton) {
this.buttons.push(button);
this.update();
}
/** @param {ComfyButton|number} indexOrButton */
remove(indexOrButton) {
remove(indexOrButton: ComfyButton | number) {
if (typeof indexOrButton !== "number") {
indexOrButton = this.buttons.indexOf(indexOrButton);
}

View File

@@ -0,0 +1,3 @@
export interface ComfyComponent<T extends HTMLElement = HTMLElement> {
element: T;
}

View File

@@ -1,24 +1,19 @@
// @ts-nocheck
import { prop } from "../../utils";
import { $el } from "../../ui";
import { applyClasses } from "../utils";
import { applyClasses, ClassList } from "../utils";
export class ComfyPopup extends EventTarget {
element = $el("div.comfyui-popup");
open: boolean;
children: HTMLElement[];
target: HTMLElement;
ignoreTarget: boolean;
container: HTMLElement;
position: string;
closeOnEscape: boolean;
horizontal: string;
classList: ClassList;
/**
* @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,
@@ -28,8 +23,16 @@ export class ComfyPopup extends EventTarget {
closeOnEscape = true,
position = "absolute",
horizontal = "left",
}: {
target: HTMLElement;
container?: HTMLElement;
classList?: ClassList;
ignoreTarget?: boolean;
closeOnEscape?: boolean;
position?: "absolute" | "relative";
horizontal?: "left" | "right";
},
...children
...children: HTMLElement[]
) {
super();
this.target = target;

View File

@@ -1,23 +1,27 @@
// @ts-nocheck
import { $el } from "../../ui";
import { ComfyButton } from "./button";
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
*/
arrow: ComfyButton;
element: HTMLElement;
popup: ComfyPopup;
items: Array<HTMLElement | ComfyButton>;
constructor(
{ primary, mode, horizontal = "left", position = "relative" },
...items
{
primary,
mode,
horizontal = "left",
position = "relative",
}: {
primary: ComfyButton;
mode?: "hover" | "click";
horizontal?: "left" | "right";
position?: "relative" | "absolute";
},
...items: Array<HTMLElement | ComfyButton>
) {
this.arrow = new ComfyButton({
icon: "chevron-down",
@@ -46,7 +50,7 @@ export class ComfySplitButton {
update() {
this.popup.element.replaceChildren(
...this.items.map((b) => b.element ?? b)
...this.items.map((b) => ("element" in b ? b.element : b))
);
}
}

View File

@@ -1,5 +1,4 @@
// @ts-nocheck
import type { ComfyApp } from "scripts/app";
import { api } from "../../api";
import { $el } from "../../ui";
import { downloadBlob } from "../../utils";
@@ -12,6 +11,9 @@ import { ComfyWorkflowsMenu } from "./workflows";
import { ComfyViewQueueButton } from "./viewQueue";
import { getInteruptButton } from "./interruptButton";
import "./menu.css";
import type { ComfySettingsDialog } from "../settings";
type MenuPosition = "Disabled" | "Top" | "Bottom";
const collapseOnMobile = (t) => {
(t.element ?? t).classList.add("comfyui-menu-mobile-collapse");
@@ -33,15 +35,23 @@ export class ComfyAppMenu {
#sizeBreaks = Object.keys(this.#lastSizeBreaks);
#cachedInnerSize = null;
#cacheTimeout = null;
app: ComfyApp;
workflows: ComfyWorkflowsMenu;
logo: HTMLElement;
saveButton: ComfySplitButton;
actionsGroup: ComfyButtonGroup;
settingsGroup: ComfyButtonGroup;
viewGroup: ComfyButtonGroup;
mobileMenuButton: ComfyButton;
element: HTMLElement;
menuPositionSetting: ReturnType<ComfySettingsDialog["addSetting"]>;
position: MenuPosition;
/**
* @param { import("../../app").ComfyApp } app
*/
constructor(app) {
constructor(app: ComfyApp) {
this.app = app;
this.workflows = new ComfyWorkflowsMenu(app);
const getSaveButton = (t) =>
const getSaveButton = (t?: string) =>
new ComfyButton({
icon: "content-save",
tooltip: "Save the current workflow",
@@ -158,14 +168,14 @@ export class ComfyAppMenu {
showOnMobile(this.mobileMenuButton).element,
]);
let resizeHandler;
let resizeHandler: () => void;
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) => {
onChange: async (v: MenuPosition) => {
if (v && v !== "Disabled") {
if (!resizeHandler) {
resizeHandler = () => {
@@ -189,7 +199,7 @@ export class ComfyAppMenu {
});
}
updatePosition(v) {
updatePosition(v: MenuPosition) {
document.body.style.display = "grid";
this.app.ui.menuContainer.style.display = "none";
this.element.style.removeProperty("display");
@@ -202,7 +212,7 @@ export class ComfyAppMenu {
this.calculateSizeBreak();
}
updateSizeBreak(idx, prevIdx, direction) {
updateSizeBreak(idx: number, prevIdx: number, direction: number) {
const newSize = this.#sizeBreaks[idx];
if (newSize === this.#sizeBreak) return;
this.#cachedInnerSize = null;
@@ -264,7 +274,7 @@ export class ComfyAppMenu {
this.updateSizeBreak(idx, currIdx, direction);
}
calculateInnerSize(idx) {
calculateInnerSize(idx: number) {
// Cache the inner size to prevent too much calculation when resizing the window
clearTimeout(this.#cacheTimeout);
if (this.#cachedInnerSize) {
@@ -293,10 +303,7 @@ export class ComfyAppMenu {
return this.#cachedInnerSize;
}
/**
* @param {string} defaultName
*/
getFilename(defaultName) {
getFilename(defaultName: string) {
if (this.app.ui.settings.getSettingValue("Comfy.PromptFilename", true)) {
defaultName = prompt("Save workflow as:", defaultName);
if (!defaultName) return;
@@ -307,11 +314,10 @@ export class ComfyAppMenu {
return defaultName;
}
/**
* @param {string} [filename]
* @param { "workflow" | "output" } [promptProperty]
*/
async exportWorkflow(filename, promptProperty) {
async exportWorkflow(
filename: string,
promptProperty: "workflow" | "output"
) {
if (this.app.workflowManager.activeWorkflow?.path) {
filename = this.app.workflowManager.activeWorkflow.name;
}

View File

@@ -1,9 +1,7 @@
// @ts-nocheck
import { api } from "../../api";
import { ComfyButton } from "../components/button";
export function getInteruptButton(visibility) {
export function getInteruptButton(visibility: string) {
const btn = new ComfyButton({
icon: "close",
tooltip: "Cancel current generation",

View File

@@ -1,17 +1,16 @@
// @ts-nocheck
import { ComfyButton } from "../components/button";
import { $el } from "../../ui";
import { api } from "../../api";
import { ComfySplitButton } from "../components/splitButton";
import { ComfyQueueOptions } from "./queueOptions";
import { prop } from "../../utils";
import type { ComfyApp } from "scripts/app";
export class ComfyQueueButton {
element = $el("div.comfyui-queue-button");
#internalQueueSize = 0;
queuePrompt = async (e) => {
queuePrompt = async (e?: MouseEvent) => {
this.#internalQueueSize += this.queueOptions.batchCount;
// Hold shift to queue front, event is undefined when auto-queue is enabled
await this.app.queuePrompt(
@@ -19,8 +18,13 @@ export class ComfyQueueButton {
this.queueOptions.batchCount
);
};
queueOptions: ComfyQueueOptions;
app: ComfyApp;
queueSizeElement: HTMLElement;
autoQueueMode: string;
graphHasChanged: boolean;
constructor(app) {
constructor(app: ComfyApp) {
this.app = app;
this.queueSizeElement = $el("span.comfyui-queue-count", {
textContent: "?",

View File

@@ -1,12 +1,17 @@
// @ts-nocheck
import type { ComfyApp } from "scripts/app";
import { $el } from "../../ui";
import { prop } from "../../utils";
export class ComfyQueueOptions extends EventTarget {
element = $el("div.comfyui-queue-options");
app: ComfyApp;
batchCountInput: HTMLInputElement;
batchCount: number;
batchCountRange: HTMLInputElement;
autoQueueMode: string;
autoQueueEl: HTMLElement;
constructor(app) {
constructor(app: ComfyApp) {
super();
this.app = app;

View File

@@ -1,10 +1,9 @@
// @ts-nocheck
import type { ComfyApp } from "scripts/app";
import { ComfyButton } from "../components/button";
import { ComfyViewList, ComfyViewListButton } from "./viewList";
export class ComfyViewHistoryButton extends ComfyViewListButton {
constructor(app) {
constructor(app: ComfyApp) {
super(app, {
button: new ComfyButton({
content: "View History",

View File

@@ -1,11 +1,18 @@
// @ts-nocheck
import { ComfyButton } from "../components/button";
import { $el } from "../../ui";
import { api } from "../../api";
import { ComfyPopup } from "../components/popup";
import type { ComfyApp } from "scripts/app";
type ViewListMode = "Queue" | "History";
export class ComfyViewListButton {
popup: ComfyPopup;
app: ComfyApp;
button: ComfyButton;
element: HTMLDivElement;
list: ComfyViewList;
get open() {
return this.popup.open;
}
@@ -14,10 +21,20 @@ export class ComfyViewListButton {
this.popup.open = open;
}
constructor(app, { button, list, mode }) {
constructor(
app: ComfyApp,
{
button,
list,
mode,
}: { button: ComfyButton; list: typeof ComfyViewList; mode: ViewListMode }
) {
this.app = app;
this.button = button;
this.element = $el("div.comfyui-button-wrapper", this.button.element);
this.element = $el(
"div.comfyui-button-wrapper",
this.button.element
) as HTMLDivElement;
this.popup = new ComfyPopup({
target: this.element,
container: this.element,
@@ -42,9 +59,15 @@ export class ComfyViewListButton {
}
export class ComfyViewList {
popup;
constructor(app, mode, popup) {
app: ComfyApp;
mode: ViewListMode;
popup: ComfyPopup;
type: string;
items: HTMLElement;
clear: ComfyButton;
refresh: ComfyButton;
element: HTMLElement;
constructor(app: ComfyApp, mode: ViewListMode, popup: ComfyPopup) {
this.app = app;
this.mode = mode;
this.popup = popup;

View File

@@ -1,11 +1,10 @@
// @ts-nocheck
import { ComfyButton } from "../components/button";
import { ComfyViewList, ComfyViewListButton } from "./viewList";
import { api } from "../../api";
import type { ComfyApp } from "scripts/app";
export class ComfyViewQueueButton extends ComfyViewListButton {
constructor(app) {
constructor(app: ComfyApp) {
super(app, {
button: new ComfyButton({
content: "View Queue",

View File

@@ -1,5 +1,3 @@
// @ts-nocheck
import { ComfyButton } from "../components/button";
import { prop, getStorageValue, setStorageValue } from "../../utils";
import { $el } from "../../ui";
@@ -8,10 +6,19 @@ import { ComfyPopup } from "../components/popup";
import { createSpinner } from "../spinner";
import { ComfyWorkflow, trimJsonExt } from "../../workflows";
import { ComfyAsyncDialog } from "../components/asyncDialog";
import type { ComfyApp } from "scripts/app";
import type { ComfyComponent } from "../components";
export class ComfyWorkflowsMenu {
#first = true;
element = $el("div.comfyui-workflows");
popup: ComfyPopup;
app: ComfyApp;
buttonProgress: HTMLElement;
workflowLabel: HTMLElement;
button: ComfyButton;
content: ComfyWorkflowsContent;
unsaved: boolean;
get open() {
return this.popup.open;
@@ -21,10 +28,7 @@ export class ComfyWorkflowsMenu {
this.popup.open = open;
}
/**
* @param {import("../../app").ComfyApp} app
*/
constructor(app) {
constructor(app: ComfyApp) {
this.app = app;
this.#bindEvents();
@@ -158,10 +162,7 @@ export class ComfyWorkflowsMenu {
return menu;
}
/**
* @param {import("../../app").ComfyApp} app
*/
registerExtension(app) {
registerExtension(app: ComfyApp) {
const self = this;
app.registerExtension({
name: "Comfy.Workflows",
@@ -192,11 +193,10 @@ export class ComfyWorkflowsMenu {
app.graph.setDirtyCanvas(true, true);
}
/**
* @param {HTMLImageElement} img
* @param {ComfyWorkflow} workflow
*/
async function sendToWorkflow(img, workflow) {
async function sendToWorkflow(
img: HTMLImageElement,
workflow: ComfyWorkflow
) {
await workflow.load();
let options = [];
const nodes = app.graph.computeExecutionOrder(false);
@@ -226,22 +226,23 @@ export class ComfyWorkflowsMenu {
}
const getExtraMenuOptions = nodeType.prototype["getExtraMenuOptions"];
nodeType.prototype["getExtraMenuOptions"] = function (_, options) {
nodeType.prototype["getExtraMenuOptions"] = function (
this: { imageIndex?: number; overIndex?: number; imgs: string[] },
_,
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
);
const t = this;
let img;
if (t.imageIndex != null) {
// An image is selected so select that
img = t.imgs?.[t.imageIndex];
} else if (t.overIndex != null) {
// No image is selected but one is hovered
img = t.img?.s[t.overIndex];
img = t.imgs?.[t.overIndex];
}
if (img) {
@@ -283,17 +284,20 @@ export class ComfyWorkflowsMenu {
export class ComfyWorkflowsContent {
element = $el("div.comfyui-workflows-panel");
treeState = {};
treeFiles = {};
/** @type { Map<ComfyWorkflow, WorkflowElement> } */
openFiles = new Map();
/** @type {WorkflowElement} */
activeElement = null;
treeFiles: Record<string, WorkflowElement> = {};
openFiles: Map<ComfyWorkflow, WorkflowElement<ComfyComponent>> = new Map();
activeElement: WorkflowElement<ComfyComponent> = null;
spinner: Element;
openElement: HTMLElement;
favoritesElement: HTMLElement;
treeElement: HTMLElement;
app: ComfyApp;
popup: ComfyPopup;
actions: HTMLElement;
filterText: string | undefined;
treeRoot: HTMLElement;
/**
* @param {import("../../app").ComfyApp} app
* @param {ComfyPopup} popup
*/
constructor(app, popup) {
constructor(app: ComfyApp, popup: ComfyPopup) {
this.app = app;
this.popup = popup;
this.actions = $el("div.comfyui-workflows-actions", [
@@ -511,7 +515,7 @@ export class ComfyWorkflowsContent {
$el("input", {
placeholder: "Search",
value: this.filterText ?? "",
oninput: (e) => {
oninput: (e: InputEvent) => {
this.filterText = e.target["value"]?.trim();
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => this.filterTree(), 250);
@@ -589,25 +593,21 @@ export class ComfyWorkflowsContent {
this.activeElement.primary.element.classList.remove("mdi-play");
}
/** @param {ComfyWorkflow} workflow */
#getFavoriteIcon(workflow) {
#getFavoriteIcon(workflow: ComfyWorkflow) {
return workflow.isFavorite ? "star" : "file-outline";
}
/** @param {ComfyWorkflow} workflow */
#getFavoriteOverIcon(workflow) {
#getFavoriteOverIcon(workflow: ComfyWorkflow) {
return workflow.isFavorite ? "star-off" : "star-outline";
}
/** @param {ComfyWorkflow} workflow */
#getFavoriteTooltip(workflow) {
#getFavoriteTooltip(workflow: ComfyWorkflow) {
return workflow.isFavorite
? "Remove this workflow from your favorites"
: "Add this workflow to your favorites";
}
/** @param {ComfyWorkflow} workflow */
#getFavoriteButton(workflow, primary) {
#getFavoriteButton(workflow: ComfyWorkflow, primary: boolean) {
return new ComfyButton({
icon: this.#getFavoriteIcon(workflow),
overIcon: this.#getFavoriteOverIcon(workflow),
@@ -623,8 +623,7 @@ export class ComfyWorkflowsContent {
});
}
/** @param {ComfyWorkflow} workflow */
#getDeleteButton(workflow) {
#getDeleteButton(workflow: ComfyWorkflow) {
const deleteButton = new ComfyButton({
icon: "delete",
tooltip: "Delete this workflow",
@@ -650,8 +649,7 @@ export class ComfyWorkflowsContent {
return deleteButton;
}
/** @param {ComfyWorkflow} workflow */
#getInsertButton(workflow) {
#getInsertButton(workflow: ComfyWorkflow) {
return new ComfyButton({
icon: "file-move-outline",
iconSize: 18,
@@ -671,7 +669,7 @@ export class ComfyWorkflowsContent {
}
/** @param {ComfyWorkflow} workflow */
#getRenameButton(workflow) {
#getRenameButton(workflow: ComfyWorkflow) {
return new ComfyButton({
icon: "pencil",
tooltip: workflow.path
@@ -690,8 +688,7 @@ export class ComfyWorkflowsContent {
});
}
/** @param {ComfyWorkflow} workflow */
#getWorkflowElement(workflow) {
#getWorkflowElement(workflow: ComfyWorkflow) {
return new WorkflowElement(this, workflow, {
primary: this.#getFavoriteButton(workflow, true),
buttons: [
@@ -702,8 +699,7 @@ export class ComfyWorkflowsContent {
});
}
/** @param {ComfyWorkflow} workflow */
#createLeafNode(workflow) {
#createLeafNode(workflow: ComfyWorkflow) {
const fileNode = this.#getWorkflowElement(workflow);
this.treeFiles[workflow.path] = fileNode;
return fileNode;
@@ -741,12 +737,21 @@ export class ComfyWorkflowsContent {
}
}
class WorkflowElement {
/**
* @param { ComfyWorkflowsContent } parent
* @param { ComfyWorkflow } workflow
*/
constructor(parent, workflow, { tagName = "li", primary, buttons }) {
class WorkflowElement<TPrimary extends ComfyComponent = ComfyButton> {
parent: ComfyWorkflowsContent;
workflow: ComfyWorkflow;
primary: TPrimary;
buttons: ComfyButton[];
element: HTMLElement;
constructor(
parent: ComfyWorkflowsContent,
workflow: ComfyWorkflow,
{
tagName = "li",
primary,
buttons,
}: { tagName?: string; primary: TPrimary; buttons: ComfyButton[] }
) {
this.parent = parent;
this.workflow = workflow;
this.primary = primary;
@@ -770,13 +775,15 @@ class WorkflowElement {
}
}
class WidgetSelectionDialog extends ComfyAsyncDialog {
#options;
type WidgetSelectionDialogOptions = Array<{
widget: { name: string };
node: { pos: [number, number]; title: string; id: string; type: string };
}>;
/**
* @param {Array<{widget: {name: string}, node: {pos: [number, number], title: string, id: string, type: string}}>} options
*/
constructor(options) {
class WidgetSelectionDialog extends ComfyAsyncDialog {
#options: WidgetSelectionDialogOptions;
constructor(options: WidgetSelectionDialogOptions) {
super();
this.#options = options;
}

View File

@@ -31,7 +31,7 @@ interface SettingParams {
onChange?: (newValue: any, oldValue?: any) => void;
attrs?: any;
tooltip?: string;
options?: SettingOption[] | ((value: any) => SettingOption[]);
options?: Array<string | SettingOption> | ((value: any) => SettingOption[]);
}
export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {

View File

@@ -1,16 +1,13 @@
/**
* @typedef { string | string[] | Record<string, boolean> } ClassList
*/
export type ClassList = string | string[] | Record<string, boolean>;
/**
* @param { HTMLElement } element
* @param { ClassList } classList
* @param { string[] } requiredClasses
*/
export function applyClasses(element, classList, ...requiredClasses) {
export function applyClasses(
element: HTMLElement,
classList: ClassList,
...requiredClasses: string[]
) {
classList ??= "";
let str;
let str: string;
if (typeof classList === "string") {
str = classList;
} else if (classList instanceof Array) {
@@ -29,14 +26,18 @@ export function applyClasses(element, classList, ...requiredClasses) {
}
}
/**
* @param { HTMLElement } element
* @param { { onHide?: (el: HTMLElement) => void, onShow?: (el: HTMLElement, value) => void } } [param1]
* @returns
*/
export function toggleElement(element, { onHide, onShow } = {}) {
let placeholder;
let hidden;
export function toggleElement(
element: HTMLElement,
{
onHide,
onShow,
}: {
onHide?: (el: HTMLElement) => void;
onShow?: (el: HTMLElement, value) => void;
} = {}
) {
let placeholder: HTMLElement | Comment;
let hidden: boolean;
return (value) => {
if (value) {
if (hidden) {

View File

@@ -136,14 +136,17 @@ export function downloadBlob(filename, blob) {
}, 0);
}
/**
* @template T
* @param {string} name
* @param {T} [defaultValue]
* @param {(currentValue: any, previousValue: any)=>void} [onChanged]
* @returns {T}
*/
export function prop(target, name, defaultValue, onChanged) {
export function prop<T>(
target: object,
name: string,
defaultValue: T,
onChanged?: (
currentValue: T,
previousValue: T,
target: object,
name: string
) => void
): T {
let currentValue;
Object.defineProperty(target, name, {
get() {

View File

@@ -1,36 +1,31 @@
// @ts-nocheck
import type { ComfyApp } from "./app";
import { api } from "./api";
import { ChangeTracker } from "./changeTracker";
import { ComfyAsyncDialog } from "./ui/components/asyncDialog";
import { getStorageValue, setStorageValue } from "./utils";
import { LGraphCanvas } from "@comfyorg/litegraph";
import { LGraphCanvas, LGraph } from "@comfyorg/litegraph";
function appendJsonExt(path) {
function appendJsonExt(path: string) {
if (!path.toLowerCase().endsWith(".json")) {
path += ".json";
}
return path;
}
export function trimJsonExt(path) {
export function trimJsonExt(path: string) {
return path?.replace(/\.json$/, "");
}
export class ComfyWorkflowManager extends EventTarget {
/** @type {string | null} */
#activePromptId = null;
#activePromptId: string | null = null;
#unsavedCount = 0;
#activeWorkflow;
#activeWorkflow: ComfyWorkflow;
/** @type {Record<string, ComfyWorkflow>} */
workflowLookup = {};
/** @type {Array<ComfyWorkflow>} */
workflows = [];
/** @type {Array<ComfyWorkflow>} */
openWorkflows = [];
/** @type {Record<string, {workflow?: ComfyWorkflow, nodes?: Record<string, boolean>}>} */
queuedPrompts = {};
workflowLookup: Record<string, ComfyWorkflow> = {};
workflows: Array<ComfyWorkflow> = [];
openWorkflows: Array<ComfyWorkflow> = [];
queuedPrompts: Record<string, { workflow?: ComfyWorkflow; nodes?: Record<string, boolean>; }> = {};
app: ComfyApp;
get activeWorkflow() {
return this.#activeWorkflow ?? this.openWorkflows[0];
@@ -44,10 +39,7 @@ export class ComfyWorkflowManager extends EventTarget {
return this.queuedPrompts[this.#activePromptId];
}
/**
* @param {import("./app").ComfyApp} app
*/
constructor(app) {
constructor(app: ComfyApp) {
super();
this.app = app;
ChangeTracker.init(app);
@@ -238,9 +230,9 @@ export class ComfyWorkflow {
#path;
#pathParts;
#isFavorite = false;
/** @type {ChangeTracker | null} */
changeTracker = null;
changeTracker: ChangeTracker | null = null;
unsaved = false;
manager: ComfyWorkflowManager;
get name() {
return this.#name;
@@ -262,25 +254,7 @@ export class ComfyWorkflow {
return !!this.changeTracker;
}
/**
* @overload
* @param {ComfyWorkflowManager} manager
* @param {string} path
*/
/**
* @overload
* @param {ComfyWorkflowManager} manager
* @param {string} path
* @param {string[]} pathParts
* @param {boolean} isFavorite
*/
/**
* @param {ComfyWorkflowManager} manager
* @param {string} path
* @param {string[]} [pathParts]
* @param {boolean} [isFavorite]
*/
constructor(manager, path, pathParts, isFavorite) {
constructor(manager: ComfyWorkflowManager, path: string, pathParts?: string[], isFavorite?: boolean) {
this.manager = manager;
if (pathParts) {
this.#updatePath(path, pathParts);
@@ -291,11 +265,7 @@ export class ComfyWorkflow {
}
}
/**
* @param {string} path
* @param {string[]} [pathParts]
*/
#updatePath(path, pathParts) {
#updatePath(path: string, pathParts: string[]) {
this.#path = path;
if (!pathParts) {
@@ -344,10 +314,7 @@ export class ComfyWorkflow {
}
}
/**
* @param {boolean} value
*/
async favorite(value) {
async favorite(value: boolean) {
try {
if (this.#isFavorite === value) return;
this.#isFavorite = value;
@@ -363,10 +330,7 @@ export class ComfyWorkflow {
}
}
/**
* @param {string} path
*/
async rename(path) {
async rename(path: string) {
path = appendJsonExt(path);
let resp = await api.moveUserData(
"workflows/" + this.path,
@@ -412,8 +376,10 @@ export class ComfyWorkflow {
if (!data) return;
const old = localStorage.getItem("litegrapheditor_clipboard");
// @ts-ignore
const graph = new LGraph(data);
const canvas = new LGraphCanvas(null, graph, {
// @ts-ignore
skip_events: true,
skip_render: true,
});
@@ -449,11 +415,7 @@ export class ComfyWorkflow {
}
}
/**
* @param {string|null} path
* @param {boolean} overwrite
*/
async #save(path, overwrite) {
async #save(path: string | null, overwrite: boolean) {
if (!path) {
path = prompt(
"Save workflow as:",