theseus/sidebar-preload.js
Local Dev 4cd9286d87 fix(theseus/sidebar): visible-at-rest separator between the tab area and the sidebar
The resize grip lived along the sidebar's left edge as a 5-px transparent
hover target — you couldn't see it existed until the pointer landed on it.
Users on both light and dark backgrounds reported the seam between the
tab area and the sidebar as invisible.

Paint a 2-px semi-opaque mid-gray line (`rgba(140,150,170,.55)`) at rest so
the boundary is legible on every panel background; hover ramps to acid
green, active-drag ramps brighter. The visible band is narrower than
before (2 px vs 5 px) so it reads as a subtle divider rather than
competing chrome; the pointer-catch zone stays wide via an invisible
outline extension, so drag-to-resize still catches slack.
2026-09-09 10:50:30 +02:00

93 lines
4.8 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)),
},
});
// 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);
});