feat(theseus/chrome): per-extension toolbar dock + Aegis placeholder

Replaces the single sidebarbtn with a dock that renders one button per
registered addon sidebar-panel. Each button shows the panel's icon
(the emoji from its manifest) and opens the sidebar on that panel.
Clicking the currently-active button collapses the sidebar; clicking a
different one swaps the visible panel. Notepad (📝) and Silent Mode
Relay (🌐) appear automatically because they already register panels.

Also lands a static Aegis Wallet placeholder button (₿) next to the
live ones, styled as .soon so it reads as coming-soon. It'll come out
once the addon registers a real panel.

Preload adds openSidebar(panelId) and closeSidebar() wrappers around
the existing sidebar-open / sidebar-close IPC handlers.
This commit is contained in:
Local Dev 2026-09-06 13:32:41 +02:00
parent 40391798e0
commit f5a500c796
2 changed files with 58 additions and 13 deletions

View file

@ -102,6 +102,20 @@
.ic.active { background: rgba(214,255,61,.12); color: var(--acid); }
.ic:active { background: var(--line); }
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
/* One toolbar button per registered addon sidebar-panel, plus a static
placeholder for the built-in wallet before it lands. Each button
shows the panel's icon (usually a single emoji from the manifest);
click toggles the sidebar open on that panel, or collapses if it
was already the active panel. */
.extdock { display: flex; gap: 2px; align-items: center; padding-left: 4px; margin-left: 2px;
border-left: 1px solid var(--line); }
.extbtn { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 7px;
cursor: pointer; border: none; background: transparent; padding: 0;
font-size: 15px; line-height: 1; color: var(--mut); }
.extbtn:hover { background: var(--line2); color: var(--ink); }
.extbtn.active { background: rgba(214,255,61,.12); color: var(--acid); box-shadow: inset 0 -2px 0 var(--acid); }
.extbtn.soon { opacity: .45; cursor: help; }
.extbtn.soon:hover { opacity: .7; background: var(--line2); }
/* downloads button — a small badge sits over the icon when there's activity */
.dlbtn { position: relative; }
.dlbtn.spin::before { content: ""; position: absolute; inset: 4px; border-radius: 999px;
@ -278,9 +292,10 @@
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 2 V10 M4.5 6.5 L8 10 L11.5 6.5"/><path d="M3 12.5 L3 13.5 L13 13.5 L13 12.5"/></svg>
<span class="dlbadge" id="dlbadge" hidden>0</span>
</button>
<button class="ic" id="sidebarbtn" title="Toggle extension sidebar" hidden>
<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="3" width="12" height="10" rx="1.5"/><path d="M10 3 L10 13"/></svg>
</button>
<span class="extdock" id="extdock" hidden>
<span id="extbuttons"></span>
<button class="extbtn soon" id="aegisSoon" title="Aegis Wallet — built-in BCH wallet, coming soon" aria-disabled="true"></button>
</span>
<span class="upchip" id="upchip" hidden><button class="upcore" id="upDownload" title=""></button><button class="updismiss" id="upDismiss" title="Not now"></button></span>
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear"></span></button>
@ -408,18 +423,43 @@
T.getDownloads && T.getDownloads().then(renderDownloads);
T.onDownloads && T.onDownloads(renderDownloads);
// ---- add-on sidebar toggle ----
// Only surfaces the button when at least one add-on has registered a
// sidebar panel; a fresh install with no add-ons keeps the toolbar clean.
const sideBtn = $("sidebarbtn");
// ---- extension dock ----
// One toolbar button per registered addon sidebar-panel, plus a static
// "coming soon" placeholder for Aegis (the built-in BCH wallet, in
// progress). Click a live button → open the sidebar on that panel; if
// the sidebar was already open on that panel, collapse it.
const extDock = $("extdock");
const extButtons = $("extbuttons");
let lastPanels = [], sidebarOpen = false, activePanelId = null;
function renderSidebarState(d) {
if (!d) return;
const has = Array.isArray(d.panels) && d.panels.length > 0;
sideBtn.hidden = !has;
sideBtn.classList.toggle("active", !!d.visible);
if (has && d.panels[0]) sideBtn.title = "Toggle " + d.panels[0].title;
lastPanels = Array.isArray(d.panels) ? d.panels : [];
sidebarOpen = !!d.visible;
activePanelId = d.active || null;
// Dock is always visible when either at least one addon panel exists
// OR the Aegis placeholder is meant to show. Aegis is always shown
// for now, so the dock is always visible.
extDock.hidden = false;
extButtons.innerHTML = lastPanels.map((p) => {
const isActive = sidebarOpen && p.panelId === activePanelId;
const icon = String(p.icon || "•");
return `<button class="extbtn${isActive ? " active" : ""}" data-panel="${p.panelId.replace(/"/g,"&quot;")}" title="${(p.title || "Extension").replace(/"/g,"&quot;")}">${icon.replace(/</g,"&lt;")}</button>`;
}).join("");
extButtons.querySelectorAll(".extbtn").forEach((btn) => {
btn.onclick = () => {
const pid = btn.dataset.panel;
if (sidebarOpen && pid === activePanelId) { T.closeSidebar && T.closeSidebar(); }
else { T.openSidebar && T.openSidebar(pid); }
};
});
}
sideBtn.onclick = () => T.toggleSidebar && T.toggleSidebar();
$("aegisSoon").onclick = () => {
// Non-blocking hint — the user asked for the placeholder to appear now
// even though the wallet ships in a later release. Once the addon is
// registered it renders as a live extbtn and this static button can
// come out.
$("aegisSoon").title = "Aegis Wallet — built-in BCH wallet, coming soon";
};
T.sidebarState && T.sidebarState().then(renderSidebarState);
T.onSidebarState && T.onSidebarState(renderSidebarState);

View file

@ -58,8 +58,13 @@ contextBridge.exposeInMainWorld("theseus", {
getDownloads: () => ipcRenderer.invoke("downloads-get"),
toggleDownloads: (rect) => ipcRenderer.invoke("toggle-downloads", rect),
onDownloads: (cb) => ipcRenderer.on("downloads", (_e, d) => cb(d)),
// Add-on sidebar toggle in the toolbar.
// Add-on sidebar: toggle, open on a specific panel, close, or subscribe
// to state (visibility + which panel is active + the panel list). The
// extension dock in chrome.html renders one button per panel and calls
// openSidebar / closeSidebar accordingly.
toggleSidebar: () => ipcRenderer.invoke("sidebar-toggle"),
openSidebar: (panelId) => ipcRenderer.invoke("sidebar-open", panelId),
closeSidebar: () => ipcRenderer.invoke("sidebar-close"),
sidebarState: () => ipcRenderer.invoke("sidebar-state"),
onSidebarState: (cb) => ipcRenderer.on("sidebar-state", (_e, d) => cb(d)),
});