mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-25 09:14:25 +00:00
v1.2.0 Side Bar & Menu rework (#189)
* Basic side tool bar skeleton + Theme toggle (#164) * Side bar skeleton * Fix grid layout * nit * Add theme toggle logic * Change primevue color theme to blue to match beta menu UI * Add litegraph canvas splitter overlay (#177) * Add vue wrapper * Splitter overlay * Move teleport to side bar comp * Toolbar placeholder * Move settings button from top menu to side bar (#178) * Reverse relationship between splitter overlay and sidebar component (#180) * Reverse relationship between splitter overlay and sidebar component * nit * Remove border on splitter * Fix canvas shift (#186) * Move queue/history display to side bar (#185) * Side bar placeholder * Pinia store for queue items * Flatten task item * Fix schema * computed * Switch running / pending order * Use class-transformer * nit * Show display status * Add tag severity style * Add execution time * nit * Rename to execution success * Add time display * Sort queue desc order * nit * Add remove item feature * Load workflow * Add confirmation popup * Add empty table placeholder * Remove beta menu UI's queue button/list * Add tests on litegraph widget text truncate (#191) * Add tests on litegraph widget text truncate * Updated screenshots * Revert port change * Remove screenshots * Update test expectations [skip ci] * Add back menu.settingsGroup for compatibility (#192) * Close side bar on menu location set as disabled (#194) * Remove placeholder side bar tabs (#196) --------- Co-authored-by: bymyself <abolkonsky.rem@gmail.com> Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -184,6 +184,11 @@ class ComfyApi extends EventTarget {
|
||||
new CustomEvent("execution_start", { detail: msg.data })
|
||||
);
|
||||
break;
|
||||
case "execution_success":
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("execution_success", { detail: msg.data })
|
||||
);
|
||||
break;
|
||||
case "execution_error":
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("execution_error", { detail: msg.data })
|
||||
@@ -315,10 +320,14 @@ class ComfyApi extends EventTarget {
|
||||
return {
|
||||
// Running action uses a different endpoint for cancelling
|
||||
Running: data.queue_running.map((prompt) => ({
|
||||
taskType: "Running",
|
||||
prompt,
|
||||
remove: { name: "Cancel", cb: () => api.interrupt() },
|
||||
})),
|
||||
Pending: data.queue_pending.map((prompt) => ({ prompt })),
|
||||
Pending: data.queue_pending.map((prompt) => ({
|
||||
taskType: "Pending",
|
||||
prompt,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -335,7 +344,11 @@ class ComfyApi extends EventTarget {
|
||||
): Promise<{ History: HistoryTaskItem[] }> {
|
||||
try {
|
||||
const res = await this.fetchApi(`/history?max_items=${max_items}`);
|
||||
return { History: Object.values(await res.json()) };
|
||||
return {
|
||||
History: Object.values(await res.json()).map(
|
||||
(item: HistoryTaskItem) => ({ ...item, taskType: "History" })
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { History: [] };
|
||||
|
||||
@@ -1845,13 +1845,18 @@ export class ComfyApp {
|
||||
await this.#setUser();
|
||||
|
||||
// Create and mount the LiteGraph in the DOM
|
||||
const canvasContainer = document.createElement("div");
|
||||
canvasContainer.id = "graph-canvas-container";
|
||||
|
||||
const mainCanvas = document.createElement("canvas");
|
||||
mainCanvas.style.touchAction = "none";
|
||||
const canvasEl = (this.canvasEl = Object.assign(mainCanvas, {
|
||||
id: "graph-canvas",
|
||||
}));
|
||||
canvasEl.tabIndex = 1;
|
||||
document.body.prepend(canvasEl);
|
||||
canvasContainer.prepend(canvasEl);
|
||||
document.body.prepend(canvasContainer);
|
||||
|
||||
this.resizeCanvas();
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@@ -5,10 +5,8 @@ import { downloadBlob } from "../../utils";
|
||||
import { ComfyButton } from "../components/button";
|
||||
import { ComfyButtonGroup } from "../components/buttonGroup";
|
||||
import { ComfySplitButton } from "../components/splitButton";
|
||||
import { ComfyViewHistoryButton } from "./viewHistory";
|
||||
import { ComfyQueueButton } from "./queueButton";
|
||||
import { ComfyWorkflowsMenu } from "./workflows";
|
||||
import { ComfyViewQueueButton } from "./viewQueue";
|
||||
import { getInteruptButton } from "./interruptButton";
|
||||
import "./menu.css";
|
||||
import type { ComfySettingsDialog } from "../settings";
|
||||
@@ -128,19 +126,10 @@ export class ComfyAppMenu {
|
||||
},
|
||||
})
|
||||
);
|
||||
this.settingsGroup = new ComfyButtonGroup(
|
||||
new ComfyButton({
|
||||
icon: "cog",
|
||||
content: "Settings",
|
||||
tooltip: "Open settings",
|
||||
action: () => {
|
||||
app.ui.settings.show();
|
||||
},
|
||||
})
|
||||
);
|
||||
// Keep the settings group as there are custom scripts attaching extra
|
||||
// elements to it.
|
||||
this.settingsGroup = new ComfyButtonGroup();
|
||||
this.viewGroup = new ComfyButtonGroup(
|
||||
new ComfyViewHistoryButton(app).element,
|
||||
new ComfyViewQueueButton(app).element,
|
||||
getInteruptButton("nlg-hide").element
|
||||
);
|
||||
this.mobileMenuButton = new ComfyButton({
|
||||
@@ -193,6 +182,7 @@ export class ComfyAppMenu {
|
||||
app.ui.menuContainer.style.removeProperty("display");
|
||||
this.element.style.display = "none";
|
||||
app.ui.restoreMenuPosition();
|
||||
document.dispatchEvent(new Event("comfy:setting:beta-menu-disabled"));
|
||||
}
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { ComfyApp } from "@/scripts/app";
|
||||
import { ComfyButton } from "../components/button";
|
||||
import { ComfyViewList, ComfyViewListButton } from "./viewList";
|
||||
|
||||
export class ComfyViewHistoryButton extends ComfyViewListButton {
|
||||
constructor(app: ComfyApp) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
set open(open) {
|
||||
this.popup.open = open;
|
||||
}
|
||||
|
||||
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
|
||||
) as HTMLDivElement;
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfyViewList {
|
||||
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;
|
||||
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.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,
|
||||
]
|
||||
);
|
||||
|
||||
api.addEventListener("status", () => {
|
||||
if (this.popup.open) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.items.replaceChildren();
|
||||
}
|
||||
|
||||
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 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"));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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: ComfyApp) {
|
||||
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) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user