theseus/sidebar-preload.js
2026-09-12 00:07:42 +02:00

97 lines
5 KiB
JavaScript

// Preload shared by every add-on sidebar panel. Exposes a small, safe
// surface to the add-on's HTML. Main-side handlers derive the add-on
// identity from the sender's URL (the panel is always loaded from
// somewhere inside <userData>/addons/<id>/), so a random page that
// happens to see the API shape can't touch another add-on's storage.
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("silentmode", {
storage: {
get: (key, fallback = null) => ipcRenderer.invoke("addon-storage-get", key, fallback),
set: (key, value) => ipcRenderer.invoke("addon-storage-set", key, value),
all: () => ipcRenderer.invoke("addon-storage-all"),
},
// Ask main which panel is currently visible — panels may want to
// suspend expensive work when hidden.
onVisibility: (cb) => ipcRenderer.on("sidebar-visibility", (_e, visible) => cb(!!visible)),
// Call into the add-on's activate() context: resolves with whatever the
// matching api.onMessage handler returned (or rejects with its error).
invoke: (msg, payload) => ipcRenderer.invoke("addon-msg", String(msg), payload),
// Events the add-on pushes via api.emit while this panel is open.
on: (msg, cb) => ipcRenderer.on("addon-event", (_e, name, payload) => { if (name === msg) cb(payload); }),
// Sidebar sizing controls a panel may want (e.g. the screenshot editor
// wants a full-window canvas). Widen fills the window minus a thin strip
// for the tab area behind it; restore returns to the pre-widen width.
sidebar: {
maximize: () => ipcRenderer.invoke("sidebar-maximize"),
restore: () => ipcRenderer.invoke("sidebar-restore"),
toggleMax:() => ipcRenderer.invoke("sidebar-toggle-max"),
isMax: () => ipcRenderer.invoke("sidebar-is-max"),
onMaxChange: (cb) => ipcRenderer.on("sidebar-max-change", (_e, max) => cb(!!max)),
// Close the sidebar entirely. Same IPC the toolbar dock icon toggles
// — the panel/editor can offer an explicit X button so users don't
// have to hunt for the dock icon just to hide the panel.
close: () => ipcRenderer.invoke("sidebar-close"),
},
});
// Panel picker strip was here — a 32-px tab bar injected at the top of
// every panel to switch between registered extensions. Removed: the
// toolbar extension dock (per-extension buttons + puzzle dropdown at
// narrow widths) is the canonical switcher now, and doubling that inside
// the sidebar wasted vertical space and made narrow panels feel cramped.
// Sidebar resize grip. Injected into every panel automatically so panel
// authors don't have to reinvent it. A thin strip along the LEFT edge
// (the boundary between the tab area and the sidebar) accepts mousedown
// and streams drag deltas to main until mouseup. Main clamps the width
// to [200, 800] and persists it in settings.sidebarWidth.
window.addEventListener("DOMContentLoaded", () => {
const grip = document.createElement("div");
grip.setAttribute("aria-label", "Resize sidebar");
// A visible-at-rest separator. Fully transparent used to hide the seam
// between the tab area and the sidebar completely — users couldn't tell
// where one ended and the other began. A mid-gray at moderate alpha
// reads on every panel background (dark and light) without competing
// for attention. Hover / drag ramps to acid so the grab affordance
// still stands out.
const IDLE_BG = "rgba(140,150,170,.55)";
const HOVER_BG = "rgba(214,255,61,.35)";
const ACTIVE_BG = "rgba(214,255,61,.55)";
grip.style.cssText = [
"position:fixed", "left:0", "top:0", "bottom:0",
"width:2px", "cursor:col-resize", "z-index:2147483647",
"background:" + IDLE_BG,
// A wider invisible hit target sits over the visible strip so
// dragging still catches a 5-px slack — visible line stays a
// clean 2 px.
"box-shadow:2px 0 0 0 transparent",
].join(";");
// A subtle overlay expands the pointer-catch zone without widening
// the visible band. Same click-through element, wider hit box.
grip.style.setProperty("outline", "2px solid transparent", "important");
grip.style.setProperty("outline-offset", "1px", "important");
grip.addEventListener("mouseenter", () => { grip.style.background = HOVER_BG; });
grip.addEventListener("mouseleave", () => { if (!dragging) grip.style.background = IDLE_BG; });
document.body.appendChild(grip);
let dragging = false;
grip.addEventListener("mousedown", (e) => {
if (e.button !== 0) return;
e.preventDefault();
dragging = true;
document.body.style.userSelect = "none";
grip.style.background = ACTIVE_BG;
});
window.addEventListener("mousemove", (e) => {
if (!dragging) return;
// Moving cursor LEFT = grow sidebar width. movementX is negative left.
if (e.movementX !== 0) ipcRenderer.invoke("sidebar-drag", -e.movementX);
});
const stop = () => {
if (!dragging) return;
dragging = false;
document.body.style.userSelect = "";
grip.style.background = IDLE_BG;
};
window.addEventListener("mouseup", stop);
window.addEventListener("mouseleave", stop);
});