@wildwinter/app-shell 0.4.1 → 0.14.0
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.
- package/README.md +35 -0
- package/dist/about.css +76 -0
- package/dist/about.css.map +1 -0
- package/dist/anchored.css +50 -0
- package/dist/anchored.css.map +1 -0
- package/dist/app-store.cjs +74 -0
- package/dist/app-store.cjs.map +1 -0
- package/dist/app-store.d.cts +75 -0
- package/dist/app-store.d.ts +75 -0
- package/dist/app-store.js +72 -0
- package/dist/app-store.js.map +1 -0
- package/dist/chunk-MDGOIT3O.js +58 -0
- package/dist/chunk-MDGOIT3O.js.map +1 -0
- package/dist/chunk-R6RO5TXB.js +16 -0
- package/dist/chunk-R6RO5TXB.js.map +1 -0
- package/dist/comments.css +109 -0
- package/dist/comments.css.map +1 -0
- package/dist/confirm.css +62 -0
- package/dist/confirm.css.map +1 -0
- package/dist/identity.css +62 -0
- package/dist/identity.css.map +1 -0
- package/dist/index.cjs +926 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +371 -3
- package/dist/index.d.ts +371 -3
- package/dist/index.js +896 -8
- package/dist/index.js.map +1 -1
- package/dist/job.cjs +61 -0
- package/dist/job.cjs.map +1 -0
- package/dist/job.css +70 -0
- package/dist/job.css.map +1 -0
- package/dist/job.d.cts +56 -0
- package/dist/job.d.ts +56 -0
- package/dist/job.js +3 -0
- package/dist/job.js.map +1 -0
- package/dist/menu.cjs +7 -0
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +20 -1
- package/dist/menu.d.ts +20 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +107 -0
- package/dist/notes-editor.css.map +1 -0
- package/dist/session.cjs +52 -0
- package/dist/session.cjs.map +1 -0
- package/dist/session.d.cts +79 -0
- package/dist/session.d.ts +79 -0
- package/dist/session.js +50 -0
- package/dist/session.js.map +1 -0
- package/dist/tool-window.cjs +79 -0
- package/dist/tool-window.cjs.map +1 -0
- package/dist/tool-window.css +60 -0
- package/dist/tool-window.css.map +1 -0
- package/dist/tool-window.d.cts +63 -0
- package/dist/tool-window.d.ts +63 -0
- package/dist/tool-window.js +73 -0
- package/dist/tool-window.js.map +1 -0
- package/dist/tooltip.css +22 -0
- package/dist/tooltip.css.map +1 -0
- package/dist/updater.cjs +315 -0
- package/dist/updater.cjs.map +1 -0
- package/dist/updater.d.cts +53 -0
- package/dist/updater.d.ts +53 -0
- package/dist/updater.js +306 -0
- package/dist/updater.js.map +1 -0
- package/dist/vc-status.cjs +77 -0
- package/dist/vc-status.cjs.map +1 -0
- package/dist/vc-status.d.cts +40 -0
- package/dist/vc-status.d.ts +40 -0
- package/dist/vc-status.js +71 -0
- package/dist/vc-status.js.map +1 -0
- package/dist/vc.css +41 -0
- package/dist/vc.css.map +1 -0
- package/package.json +90 -1
- package/dist/chunk-KQT3XYRS.js +0 -10
- package/dist/chunk-KQT3XYRS.js.map +0 -1
- package/dist/pane-shell.d.cts +0 -2
- package/dist/pane-shell.d.ts +0 -2
- package/dist/settings.d.cts +0 -2
- package/dist/settings.d.ts +0 -2
- package/dist/tokens.d.cts +0 -2
- package/dist/tokens.d.ts +0 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var electron = require('electron');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
// src/tool-window.ts
|
|
7
|
+
function savedWindowRect(saved, def, min) {
|
|
8
|
+
if (!saved) return { ...def };
|
|
9
|
+
const w = Math.max(min.width, saved.width), h = Math.max(min.height, saved.height);
|
|
10
|
+
if (saved.x != null && saved.y != null) {
|
|
11
|
+
const onScreen = electron.screen.getAllDisplays().some((d) => {
|
|
12
|
+
const a = d.workArea;
|
|
13
|
+
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;
|
|
14
|
+
});
|
|
15
|
+
if (onScreen) return { x: saved.x, y: saved.y, width: w, height: h };
|
|
16
|
+
}
|
|
17
|
+
return { width: w, height: h };
|
|
18
|
+
}
|
|
19
|
+
function rememberBounds(w, write) {
|
|
20
|
+
const saveBounds = () => {
|
|
21
|
+
if (!w.isDestroyed()) write(w.getBounds());
|
|
22
|
+
};
|
|
23
|
+
let boundsTimer;
|
|
24
|
+
const queueSave = () => {
|
|
25
|
+
clearTimeout(boundsTimer);
|
|
26
|
+
boundsTimer = setTimeout(saveBounds, 400);
|
|
27
|
+
};
|
|
28
|
+
w.on("resize", queueSave);
|
|
29
|
+
w.on("move", queueSave);
|
|
30
|
+
w.on("close", () => {
|
|
31
|
+
clearTimeout(boundsTimer);
|
|
32
|
+
saveBounds();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function centeredOnPrimary(size) {
|
|
36
|
+
const a = electron.screen.getPrimaryDisplay().workArea;
|
|
37
|
+
return { x: Math.round(a.x + (a.width - size.width) / 2), y: Math.round(a.y + (a.height - size.height) / 2) };
|
|
38
|
+
}
|
|
39
|
+
function rescueToolWindow(w, def) {
|
|
40
|
+
if (!w || w.isDestroyed()) return;
|
|
41
|
+
if (w.isMinimized()) w.restore();
|
|
42
|
+
w.setAlwaysOnTop(true);
|
|
43
|
+
w.setBounds({ ...def, ...centeredOnPrimary(def) });
|
|
44
|
+
w.show();
|
|
45
|
+
}
|
|
46
|
+
function openToolWindow(existing, opts) {
|
|
47
|
+
if (existing && !existing.isDestroyed()) {
|
|
48
|
+
existing.focus();
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
const w = new electron.BrowserWindow({
|
|
52
|
+
...opts.rect,
|
|
53
|
+
...opts.min !== void 0 ? { minWidth: opts.min.width, minHeight: opts.min.height } : {},
|
|
54
|
+
show: false,
|
|
55
|
+
title: opts.title,
|
|
56
|
+
...opts.frame !== void 0 ? { frame: opts.frame } : {},
|
|
57
|
+
...opts.alwaysOnTop !== void 0 ? { alwaysOnTop: opts.alwaysOnTop } : {},
|
|
58
|
+
webPreferences: {
|
|
59
|
+
contextIsolation: true,
|
|
60
|
+
nodeIntegration: false,
|
|
61
|
+
sandbox: true,
|
|
62
|
+
preload: opts.preload
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
if (opts.remember) rememberBounds(w, opts.remember);
|
|
66
|
+
w.once("ready-to-show", () => w.show());
|
|
67
|
+
if (opts.onClosed) w.on("closed", opts.onClosed);
|
|
68
|
+
if (process.env["ELECTRON_RENDERER_URL"]) void w.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/${opts.page}`);
|
|
69
|
+
else void w.loadFile(path.join(opts.rendererDir, opts.page));
|
|
70
|
+
return w;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
exports.centeredOnPrimary = centeredOnPrimary;
|
|
74
|
+
exports.openToolWindow = openToolWindow;
|
|
75
|
+
exports.rememberBounds = rememberBounds;
|
|
76
|
+
exports.rescueToolWindow = rescueToolWindow;
|
|
77
|
+
exports.savedWindowRect = savedWindowRect;
|
|
78
|
+
//# sourceMappingURL=tool-window.cjs.map
|
|
79
|
+
//# sourceMappingURL=tool-window.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tool-window.ts"],"names":["screen","BrowserWindow","join"],"mappings":"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","file":"tool-window.cjs","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, screen } from \"electron\";\nimport { join } from \"node:path\";\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.setAlwaysOnTop(true);\n w.setBounds({ ...def, ...centeredOnPrimary(def) });\n w.show();\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 /** Floats over the editor (the pin); remembered by the host. */\n alwaysOnTop?: 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 ...(opts.alwaysOnTop !== undefined ? { alwaysOnTop: opts.alwaysOnTop } : {}),\n webPreferences: {\n contextIsolation: true, nodeIntegration: false, sandbox: true,\n preload: opts.preload,\n },\n });\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"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* src/tool-window.css */
|
|
2
|
+
.swin-head {
|
|
3
|
+
-webkit-app-region: drag;
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 6px;
|
|
7
|
+
padding: 7px 10px;
|
|
8
|
+
background: var(--surface);
|
|
9
|
+
border-bottom: 1px solid var(--line-soft, var(--line));
|
|
10
|
+
}
|
|
11
|
+
.swin-head button,
|
|
12
|
+
.swin-head select,
|
|
13
|
+
.swin-head input {
|
|
14
|
+
-webkit-app-region: no-drag;
|
|
15
|
+
}
|
|
16
|
+
.swin-title {
|
|
17
|
+
font: 600 0.68rem/1 var(--font-ui);
|
|
18
|
+
text-transform: uppercase;
|
|
19
|
+
letter-spacing: 0.07em;
|
|
20
|
+
color: var(--muted);
|
|
21
|
+
}
|
|
22
|
+
.swin-spacer {
|
|
23
|
+
flex: 1;
|
|
24
|
+
}
|
|
25
|
+
.swin-pin,
|
|
26
|
+
.swin-close {
|
|
27
|
+
flex: none;
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
background: none;
|
|
32
|
+
border: 1px solid transparent;
|
|
33
|
+
border-radius: var(--radius-sm);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
color: var(--muted);
|
|
36
|
+
line-height: 0;
|
|
37
|
+
padding: 4px;
|
|
38
|
+
transition:
|
|
39
|
+
background var(--dur-quick) var(--ease-standard),
|
|
40
|
+
border-color var(--dur-quick) var(--ease-standard),
|
|
41
|
+
color var(--dur-quick) var(--ease-standard);
|
|
42
|
+
}
|
|
43
|
+
.swin-pin:hover,
|
|
44
|
+
.swin-close:hover {
|
|
45
|
+
border-color: var(--line);
|
|
46
|
+
color: var(--ink);
|
|
47
|
+
}
|
|
48
|
+
.swin-pin svg {
|
|
49
|
+
display: block;
|
|
50
|
+
}
|
|
51
|
+
.swin-pin.on {
|
|
52
|
+
color: var(--accent);
|
|
53
|
+
border-color: var(--accent);
|
|
54
|
+
background: color-mix(in oklab, var(--accent) 12%, transparent);
|
|
55
|
+
}
|
|
56
|
+
.swin-close {
|
|
57
|
+
font: 0.8rem var(--font-ui);
|
|
58
|
+
padding: 2px 7px;
|
|
59
|
+
}
|
|
60
|
+
/*# sourceMappingURL=tool-window.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tool-window.css"],"sourcesContent":["/* ---------------------------------------------------------------------------\n The tool-window kit, CHROME grammar: the slim drag bar a frameless helper\n window draws in place of the OS title bar, and the pin / close buttons any\n helper (framed or not) carries. Consumes the host palette (--surface /\n --ink / --muted / --line / --line-soft / --accent / --font-ui) over the\n shared tokens. Content styles stay app-side; this is the chrome only.\n --------------------------------------------------------------------------- */\n\n/* The drag bar standing in for the OS title bar (frame: false). Buttons and\n other controls inside it opt out so they stay clickable. */\n.swin-head {\n -webkit-app-region: drag;\n display: flex; align-items: center; gap: 6px;\n padding: 7px 10px;\n background: var(--surface);\n /* --line-soft is an optional host token (Storyletter defines it; Patterpad\n falls back to --line). */\n border-bottom: 1px solid var(--line-soft, var(--line));\n}\n.swin-head button, .swin-head select, .swin-head input { -webkit-app-region: no-drag; }\n.swin-title {\n font: 600 0.68rem/1 var(--font-ui);\n text-transform: uppercase; letter-spacing: 0.07em;\n color: var(--muted);\n}\n.swin-spacer { flex: 1; }\n\n/* The pin + close pair; motion is interpolation, never a jump. */\n.swin-pin, .swin-close {\n flex: none; display: inline-flex; align-items: center; justify-content: center;\n background: none; border: 1px solid transparent; border-radius: var(--radius-sm);\n cursor: pointer; color: var(--muted); line-height: 0; padding: 4px;\n transition: background var(--dur-quick) var(--ease-standard),\n border-color var(--dur-quick) var(--ease-standard),\n color var(--dur-quick) var(--ease-standard);\n}\n.swin-pin:hover, .swin-close:hover { border-color: var(--line); color: var(--ink); }\n.swin-pin svg { display: block; }\n.swin-pin.on {\n color: var(--accent); border-color: var(--accent);\n background: color-mix(in oklab, var(--accent) 12%, transparent);\n}\n.swin-close { font: 0.8rem var(--font-ui); padding: 2px 7px; }\n"],"mappings":";AAUA,CAAC;AACC,sBAAoB;AACpB,WAAS;AAAM,eAAa;AAAQ,OAAK;AACzC,WAAS,IAAI;AACb,cAAY,IAAI;AAGhB,iBAAe,IAAI,MAAM,IAAI,WAAW,EAAE,IAAI;AAChD;AACA,CATC,UASU;AAAQ,CATlB,UAS6B;AAAQ,CATrC,UASgD;AAAQ,sBAAoB;AAAS;AACtF,CAAC;AACC,QAAM,IAAI,OAAO,CAAC,EAAE,IAAI;AACxB,kBAAgB;AAAW,kBAAgB;AAC3C,SAAO,IAAI;AACb;AACA,CAAC;AAAc,QAAM;AAAG;AAGxB,CAAC;AAAU,CAAC;AACV,QAAM;AAAM,WAAS;AAAa,eAAa;AAAQ,mBAAiB;AACxE,cAAY;AAAM,UAAQ,IAAI,MAAM;AAAa,iBAAe,IAAI;AACpE,UAAQ;AAAS,SAAO,IAAI;AAAU,eAAa;AAAG,WAAS;AAC/D;AAAA,IAAY,WAAW,IAAI,aAAa,IAAI,gBAAgB;AAAA,IAC1D,aAAa,IAAI,aAAa,IAAI,gBAAgB;AAAA,IAClD,MAAM,IAAI,aAAa,IAAI;AAC/B;AACA,CARC,QAQQ;AAAQ,CARL,UAQgB;AAAS,gBAAc,IAAI;AAAS,SAAO,IAAI;AAAQ;AACnF,CATC,SASS;AAAM,WAAS;AAAO;AAChC,CAVC,QAUQ,CAAC;AACR,SAAO,IAAI;AAAW,gBAAc,IAAI;AACxC,cAAY,UAAU,GAAG,KAAK,EAAE,IAAI,UAAU,GAAG,EAAE;AACrD;AACA,CAdY;AAcE,QAAM,OAAO,IAAI;AAAY,WAAS,IAAI;AAAK;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BrowserWindow } from 'electron';
|
|
2
|
+
|
|
3
|
+
interface ToolWindowSize {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
interface ToolWindowBounds {
|
|
8
|
+
x?: number;
|
|
9
|
+
y?: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** The per-window store slice every app remembers: where it was, whether it
|
|
14
|
+
* floats. (Patterpad's PlayWindowState, generalised.) */
|
|
15
|
+
interface ToolWindowState {
|
|
16
|
+
bounds?: ToolWindowBounds;
|
|
17
|
+
pinned: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Use a remembered rect only if it still intersects a live display (a window
|
|
20
|
+
* saved on a now-disconnected monitor must not open offscreen). Falls back
|
|
21
|
+
* to the default size; a remembered size is kept even when the position is
|
|
22
|
+
* dropped (Electron then centres it). */
|
|
23
|
+
declare function savedWindowRect(saved: ToolWindowBounds | undefined, def: ToolWindowSize, min: ToolWindowSize): ToolWindowBounds;
|
|
24
|
+
/** Persist a helper window's bounds as the user moves / resizes / closes it
|
|
25
|
+
* (debounced 400ms; close flushes synchronously). */
|
|
26
|
+
declare function rememberBounds(w: BrowserWindow, write: (bounds: ToolWindowBounds) => void): void;
|
|
27
|
+
/** Centre a rect on the primary display's work area. */
|
|
28
|
+
declare function centeredOnPrimary(size: ToolWindowSize): {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
/** Reset View's per-window half: un-minimise, re-pin (always - the store
|
|
33
|
+
* says pinned:true after a rescue, so the window must agree), default size,
|
|
34
|
+
* centred on the primary display, shown. The host clears its remembered
|
|
35
|
+
* bounds itself (that's a store concern). No focus steal: helpers show
|
|
36
|
+
* behind the editor. */
|
|
37
|
+
declare function rescueToolWindow(w: BrowserWindow | undefined | null, def: ToolWindowSize): void;
|
|
38
|
+
interface ToolWindowOptions {
|
|
39
|
+
title: string;
|
|
40
|
+
/** Renderer entry, relative to rendererDir (e.g. "table.html", "search/index.html"). */
|
|
41
|
+
page: string;
|
|
42
|
+
/** Absolute path of the built renderer dir (join(import.meta.dirname, "../renderer")). */
|
|
43
|
+
rendererDir: string;
|
|
44
|
+
/** Absolute path of the preload bridge. */
|
|
45
|
+
preload: string;
|
|
46
|
+
rect: ToolWindowBounds;
|
|
47
|
+
min?: ToolWindowSize;
|
|
48
|
+
/** false = frameless: the renderer draws its own slim drag bar (.swin-head). */
|
|
49
|
+
frame?: boolean;
|
|
50
|
+
/** Floats over the editor (the pin); remembered by the host. */
|
|
51
|
+
alwaysOnTop?: boolean;
|
|
52
|
+
/** Wire debounced bounds persistence into the host's store slice. */
|
|
53
|
+
remember?: (bounds: ToolWindowBounds) => void;
|
|
54
|
+
/** Fired when THIS window closes (identity-guarded; a stale close never
|
|
55
|
+
* clears a newer window). Null the host's handle here. */
|
|
56
|
+
onClosed?: () => void;
|
|
57
|
+
}
|
|
58
|
+
/** The pinnable detached-window factory (hardened webPreferences, one
|
|
59
|
+
* preload, electron-vite's dev-URL/file split). Focuses and returns the
|
|
60
|
+
* existing window when there is one; otherwise creates, wires, and loads. */
|
|
61
|
+
declare function openToolWindow(existing: BrowserWindow | undefined | null, opts: ToolWindowOptions): BrowserWindow;
|
|
62
|
+
|
|
63
|
+
export { type ToolWindowBounds, type ToolWindowOptions, type ToolWindowSize, type ToolWindowState, centeredOnPrimary, openToolWindow, rememberBounds, rescueToolWindow, savedWindowRect };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BrowserWindow } from 'electron';
|
|
2
|
+
|
|
3
|
+
interface ToolWindowSize {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
interface ToolWindowBounds {
|
|
8
|
+
x?: number;
|
|
9
|
+
y?: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** The per-window store slice every app remembers: where it was, whether it
|
|
14
|
+
* floats. (Patterpad's PlayWindowState, generalised.) */
|
|
15
|
+
interface ToolWindowState {
|
|
16
|
+
bounds?: ToolWindowBounds;
|
|
17
|
+
pinned: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Use a remembered rect only if it still intersects a live display (a window
|
|
20
|
+
* saved on a now-disconnected monitor must not open offscreen). Falls back
|
|
21
|
+
* to the default size; a remembered size is kept even when the position is
|
|
22
|
+
* dropped (Electron then centres it). */
|
|
23
|
+
declare function savedWindowRect(saved: ToolWindowBounds | undefined, def: ToolWindowSize, min: ToolWindowSize): ToolWindowBounds;
|
|
24
|
+
/** Persist a helper window's bounds as the user moves / resizes / closes it
|
|
25
|
+
* (debounced 400ms; close flushes synchronously). */
|
|
26
|
+
declare function rememberBounds(w: BrowserWindow, write: (bounds: ToolWindowBounds) => void): void;
|
|
27
|
+
/** Centre a rect on the primary display's work area. */
|
|
28
|
+
declare function centeredOnPrimary(size: ToolWindowSize): {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
/** Reset View's per-window half: un-minimise, re-pin (always - the store
|
|
33
|
+
* says pinned:true after a rescue, so the window must agree), default size,
|
|
34
|
+
* centred on the primary display, shown. The host clears its remembered
|
|
35
|
+
* bounds itself (that's a store concern). No focus steal: helpers show
|
|
36
|
+
* behind the editor. */
|
|
37
|
+
declare function rescueToolWindow(w: BrowserWindow | undefined | null, def: ToolWindowSize): void;
|
|
38
|
+
interface ToolWindowOptions {
|
|
39
|
+
title: string;
|
|
40
|
+
/** Renderer entry, relative to rendererDir (e.g. "table.html", "search/index.html"). */
|
|
41
|
+
page: string;
|
|
42
|
+
/** Absolute path of the built renderer dir (join(import.meta.dirname, "../renderer")). */
|
|
43
|
+
rendererDir: string;
|
|
44
|
+
/** Absolute path of the preload bridge. */
|
|
45
|
+
preload: string;
|
|
46
|
+
rect: ToolWindowBounds;
|
|
47
|
+
min?: ToolWindowSize;
|
|
48
|
+
/** false = frameless: the renderer draws its own slim drag bar (.swin-head). */
|
|
49
|
+
frame?: boolean;
|
|
50
|
+
/** Floats over the editor (the pin); remembered by the host. */
|
|
51
|
+
alwaysOnTop?: boolean;
|
|
52
|
+
/** Wire debounced bounds persistence into the host's store slice. */
|
|
53
|
+
remember?: (bounds: ToolWindowBounds) => void;
|
|
54
|
+
/** Fired when THIS window closes (identity-guarded; a stale close never
|
|
55
|
+
* clears a newer window). Null the host's handle here. */
|
|
56
|
+
onClosed?: () => void;
|
|
57
|
+
}
|
|
58
|
+
/** The pinnable detached-window factory (hardened webPreferences, one
|
|
59
|
+
* preload, electron-vite's dev-URL/file split). Focuses and returns the
|
|
60
|
+
* existing window when there is one; otherwise creates, wires, and loads. */
|
|
61
|
+
declare function openToolWindow(existing: BrowserWindow | undefined | null, opts: ToolWindowOptions): BrowserWindow;
|
|
62
|
+
|
|
63
|
+
export { type ToolWindowBounds, type ToolWindowOptions, type ToolWindowSize, type ToolWindowState, centeredOnPrimary, openToolWindow, rememberBounds, rescueToolWindow, savedWindowRect };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { screen, BrowserWindow } 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.setAlwaysOnTop(true);
|
|
41
|
+
w.setBounds({ ...def, ...centeredOnPrimary(def) });
|
|
42
|
+
w.show();
|
|
43
|
+
}
|
|
44
|
+
function openToolWindow(existing, opts) {
|
|
45
|
+
if (existing && !existing.isDestroyed()) {
|
|
46
|
+
existing.focus();
|
|
47
|
+
return existing;
|
|
48
|
+
}
|
|
49
|
+
const w = new BrowserWindow({
|
|
50
|
+
...opts.rect,
|
|
51
|
+
...opts.min !== void 0 ? { minWidth: opts.min.width, minHeight: opts.min.height } : {},
|
|
52
|
+
show: false,
|
|
53
|
+
title: opts.title,
|
|
54
|
+
...opts.frame !== void 0 ? { frame: opts.frame } : {},
|
|
55
|
+
...opts.alwaysOnTop !== void 0 ? { alwaysOnTop: opts.alwaysOnTop } : {},
|
|
56
|
+
webPreferences: {
|
|
57
|
+
contextIsolation: true,
|
|
58
|
+
nodeIntegration: false,
|
|
59
|
+
sandbox: true,
|
|
60
|
+
preload: opts.preload
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (opts.remember) rememberBounds(w, opts.remember);
|
|
64
|
+
w.once("ready-to-show", () => w.show());
|
|
65
|
+
if (opts.onClosed) w.on("closed", opts.onClosed);
|
|
66
|
+
if (process.env["ELECTRON_RENDERER_URL"]) void w.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/${opts.page}`);
|
|
67
|
+
else void w.loadFile(join(opts.rendererDir, opts.page));
|
|
68
|
+
return w;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { centeredOnPrimary, openToolWindow, rememberBounds, rescueToolWindow, savedWindowRect };
|
|
72
|
+
//# sourceMappingURL=tool-window.js.map
|
|
73
|
+
//# sourceMappingURL=tool-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tool-window.ts"],"names":[],"mappings":"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","file":"tool-window.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, screen } from \"electron\";\nimport { join } from \"node:path\";\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.setAlwaysOnTop(true);\n w.setBounds({ ...def, ...centeredOnPrimary(def) });\n w.show();\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 /** Floats over the editor (the pin); remembered by the host. */\n alwaysOnTop?: 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 ...(opts.alwaysOnTop !== undefined ? { alwaysOnTop: opts.alwaysOnTop } : {}),\n webPreferences: {\n contextIsolation: true, nodeIntegration: false, sandbox: true,\n preload: opts.preload,\n },\n });\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"]}
|
package/dist/tooltip.css
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* src/tooltip.css */
|
|
2
|
+
.tooltip {
|
|
3
|
+
position: fixed;
|
|
4
|
+
z-index: 100;
|
|
5
|
+
pointer-events: none;
|
|
6
|
+
max-width: 22rem;
|
|
7
|
+
white-space: pre-line;
|
|
8
|
+
background: var(--ink);
|
|
9
|
+
color: var(--surface);
|
|
10
|
+
font: 0.72rem/1.4 var(--font-ui);
|
|
11
|
+
letter-spacing: 0.01em;
|
|
12
|
+
padding: 0.3rem 0.5rem;
|
|
13
|
+
border-radius: var(--radius-sm);
|
|
14
|
+
box-shadow: 0 4px 14px color-mix(in oklab, var(--ink) 35%, transparent);
|
|
15
|
+
}
|
|
16
|
+
.tooltip[hidden] {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
.tooltip.show {
|
|
20
|
+
animation: var(--anim-panel-in);
|
|
21
|
+
}
|
|
22
|
+
/*# sourceMappingURL=tooltip.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tooltip.css"],"sourcesContent":["/* The themed tooltip bubble (see tooltip.ts). ONE element for the whole\n document, moved and refilled per anchor, so this is the entire stylesheet.\n\n Inverted (ink ground, surface ink) on purpose: a rollover is a passing\n annotation rather than a panel, and the family's panels are all light-on-dark\n the other way round, so inverting is what marks it as temporary. */\n.tooltip {\n position: fixed; z-index: 100; pointer-events: none;\n max-width: 22rem; white-space: pre-line; /* tips may carry newlines */\n background: var(--ink); color: var(--surface);\n font: 0.72rem/1.4 var(--font-ui); letter-spacing: 0.01em;\n padding: 0.3rem 0.5rem; border-radius: var(--radius-sm);\n box-shadow: 0 4px 14px color-mix(in oklab, var(--ink) 35%, transparent);\n}\n.tooltip[hidden] { display: none; }\n.tooltip.show { animation: var(--anim-panel-in); }\n"],"mappings":";AAMA,CAAC;AACC,YAAU;AAAO,WAAS;AAAK,kBAAgB;AAC/C,aAAW;AAAO,eAAa;AAC/B,cAAY,IAAI;AAAQ,SAAO,IAAI;AACnC,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,kBAAgB;AAClD,WAAS,OAAO;AAAQ,iBAAe,IAAI;AAC3C,cAAY,EAAE,IAAI,KAAK,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAC7D;AACA,CARC,OAQO,CAAC;AAAU,WAAS;AAAM;AAClC,CATC,OASO,CAAC;AAAO,aAAW,IAAI;AAAkB;","names":[]}
|