App mode: dock for the RigShare panel and Files
ComfyUI's App view shows a fixed set of sidebar tabs (assets, apps), so extension tabs vanish there. While the tab on screen is in the App view, a small dock opens the RigShare panel and Files in a drawer, with the unread chat badge.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
### Added
|
||||
|
||||
- App mode: live workflows work in ComfyUI's App view. Each person keeps their own graph or App view through live edits, and App inputs sync. Cursors are off in the App view. Workflows saved as Apps (`.app.json`) show with an app icon in Files and in the Live list, and Save keeps them Apps
|
||||
- In the App view, where ComfyUI shows only its own sidebar tabs, a small dock at the bottom left opens the RigShare panel and Files in a drawer, with the unread chat badge
|
||||
- ComfyUI's own Workflows and Apps sidebar tabs are hidden, since Files replaces them, and their shortcuts open Files. Admin setting `hide_comfy_file_tabs`, on by default
|
||||
|
||||
## 1.1.0 (2026-09-26)
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ Only the tab on screen is live. Switch to another tab and you leave the room; sw
|
||||
- Two people editing the same node at once: the last change wins.
|
||||
- Two people adding or connecting nodes at the same moment both keep their work. If both picked the same node or link id, the server renumbers the later one and quietly resyncs that person. Links and groups merge one by one, so a connection made by one person never erases another's.
|
||||
|
||||
**App mode.** Each person chooses for themselves whether a workflow shows as the node graph or as an App. Switching doesn't change anyone else's view, and live edits keep it. Inputs changed in the App view sync like any other widget value. The app's layout, meaning its chosen inputs and outputs, belongs to the workflow and syncs. Cursors aren't shown in the App view.
|
||||
**App mode.** Each person chooses for themselves whether a workflow shows as the node graph or as an App. Switching doesn't change anyone else's view, and live edits keep it. Inputs changed in the App view sync like any other widget value. The app's layout, meaning its chosen inputs and outputs, belongs to the workflow and syncs. Cursors aren't shown in the App view. ComfyUI's App view shows only its own sidebar tabs, so RigShare adds a small dock at the bottom left there: it opens the RigShare panel and Files in a drawer, and shows the unread chat badge.
|
||||
|
||||
Viewers (no *Edit* permission) see a read-only canvas. Anything they change locally is put back.
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// RigShare in ComfyUI's App mode.
|
||||
//
|
||||
// The App view shows a fixed set of sidebar tabs (assets and apps), so the
|
||||
// RigShare and Files tabs disappear there. While the tab on screen is in the
|
||||
// App view, a small dock offers them instead, each opening the same panel in
|
||||
// a drawer.
|
||||
|
||||
import { h } from "./ui.js";
|
||||
|
||||
export class AppDock {
|
||||
/**
|
||||
* views: { id: { icon, title, mount(el), unmount() } }
|
||||
*/
|
||||
constructor({ sync, views }) {
|
||||
this.sync = sync;
|
||||
this.views = views;
|
||||
this.open = null;
|
||||
this.buttons = {};
|
||||
this.body = h("div", { class: "rs-host rs-dock-drawer-body" });
|
||||
this.drawer = h("div", { class: "rs-dock-drawer rs-panel-vars", role: "dialog" },
|
||||
h("button", { class: "rs-dock-close", title: "Close", onclick: () => this.show(null) }, h("i", { class: "pi pi-times" })),
|
||||
this.body);
|
||||
this.dock = h("div", { class: "rs-dock rs-panel-vars" }, ...Object.entries(views).map(([id, v]) => {
|
||||
const badge = h("span", { class: "rs-dock-badge" });
|
||||
const btn = h("button", {
|
||||
class: "rs-dock-btn", title: v.title, "aria-label": v.title,
|
||||
onclick: () => this.show(this.open === id ? null : id),
|
||||
}, h("i", { class: `pi ${v.icon}` }), badge);
|
||||
this.buttons[id] = { btn, badge };
|
||||
return btn;
|
||||
}));
|
||||
this.dock.style.display = "none";
|
||||
this.drawer.style.display = "none";
|
||||
document.body.append(this.dock, this.drawer);
|
||||
document.addEventListener("keydown", (e) => { if (e.key === "Escape" && this.open) this.show(null); });
|
||||
setInterval(() => this.update(), 500);
|
||||
}
|
||||
|
||||
update() {
|
||||
const active = this.sync.inAppMode;
|
||||
this.dock.style.display = active ? "" : "none";
|
||||
if (!active && this.open) this.show(null);
|
||||
}
|
||||
|
||||
show(id) {
|
||||
if (this.open) this.views[this.open].unmount?.();
|
||||
this.open = id;
|
||||
for (const [key, b] of Object.entries(this.buttons)) b.btn.classList.toggle("active", key === id);
|
||||
this.drawer.style.display = id ? "" : "none";
|
||||
if (id) this.views[id].mount(this.body);
|
||||
else this.body.replaceChildren();
|
||||
}
|
||||
|
||||
setBadge(id, text) {
|
||||
const b = this.buttons[id];
|
||||
if (!b) return;
|
||||
b.badge.textContent = text || "";
|
||||
b.badge.style.display = text ? "block" : "none";
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { STYLES } from "./styles.js";
|
||||
import { installSaveDialog } from "./save-dialog.js";
|
||||
import { FilesBrowser, openFromWorkspace } from "./files.js";
|
||||
import { h } from "./ui.js";
|
||||
import { AppDock } from "./app-dock.js";
|
||||
|
||||
const TAB_ID = "rigshare";
|
||||
const ICON_CLASS = "rigshare-tab-icon";
|
||||
@@ -330,11 +331,24 @@ app.registerExtension({
|
||||
});
|
||||
client.on("self", () => { if (filesPanel.isConnected) filesBrowser.reload(); });
|
||||
|
||||
// ComfyUI's App view hides extension sidebar tabs: offer them in a dock there.
|
||||
const dock = new AppDock({
|
||||
sync,
|
||||
views: {
|
||||
[TAB_ID]: { icon: "pi-users", title: "RigShare", mount: (el) => panel.mount(el), unmount: () => panel.unmount() },
|
||||
files: {
|
||||
icon: "pi-folder-open", title: "Files",
|
||||
mount: (el) => { el.replaceChildren(filesPanel); filesBrowser.reload(); },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// The registered tab object is wrapped by Vue; mutating it through the
|
||||
// store keeps the unread badge reactive.
|
||||
panel.onBadge = (badge) => {
|
||||
const tab = app.extensionManager.getSidebarTabs?.().find((t) => t.id === TAB_ID);
|
||||
if (tab) tab.iconBadge = badge;
|
||||
dock.setBadge(TAB_ID, badge);
|
||||
setBlink(!!badge);
|
||||
};
|
||||
panel.onNotify = () => {
|
||||
|
||||
@@ -244,6 +244,21 @@ body.rs-no-manager [aria-label="ComfyUI Manager"] { display: none !important; }
|
||||
.rs-mini-queue { color: var(--rs-muted); text-align: center; margin-top: 2px; }
|
||||
|
||||
/* remote cursors (DOM layer above canvas content) */
|
||||
.rs-dock { position: fixed; left: 64px; bottom: 16px; z-index: 1100; display: flex; gap: 4px; padding: 4px;
|
||||
background: var(--rs-bg); border: 1px solid var(--rs-border); border-radius: 12px; box-shadow: 0 6px 24px rgb(0 0 0 / .35); }
|
||||
.rs-dock-btn { position: relative; width: 36px; height: 36px; display: grid; place-items: center; border: 0; border-radius: 8px;
|
||||
background: none; color: var(--rs-muted); cursor: pointer; }
|
||||
.rs-dock-btn .pi { font-size: 16px; }
|
||||
.rs-dock-btn:hover { color: var(--rs-fg); background: var(--rs-bg2); }
|
||||
.rs-dock-btn.active { color: var(--rs-accent-fg); background: var(--rs-accent); }
|
||||
.rs-dock-badge { display: none; position: absolute; top: 1px; right: 1px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
|
||||
background: #ef4444; color: #fff; font-size: 10px; line-height: 16px; font-weight: 700; }
|
||||
.rs-dock-drawer { position: fixed; top: 8px; bottom: 64px; left: 64px; width: min(380px, calc(100vw - 80px)); z-index: 1100;
|
||||
background: var(--rs-bg); border: 1px solid var(--rs-border); border-radius: 12px; box-shadow: 0 12px 40px rgb(0 0 0 / .45); overflow: hidden; }
|
||||
.rs-dock-drawer-body { height: 100%; }
|
||||
.rs-dock-drawer .rs-header { padding-right: 40px; }
|
||||
.rs-dock-close { position: absolute; top: 8px; right: 8px; z-index: 2; border: 0; background: none; color: var(--rs-muted); cursor: pointer; padding: 4px; }
|
||||
.rs-dock-close:hover { color: var(--rs-fg); }
|
||||
.rs-cursor-layer { position: fixed; inset: 0; pointer-events: none; z-index: 900; overflow: hidden; }
|
||||
.rs-cursor { position: absolute; left: 0; top: 0; will-change: transform; transition: transform 70ms linear; }
|
||||
.rs-cursor svg { display: block; filter: drop-shadow(0 1px 1px rgb(0 0 0 / .4)); }
|
||||
|
||||
Reference in New Issue
Block a user