@wildwinter/app-shell 0.46.2 → 0.46.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,213 @@
1
+ import { screen, BrowserWindow, app } from 'electron';
2
+ import { join } from 'path';
3
+
4
+ // src/tool-window.ts
5
+ function savedWindowRect(saved, def, min) {
6
+ if (!saved) return { ...def };
7
+ const w = Math.max(min.width, saved.width), h = Math.max(min.height, saved.height);
8
+ if (saved.x != null && saved.y != null) {
9
+ const onScreen = screen.getAllDisplays().some((d) => {
10
+ const a = d.workArea;
11
+ return saved.x + w > a.x + 40 && saved.x < a.x + a.width - 40 && saved.y + h > a.y + 20 && saved.y < a.y + a.height - 20;
12
+ });
13
+ if (onScreen) return { x: saved.x, y: saved.y, width: w, height: h };
14
+ }
15
+ return { width: w, height: h };
16
+ }
17
+ function rememberBounds(w, write) {
18
+ const saveBounds = () => {
19
+ if (!w.isDestroyed()) write(w.getBounds());
20
+ };
21
+ let boundsTimer;
22
+ const queueSave = () => {
23
+ clearTimeout(boundsTimer);
24
+ boundsTimer = setTimeout(saveBounds, 400);
25
+ };
26
+ w.on("resize", queueSave);
27
+ w.on("move", queueSave);
28
+ w.on("close", () => {
29
+ clearTimeout(boundsTimer);
30
+ saveBounds();
31
+ });
32
+ }
33
+ function centeredOnPrimary(size) {
34
+ const a = screen.getPrimaryDisplay().workArea;
35
+ return { x: Math.round(a.x + (a.width - size.width) / 2), y: Math.round(a.y + (a.height - size.height) / 2) };
36
+ }
37
+ function rescueToolWindow(w, def) {
38
+ if (!w || w.isDestroyed()) return;
39
+ if (w.isMinimized()) w.restore();
40
+ w.setBounds({ ...def, ...centeredOnPrimary(def) });
41
+ w.show();
42
+ w.moveTop();
43
+ }
44
+ var macPinned = /* @__PURE__ */ new Set();
45
+ var macActivationWired = false;
46
+ function wireMacActivation() {
47
+ if (macActivationWired) return;
48
+ macActivationWired = true;
49
+ app.on("did-become-active", () => {
50
+ if (pinsHeld > 0) return;
51
+ for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(true, "floating");
52
+ });
53
+ app.on("did-resign-active", () => {
54
+ for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(false);
55
+ });
56
+ }
57
+ function pinToolWindow(w, parent, on, platform = process.platform) {
58
+ if (!w || w.isDestroyed()) return;
59
+ if (platform === "darwin") {
60
+ w.setParentWindow(null);
61
+ if (on) {
62
+ if (!macPinned.has(w)) {
63
+ macPinned.add(w);
64
+ w.once("closed", () => macPinned.delete(w));
65
+ }
66
+ wireMacActivation();
67
+ w.setAlwaysOnTop(pinsHeld === 0 && BrowserWindow.getFocusedWindow() !== null, "floating");
68
+ } else {
69
+ macPinned.delete(w);
70
+ w.setAlwaysOnTop(false);
71
+ }
72
+ return;
73
+ }
74
+ w.setAlwaysOnTop(false);
75
+ const owner = on && parent && !parent.isDestroyed() ? parent : null;
76
+ if (pinsHeld > 0) {
77
+ if (owner) heldChildren.set(w, owner);
78
+ else heldChildren.delete(w);
79
+ w.setParentWindow(null);
80
+ return;
81
+ }
82
+ w.setParentWindow(owner);
83
+ }
84
+ var pinsHeld = 0;
85
+ var heldChildren = /* @__PURE__ */ new Map();
86
+ function frontForModal(main, platform = process.platform) {
87
+ if (main.isDestroyed()) return () => {
88
+ };
89
+ pinsHeld++;
90
+ if (platform === "darwin") {
91
+ for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(false);
92
+ } else {
93
+ for (const w of BrowserWindow.getAllWindows()) {
94
+ if (w === main || w.isDestroyed()) continue;
95
+ const owner = w.getParentWindow();
96
+ if (owner !== main) continue;
97
+ heldChildren.set(w, main);
98
+ w.setParentWindow(null);
99
+ }
100
+ }
101
+ if (main.isMinimized()) main.restore();
102
+ main.show();
103
+ main.focus();
104
+ let released = false;
105
+ return () => {
106
+ if (released) return;
107
+ released = true;
108
+ if (--pinsHeld > 0) return;
109
+ if (platform === "darwin") {
110
+ const active = BrowserWindow.getFocusedWindow() !== null;
111
+ if (active) {
112
+ for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(true, "floating");
113
+ }
114
+ } else {
115
+ for (const [w, owner] of heldChildren) {
116
+ if (!w.isDestroyed() && !owner.isDestroyed()) w.setParentWindow(owner);
117
+ }
118
+ heldChildren.clear();
119
+ }
120
+ };
121
+ }
122
+ function openToolWindow(existing, opts) {
123
+ if (existing && !existing.isDestroyed()) {
124
+ existing.focus();
125
+ return existing;
126
+ }
127
+ const w = new BrowserWindow({
128
+ ...opts.rect,
129
+ ...opts.min !== void 0 ? { minWidth: opts.min.width, minHeight: opts.min.height } : {},
130
+ show: false,
131
+ title: opts.title,
132
+ ...opts.frame !== void 0 ? { frame: opts.frame } : {},
133
+ webPreferences: {
134
+ contextIsolation: true,
135
+ nodeIntegration: false,
136
+ sandbox: true,
137
+ preload: opts.preload
138
+ }
139
+ });
140
+ if (opts.pinTo !== void 0) pinToolWindow(w, opts.pinTo(), opts.pinned ?? false);
141
+ if (opts.remember) rememberBounds(w, opts.remember);
142
+ w.once("ready-to-show", () => w.show());
143
+ if (opts.onClosed) w.on("closed", opts.onClosed);
144
+ if (process.env["ELECTRON_RENDERER_URL"]) void w.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/${opts.page}`);
145
+ else void w.loadFile(join(opts.rendererDir, opts.page));
146
+ return w;
147
+ }
148
+ function defineToolWindows(specs, env) {
149
+ const live = /* @__PURE__ */ new Map();
150
+ const byName = (name) => {
151
+ const found = specs.find((s) => s.name === name);
152
+ if (!found) throw new Error(`no tool window "${name}"`);
153
+ return found;
154
+ };
155
+ const get = (name) => {
156
+ const w = live.get(name);
157
+ return w && !w.isDestroyed() ? w : void 0;
158
+ };
159
+ if (env.session) {
160
+ for (const spec of specs) {
161
+ env.session.addSatellite({
162
+ window: () => get(spec.name),
163
+ channel: spec.channel ?? env.session.channel,
164
+ ...spec.clear ? { clear: spec.clear } : {}
165
+ });
166
+ }
167
+ }
168
+ const open = (name) => {
169
+ const spec = byName(name);
170
+ const existing = get(name);
171
+ const opened = openToolWindow(existing, {
172
+ title: spec.title,
173
+ page: spec.page,
174
+ frame: spec.frame ?? false,
175
+ rendererDir: env.rendererDir,
176
+ preload: env.preload,
177
+ rect: savedWindowRect(spec.bounds(), spec.def, spec.min),
178
+ min: spec.min,
179
+ pinTo: env.pinTo,
180
+ pinned: spec.pinned(),
181
+ remember: spec.remember
182
+ });
183
+ if (opened !== existing) {
184
+ live.set(name, opened);
185
+ opened.on("closed", () => {
186
+ if (live.get(name) === opened) live.delete(name);
187
+ });
188
+ env.onOpened?.(opened, name);
189
+ }
190
+ return opened;
191
+ };
192
+ const rescue = () => {
193
+ env.resetStore?.();
194
+ for (const spec of specs) rescueToolWindow(get(spec.name), spec.def);
195
+ for (const spec of specs) {
196
+ const w = get(spec.name);
197
+ if (!w) continue;
198
+ pinToolWindow(w, env.pinTo(), true);
199
+ w.webContents.send(env.pinChannel ?? "state:pinned", true);
200
+ }
201
+ };
202
+ return {
203
+ open,
204
+ get,
205
+ rescue,
206
+ all: () => specs.map((s) => get(s.name)).filter((w) => w !== void 0),
207
+ names: specs.map((s) => s.name)
208
+ };
209
+ }
210
+
211
+ export { centeredOnPrimary, defineToolWindows, frontForModal, openToolWindow, pinToolWindow, rememberBounds, rescueToolWindow, savedWindowRect };
212
+ //# sourceMappingURL=chunk-CPNTROWD.js.map
213
+ //# sourceMappingURL=chunk-CPNTROWD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tool-window.ts"],"names":[],"mappings":"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","file":"chunk-CPNTROWD.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The tool-window kit, MAIN-PROCESS half: the detached helper-window\n// machinery both apps had grown four copies of (Patterpad's Play + Search +\n// Coverage, Storyletter's Board + Find). Remembered bounds with a\n// disconnected-monitor guard, debounced persistence, the Reset View rescue,\n// and the pinnable window factory they compose into.\n//\n// This module imports electron, so it ships as its own subpath\n// (@wildwinter/app-shell/tool-window) - never import it from renderer code.\n// The renderer half (pin button, .swin drag-bar grammar) lives in the root\n// export + tool-window.css.\n//\n// Two deliberate corrections over the Patterpad originals, for when it\n// migrates here: rescueToolWindow re-pins EVERY window it rescues (Patterpad\n// re-pinned only Play, so its store lied about Search + Coverage), and the\n// factory always wires closed-cleanup through an identity guard.\n// ---------------------------------------------------------------------------\n\nimport { BrowserWindow, app, screen } from \"electron\";\nimport { join } from \"node:path\";\nimport type { Satellite } from \"./session.js\";\n\nexport interface ToolWindowSize {\n width: number;\n height: number;\n}\n\nexport interface ToolWindowBounds {\n x?: number;\n y?: number;\n width: number;\n height: number;\n}\n\n/** The per-window store slice every app remembers: where it was, whether it\n * floats. (Patterpad's PlayWindowState, generalised.) */\nexport interface ToolWindowState {\n bounds?: ToolWindowBounds;\n pinned: boolean;\n}\n\n/** Use a remembered rect only if it still intersects a live display (a window\n * saved on a now-disconnected monitor must not open offscreen). Falls back\n * to the default size; a remembered size is kept even when the position is\n * dropped (Electron then centres it). */\nexport function savedWindowRect(\n saved: ToolWindowBounds | undefined,\n def: ToolWindowSize,\n min: ToolWindowSize,\n): ToolWindowBounds {\n if (!saved) return { ...def };\n const w = Math.max(min.width, saved.width), h = Math.max(min.height, saved.height);\n if (saved.x != null && saved.y != null) {\n const onScreen = screen.getAllDisplays().some((d) => {\n const a = d.workArea;\n return saved.x! + w > a.x + 40 && saved.x! < a.x + a.width - 40 && saved.y! + h > a.y + 20 && saved.y! < a.y + a.height - 20;\n });\n if (onScreen) return { x: saved.x, y: saved.y, width: w, height: h };\n }\n return { width: w, height: h };\n}\n\n/** Persist a helper window's bounds as the user moves / resizes / closes it\n * (debounced 400ms; close flushes synchronously). */\nexport function rememberBounds(w: BrowserWindow, write: (bounds: ToolWindowBounds) => void): void {\n const saveBounds = (): void => { if (!w.isDestroyed()) write(w.getBounds()); };\n let boundsTimer: ReturnType<typeof setTimeout> | undefined;\n const queueSave = (): void => { clearTimeout(boundsTimer); boundsTimer = setTimeout(saveBounds, 400); };\n w.on(\"resize\", queueSave);\n w.on(\"move\", queueSave);\n w.on(\"close\", () => { clearTimeout(boundsTimer); saveBounds(); });\n}\n\n/** Centre a rect on the primary display's work area. */\nexport function centeredOnPrimary(size: ToolWindowSize): { x: number; y: number } {\n const a = screen.getPrimaryDisplay().workArea;\n return { x: Math.round(a.x + (a.width - size.width) / 2), y: Math.round(a.y + (a.height - size.height) / 2) };\n}\n\n/** Reset View's per-window half: un-minimise, re-pin (always - the store\n * says pinned:true after a rescue, so the window must agree), default size,\n * centred on the primary display, shown. The host clears its remembered\n * bounds itself (that's a store concern). No focus steal: helpers show\n * behind the editor. */\nexport function rescueToolWindow(w: BrowserWindow | undefined | null, def: ToolWindowSize): void {\n if (!w || w.isDestroyed()) return;\n if (w.isMinimized()) w.restore();\n w.setBounds({ ...def, ...centeredOnPrimary(def) });\n w.show();\n // Raise within OUR stack. This used to setAlwaysOnTop(true), which on macOS\n // and Windows floats the window above every other APPLICATION - and stuck\n // that way, since nothing ever unset it.\n w.moveTop();\n}\n\n/** macOS pinned windows, for the activation toggle below. */\nconst macPinned = new Set<BrowserWindow>();\nlet macActivationWired = false;\nfunction wireMacActivation(): void {\n if (macActivationWired) return;\n macActivationWired = true;\n // Floating is a GLOBAL window level: left on while another app is front,\n // the pinned window would sit over that app too - the exact 2026-08-25\n // complaint. Scoping it to our app's active state keeps the pin meaning\n // \"above my editor\" and nothing more.\n // Not while a modal has the pins held (frontForModal): the editor is in\n // front for it, and coming back to the app must not cover it again.\n app.on(\"did-become-active\", () => { if (pinsHeld > 0) return; for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(true, \"floating\"); });\n app.on(\"did-resign-active\", () => { for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(false); });\n}\n\n/**\n * The pin: keep a tool window above THIS APP'S MAIN WINDOW, not above the\n * world. Two findings shaped the mechanism, one per platform family:\n *\n * - Bare `alwaysOnTop` (the first implementation) floats the window over\n * every other application on macOS and Windows, so a pinned Board meant\n * nothing else on the machine could come to the front (a Storyletter user\n * report, 2026-08-25).\n * - A CHILD window (the second implementation) is position-coupled to its\n * parent on macOS: dragging the editor dragged the Board with it (a\n * Storyletter user report, 2026-08-28). Windows and Linux owned windows do\n * not move with their owner, so the child mechanism stays right for them.\n *\n * So: on macOS the pin is ACTIVATION-SCOPED alwaysOnTop - floating while\n * this app is active, dropped the moment it resigns - which is above the\n * editor without following its drags and without covering anyone else. On\n * Windows and Linux the pin is a child window, which stays over its parent\n * and stacks normally against everything else. Each branch clears the other\n * mechanism first, which also heals a window an older build pinned the old\n * way. `platform` is a seam for the tests; leave it defaulted.\n */\nexport function pinToolWindow(\n w: BrowserWindow | undefined | null, parent: BrowserWindow | undefined | null, on: boolean,\n platform: NodeJS.Platform = process.platform,\n): void {\n if (!w || w.isDestroyed()) return;\n if (platform === \"darwin\") {\n w.setParentWindow(null);\n if (on) {\n if (!macPinned.has(w)) {\n macPinned.add(w);\n w.once(\"closed\", () => macPinned.delete(w));\n }\n wireMacActivation();\n // Raise only when the app is active now and no modal has the pins\n // held; otherwise the next did-become-active, or the release, raises it.\n w.setAlwaysOnTop(pinsHeld === 0 && BrowserWindow.getFocusedWindow() !== null, \"floating\");\n } else {\n macPinned.delete(w);\n w.setAlwaysOnTop(false);\n }\n return;\n }\n w.setAlwaysOnTop(false);\n const owner = on && parent && !parent.isDestroyed() ? parent : null;\n // While a modal has the pins held, remember the choice and apply it at the\n // release, so the editor stays in front until the modal closes.\n if (pinsHeld > 0) {\n if (owner) heldChildren.set(w, owner); else heldChildren.delete(w);\n w.setParentWindow(null);\n return;\n }\n w.setParentWindow(owner);\n}\n\n// ---------------------------------------------------------------------------\n// Fronting the main window for a modal. A pinned tool window rides above the\n// editor by design, so a modal the EDITOR shows (the updater's prompts) opened\n// underneath one: the editor blurred behind its scrim and the prompt was\n// hidden, with nothing on screen to say why (both apps, 2026-10-02, the Play\n// window over the editor). While such a modal is up the pins step aside and\n// the editor comes forward; when it closes, every pin goes back as it was.\n// ---------------------------------------------------------------------------\n\n/** How many modals are holding the pins down. The pins return at zero. */\nlet pinsHeld = 0;\n/** Windows and Linux: the tool windows unparented by the hold, and the window\n * each goes back to. */\nconst heldChildren = new Map<BrowserWindow, BrowserWindow>();\n\n/**\n * Bring `main` in front of every tool window, pinned ones included, for as\n * long as a modal it shows is open: un-minimise, show, focus, with the pins\n * set aside. Returns the release, which puts the pins back (idempotent; holds\n * nest, and the pins return when the last is released). Call it only while\n * the app is active: focusing a window of an inactive app is the focus steal\n * the platforms forbid. `platform` is a seam for the tests.\n */\nexport function frontForModal(main: BrowserWindow, platform: NodeJS.Platform = process.platform): () => void {\n if (main.isDestroyed()) return () => {};\n pinsHeld++;\n if (platform === \"darwin\") {\n for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(false);\n } else {\n for (const w of BrowserWindow.getAllWindows()) {\n if (w === main || w.isDestroyed()) continue;\n const owner = w.getParentWindow();\n if (owner !== main) continue;\n heldChildren.set(w, main);\n w.setParentWindow(null);\n }\n }\n if (main.isMinimized()) main.restore();\n main.show();\n main.focus();\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (--pinsHeld > 0) return;\n if (platform === \"darwin\") {\n // Only float again if the app is active; otherwise did-become-active does.\n const active = BrowserWindow.getFocusedWindow() !== null;\n if (active) for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(true, \"floating\");\n } else {\n for (const [w, owner] of heldChildren) {\n if (!w.isDestroyed() && !owner.isDestroyed()) w.setParentWindow(owner);\n }\n heldChildren.clear();\n }\n };\n}\n\nexport interface ToolWindowOptions {\n title: string;\n /** Renderer entry, relative to rendererDir (e.g. \"table.html\", \"search/index.html\"). */\n page: string;\n /** Absolute path of the built renderer dir (join(import.meta.dirname, \"../renderer\")). */\n rendererDir: string;\n /** Absolute path of the preload bridge. */\n preload: string;\n rect: ToolWindowBounds;\n min?: ToolWindowSize;\n /** false = frameless: the renderer draws its own slim drag bar (.swin-head). */\n frame?: boolean;\n /** The window to PIN ABOVE: the app's main window, resolved at open time (a\n * getter, because the main window can be recreated). With `pinned` true\n * the new tool window rides above it - on macOS via activation-scoped\n * alwaysOnTop (a child window there follows the editor's drags), on\n * Windows and Linux as a child window (bare alwaysOnTop there floats over\n * the whole machine). See pinToolWindow for both findings. */\n pinTo?: () => BrowserWindow | undefined;\n /** Start pinned (the host remembers); flip later with `pinToolWindow`. */\n pinned?: boolean;\n /** Wire debounced bounds persistence into the host's store slice. */\n remember?: (bounds: ToolWindowBounds) => void;\n /** Fired when THIS window closes (identity-guarded; a stale close never\n * clears a newer window). Null the host's handle here. */\n onClosed?: () => void;\n}\n\n/** The pinnable detached-window factory (hardened webPreferences, one\n * preload, electron-vite's dev-URL/file split). Focuses and returns the\n * existing window when there is one; otherwise creates, wires, and loads. */\nexport function openToolWindow(existing: BrowserWindow | undefined | null, opts: ToolWindowOptions): BrowserWindow {\n if (existing && !existing.isDestroyed()) { existing.focus(); return existing; }\n const w = new BrowserWindow({\n ...opts.rect,\n ...(opts.min !== undefined ? { minWidth: opts.min.width, minHeight: opts.min.height } : {}),\n show: false,\n title: opts.title,\n ...(opts.frame !== undefined ? { frame: opts.frame } : {}),\n webPreferences: {\n contextIsolation: true, nodeIntegration: false, sandbox: true,\n preload: opts.preload,\n },\n });\n if (opts.pinTo !== undefined) pinToolWindow(w, opts.pinTo(), opts.pinned ?? false);\n if (opts.remember) rememberBounds(w, opts.remember);\n w.once(\"ready-to-show\", () => w.show());\n if (opts.onClosed) w.on(\"closed\", opts.onClosed);\n if (process.env[\"ELECTRON_RENDERER_URL\"]) void w.loadURL(`${process.env[\"ELECTRON_RENDERER_URL\"]}/${opts.page}`);\n else void w.loadFile(join(opts.rendererDir, opts.page));\n return w;\n}\n\n// ---------------------------------------------------------------------------\n// The tool windows AS DATA: one row per window, and everything that walks the\n// windows walks the rows. Lifted from Storyletter's TOOL_WINDOWS table +\n// openToolWindowFor + rescueWindows (studio/src/main/index.ts), where it had\n// replaced four near-identical openX() functions whose differences were all\n// mistakes (only one forwarded its console; Reset View rescued two of four).\n// Patterpad's three hand copies (createPlayWindow / createSearchWindow /\n// createCoverageWindow and a four-branch rescueWindows) become three rows.\n//\n// Two things the table makes structurally impossible: forgetting to register\n// a window as a satellite for the project-changed nudge (every row is\n// registered when a session is given), and Reset View rescuing a subset (it\n// walks the rows).\n// ---------------------------------------------------------------------------\n\n\nexport interface ToolWindowSpec<N extends string = string> {\n /** The window's key: the store slice it remembers under, the dev-console\n * prefix, and what `open(name)` takes. */\n name: N;\n title: string;\n /** Renderer entry, relative to the env's rendererDir. */\n page: string;\n def: ToolWindowSize;\n min: ToolWindowSize;\n /** The remembered rect, read at open time (a store read-through). */\n bounds: () => ToolWindowBounds | undefined;\n /** Persist a moved / resized rect (debounced by the factory). */\n remember: (bounds: ToolWindowBounds) => void;\n /** Whether it starts pinned above the editor (both apps default true). */\n pinned: () => boolean;\n /** Frameless (the family's shape: the renderer draws `toolWindowHead`).\n * Default false = frameless; pass `frame: true` for an OS title bar. */\n frame?: boolean;\n /** The project-changed channel for THIS window when it differs from the\n * env's (Storyletter's Links window listens on \"links:focus\"). */\n channel?: string;\n /** Drop what main caches for the outgoing project on this window's behalf\n * (a coverage report, a search anchor). Runs whether the window is open\n * or not: see session.ts. */\n clear?: () => void;\n}\n\nexport interface ToolWindowEnv {\n /** Absolute path of the built renderer dir. */\n rendererDir: string;\n /** Absolute path of the preload bridge. */\n preload: string;\n /** The window to pin above: the app's main window, resolved at open time. */\n pinTo: () => BrowserWindow | undefined;\n /** Register every row as a satellite of the project session, so a new\n * project cannot open underneath a tool window that still shows the old\n * one. `channel` is the nudge each window listens on (a row may override). */\n session?: { addSatellite: (satellite: Satellite) => () => void; channel: string };\n /** Reset View's store half, run first: forget every remembered rect and\n * set pinned true (`resetWindows(store)` from app-store, or the app's own). */\n resetStore?: () => void;\n /** The channel a rescued window's renderer hears its re-pin on, so its pin\n * button agrees with the store (default \"state:pinned\"). */\n pinChannel?: string;\n /** Runs once per window created: forward its console in dev, say. */\n onOpened?: (w: BrowserWindow, name: string) => void;\n}\n\nexport interface ToolWindows<N extends string> {\n /** Open (or focus) one window by name. */\n open(name: N): BrowserWindow;\n /** The live window, if open. */\n get(name: N): BrowserWindow | undefined;\n /** Reset View's window half, over the table: store reset, then every open\n * window restored, default size, centred, re-pinned, and told so. */\n rescue(): void;\n /** Every window that is open right now. */\n all(): BrowserWindow[];\n readonly names: readonly N[];\n}\n\n/** Build the table. The windows are owned here: a host reads them through\n * `get(name)` rather than holding module variables of its own. */\nexport function defineToolWindows<N extends string>(specs: ToolWindowSpec<N>[], env: ToolWindowEnv): ToolWindows<N> {\n const live = new Map<N, BrowserWindow>();\n const byName = (name: N): ToolWindowSpec<N> => {\n const found = specs.find((s) => s.name === name);\n if (!found) throw new Error(`no tool window \"${name}\"`);\n return found;\n };\n const get = (name: N): BrowserWindow | undefined => {\n const w = live.get(name);\n return w && !w.isDestroyed() ? w : undefined;\n };\n if (env.session) {\n for (const spec of specs) {\n env.session.addSatellite({\n window: () => get(spec.name),\n channel: spec.channel ?? env.session.channel,\n ...(spec.clear ? { clear: spec.clear } : {}),\n });\n }\n }\n const open = (name: N): BrowserWindow => {\n const spec = byName(name);\n const existing = get(name);\n const opened = openToolWindow(existing, {\n title: spec.title, page: spec.page, frame: spec.frame ?? false,\n rendererDir: env.rendererDir, preload: env.preload,\n rect: savedWindowRect(spec.bounds(), spec.def, spec.min),\n min: spec.min,\n pinTo: env.pinTo,\n pinned: spec.pinned(),\n remember: spec.remember,\n });\n if (opened !== existing) {\n live.set(name, opened);\n // Identity-guarded: a stale close never clears a newer window.\n opened.on(\"closed\", () => { if (live.get(name) === opened) live.delete(name); });\n env.onOpened?.(opened, name);\n }\n return opened;\n };\n const rescue = (): void => {\n env.resetStore?.();\n for (const spec of specs) rescueToolWindow(get(spec.name), spec.def);\n // ACTUALLY pin them, then tell them. `rescueToolWindow` restores, resizes,\n // centres and raises but never pins; the store now says pinned, so the\n // window and its button have to agree before the claim is made.\n for (const spec of specs) {\n const w = get(spec.name);\n if (!w) continue;\n pinToolWindow(w, env.pinTo(), true);\n w.webContents.send(env.pinChannel ?? \"state:pinned\", true);\n }\n };\n return {\n open, get, rescue,\n all: () => specs.map((s) => get(s.name)).filter((w): w is BrowserWindow => w !== undefined),\n names: specs.map((s) => s.name),\n };\n}\n"]}
@@ -55,12 +55,12 @@ function showUpdaterDialog(opts) {
55
55
  b.type = "button";
56
56
  b.addEventListener("click", () => finish(i));
57
57
  frame.actions.append(b);
58
- if (i === defaultId) queueMicrotask(() => b.focus());
58
+ if (i === (opts.unasked ? cancelId : defaultId)) queueMicrotask(() => b.focus());
59
59
  });
60
60
  frame.open();
61
61
  });
62
62
  }
63
63
 
64
64
  export { feedUpdaterDownloadProgress, showUpdaterDialog };
65
- //# sourceMappingURL=chunk-OPZPFOPS.js.map
66
- //# sourceMappingURL=chunk-OPZPFOPS.js.map
65
+ //# sourceMappingURL=chunk-WKPKBAXS.js.map
66
+ //# sourceMappingURL=chunk-WKPKBAXS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/updater-view.ts"],"names":[],"mappings":"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","file":"chunk-WKPKBAXS.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The updater's VIEW: the themed prompt main summons over `updater:prompt`\n// (updater.ts) and reads the chosen button index back from, the same contract\n// as dialog.showMessageBox's `response`. The renderer half of the updater is\n// not optional: an unanswered prompt does not fall back to a native dialog,\n// the shell waits 300 seconds and resolves its own fallback index. Both apps\n// had written this view by hand (55 of 60 lines identical); this is the one\n// they share, on the family's dialog frame, the way save.ts has save-view.ts.\n//\n// Storyletter's copy, plus Patterpad's `links` row as an option (an About box\n// used to share this dialog there). Styles ship as updater.css.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { dialogFrame } from \"./dialog.js\";\nimport type { UpdaterDownloadProgress, UpdaterPromptOptions } from \"./updater.js\";\n\nexport interface UpdaterLink { label: string; url: string }\n\nexport interface UpdaterViewOptions extends UpdaterPromptOptions {\n /** Web links under the detail. Opened through `openExternal`, never by\n * navigating the window. */\n links?: UpdaterLink[];\n /** How a link opens (the app's allow-listed shell.openExternal bridge).\n * Required for `links` to do anything. */\n openExternal?: (url: string) => void;\n}\n\n// The progress row of the open `progress: true` dialog, if any. These are\n// modal, so one at a time, so one slot is enough. Closing clears it.\nlet liveProgress: { bar: HTMLElement; label: HTMLElement } | null = null;\n\nconst mb = (n: number): string => (n / (1024 * 1024)).toFixed(1);\n\n/** Route a download-progress tick into the open dialog's bar. No-op when none\n * is showing. Registered once at boot on the app's progress channel. */\nexport function feedUpdaterDownloadProgress(p: UpdaterDownloadProgress): void {\n if (!liveProgress) return;\n const pct = Math.max(0, Math.min(100, p.percent));\n liveProgress.bar.style.width = `${pct}%`;\n liveProgress.label.textContent =\n `${pct.toFixed(0)}%, ${mb(p.transferred)} of ${mb(p.total)} MB (${mb(p.bytesPerSecond)} MB/s)`;\n}\n\n/** Show the prompt as a modal themed dialog; resolve the index of the clicked\n * button (Esc resolves `cancelId`, which defaults to the last button). */\nexport function showUpdaterDialog(opts: UpdaterViewOptions): Promise<number> {\n return new Promise((resolve) => {\n const defaultId = opts.defaultId ?? 0;\n const cancelId = opts.cancelId ?? opts.buttons.length - 1;\n\n let done = false;\n const finish = (idx: number): void => {\n if (done) return;\n done = true;\n liveProgress = null; // stop feeding a dialog that is gone\n resolve(idx);\n frame.close();\n };\n const frame = dialogFrame({\n title: opts.message, className: \"updater-dialog\",\n // Esc is the cancel button, not a further answer: main is waiting on an index.\n onClose: () => finish(cancelId),\n });\n if (opts.detail) frame.body.append(el(\"p\", \"updater-detail\", opts.detail));\n\n if (opts.progress) {\n const track = el(\"div\", \"updater-progress-track\");\n const bar = el(\"div\", \"updater-progress-bar\");\n track.append(bar);\n const label = el(\"p\", \"updater-progress-label\", \"Starting download…\");\n frame.body.append(track, label);\n liveProgress = { bar, label };\n }\n\n if (opts.links?.length) {\n const row = el(\"p\", \"updater-links\");\n for (const link of opts.links) {\n const a = el(\"a\", undefined, link.label);\n a.href = link.url;\n a.addEventListener(\"click\", (e) => { e.preventDefault(); opts.openExternal?.(link.url); });\n row.append(a);\n }\n frame.body.append(row);\n }\n\n opts.buttons.forEach((label, i) => {\n // The default is the affirmative and wears the accent; every other\n // button, the way out included, is the plain one.\n const b = el(\"button\", i === defaultId ? \"btn primary\" : \"btn\", label);\n b.type = \"button\";\n b.addEventListener(\"click\", () => finish(i));\n frame.actions.append(b);\n // Focus the default, unless nobody asked for this prompt: then the\n // cancel button, so a stray Enter or Space dismisses it, never says yes.\n if (i === (opts.unasked ? cancelId : defaultId)) queueMicrotask(() => b.focus());\n });\n\n frame.open();\n });\n}\n"]}
package/dist/index.cjs CHANGED
@@ -2693,7 +2693,7 @@ function showUpdaterDialog(opts) {
2693
2693
  b.type = "button";
2694
2694
  b.addEventListener("click", () => finish(i));
2695
2695
  frame.actions.append(b);
2696
- if (i === defaultId) queueMicrotask(() => b.focus());
2696
+ if (i === (opts.unasked ? cancelId : defaultId)) queueMicrotask(() => b.focus());
2697
2697
  });
2698
2698
  frame.open();
2699
2699
  });