Files
Edouard Durand d3cef4a6ca App mode: open RigShare and Files in ComfyUI's docked side panel
The App view's side panel shows whichever sidebar tab is active, so our
sidebar buttons now activate the real RigShare and Files tabs instead of
opening a floating drawer.
2026-09-26 07:05:00 +00:00

124 lines
4.9 KiB
JavaScript

// RigShare in ComfyUI's App mode.
//
// 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";
const RAIL = 'nav[data-testid="side-toolbar"]';
export class AppDock {
/**
* views: { id: { icon, title, tabId } }, tabId being a registered sidebar tab.
*/
constructor({ app, sync, views }) {
this.app = app;
this.sync = sync;
this.views = views;
this.badges = {};
this.railButtons = {};
this.dockButtons = {};
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.toggle(id),
}, h("i", { class: `pi ${v.icon}` }), badge);
this.dockButtons[id] = { btn, badge };
return btn;
}));
this.dock.style.display = "none";
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.app.extensionManager?.command?.execute(`Workspace.ToggleSidebarTab.${this.views[id].tabId}`);
setTimeout(() => this.refreshButtons(), 30);
}
update() {
const active = this.sync.inAppMode;
const rail = active ? document.querySelector(RAIL) : null;
if (rail) this.addToRail(rail);
else this.removeFromRail();
this.dock.style.display = active && !rail ? "" : "none";
this.refreshButtons();
}
/** Copy one of ComfyUI's sidebar buttons for each view, placed after its own tabs. */
addToRail(rail) {
const ours = Object.values(this.railButtons);
if (ours.length && ours.every((b) => rail.contains(b.btn))) return;
const tabs = [...rail.querySelectorAll('[data-testid$="-tab-button"]')].filter((b) => !b.dataset.rigshare);
const template = tabs.at(-1);
if (!template) return;
this.removeFromRail();
let after = template;
for (const [id, view] of Object.entries(this.views)) {
const btn = template.cloneNode(true);
btn.removeAttribute("data-testid");
btn.dataset.rigshare = id;
btn.classList.remove("side-bar-button-selected");
btn.setAttribute("aria-label", view.title);
btn.title = view.title;
const icon = btn.querySelector(".side-bar-button-icon");
if (icon) {
const mine = document.createElement("i");
mine.className = `pi ${view.icon} side-bar-button-icon`;
mine.style.fontSize = "18px";
icon.replaceWith(mine);
}
btn.querySelector(".sidebar-icon-badge")?.remove();
const label = [...btn.querySelectorAll("span")].find((s) => s.textContent.trim());
if (label) label.textContent = view.title;
const badge = document.createElement("span");
badge.className = "sidebar-icon-badge rs-rail-badge";
const holder = btn.querySelector(".side-bar-button-icon")?.parentElement ?? btn;
holder.style.position = "relative"; // the badge sits on the icon's corner
holder.append(badge);
btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); this.toggle(id); });
after.after(btn);
after = btn;
this.railButtons[id] = { btn, badge };
}
this.refreshButtons();
}
removeFromRail() {
for (const b of Object.values(this.railButtons)) b.btn.remove();
this.railButtons = {};
}
refreshButtons() {
const open = this.activeTabId;
for (const [id, b] of Object.entries(this.railButtons)) {
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", this.views[id].tabId === open);
this.paintBadge(b.badge, this.badges[id]);
}
}
paintBadge(el, text) {
el.textContent = text || "";
el.style.display = text ? "block" : "none";
}
setBadge(id, text) {
this.badges[id] = text || null;
this.refreshButtons();
}
}