Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e943f3e60 | ||
|
|
f4131ac6f5 | ||
|
|
d3cef4a6ca |
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.1 (2026-09-26)
|
||||
|
||||
### Changed
|
||||
|
||||
- App mode: the **RigShare** and **Files** buttons open their tabs in ComfyUI's docked side panel, like Assets and Apps, instead of a floating drawer
|
||||
|
||||
### Fixed
|
||||
|
||||
- Switching between the App view and the graph with the RigShare or Files panel open left the panel blank
|
||||
|
||||
## 1.2.0 (2026-09-26)
|
||||
|
||||
ComfyUI's App mode works with RigShare, and every save keeps a snapshot.
|
||||
|
||||
+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. The Live tab and People show which view each person is in, using ComfyUI's Graph and App icons. 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 **RigShare** and **Files** buttons to that sidebar, with the unread chat badge. They open the panels in a drawer beside it.
|
||||
**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. The Live tab and People show which view each person is in, using ComfyUI's Graph and App icons. 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 **RigShare** and **Files** buttons to that sidebar, with the unread chat badge. They open in the same docked side panel as ComfyUI's own tabs.
|
||||
|
||||
Viewers (no *Edit* permission) see a read-only canvas. Anything they change locally is put back.
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-rigshare"
|
||||
description = "Multi-user collaboration for ComfyUI: accounts and login, live-shared workflows, cursors, chat, snapshots and a server monitor."
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = []
|
||||
|
||||
|
||||
+22
-49
@@ -1,10 +1,11 @@
|
||||
// RigShare in ComfyUI's App mode.
|
||||
//
|
||||
// The App view's sidebar shows a fixed set of tabs (assets and apps), so the
|
||||
// RigShare and Files tabs disappear there. While the tab on screen is in the
|
||||
// App view, RigShare adds its own buttons to that sidebar, copied from
|
||||
// ComfyUI's so they look the same, and opens the panels in a drawer next to it.
|
||||
// If the sidebar can't be found, a small floating dock offers them instead.
|
||||
// The App view's sidebar only draws buttons for a fixed set of tabs (assets
|
||||
// and apps), but its docked side panel shows whichever sidebar tab is active.
|
||||
// So RigShare adds its own buttons to that sidebar, copied from ComfyUI's so
|
||||
// they look the same, and they open the regular RigShare and Files tabs in the
|
||||
// docked panel, exactly like ComfyUI's own tabs. If the sidebar can't be
|
||||
// found, a small floating dock offers the same buttons.
|
||||
|
||||
import { h } from "./ui.js";
|
||||
|
||||
@@ -12,19 +13,15 @@ const RAIL = 'nav[data-testid="side-toolbar"]';
|
||||
|
||||
export class AppDock {
|
||||
/**
|
||||
* views: { id: { icon, title, mount(el), unmount() } }
|
||||
* views: { id: { icon, title, tabId } }, tabId being a registered sidebar tab.
|
||||
*/
|
||||
constructor({ sync, views }) {
|
||||
constructor({ app, sync, views }) {
|
||||
this.app = app;
|
||||
this.sync = sync;
|
||||
this.views = views;
|
||||
this.open = null;
|
||||
this.badges = {};
|
||||
this.railButtons = {};
|
||||
this.dockButtons = {};
|
||||
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", {
|
||||
@@ -35,14 +32,18 @@ export class AppDock {
|
||||
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(), 400);
|
||||
document.body.append(this.dock);
|
||||
setInterval(() => this.update(), 300);
|
||||
}
|
||||
|
||||
get activeTabId() {
|
||||
return this.app.extensionManager?.sidebarTab?.activeSidebarTabId ?? null;
|
||||
}
|
||||
|
||||
/** Open (or close) the tab in ComfyUI's docked side panel, like its own buttons do. */
|
||||
toggle(id) {
|
||||
this.show(this.open === id ? null : id);
|
||||
this.app.extensionManager?.command?.execute(`Workspace.ToggleSidebarTab.${this.views[id].tabId}`);
|
||||
setTimeout(() => this.refreshButtons(), 30);
|
||||
}
|
||||
|
||||
update() {
|
||||
@@ -51,8 +52,7 @@ export class AppDock {
|
||||
if (rail) this.addToRail(rail);
|
||||
else this.removeFromRail();
|
||||
this.dock.style.display = active && !rail ? "" : "none";
|
||||
if (!active && this.open) this.show(null);
|
||||
if (this.open) this.place(rail);
|
||||
this.refreshButtons();
|
||||
}
|
||||
|
||||
/** Copy one of ComfyUI's sidebar buttons for each view, placed after its own tabs. */
|
||||
@@ -99,41 +99,14 @@ export class AppDock {
|
||||
this.railButtons = {};
|
||||
}
|
||||
|
||||
/** Beside the sidebar when there is one, above the floating dock otherwise. */
|
||||
place(rail) {
|
||||
const d = this.drawer.style;
|
||||
if (rail) {
|
||||
const r = rail.getBoundingClientRect();
|
||||
d.left = `${Math.round(r.right + 8)}px`;
|
||||
d.top = `${Math.round(r.top + 8)}px`;
|
||||
d.bottom = "8px";
|
||||
} else {
|
||||
d.left = "64px";
|
||||
d.top = "8px";
|
||||
d.bottom = "64px";
|
||||
}
|
||||
}
|
||||
|
||||
show(id) {
|
||||
if (this.open) this.views[this.open].unmount?.();
|
||||
this.open = id;
|
||||
this.refreshButtons();
|
||||
this.drawer.style.display = id ? "" : "none";
|
||||
if (id) {
|
||||
this.place(document.querySelector(RAIL));
|
||||
this.views[id].mount(this.body);
|
||||
} else {
|
||||
this.body.replaceChildren();
|
||||
}
|
||||
}
|
||||
|
||||
refreshButtons() {
|
||||
const open = this.activeTabId;
|
||||
for (const [id, b] of Object.entries(this.railButtons)) {
|
||||
b.btn.classList.toggle("side-bar-button-selected", id === this.open);
|
||||
b.btn.classList.toggle("side-bar-button-selected", this.views[id].tabId === open);
|
||||
this.paintBadge(b.badge, this.badges[id]);
|
||||
}
|
||||
for (const [id, b] of Object.entries(this.dockButtons)) {
|
||||
b.btn.classList.toggle("active", id === this.open);
|
||||
b.btn.classList.toggle("active", this.views[id].tabId === open);
|
||||
this.paintBadge(b.badge, this.badges[id]);
|
||||
}
|
||||
}
|
||||
|
||||
+46
-9
@@ -14,6 +14,32 @@ import { h } from "./ui.js";
|
||||
import { AppDock } from "./app-dock.js";
|
||||
|
||||
const TAB_ID = "rigshare";
|
||||
const FILES_TAB_ID = "rigshare-files";
|
||||
|
||||
/** Remembers the side panel elements each custom tab was rendered into and keeps
|
||||
* the tab's content in whichever of them is on screen. */
|
||||
class SlotTracker {
|
||||
constructor(app, tabs) {
|
||||
this.app = app;
|
||||
this.tabs = tabs;
|
||||
this.slots = Object.fromEntries(Object.keys(tabs).map((id) => [id, new Set()]));
|
||||
setInterval(() => this.check(), 300);
|
||||
}
|
||||
|
||||
add(id, el) {
|
||||
this.slots[id]?.add(el);
|
||||
}
|
||||
|
||||
check() {
|
||||
const active = this.app.extensionManager?.sidebarTab?.activeSidebarTabId;
|
||||
for (const [id, set] of Object.entries(this.slots)) {
|
||||
for (const el of set) if (!el.isConnected) set.delete(el);
|
||||
if (id !== active) continue;
|
||||
const visible = [...set].find((el) => el.getClientRects().length > 0);
|
||||
if (visible && !this.tabs[id].has(visible)) this.tabs[id].fill(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
const ICON_CLASS = "rigshare-tab-icon";
|
||||
|
||||
function setting(id, fallback) {
|
||||
@@ -279,6 +305,18 @@ app.registerExtension({
|
||||
installSaveDialog(app, api, client, sync);
|
||||
presence.install();
|
||||
|
||||
// ComfyUI keeps a separate side panel for the graph and the App view and
|
||||
// shows the old one again without asking the tab to render: follow the
|
||||
// visible one, or the panel comes back blank after switching views.
|
||||
let filesPanelRef = () => null; // set once the Files panel is built below
|
||||
const slots = new SlotTracker(app, {
|
||||
[TAB_ID]: { has: (el) => el.contains(panel.root), fill: (el) => panel.mount(el) },
|
||||
[FILES_TAB_ID]: {
|
||||
has: (el) => !filesPanelRef() || el.contains(filesPanelRef()),
|
||||
fill: (el) => { if (el.contains(panel.root)) panel.unmount(); el.replaceChildren(filesPanelRef()); },
|
||||
},
|
||||
});
|
||||
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: TAB_ID,
|
||||
icon: `pi pi-users ${ICON_CLASS}`,
|
||||
@@ -287,7 +325,7 @@ app.registerExtension({
|
||||
label: "RigShare",
|
||||
type: "custom",
|
||||
iconBadge: null,
|
||||
render: (el) => panel.mount(el),
|
||||
render: (el) => { slots.add(TAB_ID, el); panel.mount(el); },
|
||||
destroy: () => panel.unmount(),
|
||||
});
|
||||
|
||||
@@ -312,15 +350,17 @@ app.registerExtension({
|
||||
onclick: () => app.extensionManager.command.execute("Comfy.OpenWorkflow"),
|
||||
}, h("i", { class: "pi pi-upload" }), "From computer")),
|
||||
h("div", { class: "rs-body" }, filesBrowser.el));
|
||||
filesPanelRef = () => filesPanel;
|
||||
let filesLoaded = false;
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "rigshare-files",
|
||||
id: FILES_TAB_ID,
|
||||
icon: "pi pi-folder-open",
|
||||
title: "Files",
|
||||
tooltip: "RigShare files: My files and Shared",
|
||||
label: "Files",
|
||||
type: "custom",
|
||||
render: (el) => {
|
||||
slots.add(FILES_TAB_ID, el);
|
||||
el.classList.add("rs-host");
|
||||
// Same slot element as the RigShare tab when switching between them
|
||||
// (ComfyUI only calls destroy on unmount): take it over cleanly.
|
||||
@@ -331,15 +371,12 @@ app.registerExtension({
|
||||
});
|
||||
client.on("self", () => { if (filesPanel.isConnected) filesBrowser.reload(); });
|
||||
|
||||
// ComfyUI's App view hides extension sidebar tabs: offer them in a dock there.
|
||||
// ComfyUI's App view draws buttons only for its own tabs: add ours to its sidebar.
|
||||
const dock = new AppDock({
|
||||
sync,
|
||||
app, 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(); },
|
||||
},
|
||||
[TAB_ID]: { icon: "pi-users", title: "RigShare", tabId: TAB_ID },
|
||||
files: { icon: "pi-folder-open", title: "Files", tabId: FILES_TAB_ID },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -253,14 +253,8 @@ body.rs-no-manager [aria-label="ComfyUI Manager"] { display: none !important; }
|
||||
.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-rail-badge { position: absolute; top: -4px; right: -6px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
|
||||
background: #ef4444; color: #fff; font-size: 10px; line-height: 16px; font-weight: 700; text-align: center; }
|
||||
.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-view-avatar { position: relative; overflow: visible; }
|
||||
.rs-comfy-icon { display: inline-block; width: 1em; height: 1em; flex: none; }
|
||||
.rs-view-mark { position: absolute; right: -6px; bottom: -5px; font-size: 8px; width: 13px; height: 13px; display: grid; place-items: center;
|
||||
|
||||
Reference in New Issue
Block a user