Close a workflow's tabs when you delete it from the file list

This commit is contained in:
Edouard Durand
2026-09-27 07:03:48 +00:00
parent 69ad03e237
commit 77575fa6b2
3 changed files with 64 additions and 2 deletions
+34
View File
@@ -128,3 +128,37 @@ test("a file renamed by someone else re-points the open tab and rejoins its room
sync.tick();
assert.equal(sent.at(-1).room, null, "moved out of reach: the tab leaves the room");
});
test("deleting a folder closes its tabs, the active one through ComfyUI's close", async () => {
const a = { path: "workflows/team/a.json", isTemporary: false, isModified: true };
const b = { path: "workflows/team/sub/b.json", isTemporary: false };
const other = { path: "workflows/other.json", isTemporary: false };
const store = {
activeWorkflow: a, openWorkflows: [a, b, other],
closeWorkflow(wf) { this.openWorkflows = this.openWorkflows.filter((w) => w !== wf); },
};
const commands = [];
const client = Object.assign(new EventTarget(), {
online: true, perms: { edit: true }, rooms: [], users: [],
on(type, fn) { this.addEventListener(type, (e) => fn(e.detail)); },
emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail })); },
send() { return true; },
});
const app = {
extensionManager: { workflow: store, command: { async execute(id) {
commands.push([id, store.activeWorkflow.isModified]);
store.closeWorkflow(store.activeWorkflow);
store.activeWorkflow = store.openWorkflows[0];
} } },
canvas: null, loadGraphData: async () => {},
rootGraph: { serialize: () => ({ nodes: [], links: [], extra: {} }), extra: {}, getNodeById: () => null },
};
const sync = new RoomSync(app, new EventTarget(), client);
const toasts = [];
client.on("toast", (t) => toasts.push(t));
await sync.closeTabsUnder("workflows/team");
assert.deepEqual(store.openWorkflows, [other]);
assert.deepEqual(commands, [["Workspace.CloseWorkflow", false]], "no unsaved-changes prompt");
client.emit("room_closed", { room: "file:workflows/team/a.json", name: "a" });
assert.equal(toasts.length, 0, "our own delete: no private-copy notice");
});
+8 -2
View File
@@ -310,10 +310,15 @@ export class FilesBrowser {
async remove(e) {
const what = e.type === "folder" ? `the folder "${e.name}"` : `"${e.name}"`;
if (!(await this.confirm("Delete", `Delete ${what}? This cannot be undone.`))) return;
// Its open tabs close first (ComfyUI would otherwise keep them as orphans).
const path = `workflows/${e.path}`;
const closing = this.sync.tabsUnder(path).map((wf) => `file:${wf.path}`);
await this.sync.closeTabsUnder(path);
let done;
if (e.type === "folder") {
await this.run(() => this.client.request("POST", "/rigshare/api/workspace/delete-folder", { path: e.path }), "Deleted");
done = await this.run(() => this.client.request("POST", "/rigshare/api/workspace/delete-folder", { path: e.path }), "Deleted");
} else {
await this.run(async () => {
done = await this.run(async () => {
const store = this.app.extensionManager.workflow;
await store.syncWorkflows?.();
const wf = store.getWorkflowByPath?.(`workflows/${e.path}`);
@@ -325,6 +330,7 @@ export class FilesBrowser {
}
}, "Deleted");
}
if (!done) for (const key of closing) this.sync.denied.delete(key); // still there: reopening it rejoins
await this.refreshComfy();
this.reload();
}
+22
View File
@@ -141,6 +141,7 @@ export class RoomSync extends EventTarget {
});
client.on("room_closed", (msg) => {
// The file was deleted: the tab stays open as a private copy.
if (this.denied.has(msg.room) && !this.findTab({ key: msg.room })) return; // we deleted it and closed the tab
this.denied.add(msg.room);
if (msg.room === this.targetKey) {
this.ready = false;
@@ -524,6 +525,27 @@ export class RoomSync extends EventTarget {
!wf.isTemporary && (wf.path === path || wf.path?.startsWith(path + "/")));
}
/**
* We are deleting a file (or folder): close its tabs first, without the
* "unsaved changes" prompt (the user already confirmed the delete). The
* active tab goes through ComfyUI's own close, which opens the next tab.
*/
async closeTabsUnder(path) {
const store = this.store;
const tabs = this.tabsUnder(path);
if (!tabs.length) return;
for (const wf of tabs) {
this.denied.add(`file:${wf.path}`); // our own delete: no "private copy" notice
if (wf === store.activeWorkflow) continue;
store.closeWorkflow?.(wf);
}
const active = store.activeWorkflow;
if (active && tabs.includes(active)) {
active.isModified = false;
await this.app.extensionManager?.command?.execute("Workspace.CloseWorkflow");
}
}
/**
* Someone renamed or moved a file (or folder) we have open: point our tabs
* at the new path. The file already moved on the server, so ComfyUI's own