@wildwinter/app-shell 0.38.0 → 0.39.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/LICENSES.md +62 -0
- package/README.md +8 -0
- package/dist/about.css +13 -34
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +5 -5
- package/dist/anchored.css.map +1 -1
- package/dist/app-store.cjs +15 -0
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +25 -1
- package/dist/app-store.d.ts +25 -1
- package/dist/app-store.js +14 -1
- package/dist/app-store.js.map +1 -1
- package/dist/chunk-2ECJUTSX.js +97 -0
- package/dist/chunk-2ECJUTSX.js.map +1 -0
- package/dist/chunk-2NMVIM54.js +55 -0
- package/dist/chunk-2NMVIM54.js.map +1 -0
- package/dist/chunk-C6UVQYKI.js +70 -0
- package/dist/chunk-C6UVQYKI.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-K2AOLQW4.js +82 -0
- package/dist/chunk-K2AOLQW4.js.map +1 -0
- package/dist/chunk-KGAMP5LW.js +87 -0
- package/dist/chunk-KGAMP5LW.js.map +1 -0
- package/dist/chunk-L5HOBEKL.js +534 -0
- package/dist/chunk-L5HOBEKL.js.map +1 -0
- package/dist/chunk-P4G4ERG6.js +60 -0
- package/dist/chunk-P4G4ERG6.js.map +1 -0
- package/dist/chunk-P6WLAEKZ.js +71 -0
- package/dist/chunk-P6WLAEKZ.js.map +1 -0
- package/dist/chunk-S4KWQLYN.js +66 -0
- package/dist/chunk-S4KWQLYN.js.map +1 -0
- package/dist/chunk-XZYZXY7I.js +554 -0
- package/dist/chunk-XZYZXY7I.js.map +1 -0
- package/dist/comments.css +5 -3
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +1 -54
- package/dist/confirm.css.map +1 -1
- package/dist/context-menu.cjs +250 -0
- package/dist/context-menu.cjs.map +1 -0
- package/dist/context-menu.css +64 -0
- package/dist/context-menu.css.map +1 -0
- package/dist/context-menu.d.cts +25 -0
- package/dist/context-menu.d.ts +25 -0
- package/dist/context-menu.js +5 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/controls.css +155 -0
- package/dist/controls.css.map +1 -0
- package/dist/dialog.cjs +222 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.css +55 -0
- package/dist/dialog.css.map +1 -0
- package/dist/dialog.d.cts +28 -0
- package/dist/dialog.d.ts +28 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/floating.cjs +83 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +25 -0
- package/dist/floating.d.ts +25 -0
- package/dist/floating.js +4 -0
- package/dist/floating.js.map +1 -0
- package/dist/identity.css +15 -50
- package/dist/identity.css.map +1 -1
- package/dist/index.cjs +1307 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -149
- package/dist/index.d.ts +349 -149
- package/dist/index.js +259 -822
- package/dist/index.js.map +1 -1
- package/dist/job.css +23 -0
- package/dist/job.css.map +1 -1
- package/dist/link-status.cjs +430 -0
- package/dist/link-status.cjs.map +1 -0
- package/dist/link-status.css +76 -0
- package/dist/link-status.css.map +1 -0
- package/dist/link-status.d.cts +53 -0
- package/dist/link-status.d.ts +53 -0
- package/dist/link-status.js +5 -0
- package/dist/link-status.js.map +1 -0
- package/dist/menu.cjs +62 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +88 -1
- package/dist/menu.d.ts +88 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +3 -46
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +16 -3
- package/dist/pane-shell.css.map +1 -1
- package/dist/property-list-D3VZCJzi.d.cts +162 -0
- package/dist/property-list-D3VZCJzi.d.ts +162 -0
- package/dist/property-list.cjs +784 -0
- package/dist/property-list.cjs.map +1 -0
- package/dist/property-list.d.cts +1 -0
- package/dist/property-list.d.ts +1 -0
- package/dist/property-list.js +4 -0
- package/dist/property-list.js.map +1 -0
- package/dist/settings.css +52 -8
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +3 -0
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +35 -0
- package/dist/stepper.css.map +1 -1
- package/dist/tokens.css +19 -0
- package/dist/tokens.css.map +1 -1
- package/dist/tool-window.cjs +63 -0
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.css +6 -3
- package/dist/tool-window.css.map +1 -1
- package/dist/tool-window.d.cts +67 -1
- package/dist/tool-window.d.ts +67 -1
- package/dist/tool-window.js +63 -1
- package/dist/tool-window.js.map +1 -1
- package/dist/updater-view.cjs +283 -0
- package/dist/updater-view.cjs.map +1 -0
- package/dist/updater-view.d.cts +23 -0
- package/dist/updater-view.d.ts +23 -0
- package/dist/updater-view.js +6 -0
- package/dist/updater-view.js.map +1 -0
- package/dist/updater.cjs +12 -16
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.css +44 -0
- package/dist/updater.css.map +1 -0
- package/dist/updater.js +12 -16
- package/dist/updater.js.map +1 -1
- package/dist/util.cjs +78 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +37 -0
- package/dist/util.d.ts +37 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/dist/vc.css +3 -2
- package/dist/vc.css.map +1 -1
- package/dist/welcome.cjs +215 -0
- package/dist/welcome.cjs.map +1 -0
- package/dist/welcome.css +112 -0
- package/dist/welcome.css.map +1 -0
- package/dist/welcome.d.cts +55 -0
- package/dist/welcome.d.ts +55 -0
- package/dist/welcome.js +4 -0
- package/dist/welcome.js.map +1 -0
- package/package.json +58 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
package/LICENSES.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This package is MIT (see `LICENSE`). The following is vendored inside it.
|
|
4
|
+
|
|
5
|
+
## Lucide
|
|
6
|
+
|
|
7
|
+
The icon vocabulary in `src/icons.ts` vendors 30 drawings from
|
|
8
|
+
[Lucide](https://lucide.dev) 1.46.0 (`lucide-static`), fetched 2026-09-16 from
|
|
9
|
+
`https://cdn.jsdelivr.net/npm/lucide-static@1.46.0/icons/<name>.svg`, with the
|
|
10
|
+
root attributes normalised (no `xmlns`, `class`, `width` or `height`; the stroke
|
|
11
|
+
width set to 2.571 so a 14px rendering strokes at 1.5px). The drawings used:
|
|
12
|
+
arrow-down, arrow-left, arrow-right, arrow-up, check, chevron-down, chevron-left,
|
|
13
|
+
chevron-right, ellipsis, file-text, grip-vertical, layout-grid, lock, lock-open,
|
|
14
|
+
message-square, pencil, pin, play, plug, plus, rotate-ccw, rows-3, search,
|
|
15
|
+
settings, triangle-alert, waypoints, x. The three filled discs (`modified`,
|
|
16
|
+
`dot`, `record`) are this package's own circle elements.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
ISC License
|
|
20
|
+
|
|
21
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
22
|
+
|
|
23
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
24
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
25
|
+
copyright notice and this permission notice appear in all copies.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
28
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
29
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
30
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
31
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
32
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
33
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Of those, the following are derived from the Feather project and carry
|
|
37
|
+
Feather's licence as well: arrow-down, arrow-left, arrow-right, arrow-up,
|
|
38
|
+
check, chevron-down, chevron-left, chevron-right, circle, lock, plus, search, x.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
The MIT License (MIT)
|
|
42
|
+
|
|
43
|
+
Copyright (c) 2013-present Cole Bemis
|
|
44
|
+
|
|
45
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
46
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
47
|
+
in the Software without restriction, including without limitation the rights
|
|
48
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
49
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
50
|
+
furnished to do so, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
The above copyright notice and this permission notice shall be included in all
|
|
53
|
+
copies or substantial portions of the Software.
|
|
54
|
+
|
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
56
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
57
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
58
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
59
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
60
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
61
|
+
SOFTWARE.
|
|
62
|
+
```
|
package/README.md
CHANGED
|
@@ -145,6 +145,14 @@ palette, and version control's renderer half. Storylet Studio migrates each
|
|
|
145
145
|
first (proving app-agnosticism); Patterpad follows - see
|
|
146
146
|
`patterkit/design/from-storylets/patterpad-onto-the-shell.md` for that order.
|
|
147
147
|
|
|
148
|
+
## Third-party
|
|
149
|
+
|
|
150
|
+
The icon vocabulary (`iconNode`, `iconSvg` in `src/icons.ts`) vendors drawings
|
|
151
|
+
from [Lucide](https://lucide.dev) 1.46.0, ISC licence, Copyright (c) 2026 Lucide
|
|
152
|
+
Icons and Contributors; some of those derive from Feather, MIT, Copyright (c)
|
|
153
|
+
2013-present Cole Bemis. The notices are in `LICENSES.md` and at the head of
|
|
154
|
+
`src/icons.ts`. Everything else in this package is MIT (see `LICENSE`).
|
|
155
|
+
|
|
148
156
|
## Release
|
|
149
157
|
|
|
150
158
|
```bash
|
package/dist/about.css
CHANGED
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
/* src/about.css */
|
|
2
2
|
.shell-about {
|
|
3
|
-
width: min(420px, 90vw);
|
|
4
|
-
border: 1px solid var(--line);
|
|
5
|
-
border-radius: var(--radius-md);
|
|
6
|
-
background: var(--surface);
|
|
7
|
-
color: var(--ink);
|
|
8
|
-
padding: 22px 24px 16px;
|
|
9
|
-
box-shadow: var(--shadow-2, var(--shadow-1));
|
|
10
3
|
text-align: center;
|
|
11
4
|
}
|
|
12
|
-
.shell-about
|
|
13
|
-
|
|
5
|
+
.shell-about .shell-dialog-title {
|
|
6
|
+
font: 600 1.15rem/1.2 var(--font-read, var(--font-ui));
|
|
7
|
+
}
|
|
8
|
+
.shell-about .shell-dialog-actions {
|
|
9
|
+
justify-content: center;
|
|
10
|
+
margin-top: 10px;
|
|
14
11
|
}
|
|
15
12
|
.shell-about-mark {
|
|
16
|
-
margin-bottom:
|
|
13
|
+
margin-bottom: 2px;
|
|
17
14
|
}
|
|
18
15
|
.shell-about-mark svg {
|
|
19
16
|
max-width: 220px;
|
|
20
17
|
height: auto;
|
|
21
18
|
}
|
|
22
|
-
.shell-about-
|
|
23
|
-
|
|
19
|
+
.shell-about .shell-dialog-body {
|
|
20
|
+
gap: 2px;
|
|
24
21
|
}
|
|
25
22
|
.shell-about-version {
|
|
26
23
|
font: 0.78rem/1.4 var(--font-ui);
|
|
27
24
|
color: var(--muted);
|
|
28
|
-
margin
|
|
25
|
+
margin: 0;
|
|
29
26
|
}
|
|
30
27
|
.shell-about-blurb {
|
|
31
28
|
font: 0.85rem/1.5 var(--font-read, var(--font-ui));
|
|
32
|
-
margin:
|
|
29
|
+
margin: 6px 0 0;
|
|
33
30
|
}
|
|
34
31
|
.shell-about-credits {
|
|
35
32
|
font: 0.78rem/1.55 var(--font-ui);
|
|
36
33
|
color: var(--muted);
|
|
37
|
-
margin:
|
|
34
|
+
margin: 2px 0 0;
|
|
38
35
|
white-space: pre-line;
|
|
39
36
|
}
|
|
40
37
|
.shell-about-links {
|
|
41
38
|
display: flex;
|
|
42
39
|
gap: 14px;
|
|
43
40
|
justify-content: center;
|
|
44
|
-
margin-top:
|
|
41
|
+
margin-top: 6px;
|
|
45
42
|
}
|
|
46
43
|
.shell-about-link {
|
|
47
44
|
border: 0;
|
|
@@ -55,22 +52,4 @@
|
|
|
55
52
|
.shell-about-link:hover {
|
|
56
53
|
text-decoration: none;
|
|
57
54
|
}
|
|
58
|
-
.shell-about-actions {
|
|
59
|
-
display: flex;
|
|
60
|
-
justify-content: center;
|
|
61
|
-
margin-top: 18px;
|
|
62
|
-
}
|
|
63
|
-
.shell-about-close {
|
|
64
|
-
font: 0.8rem/1 var(--font-ui);
|
|
65
|
-
padding: 6px 18px;
|
|
66
|
-
border-radius: var(--radius-sm);
|
|
67
|
-
border: 1px solid var(--line);
|
|
68
|
-
background: var(--card);
|
|
69
|
-
color: var(--ink);
|
|
70
|
-
cursor: pointer;
|
|
71
|
-
}
|
|
72
|
-
.shell-about-close:hover {
|
|
73
|
-
border-color: var(--accent);
|
|
74
|
-
color: var(--accent);
|
|
75
|
-
}
|
|
76
55
|
/*# sourceMappingURL=about.css.map */
|
package/dist/about.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/about.css"],"sourcesContent":["/* The About surface (about.ts): the family's, so two apps' About boxes are\n recognisably siblings. Quiet, centred, one way out.
|
|
1
|
+
{"version":3,"sources":["../src/about.css"],"sourcesContent":["/* The About surface (about.ts): the family's, so two apps' About boxes are\n recognisably siblings. Quiet, centred, one way out. The panel, scrim and\n actions row are the dialog frame's (dialog.css); the Close button is\n controls.css's `.btn`. What is About's own: everything is centred, the\n name is set in the reading face, and the wordmark sits above it. */\n.shell-about { text-align: center; }\n.shell-about .shell-dialog-title { font: 600 1.15rem/1.2 var(--font-read, var(--font-ui)); }\n.shell-about .shell-dialog-actions { justify-content: center; margin-top: 10px; }\n.shell-about-mark { margin-bottom: 2px; }\n.shell-about-mark svg { max-width: 220px; height: auto; }\n/* The body is a scrolling column, so nothing in it may carry a negative margin: the version line\n would sit above the body's top edge and be clipped. It tucks under the title through the body's\n gap instead. */\n.shell-about .shell-dialog-body { gap: 2px; }\n.shell-about-version { font: 0.78rem/1.4 var(--font-ui); color: var(--muted); margin: 0; }\n.shell-about-blurb { font: 0.85rem/1.5 var(--font-read, var(--font-ui)); margin: 6px 0 0; }\n.shell-about-credits { font: 0.78rem/1.55 var(--font-ui); color: var(--muted); margin: 2px 0 0; white-space: pre-line; }\n.shell-about-links { display: flex; gap: 14px; justify-content: center; margin-top: 6px; }\n/* A link is underlined and coloured, never bare muted text on the ground. */\n.shell-about-link {\n border: 0; background: none; padding: 0; cursor: pointer;\n font: 0.78rem/1 var(--font-ui); color: var(--accent); text-decoration: underline;\n}\n.shell-about-link:hover { text-decoration: none; }\n"],"mappings":";AAKA,CAAC;AAAc,cAAY;AAAQ;AACnC,CADC,YACY,CAAC;AAAqB,QAAM,IAAI,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa;AAC3F,CAFC,YAEY,CAAC;AAAuB,mBAAiB;AAAQ,cAAY;AAAM;AAChF,CAAC;AAAmB,iBAAe;AAAK;AACxC,CADC,iBACiB;AAAM,aAAW;AAAO,UAAQ;AAAM;AAIxD,CARC,YAQY,CAAC;AAAoB,OAAK;AAAK;AAC5C,CAAC;AAAsB,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU,UAAQ;AAAG;AACzF,CAAC;AAAoB,QAAM,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,IAAI;AAAa,UAAQ,IAAI,EAAE;AAAG;AAC1F,CAAC;AAAsB,QAAM,OAAO,CAAC,KAAK,IAAI;AAAY,SAAO,IAAI;AAAU,UAAQ,IAAI,EAAE;AAAG,eAAa;AAAU;AACvH,CAAC;AAAoB,WAAS;AAAM,OAAK;AAAM,mBAAiB;AAAQ,cAAY;AAAK;AAEzF,CAAC;AACC,UAAQ;AAAG,cAAY;AAAM,WAAS;AAAG,UAAQ;AACjD,QAAM,OAAO,CAAC,EAAE,IAAI;AAAY,SAAO,IAAI;AAAW,mBAAiB;AACzE;AACA,CAJC,gBAIgB;AAAS,mBAAiB;AAAM;","names":[]}
|
package/dist/anchored.css
CHANGED
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
border-bottom: 1px solid var(--line-soft);
|
|
27
27
|
}
|
|
28
28
|
.shell-anchored-title {
|
|
29
|
-
font: 600 0.
|
|
30
|
-
text-transform: uppercase;
|
|
31
|
-
letter-spacing: 0.04em;
|
|
29
|
+
font: 600 0.8rem/1.2 var(--font-ui);
|
|
32
30
|
color: var(--muted);
|
|
33
31
|
}
|
|
34
32
|
.shell-anchored-close {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
35
36
|
border: 0;
|
|
36
37
|
background: none;
|
|
37
38
|
color: var(--muted);
|
|
38
|
-
font-size: 0.8rem;
|
|
39
39
|
line-height: 1;
|
|
40
|
-
padding:
|
|
40
|
+
padding: 2px;
|
|
41
41
|
cursor: pointer;
|
|
42
42
|
}
|
|
43
43
|
.shell-anchored-close:hover {
|
package/dist/anchored.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/anchored.css"],"sourcesContent":["/* The anchored panel (anchored.ts): a floating panel hung off the thing it is\n about. Sized and positioned in script (the placement has to measure); this is\n only what it looks like. Its body scrolls, because the placement caps the\n height rather than letting a long thread run off the screen. */\n.shell-anchored {\n position: fixed; z-index: 40;\n display: flex; flex-direction: column;\n border: 1px solid var(--line);\n border-radius: var(--radius-md);\n background: var(--surface); color: var(--ink);\n box-shadow: var(--shadow-2, var(--shadow-1));\n overflow: hidden;\n /* The family's panel motion. Placement is set in script as left/top, so a\n transform-based keyframe composes with it rather than fighting it. */\n animation: var(--anim-panel-in);\n}\n/* Exit: .closing swaps the enter keyframe for its reverse, and the fading panel\n stops taking clicks so a second press cannot reach a control on its way out.\n Zero-duration under prefers-reduced-motion, where teardown is immediate. */\n.shell-anchored.closing { animation: var(--anim-panel-out); pointer-events: none; }\n.shell-anchored-head {\n flex: none;\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--line-soft);\n}\n.shell-anchored-title {\n font: 600 0.
|
|
1
|
+
{"version":3,"sources":["../src/anchored.css"],"sourcesContent":["/* The anchored panel (anchored.ts): a floating panel hung off the thing it is\n about. Sized and positioned in script (the placement has to measure); this is\n only what it looks like. Its body scrolls, because the placement caps the\n height rather than letting a long thread run off the screen. */\n.shell-anchored {\n position: fixed; z-index: 40;\n display: flex; flex-direction: column;\n border: 1px solid var(--line);\n border-radius: var(--radius-md);\n background: var(--surface); color: var(--ink);\n box-shadow: var(--shadow-2, var(--shadow-1));\n overflow: hidden;\n /* The family's panel motion. Placement is set in script as left/top, so a\n transform-based keyframe composes with it rather than fighting it. */\n animation: var(--anim-panel-in);\n}\n/* Exit: .closing swaps the enter keyframe for its reverse, and the fading panel\n stops taking clicks so a second press cannot reach a control on its way out.\n Zero-duration under prefers-reduced-motion, where teardown is immediate. */\n.shell-anchored.closing { animation: var(--anim-panel-out); pointer-events: none; }\n.shell-anchored-head {\n flex: none;\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--line-soft);\n}\n.shell-anchored-title {\n font: 600 0.8rem/1.2 var(--font-ui);\n color: var(--muted);\n}\n.shell-anchored-close {\n display: inline-flex; align-items: center; justify-content: center;\n border: 0; background: none; color: var(--muted);\n line-height: 1; padding: 2px; cursor: pointer;\n}\n.shell-anchored-close:hover { color: var(--accent); }\n.shell-anchored-body {\n flex: 1; min-height: 0; overflow-y: auto;\n display: flex; flex-direction: column; gap: 8px;\n padding: 10px;\n}\n\n/* The gameId editor (id-editor.ts). Refusal is the whole point of the design, so\n it has to LOOK refused: the hint reddens and Set goes flat. */\n.shell-id-editor .shell-anchored-body { display: flex; flex-direction: column; gap: 8px; }\n.shell-id-input {\n font: 0.82rem var(--font-mono, monospace);\n padding: 5px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);\n background: var(--card); color: var(--ink);\n}\n.shell-id-hint { font: 0.74rem/1.4 var(--font-ui); color: var(--muted); }\n.shell-id-hint.bad { color: var(--danger, #b00); }\n.shell-id-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n.shell-id-reset {\n font: 0.74rem var(--font-ui); padding: 3px 10px; cursor: pointer;\n background: none; border: 1px dashed var(--line); border-radius: var(--radius-sm); color: var(--muted);\n}\n.shell-id-reset:hover { border-color: var(--accent); color: var(--accent); }\n.shell-id-set {\n font: 0.78rem var(--font-ui); padding: 3px 12px; cursor: pointer;\n border: 1px solid var(--accent); border-radius: var(--radius-sm);\n background: var(--accent); color: var(--surface);\n}\n.shell-id-set:disabled {\n cursor: default; background: none; color: var(--muted); border-color: var(--line);\n}\n"],"mappings":";AAIA,CAAC;AACC,YAAU;AAAO,WAAS;AAC1B,WAAS;AAAM,kBAAgB;AAC/B,UAAQ,IAAI,MAAM,IAAI;AACtB,iBAAe,IAAI;AACnB,cAAY,IAAI;AAAY,SAAO,IAAI;AACvC,cAAY,IAAI,UAAU,EAAE,IAAI;AAChC,YAAU;AAGV,aAAW,IAAI;AACjB;AAIA,CAfC,cAec,CAAC;AAAU,aAAW,IAAI;AAAmB,kBAAgB;AAAM;AAClF,CAAC;AACC,QAAM;AACN,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AACzE,WAAS,IAAI;AACb,iBAAe,IAAI,MAAM,IAAI;AAC/B;AACA,CAAC;AACC,QAAM,IAAI,MAAM,CAAC,IAAI,IAAI;AACzB,SAAO,IAAI;AACb;AACA,CAAC;AACC,WAAS;AAAa,eAAa;AAAQ,mBAAiB;AAC5D,UAAQ;AAAG,cAAY;AAAM,SAAO,IAAI;AACxC,eAAa;AAAG,WAAS;AAAK,UAAQ;AACxC;AACA,CALC,oBAKoB;AAAS,SAAO,IAAI;AAAW;AACpD,CAAC;AACC,QAAM;AAAG,cAAY;AAAG,cAAY;AACpC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAC5C,WAAS;AACX;AAIA,CAAC,gBAAgB,CARhB;AAQuC,WAAS;AAAM,kBAAgB;AAAQ,OAAK;AAAK;AACzF,CAAC;AACC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAS,IAAI;AAAK,UAAQ,IAAI,MAAM,IAAI;AAAS,iBAAe,IAAI;AACpE,cAAY,IAAI;AAAS,SAAO,IAAI;AACtC;AACA,CAAC;AAAgB,QAAM,OAAO,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAAU;AACxE,CADC,aACa,CAAC;AAAM,SAAO,IAAI,QAAQ,EAAE;AAAO;AACjD,CAAC;AAAmB,WAAS;AAAM,eAAa;AAAQ,mBAAiB;AAAe,OAAK;AAAK;AAClG,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,cAAY;AAAM,UAAQ,IAAI,OAAO,IAAI;AAAS,iBAAe,IAAI;AAAc,SAAO,IAAI;AAChG;AACA,CAJC,cAIc;AAAS,gBAAc,IAAI;AAAW,SAAO,IAAI;AAAW;AAC3E,CAAC;AACC,QAAM,QAAQ,IAAI;AAAY,WAAS,IAAI;AAAM,UAAQ;AACzD,UAAQ,IAAI,MAAM,IAAI;AAAW,iBAAe,IAAI;AACpD,cAAY,IAAI;AAAW,SAAO,IAAI;AACxC;AACA,CALC,YAKY;AACX,UAAQ;AAAS,cAAY;AAAM,SAAO,IAAI;AAAU,gBAAc,IAAI;AAC5E;","names":[]}
|
package/dist/app-store.cjs
CHANGED
|
@@ -100,7 +100,22 @@ function createAppStore(opts) {
|
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
function windowSlice(store, name, defaults = {}) {
|
|
104
|
+
const pinnedDefault = defaults.pinned ?? true;
|
|
105
|
+
return {
|
|
106
|
+
bounds: () => store.get().windows[name]?.bounds,
|
|
107
|
+
remember: (bounds) => store.setWindow(name, { bounds }),
|
|
108
|
+
pinned: () => store.get().windows[name]?.pinned ?? pinnedDefault,
|
|
109
|
+
setPinned: (on) => store.setWindow(name, { pinned: on })
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function resetWindows(store, names = []) {
|
|
113
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(store.get().windows), ...names]);
|
|
114
|
+
for (const key of keys) store.setWindow(key, { bounds: void 0, pinned: true });
|
|
115
|
+
}
|
|
103
116
|
|
|
104
117
|
exports.createAppStore = createAppStore;
|
|
118
|
+
exports.resetWindows = resetWindows;
|
|
119
|
+
exports.windowSlice = windowSlice;
|
|
105
120
|
//# sourceMappingURL=app-store.cjs.map
|
|
106
121
|
//# sourceMappingURL=app-store.cjs.map
|
package/dist/app-store.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app-store.ts"],"names":["join","readFileSync","mkdirSync","writeFileSync","renameSync"],"mappings":";;;;;;AAgIO,SAAS,eACd,IAAA,EACsB;AACtB,EAAA,MAAM,OAAOA,SAAA,CAAK,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,YAAY,mBAAmB,CAAA;AAChE,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,IAAc,CAAA;AAEtC,EAAA,IAAI,SAA2C,EAAC;AAChD,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,KAAA,CAAMC,eAAA,CAAa,IAAA,EAAM,MAAM,CAAC,CAAA;AAAA,EAChD,CAAA,CAAA,MAAQ;AAAA,EAGR;AAEA,EAAA,MAAM,KAAA,GAAiC;AAAA,IACrC,KAAA,EAAO,IAAA,CAAK,KAAA,IAAS,EAAC;AAAA,IACtB,SAAS,EAAC;AAAA,IACV,GAAG,MAAA;AAAA;AAAA;AAAA,IAGH,OAAA,EAAA,CAAU,MAAA,CAAO,OAAA,IAAW,IAAI,GAAA,CAAI,CAAC,CAAA,KACnC,OAAO,MAAM,QAAA,GAAW,EAAE,IAAA,EAAM,CAAA,KAAM,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUzC,MAAA,EAAQ;AAAA,MACN,GAAI,MAAA,CAAO,WAAA,KAAgB,MAAA,IAAc,OAA6B,KAAA,KAAU,MAAA,GAC5E,EAAE,CAAC,OAAO,WAAW,GAAI,MAAA,CAA6B,KAAA,KACtD,EAAC;AAAA,MACL,GAAI,MAAA,CAAO,MAAA,IAAU;AAAC,KACxB;AAAA;AAAA;AAAA;AAAA,IAIA,GAAA,EAAK,EAAE,GAAG,IAAA,CAAK,UAAU,GAAI,MAAA,CAAO,GAAA,IAAO,EAAC;AAAG,GACjD;AAGA,EAAA,OAAQ,KAAA,CAA4B,KAAA;AAEpC,EAAA,MAAM,OAAO,MAAY;AACvB,IAAA,IAAI;AACF,MAAAC,YAAA,CAAU,IAAA,CAAK,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AAIvC,MAAA,MAAM,IAAA,GAAO,GAAG,IAAI,CAAA,IAAA,CAAA;AACpB,MAAAC,gBAAA,CAAc,MAAM,CAAA,EAAG,IAAA,CAAK,UAAU,KAAA,EAAO,IAAA,EAAM,CAAC,CAAC;AAAA,CAAA,EAAM,MAAM,CAAA;AACjE,MAAAC,aAAA,CAAW,MAAM,IAAI,CAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AAAA,IAGR;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,MAAM,eAAA,CAAgB,KAAK,CAAA;AAAA,IAChC,SAAS,KAAA,EAAO;AACd,MAAA,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,GAAA,EAAK,KAAK,CAAA;AAC9B,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,YAAA,CAAa,MAAM,IAAA,EAAM;AACvB,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAA;AAGvD,MAAA,MAAM,KAAA,GAAuB,EAAE,IAAA,EAAM,GAAI,QAAQ,KAAA,EAAO,IAAA,GAAO,EAAE,IAAA,EAAM,IAAA,IAAQ,KAAA,EAAO,IAAA,EAAK,GAAI,EAAC,EAAG;AACnG,MAAA,KAAA,CAAM,UAAU,CAAC,KAAA,EAAO,GAAG,KAAA,CAAM,QAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,UAAU,CAAA;AAC5F,MAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AAIpB,MAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAAG;AAC3C,QAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,GAAG,CAAA,EAAG,OAAO,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACzE;AACA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,cAAc,IAAA,EAAM;AAClB,MAAA,KAAA,CAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAA;AAC3D,MAAA,IAAI,KAAA,CAAM,WAAA,KAAgB,IAAA,EAAM,OAAO,KAAA,CAAM,WAAA;AAC7C,MAAA,OAAO,KAAA,CAAM,OAAO,IAAI,CAAA;AACxB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,gBAAA,GAAmB;AACjB,MAAA,IAAI,KAAA,CAAM,gBAAgB,MAAA,EAAW;AACrC,MAAA,OAAO,KAAA,CAAM,WAAA;AACb,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,OAAO,KAAA,CAAM,WAAA;AACnB,MAAA,IAAI,SAAS,MAAA,EAAW;AACxB,MAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAClE,MAAA,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,GAAI,KAAA;AACrB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,OAAA,EAAS,CAAC,IAAA,KAAS;AACjB,MAAA,MAAM,GAAA,GAAM,QAAQ,KAAA,CAAM,WAAA;AAC1B,MAAA,OAAO,QAAQ,MAAA,GAAY,MAAA,GAAY,gBAAgB,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IAC1E,CAAA;AAAA,IACA,SAAS,KAAA,EAAO;AACd,MAAA,KAAA,CAAM,QAAQ,EAAE,GAAG,KAAA,CAAM,KAAA,EAAO,GAAG,KAAA,EAAM;AACzC,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,YAAY,QAAA,EAAU;AACpB,MAAA,KAAA,CAAM,QAAA,GAAW,QAAA;AACjB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,SAAA,CAAU,KAAK,IAAA,EAAM;AACnB,MAAA,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,EAAE,GAAG,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,GAAG,IAAA,EAAK;AACtD,MAAA,IAAA,EAAK;AAAA,IACP;AAAA,GACF;AACF","file":"app-store.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// The user's settings for THIS app: a small JSON file in the app's userData.\n//\n// Every app in the family has one, and both of the two that exist wrote it\n// separately: the same tolerant read, the same merged defaults, the same capped\n// recents, the same panes, the same per-tool-window bounds and pin, the same\n// identity. That is the definition of shell work.\n//\n// WHAT BELONGS HERE is settings that follow the PERSON, not the project: where\n// their windows were, which panes they like open, what they are called, what\n// they had open last. Project content never comes near this file - it is not\n// version controlled, it is not shared, and losing it should cost an author\n// nothing but a re-arranged window.\n//\n// The shell owns the CORE it can reason about and hands the app one slice of\n// its own (`app`) for everything else - a theme value, a view mode, whatever it\n// has. Two stores in one file, one atomic write, so a crash cannot leave an app\n// with half its settings.\n//\n// Electron-free on purpose: it takes a directory. That keeps it unit-testable\n// and lets a test point it at a temp dir.\n// ---------------------------------------------------------------------------\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Who is at the keyboard. Never in a project: see identity.ts. */\nexport interface Identity { name: string; email?: string }\n\n/** A remembered window rectangle. Absent = the app's default, centred. */\nexport interface WindowBounds { x?: number; y?: number; width: number; height: number }\n\n/** One helper window's remembered state. */\nexport interface WindowState { bounds?: WindowBounds; pinned?: boolean }\n\n/** Side panes: open/closed, and any dragged width. */\nexport interface PaneState {\n nav?: boolean;\n inspector?: boolean;\n navW?: number;\n inspW?: number;\n}\n\n/**\n * The part every app has.\n *\n * `places` is deliberately opaque: \"open where you left off\" is a family idea,\n * but WHERE is a scene and a caret in one app and a box, a tab and a card in\n * another.\n *\n * It is keyed BY PROJECT, and that is the interesting part. This held a single\n * `place` and dropped it whenever a different project was opened, on the\n * reasoning that clearing it was the one rule that could not be app-specific.\n * That reasoning was wrong, and Patterpad disproved it by having done the other\n * thing for a long time: it remembers the line you were on in every project you\n * have opened. With one slot, working on A, glancing at B and coming back to A\n * lands you at the top of A, having forgotten. Nobody wants that, and no editor\n * outside this package does it.\n */\n/**\n * A project in the Open Recent menu.\n *\n * The NAME is here rather than being derived from the path, because a project's\n * display name is what its file says it is, not what its folder is called: two\n * projects can sit in folders called `draft`, and one can be renamed without\n * moving. Every app in the family shows this menu, so both halves are shared.\n *\n * Optional because the path is the identity and the name is only how it reads.\n * An entry recorded before a name was known still works.\n */\nexport interface RecentProject {\n path: string;\n name?: string;\n}\n\nexport interface AppSettingsCore<Place> {\n recents: RecentProject[];\n lastProject?: string;\n /** Project path -> where the author was in it. Capped with `recents`, so a\n * project forgotten from the list stops carrying a place around too. */\n places: Record<string, Place>;\n panes: PaneState;\n identity?: Identity;\n /** Keyed by whatever the app calls each window (\"board\", \"search\"). */\n windows: Record<string, WindowState>;\n}\n\nexport type AppSettings<Place, App> = AppSettingsCore<Place> & { app: App };\n\nexport interface AppStoreOptions<Place, App> {\n /** The app's userData directory. */\n dir: string;\n /** The file inside it. One per app; two apps never share. */\n fileName?: string;\n /** The app's own slice, as it should read on a first run. */\n defaults: App;\n /** How the panes should sit before anybody touches them. */\n panes?: PaneState;\n maxRecents?: number;\n}\n\nexport interface AppStore<Place, App> {\n /** A copy, always: nothing outside can mutate the state behind the store's back. */\n get(): AppSettings<Place, App>;\n /** Merge into the app's own slice and save. */\n patchApp(patch: Partial<App>): void;\n /** Record an opened project: front of recents, deduped, capped. The name is\n * refreshed each time, so renaming a project updates the menu on next open;\n * omitting it keeps whatever was known before rather than blanking it. */\n touchProject(path: string, name?: string): void;\n /** Drop one that failed to open (moved, deleted). */\n forgetProject(path: string): void;\n /** Forget WHICH project was open, keeping it in recents: Close Project's\n * half of the memory, so the next boot lands on the welcome screen. */\n clearLastProject(): void;\n /** Where the author was in the CURRENT project. Compared before writing: this\n * is called on every navigation and must not mean a disk write per click.\n * A no-op with no project open, since there is nothing to key it against. */\n setPlace(place: Place): void;\n /** Where the author was in a project, or undefined if it was never opened or\n * has aged out of `recents`. Defaults to the current one. */\n placeOf(path?: string): Place | undefined;\n setPanes(panes: PaneState): void;\n setIdentity(identity: Identity): void;\n /** One window's bounds and/or pin, merged with what it already had. */\n setWindow(key: string, state: WindowState): void;\n}\n\nexport function createAppStore<Place, App extends object>(\n opts: AppStoreOptions<Place, App>,\n): AppStore<Place, App> {\n const file = join(opts.dir, opts.fileName ?? \"app-settings.json\");\n const maxRecents = opts.maxRecents ?? 8;\n\n let loaded: Partial<AppSettings<Place, App>> = {};\n try {\n loaded = JSON.parse(readFileSync(file, \"utf8\")) as Partial<AppSettings<Place, App>>;\n } catch {\n // First run, or a file we cannot read. Defaults, never a crash: settings\n // are not worth failing to start over.\n }\n\n const state: AppSettings<Place, App> = {\n panes: opts.panes ?? {},\n windows: {},\n ...loaded,\n // Migration: recents used to be bare paths. Keep them, without names, rather\n // than emptying somebody's Open Recent menu on an update.\n recents: (loaded.recents ?? []).map((r) =>\n typeof r === \"string\" ? { path: r } : r) as RecentProject[],\n // Migration: a settings file written before places were keyed by project has\n // a single `place` belonging to `lastProject`. Carry it over rather than\n // dropping it, or everyone's first launch after this update forgets where\n // they were - but only where no keyed entry exists. The first cut of this\n // merge put the legacy value LAST, so a stale singular beat every fresh\n // per-project write on every launch, and (because the `...loaded` spread\n // above kept the key alive through save) it did so for ever: writes landed\n // correctly all session and the next start quietly reset them. Found from\n // a host's user report; the key is retired just below.\n places: {\n ...(loaded.lastProject !== undefined && (loaded as { place?: Place }).place !== undefined\n ? { [loaded.lastProject]: (loaded as { place?: Place }).place as Place }\n : {}),\n ...(loaded.places ?? {}),\n },\n // The app's slice merges FIELD BY FIELD, so a key added in a new version\n // arrives with its default rather than being absent for everybody who has\n // run the app before.\n app: { ...opts.defaults, ...(loaded.app ?? {}) },\n };\n // Retire the legacy key: it rode in on `...loaded`, and a state that still\n // carries it re-runs the fold on every future load.\n delete (state as { place?: Place }).place;\n\n const save = (): void => {\n try {\n mkdirSync(opts.dir, { recursive: true });\n // Write beside, then rename: a rename is atomic on every platform this\n // ships to, so a crash mid-write cannot leave a half-written settings\n // file - which both apps' hand-written stores could.\n const temp = `${file}.tmp`;\n writeFileSync(temp, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n renameSync(temp, file);\n } catch {\n // A read-only home directory, a full disk: the app carries on with the\n // settings it has in memory rather than dying over a preference.\n }\n };\n\n return {\n get: () => structuredClone(state),\n patchApp(patch) {\n Object.assign(state.app, patch);\n save();\n },\n touchProject(path, name) {\n const known = state.recents.find((r) => r.path === path);\n // An omitted name keeps the one we had: reopening from a route that does\n // not know the name must not blank the menu entry.\n const entry: RecentProject = { path, ...(name ?? known?.name ? { name: name ?? known?.name } : {}) };\n state.recents = [entry, ...state.recents.filter((r) => r.path !== path)].slice(0, maxRecents);\n state.lastProject = path;\n // Places follow recents: one that has aged off the list should not keep a\n // place in the file for ever. Nothing is dropped for merely opening\n // something else, which is the whole point of keying them.\n for (const key of Object.keys(state.places)) {\n if (!state.recents.some((r) => r.path === key)) delete state.places[key];\n }\n save();\n },\n forgetProject(path) {\n state.recents = state.recents.filter((r) => r.path !== path);\n if (state.lastProject === path) delete state.lastProject;\n delete state.places[path]; // it moved or went: its place is meaningless now\n save();\n },\n clearLastProject() {\n if (state.lastProject === undefined) return;\n delete state.lastProject;\n save();\n },\n setPlace(place) {\n const path = state.lastProject;\n if (path === undefined) return; // no project open: nothing to key it against\n if (JSON.stringify(state.places[path]) === JSON.stringify(place)) return;\n state.places[path] = place;\n save();\n },\n placeOf: (path) => {\n const key = path ?? state.lastProject;\n return key === undefined ? undefined : structuredClone(state.places[key]);\n },\n setPanes(panes) {\n state.panes = { ...state.panes, ...panes };\n save();\n },\n setIdentity(identity) {\n state.identity = identity;\n save();\n },\n setWindow(key, next) {\n state.windows[key] = { ...state.windows[key], ...next };\n save();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/app-store.ts"],"names":["join","readFileSync","mkdirSync","writeFileSync","renameSync"],"mappings":";;;;;;AAgIO,SAAS,eACd,IAAA,EACsB;AACtB,EAAA,MAAM,OAAOA,SAAA,CAAK,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,YAAY,mBAAmB,CAAA;AAChE,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,IAAc,CAAA;AAEtC,EAAA,IAAI,SAA2C,EAAC;AAChD,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,KAAA,CAAMC,eAAA,CAAa,IAAA,EAAM,MAAM,CAAC,CAAA;AAAA,EAChD,CAAA,CAAA,MAAQ;AAAA,EAGR;AAEA,EAAA,MAAM,KAAA,GAAiC;AAAA,IACrC,KAAA,EAAO,IAAA,CAAK,KAAA,IAAS,EAAC;AAAA,IACtB,SAAS,EAAC;AAAA,IACV,GAAG,MAAA;AAAA;AAAA;AAAA,IAGH,OAAA,EAAA,CAAU,MAAA,CAAO,OAAA,IAAW,IAAI,GAAA,CAAI,CAAC,CAAA,KACnC,OAAO,MAAM,QAAA,GAAW,EAAE,IAAA,EAAM,CAAA,KAAM,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUzC,MAAA,EAAQ;AAAA,MACN,GAAI,MAAA,CAAO,WAAA,KAAgB,MAAA,IAAc,OAA6B,KAAA,KAAU,MAAA,GAC5E,EAAE,CAAC,OAAO,WAAW,GAAI,MAAA,CAA6B,KAAA,KACtD,EAAC;AAAA,MACL,GAAI,MAAA,CAAO,MAAA,IAAU;AAAC,KACxB;AAAA;AAAA;AAAA;AAAA,IAIA,GAAA,EAAK,EAAE,GAAG,IAAA,CAAK,UAAU,GAAI,MAAA,CAAO,GAAA,IAAO,EAAC;AAAG,GACjD;AAGA,EAAA,OAAQ,KAAA,CAA4B,KAAA;AAEpC,EAAA,MAAM,OAAO,MAAY;AACvB,IAAA,IAAI;AACF,MAAAC,YAAA,CAAU,IAAA,CAAK,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AAIvC,MAAA,MAAM,IAAA,GAAO,GAAG,IAAI,CAAA,IAAA,CAAA;AACpB,MAAAC,gBAAA,CAAc,MAAM,CAAA,EAAG,IAAA,CAAK,UAAU,KAAA,EAAO,IAAA,EAAM,CAAC,CAAC;AAAA,CAAA,EAAM,MAAM,CAAA;AACjE,MAAAC,aAAA,CAAW,MAAM,IAAI,CAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AAAA,IAGR;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,MAAM,eAAA,CAAgB,KAAK,CAAA;AAAA,IAChC,SAAS,KAAA,EAAO;AACd,MAAA,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,GAAA,EAAK,KAAK,CAAA;AAC9B,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,YAAA,CAAa,MAAM,IAAA,EAAM;AACvB,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAA;AAGvD,MAAA,MAAM,KAAA,GAAuB,EAAE,IAAA,EAAM,GAAI,QAAQ,KAAA,EAAO,IAAA,GAAO,EAAE,IAAA,EAAM,IAAA,IAAQ,KAAA,EAAO,IAAA,EAAK,GAAI,EAAC,EAAG;AACnG,MAAA,KAAA,CAAM,UAAU,CAAC,KAAA,EAAO,GAAG,KAAA,CAAM,QAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,UAAU,CAAA;AAC5F,MAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AAIpB,MAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAAG;AAC3C,QAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,GAAG,CAAA,EAAG,OAAO,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACzE;AACA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,cAAc,IAAA,EAAM;AAClB,MAAA,KAAA,CAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,IAAI,CAAA;AAC3D,MAAA,IAAI,KAAA,CAAM,WAAA,KAAgB,IAAA,EAAM,OAAO,KAAA,CAAM,WAAA;AAC7C,MAAA,OAAO,KAAA,CAAM,OAAO,IAAI,CAAA;AACxB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,gBAAA,GAAmB;AACjB,MAAA,IAAI,KAAA,CAAM,gBAAgB,MAAA,EAAW;AACrC,MAAA,OAAO,KAAA,CAAM,WAAA;AACb,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,OAAO,KAAA,CAAM,WAAA;AACnB,MAAA,IAAI,SAAS,MAAA,EAAW;AACxB,MAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAClE,MAAA,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,GAAI,KAAA;AACrB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,OAAA,EAAS,CAAC,IAAA,KAAS;AACjB,MAAA,MAAM,GAAA,GAAM,QAAQ,KAAA,CAAM,WAAA;AAC1B,MAAA,OAAO,QAAQ,MAAA,GAAY,MAAA,GAAY,gBAAgB,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IAC1E,CAAA;AAAA,IACA,SAAS,KAAA,EAAO;AACd,MAAA,KAAA,CAAM,QAAQ,EAAE,GAAG,KAAA,CAAM,KAAA,EAAO,GAAG,KAAA,EAAM;AACzC,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,YAAY,QAAA,EAAU;AACpB,MAAA,KAAA,CAAM,QAAA,GAAW,QAAA;AACjB,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,SAAA,CAAU,KAAK,IAAA,EAAM;AACnB,MAAA,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,EAAE,GAAG,MAAM,OAAA,CAAQ,GAAG,CAAA,EAAG,GAAG,IAAA,EAAK;AACtD,MAAA,IAAA,EAAK;AAAA,IACP;AAAA,GACF;AACF;AAqBO,SAAS,WAAA,CAAwB,KAAA,EAA6B,IAAA,EAAc,QAAA,GAAiC,EAAC,EAAgB;AACnI,EAAA,MAAM,aAAA,GAAgB,SAAS,MAAA,IAAU,IAAA;AACzC,EAAA,OAAO;AAAA,IACL,QAAQ,MAAM,KAAA,CAAM,KAAI,CAAE,OAAA,CAAQ,IAAI,CAAA,EAAG,MAAA;AAAA,IACzC,QAAA,EAAU,CAAC,MAAA,KAAW,KAAA,CAAM,UAAU,IAAA,EAAM,EAAE,QAAQ,CAAA;AAAA,IACtD,MAAA,EAAQ,MAAM,KAAA,CAAM,GAAA,GAAM,OAAA,CAAQ,IAAI,GAAG,MAAA,IAAU,aAAA;AAAA,IACnD,SAAA,EAAW,CAAC,EAAA,KAAO,KAAA,CAAM,UAAU,IAAA,EAAM,EAAE,MAAA,EAAQ,EAAA,EAAI;AAAA,GACzD;AACF;AAMO,SAAS,YAAA,CAAyB,KAAA,EAA6B,KAAA,GAAkB,EAAC,EAAS;AAChG,EAAA,MAAM,IAAA,mBAAO,IAAI,GAAA,CAAI,CAAC,GAAG,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,GAAA,EAAI,CAAE,OAAO,CAAA,EAAG,GAAG,KAAK,CAAC,CAAA;AACpE,EAAA,KAAA,MAAW,GAAA,IAAO,IAAA,EAAM,KAAA,CAAM,SAAA,CAAU,GAAA,EAAK,EAAE,MAAA,EAAQ,MAAA,EAAW,MAAA,EAAQ,IAAA,EAAM,CAAA;AAClF","file":"app-store.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// The user's settings for THIS app: a small JSON file in the app's userData.\n//\n// Every app in the family has one, and both of the two that exist wrote it\n// separately: the same tolerant read, the same merged defaults, the same capped\n// recents, the same panes, the same per-tool-window bounds and pin, the same\n// identity. That is the definition of shell work.\n//\n// WHAT BELONGS HERE is settings that follow the PERSON, not the project: where\n// their windows were, which panes they like open, what they are called, what\n// they had open last. Project content never comes near this file - it is not\n// version controlled, it is not shared, and losing it should cost an author\n// nothing but a re-arranged window.\n//\n// The shell owns the CORE it can reason about and hands the app one slice of\n// its own (`app`) for everything else - a theme value, a view mode, whatever it\n// has. Two stores in one file, one atomic write, so a crash cannot leave an app\n// with half its settings.\n//\n// Electron-free on purpose: it takes a directory. That keeps it unit-testable\n// and lets a test point it at a temp dir.\n// ---------------------------------------------------------------------------\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Who is at the keyboard. Never in a project: see identity.ts. */\nexport interface Identity { name: string; email?: string }\n\n/** A remembered window rectangle. Absent = the app's default, centred. */\nexport interface WindowBounds { x?: number; y?: number; width: number; height: number }\n\n/** One helper window's remembered state. */\nexport interface WindowState { bounds?: WindowBounds; pinned?: boolean }\n\n/** Side panes: open/closed, and any dragged width. */\nexport interface PaneState {\n nav?: boolean;\n inspector?: boolean;\n navW?: number;\n inspW?: number;\n}\n\n/**\n * The part every app has.\n *\n * `places` is deliberately opaque: \"open where you left off\" is a family idea,\n * but WHERE is a scene and a caret in one app and a box, a tab and a card in\n * another.\n *\n * It is keyed BY PROJECT, and that is the interesting part. This held a single\n * `place` and dropped it whenever a different project was opened, on the\n * reasoning that clearing it was the one rule that could not be app-specific.\n * That reasoning was wrong, and Patterpad disproved it by having done the other\n * thing for a long time: it remembers the line you were on in every project you\n * have opened. With one slot, working on A, glancing at B and coming back to A\n * lands you at the top of A, having forgotten. Nobody wants that, and no editor\n * outside this package does it.\n */\n/**\n * A project in the Open Recent menu.\n *\n * The NAME is here rather than being derived from the path, because a project's\n * display name is what its file says it is, not what its folder is called: two\n * projects can sit in folders called `draft`, and one can be renamed without\n * moving. Every app in the family shows this menu, so both halves are shared.\n *\n * Optional because the path is the identity and the name is only how it reads.\n * An entry recorded before a name was known still works.\n */\nexport interface RecentProject {\n path: string;\n name?: string;\n}\n\nexport interface AppSettingsCore<Place> {\n recents: RecentProject[];\n lastProject?: string;\n /** Project path -> where the author was in it. Capped with `recents`, so a\n * project forgotten from the list stops carrying a place around too. */\n places: Record<string, Place>;\n panes: PaneState;\n identity?: Identity;\n /** Keyed by whatever the app calls each window (\"board\", \"search\"). */\n windows: Record<string, WindowState>;\n}\n\nexport type AppSettings<Place, App> = AppSettingsCore<Place> & { app: App };\n\nexport interface AppStoreOptions<Place, App> {\n /** The app's userData directory. */\n dir: string;\n /** The file inside it. One per app; two apps never share. */\n fileName?: string;\n /** The app's own slice, as it should read on a first run. */\n defaults: App;\n /** How the panes should sit before anybody touches them. */\n panes?: PaneState;\n maxRecents?: number;\n}\n\nexport interface AppStore<Place, App> {\n /** A copy, always: nothing outside can mutate the state behind the store's back. */\n get(): AppSettings<Place, App>;\n /** Merge into the app's own slice and save. */\n patchApp(patch: Partial<App>): void;\n /** Record an opened project: front of recents, deduped, capped. The name is\n * refreshed each time, so renaming a project updates the menu on next open;\n * omitting it keeps whatever was known before rather than blanking it. */\n touchProject(path: string, name?: string): void;\n /** Drop one that failed to open (moved, deleted). */\n forgetProject(path: string): void;\n /** Forget WHICH project was open, keeping it in recents: Close Project's\n * half of the memory, so the next boot lands on the welcome screen. */\n clearLastProject(): void;\n /** Where the author was in the CURRENT project. Compared before writing: this\n * is called on every navigation and must not mean a disk write per click.\n * A no-op with no project open, since there is nothing to key it against. */\n setPlace(place: Place): void;\n /** Where the author was in a project, or undefined if it was never opened or\n * has aged out of `recents`. Defaults to the current one. */\n placeOf(path?: string): Place | undefined;\n setPanes(panes: PaneState): void;\n setIdentity(identity: Identity): void;\n /** One window's bounds and/or pin, merged with what it already had. */\n setWindow(key: string, state: WindowState): void;\n}\n\nexport function createAppStore<Place, App extends object>(\n opts: AppStoreOptions<Place, App>,\n): AppStore<Place, App> {\n const file = join(opts.dir, opts.fileName ?? \"app-settings.json\");\n const maxRecents = opts.maxRecents ?? 8;\n\n let loaded: Partial<AppSettings<Place, App>> = {};\n try {\n loaded = JSON.parse(readFileSync(file, \"utf8\")) as Partial<AppSettings<Place, App>>;\n } catch {\n // First run, or a file we cannot read. Defaults, never a crash: settings\n // are not worth failing to start over.\n }\n\n const state: AppSettings<Place, App> = {\n panes: opts.panes ?? {},\n windows: {},\n ...loaded,\n // Migration: recents used to be bare paths. Keep them, without names, rather\n // than emptying somebody's Open Recent menu on an update.\n recents: (loaded.recents ?? []).map((r) =>\n typeof r === \"string\" ? { path: r } : r) as RecentProject[],\n // Migration: a settings file written before places were keyed by project has\n // a single `place` belonging to `lastProject`. Carry it over rather than\n // dropping it, or everyone's first launch after this update forgets where\n // they were - but only where no keyed entry exists. The first cut of this\n // merge put the legacy value LAST, so a stale singular beat every fresh\n // per-project write on every launch, and (because the `...loaded` spread\n // above kept the key alive through save) it did so for ever: writes landed\n // correctly all session and the next start quietly reset them. Found from\n // a host's user report; the key is retired just below.\n places: {\n ...(loaded.lastProject !== undefined && (loaded as { place?: Place }).place !== undefined\n ? { [loaded.lastProject]: (loaded as { place?: Place }).place as Place }\n : {}),\n ...(loaded.places ?? {}),\n },\n // The app's slice merges FIELD BY FIELD, so a key added in a new version\n // arrives with its default rather than being absent for everybody who has\n // run the app before.\n app: { ...opts.defaults, ...(loaded.app ?? {}) },\n };\n // Retire the legacy key: it rode in on `...loaded`, and a state that still\n // carries it re-runs the fold on every future load.\n delete (state as { place?: Place }).place;\n\n const save = (): void => {\n try {\n mkdirSync(opts.dir, { recursive: true });\n // Write beside, then rename: a rename is atomic on every platform this\n // ships to, so a crash mid-write cannot leave a half-written settings\n // file - which both apps' hand-written stores could.\n const temp = `${file}.tmp`;\n writeFileSync(temp, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n renameSync(temp, file);\n } catch {\n // A read-only home directory, a full disk: the app carries on with the\n // settings it has in memory rather than dying over a preference.\n }\n };\n\n return {\n get: () => structuredClone(state),\n patchApp(patch) {\n Object.assign(state.app, patch);\n save();\n },\n touchProject(path, name) {\n const known = state.recents.find((r) => r.path === path);\n // An omitted name keeps the one we had: reopening from a route that does\n // not know the name must not blank the menu entry.\n const entry: RecentProject = { path, ...(name ?? known?.name ? { name: name ?? known?.name } : {}) };\n state.recents = [entry, ...state.recents.filter((r) => r.path !== path)].slice(0, maxRecents);\n state.lastProject = path;\n // Places follow recents: one that has aged off the list should not keep a\n // place in the file for ever. Nothing is dropped for merely opening\n // something else, which is the whole point of keying them.\n for (const key of Object.keys(state.places)) {\n if (!state.recents.some((r) => r.path === key)) delete state.places[key];\n }\n save();\n },\n forgetProject(path) {\n state.recents = state.recents.filter((r) => r.path !== path);\n if (state.lastProject === path) delete state.lastProject;\n delete state.places[path]; // it moved or went: its place is meaningless now\n save();\n },\n clearLastProject() {\n if (state.lastProject === undefined) return;\n delete state.lastProject;\n save();\n },\n setPlace(place) {\n const path = state.lastProject;\n if (path === undefined) return; // no project open: nothing to key it against\n if (JSON.stringify(state.places[path]) === JSON.stringify(place)) return;\n state.places[path] = place;\n save();\n },\n placeOf: (path) => {\n const key = path ?? state.lastProject;\n return key === undefined ? undefined : structuredClone(state.places[key]);\n },\n setPanes(panes) {\n state.panes = { ...state.panes, ...panes };\n save();\n },\n setIdentity(identity) {\n state.identity = identity;\n save();\n },\n setWindow(key, next) {\n state.windows[key] = { ...state.windows[key], ...next };\n save();\n },\n };\n}\n\n// --- the per-window adapters (ui-review-2026-09, finding 19) ---------------------\n\n/** One helper window's slice of the store, in the shape a tool-window spec\n * reads (`bounds` / `remember` / `pinned`), so a row is\n * `{ name, title, page, def, min, ...windowSlice(store, name) }`. */\nexport interface WindowSlice {\n bounds(): WindowBounds | undefined;\n remember(bounds: WindowBounds): void;\n pinned(): boolean;\n setPinned(on: boolean): void;\n}\n\n/**\n * Both apps flattened the shell's `windows` record back into named fields\n * (`boardPinned`, `searchBounds`, eight setters) and re-keyed it going in.\n * This is the record read through, per window. `pinned` defaults TRUE: a\n * helper window floats above the editor until the author unpins it, and the\n * stored flag is absent until something writes it.\n */\nexport function windowSlice<Place, App>(store: AppStore<Place, App>, name: string, defaults: { pinned?: boolean } = {}): WindowSlice {\n const pinnedDefault = defaults.pinned ?? true;\n return {\n bounds: () => store.get().windows[name]?.bounds,\n remember: (bounds) => store.setWindow(name, { bounds }),\n pinned: () => store.get().windows[name]?.pinned ?? pinnedDefault,\n setPinned: (on) => store.setWindow(name, { pinned: on }),\n };\n}\n\n/** Reset View's store half: forget every remembered rectangle (so a window\n * lost off a disconnected display comes back centred at its default size)\n * and re-pin them all. `names` adds windows that have no entry yet; without\n * it, every window the store knows is reset. */\nexport function resetWindows<Place, App>(store: AppStore<Place, App>, names: string[] = []): void {\n const keys = new Set([...Object.keys(store.get().windows), ...names]);\n for (const key of keys) store.setWindow(key, { bounds: undefined, pinned: true });\n}\n"]}
|
package/dist/app-store.d.cts
CHANGED
|
@@ -105,5 +105,29 @@ interface AppStore<Place, App> {
|
|
|
105
105
|
setWindow(key: string, state: WindowState): void;
|
|
106
106
|
}
|
|
107
107
|
declare function createAppStore<Place, App extends object>(opts: AppStoreOptions<Place, App>): AppStore<Place, App>;
|
|
108
|
+
/** One helper window's slice of the store, in the shape a tool-window spec
|
|
109
|
+
* reads (`bounds` / `remember` / `pinned`), so a row is
|
|
110
|
+
* `{ name, title, page, def, min, ...windowSlice(store, name) }`. */
|
|
111
|
+
interface WindowSlice {
|
|
112
|
+
bounds(): WindowBounds | undefined;
|
|
113
|
+
remember(bounds: WindowBounds): void;
|
|
114
|
+
pinned(): boolean;
|
|
115
|
+
setPinned(on: boolean): void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Both apps flattened the shell's `windows` record back into named fields
|
|
119
|
+
* (`boardPinned`, `searchBounds`, eight setters) and re-keyed it going in.
|
|
120
|
+
* This is the record read through, per window. `pinned` defaults TRUE: a
|
|
121
|
+
* helper window floats above the editor until the author unpins it, and the
|
|
122
|
+
* stored flag is absent until something writes it.
|
|
123
|
+
*/
|
|
124
|
+
declare function windowSlice<Place, App>(store: AppStore<Place, App>, name: string, defaults?: {
|
|
125
|
+
pinned?: boolean;
|
|
126
|
+
}): WindowSlice;
|
|
127
|
+
/** Reset View's store half: forget every remembered rectangle (so a window
|
|
128
|
+
* lost off a disconnected display comes back centred at its default size)
|
|
129
|
+
* and re-pin them all. `names` adds windows that have no entry yet; without
|
|
130
|
+
* it, every window the store knows is reset. */
|
|
131
|
+
declare function resetWindows<Place, App>(store: AppStore<Place, App>, names?: string[]): void;
|
|
108
132
|
|
|
109
|
-
export { type AppSettings, type AppSettingsCore, type AppStore, type AppStoreOptions, type Identity, type PaneState, type RecentProject, type WindowBounds, type WindowState, createAppStore };
|
|
133
|
+
export { type AppSettings, type AppSettingsCore, type AppStore, type AppStoreOptions, type Identity, type PaneState, type RecentProject, type WindowBounds, type WindowSlice, type WindowState, createAppStore, resetWindows, windowSlice };
|
package/dist/app-store.d.ts
CHANGED
|
@@ -105,5 +105,29 @@ interface AppStore<Place, App> {
|
|
|
105
105
|
setWindow(key: string, state: WindowState): void;
|
|
106
106
|
}
|
|
107
107
|
declare function createAppStore<Place, App extends object>(opts: AppStoreOptions<Place, App>): AppStore<Place, App>;
|
|
108
|
+
/** One helper window's slice of the store, in the shape a tool-window spec
|
|
109
|
+
* reads (`bounds` / `remember` / `pinned`), so a row is
|
|
110
|
+
* `{ name, title, page, def, min, ...windowSlice(store, name) }`. */
|
|
111
|
+
interface WindowSlice {
|
|
112
|
+
bounds(): WindowBounds | undefined;
|
|
113
|
+
remember(bounds: WindowBounds): void;
|
|
114
|
+
pinned(): boolean;
|
|
115
|
+
setPinned(on: boolean): void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Both apps flattened the shell's `windows` record back into named fields
|
|
119
|
+
* (`boardPinned`, `searchBounds`, eight setters) and re-keyed it going in.
|
|
120
|
+
* This is the record read through, per window. `pinned` defaults TRUE: a
|
|
121
|
+
* helper window floats above the editor until the author unpins it, and the
|
|
122
|
+
* stored flag is absent until something writes it.
|
|
123
|
+
*/
|
|
124
|
+
declare function windowSlice<Place, App>(store: AppStore<Place, App>, name: string, defaults?: {
|
|
125
|
+
pinned?: boolean;
|
|
126
|
+
}): WindowSlice;
|
|
127
|
+
/** Reset View's store half: forget every remembered rectangle (so a window
|
|
128
|
+
* lost off a disconnected display comes back centred at its default size)
|
|
129
|
+
* and re-pin them all. `names` adds windows that have no entry yet; without
|
|
130
|
+
* it, every window the store knows is reset. */
|
|
131
|
+
declare function resetWindows<Place, App>(store: AppStore<Place, App>, names?: string[]): void;
|
|
108
132
|
|
|
109
|
-
export { type AppSettings, type AppSettingsCore, type AppStore, type AppStoreOptions, type Identity, type PaneState, type RecentProject, type WindowBounds, type WindowState, createAppStore };
|
|
133
|
+
export { type AppSettings, type AppSettingsCore, type AppStore, type AppStoreOptions, type Identity, type PaneState, type RecentProject, type WindowBounds, type WindowSlice, type WindowState, createAppStore, resetWindows, windowSlice };
|
package/dist/app-store.js
CHANGED
|
@@ -98,7 +98,20 @@ function createAppStore(opts) {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
function windowSlice(store, name, defaults = {}) {
|
|
102
|
+
const pinnedDefault = defaults.pinned ?? true;
|
|
103
|
+
return {
|
|
104
|
+
bounds: () => store.get().windows[name]?.bounds,
|
|
105
|
+
remember: (bounds) => store.setWindow(name, { bounds }),
|
|
106
|
+
pinned: () => store.get().windows[name]?.pinned ?? pinnedDefault,
|
|
107
|
+
setPinned: (on) => store.setWindow(name, { pinned: on })
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function resetWindows(store, names = []) {
|
|
111
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(store.get().windows), ...names]);
|
|
112
|
+
for (const key of keys) store.setWindow(key, { bounds: void 0, pinned: true });
|
|
113
|
+
}
|
|
101
114
|
|
|
102
|
-
export { createAppStore };
|
|
115
|
+
export { createAppStore, resetWindows, windowSlice };
|
|
103
116
|
//# sourceMappingURL=app-store.js.map
|
|
104
117
|
//# sourceMappingURL=app-store.js.map
|
package/dist/app-store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app-store.ts"],"names":[],"mappings":"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","file":"app-store.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The user's settings for THIS app: a small JSON file in the app's userData.\n//\n// Every app in the family has one, and both of the two that exist wrote it\n// separately: the same tolerant read, the same merged defaults, the same capped\n// recents, the same panes, the same per-tool-window bounds and pin, the same\n// identity. That is the definition of shell work.\n//\n// WHAT BELONGS HERE is settings that follow the PERSON, not the project: where\n// their windows were, which panes they like open, what they are called, what\n// they had open last. Project content never comes near this file - it is not\n// version controlled, it is not shared, and losing it should cost an author\n// nothing but a re-arranged window.\n//\n// The shell owns the CORE it can reason about and hands the app one slice of\n// its own (`app`) for everything else - a theme value, a view mode, whatever it\n// has. Two stores in one file, one atomic write, so a crash cannot leave an app\n// with half its settings.\n//\n// Electron-free on purpose: it takes a directory. That keeps it unit-testable\n// and lets a test point it at a temp dir.\n// ---------------------------------------------------------------------------\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Who is at the keyboard. Never in a project: see identity.ts. */\nexport interface Identity { name: string; email?: string }\n\n/** A remembered window rectangle. Absent = the app's default, centred. */\nexport interface WindowBounds { x?: number; y?: number; width: number; height: number }\n\n/** One helper window's remembered state. */\nexport interface WindowState { bounds?: WindowBounds; pinned?: boolean }\n\n/** Side panes: open/closed, and any dragged width. */\nexport interface PaneState {\n nav?: boolean;\n inspector?: boolean;\n navW?: number;\n inspW?: number;\n}\n\n/**\n * The part every app has.\n *\n * `places` is deliberately opaque: \"open where you left off\" is a family idea,\n * but WHERE is a scene and a caret in one app and a box, a tab and a card in\n * another.\n *\n * It is keyed BY PROJECT, and that is the interesting part. This held a single\n * `place` and dropped it whenever a different project was opened, on the\n * reasoning that clearing it was the one rule that could not be app-specific.\n * That reasoning was wrong, and Patterpad disproved it by having done the other\n * thing for a long time: it remembers the line you were on in every project you\n * have opened. With one slot, working on A, glancing at B and coming back to A\n * lands you at the top of A, having forgotten. Nobody wants that, and no editor\n * outside this package does it.\n */\n/**\n * A project in the Open Recent menu.\n *\n * The NAME is here rather than being derived from the path, because a project's\n * display name is what its file says it is, not what its folder is called: two\n * projects can sit in folders called `draft`, and one can be renamed without\n * moving. Every app in the family shows this menu, so both halves are shared.\n *\n * Optional because the path is the identity and the name is only how it reads.\n * An entry recorded before a name was known still works.\n */\nexport interface RecentProject {\n path: string;\n name?: string;\n}\n\nexport interface AppSettingsCore<Place> {\n recents: RecentProject[];\n lastProject?: string;\n /** Project path -> where the author was in it. Capped with `recents`, so a\n * project forgotten from the list stops carrying a place around too. */\n places: Record<string, Place>;\n panes: PaneState;\n identity?: Identity;\n /** Keyed by whatever the app calls each window (\"board\", \"search\"). */\n windows: Record<string, WindowState>;\n}\n\nexport type AppSettings<Place, App> = AppSettingsCore<Place> & { app: App };\n\nexport interface AppStoreOptions<Place, App> {\n /** The app's userData directory. */\n dir: string;\n /** The file inside it. One per app; two apps never share. */\n fileName?: string;\n /** The app's own slice, as it should read on a first run. */\n defaults: App;\n /** How the panes should sit before anybody touches them. */\n panes?: PaneState;\n maxRecents?: number;\n}\n\nexport interface AppStore<Place, App> {\n /** A copy, always: nothing outside can mutate the state behind the store's back. */\n get(): AppSettings<Place, App>;\n /** Merge into the app's own slice and save. */\n patchApp(patch: Partial<App>): void;\n /** Record an opened project: front of recents, deduped, capped. The name is\n * refreshed each time, so renaming a project updates the menu on next open;\n * omitting it keeps whatever was known before rather than blanking it. */\n touchProject(path: string, name?: string): void;\n /** Drop one that failed to open (moved, deleted). */\n forgetProject(path: string): void;\n /** Forget WHICH project was open, keeping it in recents: Close Project's\n * half of the memory, so the next boot lands on the welcome screen. */\n clearLastProject(): void;\n /** Where the author was in the CURRENT project. Compared before writing: this\n * is called on every navigation and must not mean a disk write per click.\n * A no-op with no project open, since there is nothing to key it against. */\n setPlace(place: Place): void;\n /** Where the author was in a project, or undefined if it was never opened or\n * has aged out of `recents`. Defaults to the current one. */\n placeOf(path?: string): Place | undefined;\n setPanes(panes: PaneState): void;\n setIdentity(identity: Identity): void;\n /** One window's bounds and/or pin, merged with what it already had. */\n setWindow(key: string, state: WindowState): void;\n}\n\nexport function createAppStore<Place, App extends object>(\n opts: AppStoreOptions<Place, App>,\n): AppStore<Place, App> {\n const file = join(opts.dir, opts.fileName ?? \"app-settings.json\");\n const maxRecents = opts.maxRecents ?? 8;\n\n let loaded: Partial<AppSettings<Place, App>> = {};\n try {\n loaded = JSON.parse(readFileSync(file, \"utf8\")) as Partial<AppSettings<Place, App>>;\n } catch {\n // First run, or a file we cannot read. Defaults, never a crash: settings\n // are not worth failing to start over.\n }\n\n const state: AppSettings<Place, App> = {\n panes: opts.panes ?? {},\n windows: {},\n ...loaded,\n // Migration: recents used to be bare paths. Keep them, without names, rather\n // than emptying somebody's Open Recent menu on an update.\n recents: (loaded.recents ?? []).map((r) =>\n typeof r === \"string\" ? { path: r } : r) as RecentProject[],\n // Migration: a settings file written before places were keyed by project has\n // a single `place` belonging to `lastProject`. Carry it over rather than\n // dropping it, or everyone's first launch after this update forgets where\n // they were - but only where no keyed entry exists. The first cut of this\n // merge put the legacy value LAST, so a stale singular beat every fresh\n // per-project write on every launch, and (because the `...loaded` spread\n // above kept the key alive through save) it did so for ever: writes landed\n // correctly all session and the next start quietly reset them. Found from\n // a host's user report; the key is retired just below.\n places: {\n ...(loaded.lastProject !== undefined && (loaded as { place?: Place }).place !== undefined\n ? { [loaded.lastProject]: (loaded as { place?: Place }).place as Place }\n : {}),\n ...(loaded.places ?? {}),\n },\n // The app's slice merges FIELD BY FIELD, so a key added in a new version\n // arrives with its default rather than being absent for everybody who has\n // run the app before.\n app: { ...opts.defaults, ...(loaded.app ?? {}) },\n };\n // Retire the legacy key: it rode in on `...loaded`, and a state that still\n // carries it re-runs the fold on every future load.\n delete (state as { place?: Place }).place;\n\n const save = (): void => {\n try {\n mkdirSync(opts.dir, { recursive: true });\n // Write beside, then rename: a rename is atomic on every platform this\n // ships to, so a crash mid-write cannot leave a half-written settings\n // file - which both apps' hand-written stores could.\n const temp = `${file}.tmp`;\n writeFileSync(temp, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n renameSync(temp, file);\n } catch {\n // A read-only home directory, a full disk: the app carries on with the\n // settings it has in memory rather than dying over a preference.\n }\n };\n\n return {\n get: () => structuredClone(state),\n patchApp(patch) {\n Object.assign(state.app, patch);\n save();\n },\n touchProject(path, name) {\n const known = state.recents.find((r) => r.path === path);\n // An omitted name keeps the one we had: reopening from a route that does\n // not know the name must not blank the menu entry.\n const entry: RecentProject = { path, ...(name ?? known?.name ? { name: name ?? known?.name } : {}) };\n state.recents = [entry, ...state.recents.filter((r) => r.path !== path)].slice(0, maxRecents);\n state.lastProject = path;\n // Places follow recents: one that has aged off the list should not keep a\n // place in the file for ever. Nothing is dropped for merely opening\n // something else, which is the whole point of keying them.\n for (const key of Object.keys(state.places)) {\n if (!state.recents.some((r) => r.path === key)) delete state.places[key];\n }\n save();\n },\n forgetProject(path) {\n state.recents = state.recents.filter((r) => r.path !== path);\n if (state.lastProject === path) delete state.lastProject;\n delete state.places[path]; // it moved or went: its place is meaningless now\n save();\n },\n clearLastProject() {\n if (state.lastProject === undefined) return;\n delete state.lastProject;\n save();\n },\n setPlace(place) {\n const path = state.lastProject;\n if (path === undefined) return; // no project open: nothing to key it against\n if (JSON.stringify(state.places[path]) === JSON.stringify(place)) return;\n state.places[path] = place;\n save();\n },\n placeOf: (path) => {\n const key = path ?? state.lastProject;\n return key === undefined ? undefined : structuredClone(state.places[key]);\n },\n setPanes(panes) {\n state.panes = { ...state.panes, ...panes };\n save();\n },\n setIdentity(identity) {\n state.identity = identity;\n save();\n },\n setWindow(key, next) {\n state.windows[key] = { ...state.windows[key], ...next };\n save();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/app-store.ts"],"names":[],"mappings":"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","file":"app-store.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The user's settings for THIS app: a small JSON file in the app's userData.\n//\n// Every app in the family has one, and both of the two that exist wrote it\n// separately: the same tolerant read, the same merged defaults, the same capped\n// recents, the same panes, the same per-tool-window bounds and pin, the same\n// identity. That is the definition of shell work.\n//\n// WHAT BELONGS HERE is settings that follow the PERSON, not the project: where\n// their windows were, which panes they like open, what they are called, what\n// they had open last. Project content never comes near this file - it is not\n// version controlled, it is not shared, and losing it should cost an author\n// nothing but a re-arranged window.\n//\n// The shell owns the CORE it can reason about and hands the app one slice of\n// its own (`app`) for everything else - a theme value, a view mode, whatever it\n// has. Two stores in one file, one atomic write, so a crash cannot leave an app\n// with half its settings.\n//\n// Electron-free on purpose: it takes a directory. That keeps it unit-testable\n// and lets a test point it at a temp dir.\n// ---------------------------------------------------------------------------\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/** Who is at the keyboard. Never in a project: see identity.ts. */\nexport interface Identity { name: string; email?: string }\n\n/** A remembered window rectangle. Absent = the app's default, centred. */\nexport interface WindowBounds { x?: number; y?: number; width: number; height: number }\n\n/** One helper window's remembered state. */\nexport interface WindowState { bounds?: WindowBounds; pinned?: boolean }\n\n/** Side panes: open/closed, and any dragged width. */\nexport interface PaneState {\n nav?: boolean;\n inspector?: boolean;\n navW?: number;\n inspW?: number;\n}\n\n/**\n * The part every app has.\n *\n * `places` is deliberately opaque: \"open where you left off\" is a family idea,\n * but WHERE is a scene and a caret in one app and a box, a tab and a card in\n * another.\n *\n * It is keyed BY PROJECT, and that is the interesting part. This held a single\n * `place` and dropped it whenever a different project was opened, on the\n * reasoning that clearing it was the one rule that could not be app-specific.\n * That reasoning was wrong, and Patterpad disproved it by having done the other\n * thing for a long time: it remembers the line you were on in every project you\n * have opened. With one slot, working on A, glancing at B and coming back to A\n * lands you at the top of A, having forgotten. Nobody wants that, and no editor\n * outside this package does it.\n */\n/**\n * A project in the Open Recent menu.\n *\n * The NAME is here rather than being derived from the path, because a project's\n * display name is what its file says it is, not what its folder is called: two\n * projects can sit in folders called `draft`, and one can be renamed without\n * moving. Every app in the family shows this menu, so both halves are shared.\n *\n * Optional because the path is the identity and the name is only how it reads.\n * An entry recorded before a name was known still works.\n */\nexport interface RecentProject {\n path: string;\n name?: string;\n}\n\nexport interface AppSettingsCore<Place> {\n recents: RecentProject[];\n lastProject?: string;\n /** Project path -> where the author was in it. Capped with `recents`, so a\n * project forgotten from the list stops carrying a place around too. */\n places: Record<string, Place>;\n panes: PaneState;\n identity?: Identity;\n /** Keyed by whatever the app calls each window (\"board\", \"search\"). */\n windows: Record<string, WindowState>;\n}\n\nexport type AppSettings<Place, App> = AppSettingsCore<Place> & { app: App };\n\nexport interface AppStoreOptions<Place, App> {\n /** The app's userData directory. */\n dir: string;\n /** The file inside it. One per app; two apps never share. */\n fileName?: string;\n /** The app's own slice, as it should read on a first run. */\n defaults: App;\n /** How the panes should sit before anybody touches them. */\n panes?: PaneState;\n maxRecents?: number;\n}\n\nexport interface AppStore<Place, App> {\n /** A copy, always: nothing outside can mutate the state behind the store's back. */\n get(): AppSettings<Place, App>;\n /** Merge into the app's own slice and save. */\n patchApp(patch: Partial<App>): void;\n /** Record an opened project: front of recents, deduped, capped. The name is\n * refreshed each time, so renaming a project updates the menu on next open;\n * omitting it keeps whatever was known before rather than blanking it. */\n touchProject(path: string, name?: string): void;\n /** Drop one that failed to open (moved, deleted). */\n forgetProject(path: string): void;\n /** Forget WHICH project was open, keeping it in recents: Close Project's\n * half of the memory, so the next boot lands on the welcome screen. */\n clearLastProject(): void;\n /** Where the author was in the CURRENT project. Compared before writing: this\n * is called on every navigation and must not mean a disk write per click.\n * A no-op with no project open, since there is nothing to key it against. */\n setPlace(place: Place): void;\n /** Where the author was in a project, or undefined if it was never opened or\n * has aged out of `recents`. Defaults to the current one. */\n placeOf(path?: string): Place | undefined;\n setPanes(panes: PaneState): void;\n setIdentity(identity: Identity): void;\n /** One window's bounds and/or pin, merged with what it already had. */\n setWindow(key: string, state: WindowState): void;\n}\n\nexport function createAppStore<Place, App extends object>(\n opts: AppStoreOptions<Place, App>,\n): AppStore<Place, App> {\n const file = join(opts.dir, opts.fileName ?? \"app-settings.json\");\n const maxRecents = opts.maxRecents ?? 8;\n\n let loaded: Partial<AppSettings<Place, App>> = {};\n try {\n loaded = JSON.parse(readFileSync(file, \"utf8\")) as Partial<AppSettings<Place, App>>;\n } catch {\n // First run, or a file we cannot read. Defaults, never a crash: settings\n // are not worth failing to start over.\n }\n\n const state: AppSettings<Place, App> = {\n panes: opts.panes ?? {},\n windows: {},\n ...loaded,\n // Migration: recents used to be bare paths. Keep them, without names, rather\n // than emptying somebody's Open Recent menu on an update.\n recents: (loaded.recents ?? []).map((r) =>\n typeof r === \"string\" ? { path: r } : r) as RecentProject[],\n // Migration: a settings file written before places were keyed by project has\n // a single `place` belonging to `lastProject`. Carry it over rather than\n // dropping it, or everyone's first launch after this update forgets where\n // they were - but only where no keyed entry exists. The first cut of this\n // merge put the legacy value LAST, so a stale singular beat every fresh\n // per-project write on every launch, and (because the `...loaded` spread\n // above kept the key alive through save) it did so for ever: writes landed\n // correctly all session and the next start quietly reset them. Found from\n // a host's user report; the key is retired just below.\n places: {\n ...(loaded.lastProject !== undefined && (loaded as { place?: Place }).place !== undefined\n ? { [loaded.lastProject]: (loaded as { place?: Place }).place as Place }\n : {}),\n ...(loaded.places ?? {}),\n },\n // The app's slice merges FIELD BY FIELD, so a key added in a new version\n // arrives with its default rather than being absent for everybody who has\n // run the app before.\n app: { ...opts.defaults, ...(loaded.app ?? {}) },\n };\n // Retire the legacy key: it rode in on `...loaded`, and a state that still\n // carries it re-runs the fold on every future load.\n delete (state as { place?: Place }).place;\n\n const save = (): void => {\n try {\n mkdirSync(opts.dir, { recursive: true });\n // Write beside, then rename: a rename is atomic on every platform this\n // ships to, so a crash mid-write cannot leave a half-written settings\n // file - which both apps' hand-written stores could.\n const temp = `${file}.tmp`;\n writeFileSync(temp, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n renameSync(temp, file);\n } catch {\n // A read-only home directory, a full disk: the app carries on with the\n // settings it has in memory rather than dying over a preference.\n }\n };\n\n return {\n get: () => structuredClone(state),\n patchApp(patch) {\n Object.assign(state.app, patch);\n save();\n },\n touchProject(path, name) {\n const known = state.recents.find((r) => r.path === path);\n // An omitted name keeps the one we had: reopening from a route that does\n // not know the name must not blank the menu entry.\n const entry: RecentProject = { path, ...(name ?? known?.name ? { name: name ?? known?.name } : {}) };\n state.recents = [entry, ...state.recents.filter((r) => r.path !== path)].slice(0, maxRecents);\n state.lastProject = path;\n // Places follow recents: one that has aged off the list should not keep a\n // place in the file for ever. Nothing is dropped for merely opening\n // something else, which is the whole point of keying them.\n for (const key of Object.keys(state.places)) {\n if (!state.recents.some((r) => r.path === key)) delete state.places[key];\n }\n save();\n },\n forgetProject(path) {\n state.recents = state.recents.filter((r) => r.path !== path);\n if (state.lastProject === path) delete state.lastProject;\n delete state.places[path]; // it moved or went: its place is meaningless now\n save();\n },\n clearLastProject() {\n if (state.lastProject === undefined) return;\n delete state.lastProject;\n save();\n },\n setPlace(place) {\n const path = state.lastProject;\n if (path === undefined) return; // no project open: nothing to key it against\n if (JSON.stringify(state.places[path]) === JSON.stringify(place)) return;\n state.places[path] = place;\n save();\n },\n placeOf: (path) => {\n const key = path ?? state.lastProject;\n return key === undefined ? undefined : structuredClone(state.places[key]);\n },\n setPanes(panes) {\n state.panes = { ...state.panes, ...panes };\n save();\n },\n setIdentity(identity) {\n state.identity = identity;\n save();\n },\n setWindow(key, next) {\n state.windows[key] = { ...state.windows[key], ...next };\n save();\n },\n };\n}\n\n// --- the per-window adapters (ui-review-2026-09, finding 19) ---------------------\n\n/** One helper window's slice of the store, in the shape a tool-window spec\n * reads (`bounds` / `remember` / `pinned`), so a row is\n * `{ name, title, page, def, min, ...windowSlice(store, name) }`. */\nexport interface WindowSlice {\n bounds(): WindowBounds | undefined;\n remember(bounds: WindowBounds): void;\n pinned(): boolean;\n setPinned(on: boolean): void;\n}\n\n/**\n * Both apps flattened the shell's `windows` record back into named fields\n * (`boardPinned`, `searchBounds`, eight setters) and re-keyed it going in.\n * This is the record read through, per window. `pinned` defaults TRUE: a\n * helper window floats above the editor until the author unpins it, and the\n * stored flag is absent until something writes it.\n */\nexport function windowSlice<Place, App>(store: AppStore<Place, App>, name: string, defaults: { pinned?: boolean } = {}): WindowSlice {\n const pinnedDefault = defaults.pinned ?? true;\n return {\n bounds: () => store.get().windows[name]?.bounds,\n remember: (bounds) => store.setWindow(name, { bounds }),\n pinned: () => store.get().windows[name]?.pinned ?? pinnedDefault,\n setPinned: (on) => store.setWindow(name, { pinned: on }),\n };\n}\n\n/** Reset View's store half: forget every remembered rectangle (so a window\n * lost off a disconnected display comes back centred at its default size)\n * and re-pin them all. `names` adds windows that have no entry yet; without\n * it, every window the store knows is reset. */\nexport function resetWindows<Place, App>(store: AppStore<Place, App>, names: string[] = []): void {\n const keys = new Set([...Object.keys(store.get().windows), ...names]);\n for (const key of keys) store.setWindow(key, { bounds: undefined, pinned: true });\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/menu.ts
|
|
2
|
+
var EDIT_MENU = {
|
|
3
|
+
undo: { label: "Undo", accelerator: "CmdOrCtrl+Z" },
|
|
4
|
+
redo: { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z" },
|
|
5
|
+
duplicate: { label: "Duplicate", accelerator: "CmdOrCtrl+D" },
|
|
6
|
+
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" },
|
|
7
|
+
replace: { label: "Replace\u2026", acceleratorMac: "Cmd+Alt+F", acceleratorOther: "Ctrl+H" }
|
|
8
|
+
};
|
|
9
|
+
var GO_MENU = {
|
|
10
|
+
back: { label: "Back", acceleratorMac: "Ctrl+Cmd+Left", acceleratorOther: "Alt+Left" },
|
|
11
|
+
forward: { label: "Forward", acceleratorMac: "Ctrl+Cmd+Right", acceleratorOther: "Alt+Right" }
|
|
12
|
+
};
|
|
13
|
+
var PANE_MENU = {
|
|
14
|
+
showNav: { label: "Show Navigator", accelerator: "CmdOrCtrl+1" },
|
|
15
|
+
showInspector: { label: "Show Inspector", accelerator: "CmdOrCtrl+2" },
|
|
16
|
+
resetView: { label: "Reset View" }
|
|
17
|
+
};
|
|
18
|
+
var HELP_MENU = {
|
|
19
|
+
checkForUpdates: { label: "Check for Updates\u2026" }
|
|
20
|
+
};
|
|
21
|
+
var APP_MENU = {
|
|
22
|
+
/** Name + optional email, used to sign edits and comments. One label across the suite. */
|
|
23
|
+
userInfo: { label: "User Information\u2026" }
|
|
24
|
+
};
|
|
25
|
+
function namedMenuItems(naming) {
|
|
26
|
+
const suite = naming.suiteName ?? "Patter";
|
|
27
|
+
return {
|
|
28
|
+
about: { label: `About ${naming.appName}`, ready: true },
|
|
29
|
+
docs: {
|
|
30
|
+
label: `${naming.appName} Documentation`,
|
|
31
|
+
...naming.docsUrl ? { url: naming.docsUrl } : {},
|
|
32
|
+
ready: naming.docsUrl !== void 0
|
|
33
|
+
},
|
|
34
|
+
suiteDocs: {
|
|
35
|
+
label: `${suite} Documentation Home`,
|
|
36
|
+
...naming.suiteDocsUrl ? { url: naming.suiteDocsUrl } : {},
|
|
37
|
+
ready: naming.suiteDocsUrl !== void 0
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var FILE_MENU = {
|
|
42
|
+
newProject: { label: "New Project\u2026", accelerator: "CmdOrCtrl+N" },
|
|
43
|
+
openProject: { label: "Open Project\u2026", accelerator: "CmdOrCtrl+O" },
|
|
44
|
+
openRecent: { label: "Open Recent" },
|
|
45
|
+
noRecents: { label: "No Recent Projects" },
|
|
46
|
+
clearRecents: { label: "Clear Recents" },
|
|
47
|
+
closeProject: { label: "Close Project" },
|
|
48
|
+
save: { label: "Save", accelerator: "CmdOrCtrl+S" },
|
|
49
|
+
saveAs: { label: "Save As\u2026", accelerator: "Shift+CmdOrCtrl+S" },
|
|
50
|
+
projectSettings: { label: "Project Settings\u2026", accelerator: "CmdOrCtrl+," }
|
|
51
|
+
};
|
|
52
|
+
var PLAY_MENU = {
|
|
53
|
+
liveLink: { label: "Live Link" }
|
|
54
|
+
};
|
|
55
|
+
var REVIEW_MENU = {
|
|
56
|
+
reviewFeedback: { label: "Review Feedback", accelerator: "CmdOrCtrl+Shift+R" },
|
|
57
|
+
nextFeedback: { label: "Next Feedback", accelerator: "F8" },
|
|
58
|
+
previousFeedback: { label: "Previous Feedback", accelerator: "Shift+F8" },
|
|
59
|
+
coverageTest: { label: "Coverage Test\u2026", accelerator: "Shift+CmdOrCtrl+C" },
|
|
60
|
+
findPropertyUsage: { label: "Find Property Usage\u2026" },
|
|
61
|
+
showResolvedComments: { label: "Show Resolved Comments" }
|
|
62
|
+
};
|
|
63
|
+
var PUBLISH_MENU = {
|
|
64
|
+
playableHtml: { label: "Publish Playable HTML\u2026" },
|
|
65
|
+
bundle: { label: "Publish Bundle", accelerator: "Shift+CmdOrCtrl+B" },
|
|
66
|
+
autoRebuild: { label: "Auto Rebuild" }
|
|
67
|
+
};
|
|
68
|
+
var VIEW_MENU = {
|
|
69
|
+
projectOverview: { label: "Project Overview" },
|
|
70
|
+
upALevel: { label: "Up a Level", accelerator: "CmdOrCtrl+[" },
|
|
71
|
+
colourTheme: { label: "Colour Theme" }
|
|
72
|
+
};
|
|
73
|
+
function tildePath(path, home) {
|
|
74
|
+
if (path === home) return "~";
|
|
75
|
+
const sep = home.includes("\\") ? "\\" : "/";
|
|
76
|
+
return path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
|
|
77
|
+
}
|
|
78
|
+
function recentsSubmenu(recents, opts) {
|
|
79
|
+
const isMac = (opts.platform ?? process.platform) === "darwin";
|
|
80
|
+
if (recents.length === 0) return [{ label: FILE_MENU.noRecents.label, enabled: false }];
|
|
81
|
+
const items = recents.map((r) => {
|
|
82
|
+
const shown = tildePath(r.path, opts.home);
|
|
83
|
+
const name = r.name ?? r.path.split(/[\\/]/).filter(Boolean).pop() ?? r.path;
|
|
84
|
+
return {
|
|
85
|
+
label: isMac ? name : `${name} (${shown})`,
|
|
86
|
+
sublabel: shown,
|
|
87
|
+
toolTip: r.path,
|
|
88
|
+
click: () => opts.onOpen(r.path)
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
if (opts.onClear) items.push({ type: "separator" }, { label: FILE_MENU.clearRecents.label, click: opts.onClear });
|
|
92
|
+
return items;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { APP_MENU, EDIT_MENU, FILE_MENU, GO_MENU, HELP_MENU, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU, namedMenuItems, recentsSubmenu, tildePath };
|
|
96
|
+
//# sourceMappingURL=chunk-2ECJUTSX.js.map
|
|
97
|
+
//# sourceMappingURL=chunk-2ECJUTSX.js.map
|