App mode: RigShare and Files buttons in the App view's own sidebar
Copies ComfyUI's sidebar button so they look the same, keeps the unread badge and selected state, and opens the panels in a drawer beside the sidebar. The floating dock remains only as a fallback when the sidebar can't be found.
This commit is contained in:
+105
-15
@@ -1,12 +1,15 @@
|
||||
// RigShare in ComfyUI's App mode.
|
||||
//
|
||||
// The App view shows a fixed set of sidebar tabs (assets and apps), so the
|
||||
// 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, a small dock offers them instead, each opening the same panel in
|
||||
// a drawer.
|
||||
// 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.
|
||||
|
||||
import { h } from "./ui.js";
|
||||
|
||||
const RAIL = 'nav[data-testid="side-toolbar"]';
|
||||
|
||||
export class AppDock {
|
||||
/**
|
||||
* views: { id: { icon, title, mount(el), unmount() } }
|
||||
@@ -15,7 +18,9 @@ export class AppDock {
|
||||
this.sync = sync;
|
||||
this.views = views;
|
||||
this.open = null;
|
||||
this.buttons = {};
|
||||
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" })),
|
||||
@@ -24,37 +29,122 @@ export class AppDock {
|
||||
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),
|
||||
onclick: () => this.toggle(id),
|
||||
}, h("i", { class: `pi ${v.icon}` }), badge);
|
||||
this.buttons[id] = { btn, badge };
|
||||
this.dockButtons[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);
|
||||
setInterval(() => this.update(), 400);
|
||||
}
|
||||
|
||||
toggle(id) {
|
||||
this.show(this.open === id ? null : id);
|
||||
}
|
||||
|
||||
update() {
|
||||
const active = this.sync.inAppMode;
|
||||
this.dock.style.display = active ? "" : "none";
|
||||
const rail = active ? document.querySelector(RAIL) : null;
|
||||
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);
|
||||
}
|
||||
|
||||
/** 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 = {};
|
||||
}
|
||||
|
||||
/** 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;
|
||||
for (const [key, b] of Object.entries(this.buttons)) b.btn.classList.toggle("active", key === id);
|
||||
this.refreshButtons();
|
||||
this.drawer.style.display = id ? "" : "none";
|
||||
if (id) this.views[id].mount(this.body);
|
||||
else this.body.replaceChildren();
|
||||
if (id) {
|
||||
this.place(document.querySelector(RAIL));
|
||||
this.views[id].mount(this.body);
|
||||
} else {
|
||||
this.body.replaceChildren();
|
||||
}
|
||||
}
|
||||
|
||||
refreshButtons() {
|
||||
for (const [id, b] of Object.entries(this.railButtons)) {
|
||||
b.btn.classList.toggle("side-bar-button-selected", id === this.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);
|
||||
this.paintBadge(b.badge, this.badges[id]);
|
||||
}
|
||||
}
|
||||
|
||||
paintBadge(el, text) {
|
||||
el.textContent = text || "";
|
||||
el.style.display = text ? "block" : "none";
|
||||
}
|
||||
|
||||
setBadge(id, text) {
|
||||
const b = this.buttons[id];
|
||||
if (!b) return;
|
||||
b.badge.textContent = text || "";
|
||||
b.badge.style.display = text ? "block" : "none";
|
||||
this.badges[id] = text || null;
|
||||
this.refreshButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@ body.rs-no-manager [aria-label="ComfyUI Manager"] { display: none !important; }
|
||||
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-cursor-layer { position: fixed; inset: 0; pointer-events: none; z-index: 900; overflow: hidden; }
|
||||
|
||||
Reference in New Issue
Block a user