@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/dist/index.js
CHANGED
|
@@ -1,241 +1,19 @@
|
|
|
1
|
+
export { mountWelcome } from './chunk-C6UVQYKI.js';
|
|
2
|
+
export { feedUpdaterDownloadProgress, showUpdaterDialog } from './chunk-S4KWQLYN.js';
|
|
3
|
+
export { linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus } from './chunk-KGAMP5LW.js';
|
|
4
|
+
import { isEditableTarget } from './chunk-P6WLAEKZ.js';
|
|
5
|
+
export { copyWithFeedback, debounce, formatCount, isEditableTarget, plural, relativeTime } from './chunk-P6WLAEKZ.js';
|
|
6
|
+
export { EDIT_MENU, FILE_MENU, GO_MENU, PANE_MENU, PLAY_MENU, PUBLISH_MENU, REVIEW_MENU, VIEW_MENU } from './chunk-2ECJUTSX.js';
|
|
7
|
+
export { PROPERTY_NAME_HINT, PROPERTY_TYPES, RESERVED_PROPERTY_NAMES, bindPropertyName, bindPropertyRef, defaultControl, dupGuard, expandableRow, firstIllegalPropertyName, focusNewRow, isCaseOnlyPropertyName, isValidPropertyName, mountPropertyList, mountSettingsDialog, propertyNameProblem, propertyNameify, propertyRefProblem, revalidatePropertyRefs, revealRow, revealRowWhenReady } from './chunk-L5HOBEKL.js';
|
|
8
|
+
import { dialogFrame } from './chunk-2NMVIM54.js';
|
|
9
|
+
export { dialogFrame } from './chunk-2NMVIM54.js';
|
|
10
|
+
export { createFloating } from './chunk-P4G4ERG6.js';
|
|
1
11
|
export { JOB_PROGRESS } from './chunk-MDGOIT3O.js';
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function tipBold(s) {
|
|
8
|
-
return `${B_OPEN}${s}${B_CLOSE}`;
|
|
9
|
-
}
|
|
10
|
-
function renderTip(host, text) {
|
|
11
|
-
host.textContent = "";
|
|
12
|
-
const re = /\u0002([^\u0003]*)\u0003/g;
|
|
13
|
-
let last = 0;
|
|
14
|
-
let m;
|
|
15
|
-
while ((m = re.exec(text)) !== null) {
|
|
16
|
-
if (m.index > last) host.append(text.slice(last, m.index));
|
|
17
|
-
const strong = document.createElement("strong");
|
|
18
|
-
strong.textContent = m[1] ?? "";
|
|
19
|
-
host.append(strong);
|
|
20
|
-
last = m.index + m[0].length;
|
|
21
|
-
}
|
|
22
|
-
if (last < text.length) host.append(text.slice(last));
|
|
23
|
-
}
|
|
24
|
-
var inited = false;
|
|
25
|
-
var checked = false;
|
|
26
|
-
var suppressed;
|
|
27
|
-
var SHOW_DELAY = 350;
|
|
28
|
-
var EDGE = 6;
|
|
29
|
-
var GAP = 7;
|
|
30
|
-
var tip = null;
|
|
31
|
-
var timer = 0;
|
|
32
|
-
var active = null;
|
|
33
|
-
var activeKey = null;
|
|
34
|
-
function ensureEl() {
|
|
35
|
-
if (tip) return tip;
|
|
36
|
-
const el2 = document.createElement("div");
|
|
37
|
-
el2.className = "tooltip";
|
|
38
|
-
el2.setAttribute("role", "tooltip");
|
|
39
|
-
el2.hidden = true;
|
|
40
|
-
document.body.appendChild(el2);
|
|
41
|
-
tip = el2;
|
|
42
|
-
return el2;
|
|
43
|
-
}
|
|
44
|
-
var rectOf = (anchor) => anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor;
|
|
45
|
-
function place(anchor) {
|
|
46
|
-
const t = ensureEl();
|
|
47
|
-
const r = rectOf(anchor);
|
|
48
|
-
const tr = t.getBoundingClientRect();
|
|
49
|
-
let top = r.top - tr.height - GAP;
|
|
50
|
-
if (top < EDGE) top = r.top + r.height + GAP;
|
|
51
|
-
let left = r.left + r.width / 2 - tr.width / 2;
|
|
52
|
-
left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));
|
|
53
|
-
t.style.left = `${Math.round(left)}px`;
|
|
54
|
-
t.style.top = `${Math.round(top)}px`;
|
|
55
|
-
}
|
|
56
|
-
function show(anchor) {
|
|
57
|
-
const text = anchor.dataset["tip"];
|
|
58
|
-
if (!text) return;
|
|
59
|
-
paint(anchor, text);
|
|
60
|
-
}
|
|
61
|
-
function paint(anchor, text) {
|
|
62
|
-
if (suppressed?.()) return;
|
|
63
|
-
const t = ensureEl();
|
|
64
|
-
const host = (anchor instanceof HTMLElement ? anchor.closest("dialog[open]") : null) ?? document.body;
|
|
65
|
-
if (t.parentElement !== host) host.appendChild(t);
|
|
66
|
-
active = anchor instanceof HTMLElement ? anchor : null;
|
|
67
|
-
renderTip(t, text);
|
|
68
|
-
t.hidden = false;
|
|
69
|
-
t.classList.remove("show");
|
|
70
|
-
void t.offsetWidth;
|
|
71
|
-
place(anchor);
|
|
72
|
-
t.classList.add("show");
|
|
73
|
-
}
|
|
74
|
-
function hide() {
|
|
75
|
-
if (timer) {
|
|
76
|
-
window.clearTimeout(timer);
|
|
77
|
-
timer = 0;
|
|
78
|
-
}
|
|
79
|
-
active = null;
|
|
80
|
-
activeKey = null;
|
|
81
|
-
if (tip) {
|
|
82
|
-
tip.hidden = true;
|
|
83
|
-
tip.classList.remove("show");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function schedule(anchor) {
|
|
87
|
-
if (anchor === active) return;
|
|
88
|
-
if (timer) window.clearTimeout(timer);
|
|
89
|
-
timer = window.setTimeout(() => show(anchor), SHOW_DELAY);
|
|
90
|
-
}
|
|
91
|
-
function tipAt(key, rect, text) {
|
|
92
|
-
if (key === activeKey) {
|
|
93
|
-
if (tip && !tip.hidden) {
|
|
94
|
-
renderTip(tip, text);
|
|
95
|
-
place(rect);
|
|
96
|
-
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
hide();
|
|
100
|
-
activeKey = key;
|
|
101
|
-
timer = window.setTimeout(() => {
|
|
102
|
-
timer = 0;
|
|
103
|
-
paint(rect, text);
|
|
104
|
-
}, SHOW_DELAY);
|
|
105
|
-
}
|
|
106
|
-
function hideTip() {
|
|
107
|
-
hide();
|
|
108
|
-
}
|
|
109
|
-
function ensureTooltipHost() {
|
|
110
|
-
initTooltips();
|
|
111
|
-
}
|
|
112
|
-
function checkTooltipHost() {
|
|
113
|
-
if (checked) return;
|
|
114
|
-
checked = true;
|
|
115
|
-
setTimeout(() => {
|
|
116
|
-
if (inited || typeof document === "undefined") return;
|
|
117
|
-
if (document.querySelector("[data-tip]")) {
|
|
118
|
-
console.warn("app-shell: something set `data-tip` but no tooltip host is mounted, so those rollovers will not draw. Call initTooltips() once per window.");
|
|
119
|
-
}
|
|
120
|
-
}, 0);
|
|
121
|
-
}
|
|
122
|
-
var tipAncestor = (n) => n instanceof Element ? n.closest("[data-tip]") : null;
|
|
123
|
-
function initTooltips(opts = {}) {
|
|
124
|
-
if (opts.suppressed !== void 0) suppressed = opts.suppressed;
|
|
125
|
-
if (inited) return;
|
|
126
|
-
inited = true;
|
|
127
|
-
document.addEventListener("pointerover", (e) => {
|
|
128
|
-
const el2 = tipAncestor(e.target);
|
|
129
|
-
if (el2) schedule(el2);
|
|
130
|
-
});
|
|
131
|
-
document.addEventListener("pointerout", (e) => {
|
|
132
|
-
const el2 = tipAncestor(e.target);
|
|
133
|
-
if (!el2) return;
|
|
134
|
-
const to = e.relatedTarget;
|
|
135
|
-
if (to instanceof Node && el2.contains(to)) return;
|
|
136
|
-
hide();
|
|
137
|
-
});
|
|
138
|
-
document.addEventListener("focusin", (e) => {
|
|
139
|
-
const el2 = tipAncestor(e.target);
|
|
140
|
-
if (el2) schedule(el2);
|
|
141
|
-
});
|
|
142
|
-
document.addEventListener("focusout", () => hide());
|
|
143
|
-
document.addEventListener("pointerdown", () => hide(), true);
|
|
144
|
-
document.addEventListener("scroll", () => {
|
|
145
|
-
if (active) hide();
|
|
146
|
-
}, true);
|
|
147
|
-
document.addEventListener("keydown", (e) => {
|
|
148
|
-
if (e.key === "Escape") hide();
|
|
149
|
-
});
|
|
150
|
-
window.addEventListener("blur", () => hide());
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// src/dom.ts
|
|
154
|
-
function el(tag, classNameOrProps, ...children) {
|
|
155
|
-
const node2 = document.createElement(tag);
|
|
156
|
-
const props = typeof classNameOrProps === "string" ? { className: classNameOrProps } : classNameOrProps ?? {};
|
|
157
|
-
if (props.className) node2.className = props.className;
|
|
158
|
-
if (props.text !== void 0 && props.text !== null) node2.textContent = props.text;
|
|
159
|
-
if (props.title !== void 0) node2.title = props.title;
|
|
160
|
-
if (props.tip !== void 0) {
|
|
161
|
-
node2.dataset["tip"] = props.tip;
|
|
162
|
-
node2.setAttribute("aria-label", props.tip);
|
|
163
|
-
ensureTooltipHost();
|
|
164
|
-
}
|
|
165
|
-
if (props.onClick) node2.addEventListener("click", props.onClick);
|
|
166
|
-
for (const child of children) {
|
|
167
|
-
if (child === null || child === void 0) continue;
|
|
168
|
-
node2.append(child);
|
|
169
|
-
}
|
|
170
|
-
checkTooltipHost();
|
|
171
|
-
return node2;
|
|
172
|
-
}
|
|
173
|
-
function iconBtn(glyph, title, onClick, disabled = false, danger = false) {
|
|
174
|
-
const b = el("button", `shell-icon${danger ? " danger" : ""}`);
|
|
175
|
-
b.type = "button";
|
|
176
|
-
b.textContent = glyph;
|
|
177
|
-
b.dataset["tip"] = title;
|
|
178
|
-
b.setAttribute("aria-label", title);
|
|
179
|
-
b.disabled = disabled;
|
|
180
|
-
b.addEventListener("click", onClick);
|
|
181
|
-
return b;
|
|
182
|
-
}
|
|
183
|
-
function labelled(label2, control) {
|
|
184
|
-
const target = control.matches("input, select, textarea") ? control : control.querySelector("input:not([type=button]):not([type=submit]), select, textarea");
|
|
185
|
-
const w = el(target ? "label" : "div", "shell-labelled");
|
|
186
|
-
w.append(el("span", "shell-fieldcap", label2), control);
|
|
187
|
-
if (target) {
|
|
188
|
-
if (!target.id) target.id = `shell-f-${Math.random().toString(36).slice(2, 9)}`;
|
|
189
|
-
w.htmlFor = target.id;
|
|
190
|
-
}
|
|
191
|
-
return w;
|
|
192
|
-
}
|
|
193
|
-
function moveItem(arr, i, delta) {
|
|
194
|
-
const j = i + delta;
|
|
195
|
-
if (j < 0 || j >= arr.length) return false;
|
|
196
|
-
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
function tagChips(holder, onChange) {
|
|
200
|
-
const wrap = el("div", "shell-tags");
|
|
201
|
-
const input = el("input", "shell-tag-input");
|
|
202
|
-
input.type = "text";
|
|
203
|
-
input.placeholder = "Add value";
|
|
204
|
-
input.spellcheck = false;
|
|
205
|
-
const makeChip = (v) => {
|
|
206
|
-
const chip = el("span", "shell-tag", v);
|
|
207
|
-
const x = el("button", "shell-tag-x", "\u2715");
|
|
208
|
-
x.type = "button";
|
|
209
|
-
x.dataset["tip"] = `remove ${v}`;
|
|
210
|
-
x.setAttribute("aria-label", `remove ${v}`);
|
|
211
|
-
x.addEventListener("click", () => {
|
|
212
|
-
holder.values = (holder.values ?? []).filter((o) => o !== v);
|
|
213
|
-
chip.remove();
|
|
214
|
-
onChange?.();
|
|
215
|
-
});
|
|
216
|
-
chip.append(x);
|
|
217
|
-
return chip;
|
|
218
|
-
};
|
|
219
|
-
const commit = () => {
|
|
220
|
-
const v = input.value.trim();
|
|
221
|
-
if (v && !(holder.values ?? []).includes(v)) {
|
|
222
|
-
(holder.values ??= []).push(v);
|
|
223
|
-
wrap.insertBefore(makeChip(v), input);
|
|
224
|
-
onChange?.();
|
|
225
|
-
}
|
|
226
|
-
input.value = "";
|
|
227
|
-
};
|
|
228
|
-
input.addEventListener("keydown", (e) => {
|
|
229
|
-
if (e.key === "Enter" || e.key === ",") {
|
|
230
|
-
e.preventDefault();
|
|
231
|
-
commit();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
input.addEventListener("blur", commit);
|
|
235
|
-
for (const v of holder.values ?? []) wrap.append(makeChip(v));
|
|
236
|
-
wrap.append(input);
|
|
237
|
-
return wrap;
|
|
238
|
-
}
|
|
12
|
+
export { openContextMenu, openPopover } from './chunk-K2AOLQW4.js';
|
|
13
|
+
import { ensureTooltipHost, el, iconNode, labelled, icon } from './chunk-XZYZXY7I.js';
|
|
14
|
+
export { ICON_NAMES, ICON_SIZE, el, hideTip, icon, iconBtn, iconHtml, iconNameOfGlyph, iconNode, iconSvg, initTooltips, isIconName, labelled, moveItem, stageChips, tagChips, tipAt, tipBold, wireReorder } from './chunk-XZYZXY7I.js';
|
|
15
|
+
import { closeWithExit } from './chunk-IU7EY6BI.js';
|
|
16
|
+
export { closeWithExit } from './chunk-IU7EY6BI.js';
|
|
239
17
|
|
|
240
18
|
// src/colour.ts
|
|
241
19
|
var PALETTE_SIZE = 12;
|
|
@@ -257,79 +35,6 @@ function colourFor(name) {
|
|
|
257
35
|
return PALETTE[colourIndex(name)];
|
|
258
36
|
}
|
|
259
37
|
|
|
260
|
-
// src/icons.ts
|
|
261
|
-
var icon = {
|
|
262
|
-
// --- dismissing and revealing ---------------------------------------------
|
|
263
|
-
/** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
|
|
264
|
-
close: "\u2715",
|
|
265
|
-
/** The overflow menu on a document or a row: "more about this". */
|
|
266
|
-
more: "\u22EF",
|
|
267
|
-
/** Collapsed disclosure, pointing at what would open. */
|
|
268
|
-
collapsed: "\u25B8",
|
|
269
|
-
/** Expanded disclosure. */
|
|
270
|
-
expanded: "\u25BE",
|
|
271
|
-
/** A dropdown's affordance, where it is not a real <select>. */
|
|
272
|
-
dropdown: "\u25BE",
|
|
273
|
-
// --- moving through things ------------------------------------------------
|
|
274
|
-
/** Back / previous, in a breadcrumb or a stepper. */
|
|
275
|
-
back: "\u2039",
|
|
276
|
-
/** Forward / next. */
|
|
277
|
-
forward: "\u203A",
|
|
278
|
-
/** Up one, in a list. */
|
|
279
|
-
up: "\u2191",
|
|
280
|
-
/** Down one. */
|
|
281
|
-
down: "\u2193",
|
|
282
|
-
// --- state ----------------------------------------------------------------
|
|
283
|
-
/** Done, applied, agreed. */
|
|
284
|
-
tick: "\u2713",
|
|
285
|
-
/** Somebody else holds this: the version-control lock. */
|
|
286
|
-
locked: "\u2298",
|
|
287
|
-
/** Read-only on disk with no other holder: the resting state of everything
|
|
288
|
-
* in a lock-based working copy that nobody has checked out yet. */
|
|
289
|
-
readOnly: "\u25CB",
|
|
290
|
-
/** YOU hold this one: checked out / opened by the current user, still yours to
|
|
291
|
-
* edit. Shares its glyph with `note`, which the table permits: an entry is a
|
|
292
|
-
* MEANING, and two meanings may draw the same shape. */
|
|
293
|
-
checkedOut: "\u270E",
|
|
294
|
-
/** Tracked, with local changes not yet committed. */
|
|
295
|
-
modified: "\u25CF",
|
|
296
|
-
/** Not in version control yet: new since the last commit. */
|
|
297
|
-
untracked: "+",
|
|
298
|
-
/** Something needs attention but is not fatal. */
|
|
299
|
-
warning: "\u203C",
|
|
300
|
-
/** A live thing: the running position, an active marker. */
|
|
301
|
-
dot: "\u25CF",
|
|
302
|
-
/** Play, in the sense of "run this". */
|
|
303
|
-
play: "\u25B6",
|
|
304
|
-
/** Start the thing again from the top: a play session, a sweep. Named for the
|
|
305
|
-
* act, not the arrow, which is the table's rule. */
|
|
306
|
-
restart: "\u27F2",
|
|
307
|
-
/** The three ways a collection can be shown. Named by what you get rather than
|
|
308
|
-
* by the glyph, so a second app can pick the same three without copying
|
|
309
|
-
* somebody's taste in symbols. */
|
|
310
|
-
viewCards: "\u25A6",
|
|
311
|
-
viewTable: "\u2630",
|
|
312
|
-
viewNode: "\u25C8",
|
|
313
|
-
// --- handling -------------------------------------------------------------
|
|
314
|
-
/** The drag grip on a reorderable row. */
|
|
315
|
-
grip: "\u283F",
|
|
316
|
-
/** Add one of whatever this list holds. */
|
|
317
|
-
add: "+",
|
|
318
|
-
// --- annotation -----------------------------------------------------------
|
|
319
|
-
/** Documentation notes: the pencil. Both apps had chosen it independently,
|
|
320
|
-
* which is the happiest way for a vocabulary entry to arrive. */
|
|
321
|
-
note: "\u270E",
|
|
322
|
-
/** A comment thread. Speech, not a pencil: documentation states the reason,
|
|
323
|
-
* comments are the conversation, and the two must never look alike. */
|
|
324
|
-
comment: "\u275D"
|
|
325
|
-
};
|
|
326
|
-
var iconSvg = {
|
|
327
|
-
/** A document with lines: notes exist on this thing. */
|
|
328
|
-
noteFilled: '<svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M3.4 1.4h5.3L12.6 5.3v8.1a1.2 1.2 0 0 1-1.2 1.2H3.4a1.2 1.2 0 0 1-1.2-1.2V2.6A1.2 1.2 0 0 1 3.4 1.4ZM4.9 6.8h6.2v1.1H4.9Zm0 2.1h6.2v1.1H4.9Zm0 2.1h3.7v1.1H4.9Z"/></svg>',
|
|
329
|
-
/** The same document, empty: nothing written here yet. */
|
|
330
|
-
noteOutline: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linejoin="round" stroke-linecap="round" aria-hidden="true"><path d="M3.4 2h4.9l3.9 3.9v7.5a1 1 0 0 1-1 1H3.4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z"/><path d="M8.3 2.2v3.8h3.8"/><path d="M4.9 8h6.2M4.9 10h6.2M4.9 12h3.7" stroke-width="0.95"/></svg>'
|
|
331
|
-
};
|
|
332
|
-
|
|
333
38
|
// src/pane-shell.ts
|
|
334
39
|
var DEFAULT_MAX = (w) => Math.min(640, w - 420);
|
|
335
40
|
var openVar = { nav: "--nav-open-w", inspector: "--insp-open-w" };
|
|
@@ -342,6 +47,8 @@ function mountPaneShell(host, opts) {
|
|
|
342
47
|
inspector: offered("inspector") && (opts.initial?.open?.inspector ?? true)
|
|
343
48
|
};
|
|
344
49
|
const width = { ...opts.initial?.width ?? {} };
|
|
50
|
+
const held = { nav: false, inspector: false };
|
|
51
|
+
const showing = (side) => open[side] && !held[side];
|
|
345
52
|
const el2 = (tag, cls) => {
|
|
346
53
|
const n = document.createElement(tag);
|
|
347
54
|
if (cls) n.className = cls;
|
|
@@ -372,18 +79,28 @@ function mountPaneShell(host, opts) {
|
|
|
372
79
|
host.append(root);
|
|
373
80
|
if (!offered("nav")) navToggle.remove();
|
|
374
81
|
if (!offered("inspector")) inspToggle.remove();
|
|
375
|
-
|
|
376
|
-
const isOpen = open[side];
|
|
377
|
-
button.textContent = side === "nav" ? isOpen ? "\u2039" : "\u203A" : isOpen ? "\u203A" : "\u2039";
|
|
82
|
+
const defaultTip = (side, isOpen) => {
|
|
378
83
|
const noun = cfg[side].label ?? side;
|
|
379
84
|
const hint = cfg[side].shortcutHint ? ` (${cfg[side].shortcutHint})` : "";
|
|
380
|
-
|
|
381
|
-
|
|
85
|
+
return `${isOpen ? "Hide" : "Show"} ${noun}${hint}`;
|
|
86
|
+
};
|
|
87
|
+
const tipFor = opts.tipFor ?? defaultTip;
|
|
88
|
+
function applyToggleGlyph(side, button) {
|
|
89
|
+
const isOpen = open[side];
|
|
90
|
+
button.replaceChildren(iconNode(side === "nav" ? isOpen ? "back" : "forward" : isOpen ? "forward" : "back"));
|
|
91
|
+
const tip = tipFor(side, isOpen);
|
|
92
|
+
button.dataset["tip"] = tip;
|
|
93
|
+
button.setAttribute("aria-label", tip);
|
|
382
94
|
button.setAttribute("aria-pressed", String(isOpen));
|
|
383
95
|
}
|
|
96
|
+
ensureTooltipHost();
|
|
384
97
|
function apply() {
|
|
385
|
-
|
|
386
|
-
|
|
98
|
+
for (const side of ["nav", "inspector"]) {
|
|
99
|
+
const collapsed = !showing(side);
|
|
100
|
+
panes.classList.toggle(collapseClass[side], collapsed);
|
|
101
|
+
const also = opts.collapseAlso?.[side];
|
|
102
|
+
if (also) panes.classList.toggle(also, collapsed);
|
|
103
|
+
}
|
|
387
104
|
panes.style.setProperty(openVar.nav, width.nav !== void 0 ? `${width.nav}px` : cfg.nav.defaultWidth);
|
|
388
105
|
panes.style.setProperty(openVar.inspector, width.inspector !== void 0 ? `${width.inspector}px` : cfg.inspector.defaultWidth);
|
|
389
106
|
applyToggleGlyph("nav", navToggle);
|
|
@@ -402,6 +119,11 @@ function mountPaneShell(host, opts) {
|
|
|
402
119
|
function togglePane(side) {
|
|
403
120
|
if (offered(side)) setPaneOpen(side, !open[side]);
|
|
404
121
|
}
|
|
122
|
+
function holdClosed(side, next) {
|
|
123
|
+
if (held[side] === next) return;
|
|
124
|
+
held[side] = next;
|
|
125
|
+
apply();
|
|
126
|
+
}
|
|
405
127
|
function resetWidths() {
|
|
406
128
|
delete width.nav;
|
|
407
129
|
delete width.inspector;
|
|
@@ -411,9 +133,11 @@ function mountPaneShell(host, opts) {
|
|
|
411
133
|
navToggle.addEventListener("click", () => togglePane("nav"));
|
|
412
134
|
inspToggle.addEventListener("click", () => togglePane("inspector"));
|
|
413
135
|
function beginResize(side, startEvent) {
|
|
414
|
-
if (cfg[side].resizable === false || !
|
|
136
|
+
if (cfg[side].resizable === false || !showing(side)) return;
|
|
415
137
|
startEvent.preventDefault();
|
|
416
138
|
const pane = side === "nav" ? navPane : inspPane;
|
|
139
|
+
const seam = side === "nav" ? navResizer : inspResizer;
|
|
140
|
+
seam.classList.add("dragging");
|
|
417
141
|
const min = cfg[side].minWidth ?? 160;
|
|
418
142
|
const max = cfg[side].maxWidth ?? DEFAULT_MAX;
|
|
419
143
|
const startX = startEvent.clientX;
|
|
@@ -429,6 +153,7 @@ function mountPaneShell(host, opts) {
|
|
|
429
153
|
window.removeEventListener("pointermove", onMove);
|
|
430
154
|
window.removeEventListener("pointerup", onUp);
|
|
431
155
|
document.body.classList.remove("pane-resizing");
|
|
156
|
+
seam.classList.remove("dragging");
|
|
432
157
|
width[side] = latest;
|
|
433
158
|
persist();
|
|
434
159
|
};
|
|
@@ -458,7 +183,8 @@ function mountPaneShell(host, opts) {
|
|
|
458
183
|
inspector: inspInner,
|
|
459
184
|
togglePane,
|
|
460
185
|
setPaneOpen,
|
|
461
|
-
isOpen:
|
|
186
|
+
isOpen: showing,
|
|
187
|
+
holdClosed,
|
|
462
188
|
resetWidths,
|
|
463
189
|
state: snapshot,
|
|
464
190
|
destroy: () => {
|
|
@@ -467,189 +193,16 @@ function mountPaneShell(host, opts) {
|
|
|
467
193
|
};
|
|
468
194
|
}
|
|
469
195
|
|
|
470
|
-
// src/settings.ts
|
|
471
|
-
function expandableRow(opts) {
|
|
472
|
-
const row = el("div", "set-row");
|
|
473
|
-
if (opts.name !== void 0) row.dataset.name = opts.name;
|
|
474
|
-
const line = el("div", "set-rowline");
|
|
475
|
-
if (opts.details && opts.details.length) {
|
|
476
|
-
const details = el("div", "set-details");
|
|
477
|
-
details.hidden = true;
|
|
478
|
-
for (const d of opts.details) details.append(d);
|
|
479
|
-
const toggle = el("button", "set-expand");
|
|
480
|
-
toggle.type = "button";
|
|
481
|
-
toggle.dataset.tip = "More";
|
|
482
|
-
toggle.setAttribute("aria-label", "More");
|
|
483
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
484
|
-
toggle.addEventListener("click", () => {
|
|
485
|
-
const open = details.hidden;
|
|
486
|
-
details.hidden = !open;
|
|
487
|
-
toggle.setAttribute("aria-expanded", String(open));
|
|
488
|
-
toggle.classList.toggle("open", open);
|
|
489
|
-
});
|
|
490
|
-
line.append(toggle);
|
|
491
|
-
opts.line.forEach((c) => line.append(c));
|
|
492
|
-
row.append(line, details);
|
|
493
|
-
} else {
|
|
494
|
-
opts.line.forEach((c) => line.append(c));
|
|
495
|
-
row.append(line);
|
|
496
|
-
}
|
|
497
|
-
return row;
|
|
498
|
-
}
|
|
499
|
-
function revealRow(within, name) {
|
|
500
|
-
const row = [...within.querySelectorAll(".set-row")].find((r) => r.dataset.name === name);
|
|
501
|
-
if (!row) return false;
|
|
502
|
-
const toggle = row.querySelector(".set-rowline > .set-expand");
|
|
503
|
-
if (toggle && toggle.getAttribute("aria-expanded") !== "true") toggle.click();
|
|
504
|
-
const still = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
505
|
-
row.scrollIntoView({ block: "center", behavior: still ? "auto" : "smooth" });
|
|
506
|
-
row.classList.remove("landed");
|
|
507
|
-
void row.offsetWidth;
|
|
508
|
-
row.classList.add("landed");
|
|
509
|
-
row.addEventListener("animationend", () => row.classList.remove("landed"), { once: true });
|
|
510
|
-
return true;
|
|
511
|
-
}
|
|
512
|
-
function focusNewRow(listEl) {
|
|
513
|
-
const last = listEl?.lastElementChild;
|
|
514
|
-
if (!(last instanceof HTMLElement)) return;
|
|
515
|
-
last.scrollIntoView({ block: "nearest" });
|
|
516
|
-
const name = last.querySelector("input.set-name") ?? last.querySelector("input");
|
|
517
|
-
name?.focus();
|
|
518
|
-
}
|
|
519
|
-
function dupGuard() {
|
|
520
|
-
const items = [];
|
|
521
|
-
const check = () => {
|
|
522
|
-
const counts = /* @__PURE__ */ new Map();
|
|
523
|
-
for (const it of items) {
|
|
524
|
-
const k = it.key().trim().toLowerCase();
|
|
525
|
-
if (k) counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
526
|
-
}
|
|
527
|
-
let any = false;
|
|
528
|
-
for (const it of items) {
|
|
529
|
-
const k = it.key().trim().toLowerCase();
|
|
530
|
-
const dup = !!k && (counts.get(k) ?? 0) > 1;
|
|
531
|
-
it.input.classList.toggle("invalid", dup);
|
|
532
|
-
if (dup) {
|
|
533
|
-
it.input.title = "Duplicate name. Names must be unique.";
|
|
534
|
-
any = true;
|
|
535
|
-
} else if (it.input.title === "Duplicate name. Names must be unique.") it.input.removeAttribute("title");
|
|
536
|
-
}
|
|
537
|
-
return any;
|
|
538
|
-
};
|
|
539
|
-
return {
|
|
540
|
-
reset() {
|
|
541
|
-
items.length = 0;
|
|
542
|
-
},
|
|
543
|
-
track(input, key = () => input.value, also) {
|
|
544
|
-
items.push({ input, key });
|
|
545
|
-
input.addEventListener("input", check);
|
|
546
|
-
for (const a of also ?? []) a.addEventListener("input", check);
|
|
547
|
-
},
|
|
548
|
-
check,
|
|
549
|
-
firstDuplicate() {
|
|
550
|
-
check();
|
|
551
|
-
return items.find((it) => it.input.classList.contains("invalid"))?.input ?? null;
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
function mountSettingsDialog(opts) {
|
|
556
|
-
const dialog = el("dialog", "settings-dialog");
|
|
557
|
-
const form = el("form", "settings-form");
|
|
558
|
-
form.method = "dialog";
|
|
559
|
-
const tabs = el("nav", "settings-tabs");
|
|
560
|
-
const panels = el("div", "settings-panels");
|
|
561
|
-
const error = el("p", "settings-error");
|
|
562
|
-
error.hidden = true;
|
|
563
|
-
const cancel = el("button", "settings-cancel", "Cancel");
|
|
564
|
-
cancel.type = "button";
|
|
565
|
-
cancel.value = "cancel";
|
|
566
|
-
const save = el("button", "settings-save primary", "Save");
|
|
567
|
-
save.value = "save";
|
|
568
|
-
const panelFor = /* @__PURE__ */ new Map();
|
|
569
|
-
const tabFor = /* @__PURE__ */ new Map();
|
|
570
|
-
const handles = /* @__PURE__ */ new Map();
|
|
571
|
-
let active2 = opts.sections[0]?.id ?? "";
|
|
572
|
-
let lastGroup;
|
|
573
|
-
for (const s of opts.sections) {
|
|
574
|
-
if (s.group && s.group !== lastGroup) {
|
|
575
|
-
tabs.append(el("div", "settings-group", s.group));
|
|
576
|
-
lastGroup = s.group;
|
|
577
|
-
}
|
|
578
|
-
const tab = el("button", "settings-tab", s.label);
|
|
579
|
-
tab.type = "button";
|
|
580
|
-
tab.dataset["tab"] = s.id;
|
|
581
|
-
tab.addEventListener("click", () => showTab(s.id));
|
|
582
|
-
tabs.append(tab);
|
|
583
|
-
tabFor.set(s.id, tab);
|
|
584
|
-
const panel = el("section", "settings-panel");
|
|
585
|
-
panel.dataset["panel"] = s.id;
|
|
586
|
-
panel.hidden = true;
|
|
587
|
-
panels.append(panel);
|
|
588
|
-
panelFor.set(s.id, panel);
|
|
589
|
-
}
|
|
590
|
-
function showTab(id) {
|
|
591
|
-
active2 = id;
|
|
592
|
-
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
593
|
-
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
594
|
-
}
|
|
595
|
-
form.append(
|
|
596
|
-
el("h2", "settings-title", opts.title),
|
|
597
|
-
el("div", "settings-body", tabs, panels),
|
|
598
|
-
el("div", "settings-actions", error, cancel, save)
|
|
599
|
-
);
|
|
600
|
-
dialog.append(form);
|
|
601
|
-
document.body.append(dialog);
|
|
602
|
-
form.addEventListener("keydown", (e) => {
|
|
603
|
-
if (e.key === "Enter" && e.target?.tagName === "INPUT") e.preventDefault();
|
|
604
|
-
});
|
|
605
|
-
cancel.addEventListener("click", () => dialog.close("cancel"));
|
|
606
|
-
save.addEventListener("click", (e) => {
|
|
607
|
-
e.preventDefault();
|
|
608
|
-
error.hidden = true;
|
|
609
|
-
for (const s of opts.sections) {
|
|
610
|
-
const bad = handles.get(s.id)?.firstInvalid?.();
|
|
611
|
-
if (bad) {
|
|
612
|
-
showTab(s.id);
|
|
613
|
-
bad.focus();
|
|
614
|
-
error.textContent = "Fix the highlighted fields first.";
|
|
615
|
-
error.hidden = false;
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
void (async () => {
|
|
620
|
-
await opts.onSave();
|
|
621
|
-
dialog.close("save");
|
|
622
|
-
})();
|
|
623
|
-
});
|
|
624
|
-
return {
|
|
625
|
-
open(tabId) {
|
|
626
|
-
handles.clear();
|
|
627
|
-
for (const s of opts.sections) {
|
|
628
|
-
const panel = panelFor.get(s.id);
|
|
629
|
-
panel.replaceChildren();
|
|
630
|
-
handles.set(s.id, s.mount(panel));
|
|
631
|
-
}
|
|
632
|
-
error.hidden = true;
|
|
633
|
-
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
634
|
-
dialog.showModal();
|
|
635
|
-
},
|
|
636
|
-
destroy() {
|
|
637
|
-
dialog.remove();
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
196
|
// src/tool-window-web.ts
|
|
643
|
-
var PIN_ICON = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="17" x2="12" y2="22"/><path d="M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.3V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.7a2 2 0 0 0-1.1-1.8l-1.8-.9A2 2 0 0 1 15 10.8V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>`;
|
|
644
197
|
function pinButton(opts) {
|
|
645
198
|
const b = el("button", "swin-pin");
|
|
646
199
|
b.type = "button";
|
|
647
|
-
b.
|
|
200
|
+
b.append(iconNode("pin"));
|
|
648
201
|
let pinned = opts.pinned;
|
|
649
202
|
const reflect = () => {
|
|
650
203
|
b.classList.toggle("on", pinned);
|
|
651
204
|
b.setAttribute("aria-pressed", String(pinned));
|
|
652
|
-
const label2 = pinned ? "Pinned on top
|
|
205
|
+
const label2 = pinned ? "Pinned on top. Click to unpin." : "Keep on top";
|
|
653
206
|
b.dataset["tip"] = label2;
|
|
654
207
|
b.setAttribute("aria-label", label2);
|
|
655
208
|
};
|
|
@@ -664,66 +217,91 @@ function pinButton(opts) {
|
|
|
664
217
|
reflect();
|
|
665
218
|
} };
|
|
666
219
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
function
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
return;
|
|
220
|
+
var escSlot;
|
|
221
|
+
var escWired = false;
|
|
222
|
+
function wireEsc() {
|
|
223
|
+
if (escWired) return;
|
|
224
|
+
escWired = true;
|
|
225
|
+
window.addEventListener("keydown", (e) => {
|
|
226
|
+
if (e.key !== "Escape" || !escSlot) return;
|
|
227
|
+
if (e.defaultPrevented) return;
|
|
228
|
+
if (!escSlot.fromFields && isEditableTarget(e.target)) return;
|
|
229
|
+
if (escSlot.onEscape?.() === true) return;
|
|
230
|
+
escSlot.onClose();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function toolWindowHead(opts) {
|
|
234
|
+
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
235
|
+
const pin = opts.pin ?? pinButton({ pinned: opts.pinned ?? true, onToggle: opts.onPin ?? (() => {
|
|
236
|
+
}) });
|
|
237
|
+
if (opts.esc !== false) {
|
|
238
|
+
escSlot = { onClose: opts.onClose, onEscape: opts.onEscape, fromFields: opts.esc === "always" };
|
|
239
|
+
wireEsc();
|
|
675
240
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
241
|
+
return el(
|
|
242
|
+
"header",
|
|
243
|
+
{ className: `swin-head${opts.className ? ` ${opts.className}` : ""}` },
|
|
244
|
+
...opts.title !== void 0 ? [el("span", { className: "swin-title", text: opts.title })] : [],
|
|
245
|
+
...opts.tabs ? [opts.tabs] : [],
|
|
246
|
+
...keep(opts.lead),
|
|
247
|
+
el("span", { className: "swin-spacer" }),
|
|
248
|
+
...keep(opts.trail),
|
|
249
|
+
pin.el,
|
|
250
|
+
el("button", { className: "swin-close", tip: "Close (Esc)", onClick: opts.onClose }, iconNode("close"))
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
function followButton(opts) {
|
|
254
|
+
const what = opts.what ?? "line";
|
|
255
|
+
const b = el("button", "btn swin-follow", "Follow in the editor");
|
|
256
|
+
b.type = "button";
|
|
257
|
+
let on = opts.on;
|
|
258
|
+
const reflect = () => {
|
|
259
|
+
b.classList.toggle("on", on);
|
|
260
|
+
b.setAttribute("aria-pressed", String(on));
|
|
261
|
+
const label2 = on ? `The editor opens each ${what} as it plays. Click to stop.` : `Open each ${what} in the editor as it plays`;
|
|
262
|
+
b.dataset["tip"] = label2;
|
|
686
263
|
};
|
|
687
|
-
|
|
688
|
-
|
|
264
|
+
reflect();
|
|
265
|
+
b.addEventListener("click", () => {
|
|
266
|
+
on = !on;
|
|
267
|
+
reflect();
|
|
268
|
+
opts.onToggle(on);
|
|
269
|
+
});
|
|
270
|
+
return { el: b, set: (next) => {
|
|
271
|
+
on = next;
|
|
272
|
+
reflect();
|
|
273
|
+
} };
|
|
689
274
|
}
|
|
690
275
|
|
|
691
276
|
// src/confirm.ts
|
|
692
277
|
function confirmDialog(opts) {
|
|
693
278
|
return new Promise((resolve) => {
|
|
694
|
-
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
695
|
-
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
696
|
-
cancel.type = "button";
|
|
697
|
-
danger.type = "button";
|
|
698
|
-
const dlg = el(
|
|
699
|
-
"dialog",
|
|
700
|
-
"confirm-dialog",
|
|
701
|
-
el("div", "confirm-title", opts.title),
|
|
702
|
-
el("div", "confirm-body", opts.body),
|
|
703
|
-
el("div", "confirm-actions", cancel, danger)
|
|
704
|
-
);
|
|
705
|
-
dlg.setAttribute("aria-label", opts.title);
|
|
706
|
-
document.body.append(dlg);
|
|
707
279
|
let done = false;
|
|
708
280
|
const finish = (ok) => {
|
|
709
281
|
if (done) return;
|
|
710
282
|
done = true;
|
|
711
283
|
resolve(ok);
|
|
712
|
-
|
|
713
|
-
dlg.close();
|
|
714
|
-
dlg.remove();
|
|
715
|
-
});
|
|
284
|
+
frame.close();
|
|
716
285
|
};
|
|
286
|
+
const frame = dialogFrame({
|
|
287
|
+
title: opts.title,
|
|
288
|
+
className: "confirm-dialog",
|
|
289
|
+
onClose: () => finish(false)
|
|
290
|
+
// Esc, or closed by someone else
|
|
291
|
+
});
|
|
292
|
+
const cancel = el("button", "btn confirm-btn cancel", "Cancel");
|
|
293
|
+
const danger = el("button", "btn danger confirm-btn", opts.confirmLabel);
|
|
294
|
+
cancel.type = "button";
|
|
295
|
+
danger.type = "button";
|
|
296
|
+
if (opts.body !== void 0) frame.body.append(el("div", "confirm-body", opts.body));
|
|
297
|
+
if (opts.bodyNode) frame.body.append(opts.bodyNode);
|
|
298
|
+
frame.actions.append(cancel, danger);
|
|
717
299
|
cancel.addEventListener("click", () => finish(false));
|
|
718
300
|
danger.addEventListener("click", () => finish(true));
|
|
719
|
-
|
|
720
|
-
e.
|
|
721
|
-
finish(false);
|
|
301
|
+
frame.dialog.addEventListener("mousedown", (e) => {
|
|
302
|
+
if (e.target === frame.dialog) finish(false);
|
|
722
303
|
});
|
|
723
|
-
|
|
724
|
-
if (e.target === dlg) finish(false);
|
|
725
|
-
});
|
|
726
|
-
dlg.showModal();
|
|
304
|
+
frame.open();
|
|
727
305
|
cancel.focus();
|
|
728
306
|
});
|
|
729
307
|
}
|
|
@@ -731,18 +309,16 @@ function confirmDialog(opts) {
|
|
|
731
309
|
// src/about.ts
|
|
732
310
|
function showAbout(opts) {
|
|
733
311
|
return new Promise((resolve) => {
|
|
734
|
-
const
|
|
312
|
+
const frame = dialogFrame({ title: opts.appName, className: "shell-about", onClose: () => resolve() });
|
|
313
|
+
frame.dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
735
314
|
if (opts.wordmark !== void 0) {
|
|
736
315
|
const mark = el("div", "shell-about-mark");
|
|
737
316
|
mark.innerHTML = opts.wordmark;
|
|
738
|
-
dialog.
|
|
317
|
+
frame.dialog.prepend(mark);
|
|
739
318
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
);
|
|
744
|
-
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
745
|
-
if (opts.credits !== void 0) dialog.append(el("p", "shell-about-credits", opts.credits));
|
|
319
|
+
frame.body.append(el("div", "shell-about-version", `Version ${opts.version}`));
|
|
320
|
+
if (opts.blurb !== void 0) frame.body.append(el("p", "shell-about-blurb", opts.blurb));
|
|
321
|
+
if (opts.credits !== void 0) frame.body.append(el("p", "shell-about-credits", opts.credits));
|
|
746
322
|
if (opts.links?.length) {
|
|
747
323
|
const row = el("div", "shell-about-links");
|
|
748
324
|
for (const link of opts.links) {
|
|
@@ -755,24 +331,13 @@ function showAbout(opts) {
|
|
|
755
331
|
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
756
332
|
row.append(a);
|
|
757
333
|
}
|
|
758
|
-
|
|
334
|
+
frame.body.append(row);
|
|
759
335
|
}
|
|
760
|
-
const close = el("button", "shell-about-close", "Close");
|
|
336
|
+
const close = el("button", "btn shell-about-close", "Close");
|
|
761
337
|
close.type = "button";
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
let done = false;
|
|
766
|
-
const finish = () => {
|
|
767
|
-
if (done) return;
|
|
768
|
-
done = true;
|
|
769
|
-
dialog.close();
|
|
770
|
-
dialog.remove();
|
|
771
|
-
resolve();
|
|
772
|
-
};
|
|
773
|
-
close.addEventListener("click", finish);
|
|
774
|
-
dialog.addEventListener("close", finish);
|
|
775
|
-
dialog.showModal();
|
|
338
|
+
close.addEventListener("click", () => frame.close());
|
|
339
|
+
frame.actions.append(close);
|
|
340
|
+
frame.open();
|
|
776
341
|
close.focus();
|
|
777
342
|
});
|
|
778
343
|
}
|
|
@@ -787,7 +352,7 @@ function openAnchoredPanel(opts) {
|
|
|
787
352
|
if (prev.anchor === opts.anchor) return null;
|
|
788
353
|
}
|
|
789
354
|
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
790
|
-
const closeBtn = el("button", "shell-anchored-close", "
|
|
355
|
+
const closeBtn = el("button", "shell-anchored-close", iconNode("close", 12));
|
|
791
356
|
closeBtn.type = "button";
|
|
792
357
|
closeBtn.setAttribute("aria-label", "Close");
|
|
793
358
|
const body = el("div", "shell-anchored-body");
|
|
@@ -795,7 +360,7 @@ function openAnchoredPanel(opts) {
|
|
|
795
360
|
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
796
361
|
body
|
|
797
362
|
);
|
|
798
|
-
document.body.append(panel);
|
|
363
|
+
(opts.host ?? document.body).append(panel);
|
|
799
364
|
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
800
365
|
let closed = false;
|
|
801
366
|
const close = () => {
|
|
@@ -835,7 +400,7 @@ function closeAnchoredPanel() {
|
|
|
835
400
|
}
|
|
836
401
|
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
837
402
|
const a = anchor.getBoundingClientRect();
|
|
838
|
-
const
|
|
403
|
+
const GAP = 10;
|
|
839
404
|
const CAP = window.innerHeight * 0.7;
|
|
840
405
|
const MIN_H = 140;
|
|
841
406
|
panel.style.width = `${width}px`;
|
|
@@ -843,7 +408,7 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
843
408
|
const centred = a.left + a.width / 2 - width / 2;
|
|
844
409
|
panel.style.left = `${Math.round(Math.min(Math.max(8, centred), window.innerWidth - width - 8))}px`;
|
|
845
410
|
} else {
|
|
846
|
-
const leftOf = a.left - width -
|
|
411
|
+
const leftOf = a.left - width - GAP;
|
|
847
412
|
const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
|
|
848
413
|
panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
|
|
849
414
|
}
|
|
@@ -864,15 +429,15 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
864
429
|
return;
|
|
865
430
|
}
|
|
866
431
|
}
|
|
867
|
-
const roomBelow = bottomLimit - (a.bottom +
|
|
868
|
-
const roomAbove = a.top -
|
|
432
|
+
const roomBelow = bottomLimit - (a.bottom + GAP);
|
|
433
|
+
const roomAbove = a.top - GAP - 8;
|
|
869
434
|
if (roomAbove > roomBelow) {
|
|
870
435
|
panel.style.top = "auto";
|
|
871
|
-
panel.style.bottom = `${Math.round(window.innerHeight - (a.top -
|
|
436
|
+
panel.style.bottom = `${Math.round(window.innerHeight - (a.top - GAP))}px`;
|
|
872
437
|
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomAbove)))}px`;
|
|
873
438
|
} else {
|
|
874
439
|
panel.style.bottom = "auto";
|
|
875
|
-
panel.style.top = `${Math.round(Math.max(8, a.bottom +
|
|
440
|
+
panel.style.top = `${Math.round(Math.max(8, a.bottom + GAP))}px`;
|
|
876
441
|
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomBelow)))}px`;
|
|
877
442
|
}
|
|
878
443
|
}
|
|
@@ -885,164 +450,6 @@ function isValidGameId(gameId) {
|
|
|
885
450
|
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
|
|
886
451
|
}
|
|
887
452
|
|
|
888
|
-
// src/property-names.ts
|
|
889
|
-
var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
|
|
890
|
-
var PROPERTY_NAME_HINT = "Lower case letters, digits and underscores. Cannot start with a digit. No hyphens or spaces.";
|
|
891
|
-
function propertyNameify(text) {
|
|
892
|
-
const trimmed = text.trim();
|
|
893
|
-
const deliberateLeading = trimmed.startsWith("_");
|
|
894
|
-
let out = trimmed.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
895
|
-
if (out === "") return "";
|
|
896
|
-
if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;
|
|
897
|
-
if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;
|
|
898
|
-
return out;
|
|
899
|
-
}
|
|
900
|
-
function isValidPropertyName(name) {
|
|
901
|
-
return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);
|
|
902
|
-
}
|
|
903
|
-
function isCaseOnlyPropertyName(name) {
|
|
904
|
-
return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// src/property-name-field.ts
|
|
908
|
-
var refSyncs = /* @__PURE__ */ new WeakMap();
|
|
909
|
-
function propertyNameProblem(name) {
|
|
910
|
-
if (isValidPropertyName(name)) return void 0;
|
|
911
|
-
const suggestion = propertyNameify(name);
|
|
912
|
-
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
913
|
-
if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
|
|
914
|
-
return `Cannot be used: "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
|
|
915
|
-
}
|
|
916
|
-
if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
|
|
917
|
-
if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
|
|
918
|
-
if (name !== name.toLowerCase()) {
|
|
919
|
-
return `Cannot be used: expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
|
|
920
|
-
}
|
|
921
|
-
return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
|
|
922
|
-
}
|
|
923
|
-
function bindPropertyName(input, onCommit, opts = {}) {
|
|
924
|
-
const hint = opts.hint;
|
|
925
|
-
const sync = () => {
|
|
926
|
-
const v = input.value.trim();
|
|
927
|
-
const problem = v === "" ? void 0 : propertyNameProblem(v);
|
|
928
|
-
input.classList.toggle("illegal", !!problem);
|
|
929
|
-
if (problem) input.title = problem;
|
|
930
|
-
else if (input.title.startsWith("Cannot be used")) {
|
|
931
|
-
if (hint) input.title = hint;
|
|
932
|
-
else input.removeAttribute("title");
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
input.addEventListener("input", () => {
|
|
936
|
-
onCommit(input.value);
|
|
937
|
-
sync();
|
|
938
|
-
});
|
|
939
|
-
input.addEventListener("keydown", (e) => {
|
|
940
|
-
if (e.key !== "Tab" || e.shiftKey) return;
|
|
941
|
-
const typed = input.value.trim();
|
|
942
|
-
if (typed === "" || isValidPropertyName(typed)) return;
|
|
943
|
-
const coerced = propertyNameify(typed);
|
|
944
|
-
if (!coerced || coerced === input.value) return;
|
|
945
|
-
e.preventDefault();
|
|
946
|
-
input.value = coerced;
|
|
947
|
-
onCommit(coerced);
|
|
948
|
-
sync();
|
|
949
|
-
});
|
|
950
|
-
sync();
|
|
951
|
-
}
|
|
952
|
-
function firstIllegalPropertyName(host) {
|
|
953
|
-
return host.querySelector("input.illegal");
|
|
954
|
-
}
|
|
955
|
-
function nearest(name, known) {
|
|
956
|
-
const n = name.toLowerCase();
|
|
957
|
-
const byPrefix = known.filter((k) => k.toLowerCase().startsWith(n) || n.startsWith(k.toLowerCase()));
|
|
958
|
-
if (byPrefix.length === 1) return byPrefix[0];
|
|
959
|
-
let best;
|
|
960
|
-
let bestScore = 3;
|
|
961
|
-
for (const k of known) {
|
|
962
|
-
const a = k.toLowerCase();
|
|
963
|
-
if (Math.abs(a.length - n.length) > 2) continue;
|
|
964
|
-
let prev = Array.from({ length: n.length + 1 }, (_, i) => i);
|
|
965
|
-
for (let i = 1; i <= a.length; i++) {
|
|
966
|
-
const row = [i];
|
|
967
|
-
for (let j = 1; j <= n.length; j++) {
|
|
968
|
-
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === n[j - 1] ? 0 : 1));
|
|
969
|
-
}
|
|
970
|
-
prev = row;
|
|
971
|
-
}
|
|
972
|
-
const d = prev[n.length];
|
|
973
|
-
if (d < bestScore) {
|
|
974
|
-
bestScore = d;
|
|
975
|
-
best = k;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
return best;
|
|
979
|
-
}
|
|
980
|
-
function propertyRefProblem(name, opts) {
|
|
981
|
-
const scope = opts.scope ? `@${opts.scope}` : "that scope";
|
|
982
|
-
const folded = name.toLowerCase();
|
|
983
|
-
if (!isValidPropertyName(folded)) {
|
|
984
|
-
const suggestion = propertyNameify(name);
|
|
985
|
-
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
986
|
-
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Cannot be used: "${folded}" is a keyword in expressions.${tail}`;
|
|
987
|
-
if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
|
|
988
|
-
if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
|
|
989
|
-
return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
|
|
990
|
-
}
|
|
991
|
-
const known = opts.known();
|
|
992
|
-
if (known.some((k) => k.toLowerCase() === folded)) return void 0;
|
|
993
|
-
const near = nearest(folded, known);
|
|
994
|
-
if (near) return `No ${scope} property is called "${name}". Did you mean "${near}"?`;
|
|
995
|
-
if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
|
|
996
|
-
const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
|
|
997
|
-
const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
|
|
998
|
-
return `No ${scope} property is called "${name}". Declared: ${shown}${more}.`;
|
|
999
|
-
}
|
|
1000
|
-
function bindPropertyRef(input, onCommit, opts) {
|
|
1001
|
-
const list = document.createElement("datalist");
|
|
1002
|
-
list.id = `shell-propref-${Math.random().toString(36).slice(2, 9)}`;
|
|
1003
|
-
input.setAttribute("list", list.id);
|
|
1004
|
-
const attach = () => {
|
|
1005
|
-
if (!list.isConnected && input.parentNode) input.after(list);
|
|
1006
|
-
};
|
|
1007
|
-
const sync = () => {
|
|
1008
|
-
attach();
|
|
1009
|
-
const known = opts.known();
|
|
1010
|
-
list.replaceChildren(...known.map((k) => {
|
|
1011
|
-
const o = document.createElement("option");
|
|
1012
|
-
o.value = k;
|
|
1013
|
-
return o;
|
|
1014
|
-
}));
|
|
1015
|
-
const v = input.value.trim();
|
|
1016
|
-
const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
|
|
1017
|
-
input.classList.toggle("illegal", !!problem);
|
|
1018
|
-
if (problem) input.title = problem;
|
|
1019
|
-
else if (input.title.startsWith("Cannot be used") || input.title.startsWith("No ")) {
|
|
1020
|
-
if (opts.hint) input.title = opts.hint;
|
|
1021
|
-
else input.removeAttribute("title");
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
refSyncs.set(input, sync);
|
|
1025
|
-
input.addEventListener("input", () => {
|
|
1026
|
-
onCommit(input.value);
|
|
1027
|
-
sync();
|
|
1028
|
-
});
|
|
1029
|
-
input.addEventListener("keydown", (e) => {
|
|
1030
|
-
if (e.key !== "Tab" || e.shiftKey) return;
|
|
1031
|
-
const typed = input.value.trim();
|
|
1032
|
-
if (typed === "" || isValidPropertyName(typed.toLowerCase())) return;
|
|
1033
|
-
const coerced = propertyNameify(typed);
|
|
1034
|
-
if (!coerced || coerced === input.value) return;
|
|
1035
|
-
e.preventDefault();
|
|
1036
|
-
input.value = coerced;
|
|
1037
|
-
onCommit(coerced);
|
|
1038
|
-
sync();
|
|
1039
|
-
});
|
|
1040
|
-
sync();
|
|
1041
|
-
}
|
|
1042
|
-
function revalidatePropertyRefs(host) {
|
|
1043
|
-
for (const input of host.querySelectorAll("input")) refSyncs.get(input)?.();
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
453
|
// src/id-editor.ts
|
|
1047
454
|
function openGameIdEditor(opts) {
|
|
1048
455
|
const slugify = opts.slugify ?? gameIdify;
|
|
@@ -1059,9 +466,9 @@ function openGameIdEditor(opts) {
|
|
|
1059
466
|
const input = el("input", "shell-id-input");
|
|
1060
467
|
input.type = "text";
|
|
1061
468
|
input.value = opts.value;
|
|
1062
|
-
input.placeholder = opts.derived || "
|
|
469
|
+
input.placeholder = opts.derived || "game-id";
|
|
1063
470
|
input.spellcheck = false;
|
|
1064
|
-
const hint = el("div", "shell-id-hint", "Lower case, digits, hyphens.
|
|
471
|
+
const hint = el("div", "shell-id-hint", "Lower case letters, digits, and hyphens. Leave it empty to derive one from the name.");
|
|
1065
472
|
const actions = el("div", "shell-id-actions");
|
|
1066
473
|
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
1067
474
|
const set = el("button", "shell-id-set", "Set");
|
|
@@ -1123,12 +530,13 @@ function foldVc(shards, keys) {
|
|
|
1123
530
|
return out;
|
|
1124
531
|
}
|
|
1125
532
|
function vcBadgeFor(s) {
|
|
1126
|
-
|
|
1127
|
-
if (s?.
|
|
1128
|
-
if (s?.
|
|
1129
|
-
if (s?.
|
|
1130
|
-
if (s?.
|
|
1131
|
-
if (s
|
|
533
|
+
const badge = (name, cls, title) => ({ name, glyph: icon[name] ?? "", cls, title });
|
|
534
|
+
if (s?.lockedBy?.length) return badge("locked", "vc-locked", `Locked by ${s.lockedBy.join(", ")}`);
|
|
535
|
+
if (s?.outOfDate) return badge("down", "vc-stale", "Out of date. A newer version is on the server.");
|
|
536
|
+
if (s?.checkedOutByMe) return badge("checkedOut", "vc-mine", "Checked out by you");
|
|
537
|
+
if (s?.dirty) return badge("modified", "vc-dirty", "Modified. Local changes aren't committed yet.");
|
|
538
|
+
if (s?.untracked) return badge("untracked", "vc-new", "New. Not in version control yet.");
|
|
539
|
+
if (s && !s.writable) return badge("readOnly", "vc-frozen", "Read-only on disk. Saving checks it out.");
|
|
1132
540
|
return null;
|
|
1133
541
|
}
|
|
1134
542
|
function paintVcBadges(root, shards) {
|
|
@@ -1136,7 +544,7 @@ function paintVcBadges(root, shards) {
|
|
|
1136
544
|
host.querySelector(":scope > .vc-badge")?.remove();
|
|
1137
545
|
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
1138
546
|
if (!badge) return;
|
|
1139
|
-
const span = el("span", { className: `vc-badge ${badge.cls}`,
|
|
547
|
+
const span = el("span", { className: `vc-badge ${badge.cls}`, tip: badge.title }, iconNode(badge.name, 12));
|
|
1140
548
|
host.append(span);
|
|
1141
549
|
});
|
|
1142
550
|
}
|
|
@@ -1159,8 +567,8 @@ function lockNotice(holders) {
|
|
|
1159
567
|
return el(
|
|
1160
568
|
"div",
|
|
1161
569
|
{ className: "vc-lock" },
|
|
1162
|
-
el("span", { className: "vc-lock-glyph",
|
|
1163
|
-
el("span", { text: `Locked by ${holders.join(", ")} -
|
|
570
|
+
el("span", { className: "vc-lock-glyph" }, iconNode("locked")),
|
|
571
|
+
el("span", { text: `Locked by ${holders.join(", ")}. Read-only until they release it.` })
|
|
1164
572
|
);
|
|
1165
573
|
}
|
|
1166
574
|
|
|
@@ -1168,41 +576,36 @@ function lockNotice(holders) {
|
|
|
1168
576
|
function askIdentity(opts = {}) {
|
|
1169
577
|
return new Promise((resolve) => {
|
|
1170
578
|
const seed = opts.current ?? opts.suggested;
|
|
1171
|
-
const name = el("input", "shell-ident-input");
|
|
579
|
+
const name = el("input", "field shell-ident-input");
|
|
1172
580
|
name.placeholder = "Your name";
|
|
1173
581
|
name.value = seed?.name ?? "";
|
|
1174
|
-
const email = el("input", "shell-ident-input");
|
|
1175
|
-
email.placeholder = "
|
|
582
|
+
const email = el("input", "field shell-ident-input");
|
|
583
|
+
email.placeholder = "Optional";
|
|
1176
584
|
email.type = "email";
|
|
1177
585
|
email.value = seed?.email ?? "";
|
|
1178
|
-
const skip = el("button", "shell-ident-btn", "Skip");
|
|
1179
|
-
const save = el("button", "shell-ident-btn
|
|
586
|
+
const skip = el("button", "btn shell-ident-btn", "Skip");
|
|
587
|
+
const save = el("button", "btn primary shell-ident-btn", opts.mode === "welcome" ? "Continue" : "Save");
|
|
1180
588
|
skip.type = "button";
|
|
1181
589
|
save.type = "button";
|
|
1182
|
-
const dialog = el(
|
|
1183
|
-
"dialog",
|
|
1184
|
-
"shell-ident",
|
|
1185
|
-
el("div", "shell-ident-title", "Who is working here?"),
|
|
1186
|
-
el(
|
|
1187
|
-
"p",
|
|
1188
|
-
"shell-ident-hint",
|
|
1189
|
-
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
1190
|
-
),
|
|
1191
|
-
el("div", "shell-ident-fields", name, email),
|
|
1192
|
-
el("div", "shell-ident-actions", skip, save)
|
|
1193
|
-
);
|
|
1194
|
-
document.body.append(dialog);
|
|
1195
590
|
let done = false;
|
|
1196
591
|
const finish = (keep) => {
|
|
1197
592
|
if (done) return;
|
|
1198
593
|
done = true;
|
|
1199
594
|
const typed = name.value.trim();
|
|
1200
595
|
const value = email.value.trim();
|
|
1201
|
-
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
1202
|
-
dialog.close();
|
|
1203
|
-
dialog.remove();
|
|
596
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : opts.neverAskAgainOnSkip === true ? { name: "" } : void 0;
|
|
1204
597
|
resolve(answer);
|
|
598
|
+
frame.close();
|
|
1205
599
|
};
|
|
600
|
+
const frame = dialogFrame({
|
|
601
|
+
title: titleFor(opts),
|
|
602
|
+
sub: subFor(opts),
|
|
603
|
+
className: "shell-ident",
|
|
604
|
+
onClose: () => finish(false)
|
|
605
|
+
// Escape, or closed by someone else: a skip
|
|
606
|
+
});
|
|
607
|
+
frame.body.append(el("div", "shell-ident-fields", labelled("Name", name), labelled("Email", email)));
|
|
608
|
+
frame.actions.append(skip, save);
|
|
1206
609
|
skip.addEventListener("click", () => finish(false));
|
|
1207
610
|
save.addEventListener("click", () => finish(true));
|
|
1208
611
|
name.addEventListener("keydown", (e) => {
|
|
@@ -1211,11 +614,19 @@ function askIdentity(opts = {}) {
|
|
|
1211
614
|
email.addEventListener("keydown", (e) => {
|
|
1212
615
|
if (e.key === "Enter") finish(true);
|
|
1213
616
|
});
|
|
1214
|
-
|
|
1215
|
-
dialog.showModal();
|
|
617
|
+
frame.open();
|
|
1216
618
|
name.focus();
|
|
1217
619
|
});
|
|
1218
620
|
}
|
|
621
|
+
function titleFor(opts) {
|
|
622
|
+
if (opts.mode === "welcome") return opts.appName ? `Welcome to ${opts.appName}` : "Welcome";
|
|
623
|
+
if (opts.mode === "edit") return "User information";
|
|
624
|
+
return "Who is working here?";
|
|
625
|
+
}
|
|
626
|
+
function subFor(opts) {
|
|
627
|
+
const why = "Your name goes on the comments you write. It's kept in this app, not in the project.";
|
|
628
|
+
return opts.mode === "edit" ? why : `${why} You can change it later.`;
|
|
629
|
+
}
|
|
1219
630
|
|
|
1220
631
|
// src/notes-editor.ts
|
|
1221
632
|
var EDITOR_ONLY = "";
|
|
@@ -1228,10 +639,10 @@ var CLASS_LABEL = {
|
|
|
1228
639
|
};
|
|
1229
640
|
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
1230
641
|
var PLACEHOLDER = {
|
|
1231
|
-
vo: "
|
|
1232
|
-
loc: "
|
|
642
|
+
vo: "Direction for the voice actor or director",
|
|
643
|
+
loc: "Context for translators"
|
|
1233
644
|
};
|
|
1234
|
-
var placeholder = (cls) => cls === EDITOR_ONLY ? "
|
|
645
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
|
|
1235
646
|
function openNotesEditor(opts) {
|
|
1236
647
|
const { notes } = opts;
|
|
1237
648
|
const seed = /* @__PURE__ */ new Map();
|
|
@@ -1285,7 +696,7 @@ ${line.text}` : line.text);
|
|
|
1285
696
|
{ className: "shell-notes-class" },
|
|
1286
697
|
el("label", "shell-notes-caption", label(cls))
|
|
1287
698
|
);
|
|
1288
|
-
const area = el("textarea", "shell-notes-text");
|
|
699
|
+
const area = el("textarea", "field shell-notes-text");
|
|
1289
700
|
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
1290
701
|
area.rows = 4;
|
|
1291
702
|
area.placeholder = placeholder(cls);
|
|
@@ -1311,29 +722,24 @@ ${line.text}` : line.text);
|
|
|
1311
722
|
}
|
|
1312
723
|
};
|
|
1313
724
|
render();
|
|
1314
|
-
const done = el("button", "shell-notes-btn
|
|
1315
|
-
|
|
1316
|
-
"dialog",
|
|
1317
|
-
{ className: "shell-notes" },
|
|
1318
|
-
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
1319
|
-
body,
|
|
1320
|
-
el("div", { className: "shell-notes-actions" }, done)
|
|
1321
|
-
);
|
|
1322
|
-
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
1323
|
-
document.body.append(dialog);
|
|
725
|
+
const done = el("button", "btn primary shell-notes-btn", "Done");
|
|
726
|
+
done.type = "button";
|
|
1324
727
|
const before = JSON.stringify(notes.own);
|
|
1325
728
|
let closed = false;
|
|
1326
|
-
const
|
|
729
|
+
const commit = () => {
|
|
1327
730
|
if (closed) return;
|
|
1328
731
|
closed = true;
|
|
1329
732
|
const next = value();
|
|
1330
733
|
if (JSON.stringify(next) !== before) opts.save(next);
|
|
1331
|
-
dialog.close();
|
|
1332
|
-
dialog.remove();
|
|
1333
734
|
};
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
735
|
+
const frame = dialogFrame({ title: `Notes: ${opts.subject}`, className: "shell-notes", onClose: commit });
|
|
736
|
+
frame.body.append(body);
|
|
737
|
+
frame.actions.append(done);
|
|
738
|
+
done.addEventListener("click", () => {
|
|
739
|
+
commit();
|
|
740
|
+
frame.close();
|
|
741
|
+
});
|
|
742
|
+
frame.open();
|
|
1337
743
|
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
1338
744
|
}
|
|
1339
745
|
|
|
@@ -1405,7 +811,7 @@ function openComments(opts) {
|
|
|
1405
811
|
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1406
812
|
);
|
|
1407
813
|
if (opts.deleteMessage && message.deleted !== true) {
|
|
1408
|
-
const bin = el("button", { className: "shell-cmt-del",
|
|
814
|
+
const bin = el("button", { className: "shell-cmt-del" }, iconNode("close", 12));
|
|
1409
815
|
bin.dataset.tip = "Delete this comment";
|
|
1410
816
|
bin.setAttribute("aria-label", "Delete this comment");
|
|
1411
817
|
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
@@ -1470,37 +876,65 @@ var clock = (ms) => {
|
|
|
1470
876
|
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1471
877
|
function mountJobProgress(host, opts) {
|
|
1472
878
|
const units = opts.units ?? "runs";
|
|
879
|
+
const pinned = opts.indeterminate === true;
|
|
880
|
+
const cancellableByDefault = opts.cancellable ?? opts.onCancel !== void 0;
|
|
1473
881
|
const fill = el("i", { className: "job-fill" });
|
|
1474
882
|
const bar = el("div", { className: "job-bar" }, fill);
|
|
883
|
+
bar.setAttribute("role", "progressbar");
|
|
884
|
+
bar.setAttribute("aria-valuemin", "0");
|
|
885
|
+
bar.setAttribute("aria-valuemax", "100");
|
|
1475
886
|
const what = el("span", { className: "job-label" });
|
|
1476
887
|
const count = el("span", { className: "job-count" });
|
|
1477
888
|
const time = el("span", { className: "job-time" });
|
|
1478
889
|
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
890
|
+
cancel.type = "button";
|
|
1479
891
|
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1480
892
|
strip.hidden = true;
|
|
1481
893
|
host.append(strip);
|
|
894
|
+
let runPinned = pinned;
|
|
895
|
+
let indeterminate = false;
|
|
896
|
+
const setMode = (band) => {
|
|
897
|
+
indeterminate = band;
|
|
898
|
+
strip.classList.toggle("indeterminate", band);
|
|
899
|
+
count.hidden = band;
|
|
900
|
+
if (band) {
|
|
901
|
+
fill.style.width = "";
|
|
902
|
+
bar.removeAttribute("aria-valuenow");
|
|
903
|
+
}
|
|
904
|
+
};
|
|
1482
905
|
const view = {
|
|
1483
906
|
element: strip,
|
|
1484
907
|
get visible() {
|
|
1485
908
|
return !strip.hidden;
|
|
1486
909
|
},
|
|
1487
|
-
|
|
910
|
+
get indeterminate() {
|
|
911
|
+
return indeterminate;
|
|
912
|
+
},
|
|
913
|
+
begin(label2, run) {
|
|
914
|
+
runPinned = run?.indeterminate ?? pinned;
|
|
915
|
+
const cancellable = run?.cancellable ?? cancellableByDefault;
|
|
1488
916
|
what.textContent = label2 ?? "Running\u2026";
|
|
1489
917
|
count.textContent = "";
|
|
1490
918
|
time.textContent = "";
|
|
1491
|
-
|
|
919
|
+
cancel.hidden = !cancellable;
|
|
1492
920
|
cancel.disabled = false;
|
|
1493
921
|
cancel.textContent = "Cancel";
|
|
922
|
+
setMode(true);
|
|
1494
923
|
strip.hidden = false;
|
|
1495
924
|
},
|
|
1496
925
|
update(done, total, elapsedMs) {
|
|
1497
926
|
if (strip.hidden) strip.hidden = false;
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
927
|
+
const band = runPinned || !(total > 0);
|
|
928
|
+
setMode(band);
|
|
929
|
+
if (!band) {
|
|
930
|
+
const pct = Math.min(100, done / total * 100);
|
|
931
|
+
fill.style.width = `${pct}%`;
|
|
932
|
+
bar.setAttribute("aria-valuenow", String(Math.round(pct)));
|
|
933
|
+
count.textContent = `${done} / ${total} ${units}`.trimEnd();
|
|
934
|
+
}
|
|
935
|
+
if (!band && estimable(done, total, elapsedMs)) {
|
|
1502
936
|
const remaining = elapsedMs / done * (total - done);
|
|
1503
|
-
time.textContent = `${clock(elapsedMs)} elapsed
|
|
937
|
+
time.textContent = `${clock(elapsedMs)} elapsed, about ${clock(remaining)} left`;
|
|
1504
938
|
} else {
|
|
1505
939
|
time.textContent = clock(elapsedMs);
|
|
1506
940
|
}
|
|
@@ -1512,8 +946,10 @@ function mountJobProgress(host, opts) {
|
|
|
1512
946
|
cancel.addEventListener("click", () => {
|
|
1513
947
|
cancel.disabled = true;
|
|
1514
948
|
cancel.textContent = "Stopping\u2026";
|
|
1515
|
-
opts.onCancel();
|
|
949
|
+
opts.onCancel?.();
|
|
1516
950
|
});
|
|
951
|
+
cancel.hidden = !cancellableByDefault;
|
|
952
|
+
setMode(true);
|
|
1517
953
|
return view;
|
|
1518
954
|
}
|
|
1519
955
|
|
|
@@ -1525,7 +961,7 @@ function createSaveController(opts) {
|
|
|
1525
961
|
let auto = true;
|
|
1526
962
|
let dirty = false;
|
|
1527
963
|
let dirtySince = 0;
|
|
1528
|
-
let
|
|
964
|
+
let timer2;
|
|
1529
965
|
let inFlight;
|
|
1530
966
|
const setStatus = (next) => {
|
|
1531
967
|
if (next === status) return;
|
|
@@ -1533,17 +969,17 @@ function createSaveController(opts) {
|
|
|
1533
969
|
opts.onStatus?.(status);
|
|
1534
970
|
};
|
|
1535
971
|
const clearTimer = () => {
|
|
1536
|
-
if (
|
|
1537
|
-
clearTimeout(
|
|
1538
|
-
|
|
972
|
+
if (timer2 === void 0) return;
|
|
973
|
+
clearTimeout(timer2);
|
|
974
|
+
timer2 = void 0;
|
|
1539
975
|
};
|
|
1540
976
|
const arm = () => {
|
|
1541
977
|
clearTimer();
|
|
1542
978
|
if (!auto) return;
|
|
1543
979
|
const deadline = dirtySince + maxWaitMs;
|
|
1544
980
|
const wait = Math.max(0, Math.min(delayMs, deadline - Date.now()));
|
|
1545
|
-
|
|
1546
|
-
|
|
981
|
+
timer2 = setTimeout(() => {
|
|
982
|
+
timer2 = void 0;
|
|
1547
983
|
void run();
|
|
1548
984
|
}, wait);
|
|
1549
985
|
};
|
|
@@ -1636,18 +1072,21 @@ function staleBar(opts) {
|
|
|
1636
1072
|
className: "stale-bar-msg",
|
|
1637
1073
|
text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
|
|
1638
1074
|
}),
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1075
|
+
// The icon is a node beside the word, not a character inside it; the
|
|
1076
|
+
// button's flex gap sets the space (stale.css).
|
|
1077
|
+
el(
|
|
1078
|
+
"button",
|
|
1079
|
+
{ className: "stale-bar-go", onClick: () => opts.onRestart() },
|
|
1080
|
+
iconNode("restart", 12),
|
|
1081
|
+
label2
|
|
1082
|
+
)
|
|
1644
1083
|
);
|
|
1645
1084
|
}
|
|
1646
1085
|
|
|
1647
1086
|
// src/toast.ts
|
|
1648
1087
|
var DURATION = { info: 4e3, ok: 4e3, error: 7e3 };
|
|
1649
1088
|
var node;
|
|
1650
|
-
var
|
|
1089
|
+
var timer = 0;
|
|
1651
1090
|
var seq = 0;
|
|
1652
1091
|
function mount() {
|
|
1653
1092
|
if (node && node.isConnected) return node;
|
|
@@ -1664,8 +1103,8 @@ function toast(message, kind = "info", opts = {}) {
|
|
|
1664
1103
|
t.textContent = message;
|
|
1665
1104
|
t.className = `shell-toast ${kind}`;
|
|
1666
1105
|
t.hidden = false;
|
|
1667
|
-
window.clearTimeout(
|
|
1668
|
-
|
|
1106
|
+
window.clearTimeout(timer);
|
|
1107
|
+
timer = window.setTimeout(() => {
|
|
1669
1108
|
closeWithExit(t, () => {
|
|
1670
1109
|
if (seq === id) t.hidden = true;
|
|
1671
1110
|
});
|
|
@@ -1673,7 +1112,7 @@ function toast(message, kind = "info", opts = {}) {
|
|
|
1673
1112
|
}
|
|
1674
1113
|
function dismissToast() {
|
|
1675
1114
|
if (!node || node.hidden) return;
|
|
1676
|
-
window.clearTimeout(
|
|
1115
|
+
window.clearTimeout(timer);
|
|
1677
1116
|
const id = seq;
|
|
1678
1117
|
const t = node;
|
|
1679
1118
|
closeWithExit(t, () => {
|
|
@@ -1682,14 +1121,14 @@ function dismissToast() {
|
|
|
1682
1121
|
}
|
|
1683
1122
|
|
|
1684
1123
|
// src/stepper.ts
|
|
1124
|
+
var STEP_ICON = 12;
|
|
1685
1125
|
function renderStepperBar(host, opts) {
|
|
1686
1126
|
const { items } = opts;
|
|
1687
1127
|
const close = opts.onClose === void 0 ? null : el("button", {
|
|
1688
1128
|
className: "stepbar-nav stepbar-close",
|
|
1689
|
-
text: icon.close,
|
|
1690
1129
|
tip: opts.closeTip ?? "Close",
|
|
1691
1130
|
onClick: opts.onClose
|
|
1692
|
-
});
|
|
1131
|
+
}, iconNode("close", STEP_ICON));
|
|
1693
1132
|
const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
|
|
1694
1133
|
if (items.length === 0) {
|
|
1695
1134
|
if (opts.empty === void 0) {
|
|
@@ -1713,17 +1152,15 @@ function renderStepperBar(host, opts) {
|
|
|
1713
1152
|
const nav = items.length > 1 ? [
|
|
1714
1153
|
el("button", {
|
|
1715
1154
|
className: "stepbar-nav",
|
|
1716
|
-
text: icon.back,
|
|
1717
1155
|
tip: opts.tips?.prev ?? "Previous",
|
|
1718
1156
|
onClick: () => step(-1)
|
|
1719
|
-
}),
|
|
1157
|
+
}, iconNode("back", STEP_ICON)),
|
|
1720
1158
|
el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
|
|
1721
1159
|
el("button", {
|
|
1722
1160
|
className: "stepbar-nav",
|
|
1723
|
-
text: icon.forward,
|
|
1724
1161
|
tip: opts.tips?.next ?? "Next",
|
|
1725
1162
|
onClick: () => step(1)
|
|
1726
|
-
})
|
|
1163
|
+
}, iconNode("forward", STEP_ICON))
|
|
1727
1164
|
] : [];
|
|
1728
1165
|
host.replaceChildren(
|
|
1729
1166
|
el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
|
|
@@ -1757,11 +1194,11 @@ function createNavHistory(opts) {
|
|
|
1757
1194
|
}
|
|
1758
1195
|
};
|
|
1759
1196
|
return {
|
|
1760
|
-
visit(
|
|
1197
|
+
visit(place) {
|
|
1761
1198
|
ahead = [];
|
|
1762
1199
|
const top = past[past.length - 1];
|
|
1763
|
-
if (top !== void 0 && opts.same(top,
|
|
1764
|
-
else past.push(
|
|
1200
|
+
if (top !== void 0 && opts.same(top, place)) past[past.length - 1] = place;
|
|
1201
|
+
else past.push(place);
|
|
1765
1202
|
if (past.length > cap) past = past.slice(past.length - cap);
|
|
1766
1203
|
},
|
|
1767
1204
|
back(current2) {
|
|
@@ -1779,18 +1216,18 @@ function createNavHistory(opts) {
|
|
|
1779
1216
|
};
|
|
1780
1217
|
}
|
|
1781
1218
|
function historyNav(onBack, onForward) {
|
|
1782
|
-
const btn = (
|
|
1219
|
+
const btn = (name, tip, onClick) => {
|
|
1783
1220
|
const b = document.createElement("button");
|
|
1784
1221
|
b.type = "button";
|
|
1785
1222
|
b.className = "shell-histnav-btn";
|
|
1786
|
-
b.
|
|
1787
|
-
b.dataset["tip"] =
|
|
1788
|
-
b.setAttribute("aria-label",
|
|
1223
|
+
b.append(iconNode(name));
|
|
1224
|
+
b.dataset["tip"] = tip;
|
|
1225
|
+
b.setAttribute("aria-label", tip);
|
|
1789
1226
|
b.addEventListener("click", onClick);
|
|
1790
1227
|
return b;
|
|
1791
1228
|
};
|
|
1792
|
-
const back = btn("
|
|
1793
|
-
const forward = btn("
|
|
1229
|
+
const back = btn("arrowLeft", "Back", onBack);
|
|
1230
|
+
const forward = btn("arrowRight", "Forward", onForward);
|
|
1794
1231
|
const el2 = document.createElement("span");
|
|
1795
1232
|
el2.className = "shell-histnav";
|
|
1796
1233
|
el2.append(back, forward);
|
|
@@ -1803,6 +1240,6 @@ function historyNav(onBack, onForward) {
|
|
|
1803
1240
|
};
|
|
1804
1241
|
}
|
|
1805
1242
|
|
|
1806
|
-
export { PALETTE, PALETTE_SIZE,
|
|
1243
|
+
export { PALETTE, PALETTE_SIZE, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createNavHistory, createSaveController, dismissToast, foldVc, followButton, gameIdify, historyNav, isValidGameId, lockControls, lockNotice, mountJobProgress, mountPaneShell, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, renderStepperBar, saveIndicator, showAbout, staleBar, toast, toolWindowHead, vcBadgeFor };
|
|
1807
1244
|
//# sourceMappingURL=index.js.map
|
|
1808
1245
|
//# sourceMappingURL=index.js.map
|