@wildwinter/app-shell 0.37.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 +21 -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 +1322 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -131
- package/dist/index.d.ts +349 -131
- package/dist/index.js +259 -808
- 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 +63 -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,175 +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
|
-
const line = el("div", "set-rowline");
|
|
474
|
-
if (opts.details && opts.details.length) {
|
|
475
|
-
const details = el("div", "set-details");
|
|
476
|
-
details.hidden = true;
|
|
477
|
-
for (const d of opts.details) details.append(d);
|
|
478
|
-
const toggle = el("button", "set-expand");
|
|
479
|
-
toggle.type = "button";
|
|
480
|
-
toggle.dataset.tip = "More";
|
|
481
|
-
toggle.setAttribute("aria-label", "More");
|
|
482
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
483
|
-
toggle.addEventListener("click", () => {
|
|
484
|
-
const open = details.hidden;
|
|
485
|
-
details.hidden = !open;
|
|
486
|
-
toggle.setAttribute("aria-expanded", String(open));
|
|
487
|
-
toggle.classList.toggle("open", open);
|
|
488
|
-
});
|
|
489
|
-
line.append(toggle);
|
|
490
|
-
opts.line.forEach((c) => line.append(c));
|
|
491
|
-
row.append(line, details);
|
|
492
|
-
} else {
|
|
493
|
-
opts.line.forEach((c) => line.append(c));
|
|
494
|
-
row.append(line);
|
|
495
|
-
}
|
|
496
|
-
return row;
|
|
497
|
-
}
|
|
498
|
-
function focusNewRow(listEl) {
|
|
499
|
-
const last = listEl?.lastElementChild;
|
|
500
|
-
if (!(last instanceof HTMLElement)) return;
|
|
501
|
-
last.scrollIntoView({ block: "nearest" });
|
|
502
|
-
const name = last.querySelector("input.set-name") ?? last.querySelector("input");
|
|
503
|
-
name?.focus();
|
|
504
|
-
}
|
|
505
|
-
function dupGuard() {
|
|
506
|
-
const items = [];
|
|
507
|
-
const check = () => {
|
|
508
|
-
const counts = /* @__PURE__ */ new Map();
|
|
509
|
-
for (const it of items) {
|
|
510
|
-
const k = it.key().trim().toLowerCase();
|
|
511
|
-
if (k) counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
512
|
-
}
|
|
513
|
-
let any = false;
|
|
514
|
-
for (const it of items) {
|
|
515
|
-
const k = it.key().trim().toLowerCase();
|
|
516
|
-
const dup = !!k && (counts.get(k) ?? 0) > 1;
|
|
517
|
-
it.input.classList.toggle("invalid", dup);
|
|
518
|
-
if (dup) {
|
|
519
|
-
it.input.title = "Duplicate name. Names must be unique.";
|
|
520
|
-
any = true;
|
|
521
|
-
} else if (it.input.title === "Duplicate name. Names must be unique.") it.input.removeAttribute("title");
|
|
522
|
-
}
|
|
523
|
-
return any;
|
|
524
|
-
};
|
|
525
|
-
return {
|
|
526
|
-
reset() {
|
|
527
|
-
items.length = 0;
|
|
528
|
-
},
|
|
529
|
-
track(input, key = () => input.value, also) {
|
|
530
|
-
items.push({ input, key });
|
|
531
|
-
input.addEventListener("input", check);
|
|
532
|
-
for (const a of also ?? []) a.addEventListener("input", check);
|
|
533
|
-
},
|
|
534
|
-
check,
|
|
535
|
-
firstDuplicate() {
|
|
536
|
-
check();
|
|
537
|
-
return items.find((it) => it.input.classList.contains("invalid"))?.input ?? null;
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
function mountSettingsDialog(opts) {
|
|
542
|
-
const dialog = el("dialog", "settings-dialog");
|
|
543
|
-
const form = el("form", "settings-form");
|
|
544
|
-
form.method = "dialog";
|
|
545
|
-
const tabs = el("nav", "settings-tabs");
|
|
546
|
-
const panels = el("div", "settings-panels");
|
|
547
|
-
const error = el("p", "settings-error");
|
|
548
|
-
error.hidden = true;
|
|
549
|
-
const cancel = el("button", "settings-cancel", "Cancel");
|
|
550
|
-
cancel.type = "button";
|
|
551
|
-
cancel.value = "cancel";
|
|
552
|
-
const save = el("button", "settings-save primary", "Save");
|
|
553
|
-
save.value = "save";
|
|
554
|
-
const panelFor = /* @__PURE__ */ new Map();
|
|
555
|
-
const tabFor = /* @__PURE__ */ new Map();
|
|
556
|
-
const handles = /* @__PURE__ */ new Map();
|
|
557
|
-
let active2 = opts.sections[0]?.id ?? "";
|
|
558
|
-
let lastGroup;
|
|
559
|
-
for (const s of opts.sections) {
|
|
560
|
-
if (s.group && s.group !== lastGroup) {
|
|
561
|
-
tabs.append(el("div", "settings-group", s.group));
|
|
562
|
-
lastGroup = s.group;
|
|
563
|
-
}
|
|
564
|
-
const tab = el("button", "settings-tab", s.label);
|
|
565
|
-
tab.type = "button";
|
|
566
|
-
tab.dataset["tab"] = s.id;
|
|
567
|
-
tab.addEventListener("click", () => showTab(s.id));
|
|
568
|
-
tabs.append(tab);
|
|
569
|
-
tabFor.set(s.id, tab);
|
|
570
|
-
const panel = el("section", "settings-panel");
|
|
571
|
-
panel.dataset["panel"] = s.id;
|
|
572
|
-
panel.hidden = true;
|
|
573
|
-
panels.append(panel);
|
|
574
|
-
panelFor.set(s.id, panel);
|
|
575
|
-
}
|
|
576
|
-
function showTab(id) {
|
|
577
|
-
active2 = id;
|
|
578
|
-
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
579
|
-
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
580
|
-
}
|
|
581
|
-
form.append(
|
|
582
|
-
el("h2", "settings-title", opts.title),
|
|
583
|
-
el("div", "settings-body", tabs, panels),
|
|
584
|
-
el("div", "settings-actions", error, cancel, save)
|
|
585
|
-
);
|
|
586
|
-
dialog.append(form);
|
|
587
|
-
document.body.append(dialog);
|
|
588
|
-
form.addEventListener("keydown", (e) => {
|
|
589
|
-
if (e.key === "Enter" && e.target?.tagName === "INPUT") e.preventDefault();
|
|
590
|
-
});
|
|
591
|
-
cancel.addEventListener("click", () => dialog.close("cancel"));
|
|
592
|
-
save.addEventListener("click", (e) => {
|
|
593
|
-
e.preventDefault();
|
|
594
|
-
error.hidden = true;
|
|
595
|
-
for (const s of opts.sections) {
|
|
596
|
-
const bad = handles.get(s.id)?.firstInvalid?.();
|
|
597
|
-
if (bad) {
|
|
598
|
-
showTab(s.id);
|
|
599
|
-
bad.focus();
|
|
600
|
-
error.textContent = "Fix the highlighted fields first.";
|
|
601
|
-
error.hidden = false;
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
void (async () => {
|
|
606
|
-
await opts.onSave();
|
|
607
|
-
dialog.close("save");
|
|
608
|
-
})();
|
|
609
|
-
});
|
|
610
|
-
return {
|
|
611
|
-
open(tabId) {
|
|
612
|
-
handles.clear();
|
|
613
|
-
for (const s of opts.sections) {
|
|
614
|
-
const panel = panelFor.get(s.id);
|
|
615
|
-
panel.replaceChildren();
|
|
616
|
-
handles.set(s.id, s.mount(panel));
|
|
617
|
-
}
|
|
618
|
-
error.hidden = true;
|
|
619
|
-
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
620
|
-
dialog.showModal();
|
|
621
|
-
},
|
|
622
|
-
destroy() {
|
|
623
|
-
dialog.remove();
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
|
|
628
196
|
// src/tool-window-web.ts
|
|
629
|
-
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>`;
|
|
630
197
|
function pinButton(opts) {
|
|
631
198
|
const b = el("button", "swin-pin");
|
|
632
199
|
b.type = "button";
|
|
633
|
-
b.
|
|
200
|
+
b.append(iconNode("pin"));
|
|
634
201
|
let pinned = opts.pinned;
|
|
635
202
|
const reflect = () => {
|
|
636
203
|
b.classList.toggle("on", pinned);
|
|
637
204
|
b.setAttribute("aria-pressed", String(pinned));
|
|
638
|
-
const label2 = pinned ? "Pinned on top
|
|
205
|
+
const label2 = pinned ? "Pinned on top. Click to unpin." : "Keep on top";
|
|
639
206
|
b.dataset["tip"] = label2;
|
|
640
207
|
b.setAttribute("aria-label", label2);
|
|
641
208
|
};
|
|
@@ -650,66 +217,91 @@ function pinButton(opts) {
|
|
|
650
217
|
reflect();
|
|
651
218
|
} };
|
|
652
219
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
function
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
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();
|
|
661
240
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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;
|
|
672
263
|
};
|
|
673
|
-
|
|
674
|
-
|
|
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
|
+
} };
|
|
675
274
|
}
|
|
676
275
|
|
|
677
276
|
// src/confirm.ts
|
|
678
277
|
function confirmDialog(opts) {
|
|
679
278
|
return new Promise((resolve) => {
|
|
680
|
-
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
681
|
-
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
682
|
-
cancel.type = "button";
|
|
683
|
-
danger.type = "button";
|
|
684
|
-
const dlg = el(
|
|
685
|
-
"dialog",
|
|
686
|
-
"confirm-dialog",
|
|
687
|
-
el("div", "confirm-title", opts.title),
|
|
688
|
-
el("div", "confirm-body", opts.body),
|
|
689
|
-
el("div", "confirm-actions", cancel, danger)
|
|
690
|
-
);
|
|
691
|
-
dlg.setAttribute("aria-label", opts.title);
|
|
692
|
-
document.body.append(dlg);
|
|
693
279
|
let done = false;
|
|
694
280
|
const finish = (ok) => {
|
|
695
281
|
if (done) return;
|
|
696
282
|
done = true;
|
|
697
283
|
resolve(ok);
|
|
698
|
-
|
|
699
|
-
dlg.close();
|
|
700
|
-
dlg.remove();
|
|
701
|
-
});
|
|
284
|
+
frame.close();
|
|
702
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);
|
|
703
299
|
cancel.addEventListener("click", () => finish(false));
|
|
704
300
|
danger.addEventListener("click", () => finish(true));
|
|
705
|
-
|
|
706
|
-
e.
|
|
707
|
-
finish(false);
|
|
301
|
+
frame.dialog.addEventListener("mousedown", (e) => {
|
|
302
|
+
if (e.target === frame.dialog) finish(false);
|
|
708
303
|
});
|
|
709
|
-
|
|
710
|
-
if (e.target === dlg) finish(false);
|
|
711
|
-
});
|
|
712
|
-
dlg.showModal();
|
|
304
|
+
frame.open();
|
|
713
305
|
cancel.focus();
|
|
714
306
|
});
|
|
715
307
|
}
|
|
@@ -717,18 +309,16 @@ function confirmDialog(opts) {
|
|
|
717
309
|
// src/about.ts
|
|
718
310
|
function showAbout(opts) {
|
|
719
311
|
return new Promise((resolve) => {
|
|
720
|
-
const
|
|
312
|
+
const frame = dialogFrame({ title: opts.appName, className: "shell-about", onClose: () => resolve() });
|
|
313
|
+
frame.dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
721
314
|
if (opts.wordmark !== void 0) {
|
|
722
315
|
const mark = el("div", "shell-about-mark");
|
|
723
316
|
mark.innerHTML = opts.wordmark;
|
|
724
|
-
dialog.
|
|
317
|
+
frame.dialog.prepend(mark);
|
|
725
318
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
);
|
|
730
|
-
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
731
|
-
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));
|
|
732
322
|
if (opts.links?.length) {
|
|
733
323
|
const row = el("div", "shell-about-links");
|
|
734
324
|
for (const link of opts.links) {
|
|
@@ -741,24 +331,13 @@ function showAbout(opts) {
|
|
|
741
331
|
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
742
332
|
row.append(a);
|
|
743
333
|
}
|
|
744
|
-
|
|
334
|
+
frame.body.append(row);
|
|
745
335
|
}
|
|
746
|
-
const close = el("button", "shell-about-close", "Close");
|
|
336
|
+
const close = el("button", "btn shell-about-close", "Close");
|
|
747
337
|
close.type = "button";
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
let done = false;
|
|
752
|
-
const finish = () => {
|
|
753
|
-
if (done) return;
|
|
754
|
-
done = true;
|
|
755
|
-
dialog.close();
|
|
756
|
-
dialog.remove();
|
|
757
|
-
resolve();
|
|
758
|
-
};
|
|
759
|
-
close.addEventListener("click", finish);
|
|
760
|
-
dialog.addEventListener("close", finish);
|
|
761
|
-
dialog.showModal();
|
|
338
|
+
close.addEventListener("click", () => frame.close());
|
|
339
|
+
frame.actions.append(close);
|
|
340
|
+
frame.open();
|
|
762
341
|
close.focus();
|
|
763
342
|
});
|
|
764
343
|
}
|
|
@@ -773,7 +352,7 @@ function openAnchoredPanel(opts) {
|
|
|
773
352
|
if (prev.anchor === opts.anchor) return null;
|
|
774
353
|
}
|
|
775
354
|
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
776
|
-
const closeBtn = el("button", "shell-anchored-close", "
|
|
355
|
+
const closeBtn = el("button", "shell-anchored-close", iconNode("close", 12));
|
|
777
356
|
closeBtn.type = "button";
|
|
778
357
|
closeBtn.setAttribute("aria-label", "Close");
|
|
779
358
|
const body = el("div", "shell-anchored-body");
|
|
@@ -781,7 +360,7 @@ function openAnchoredPanel(opts) {
|
|
|
781
360
|
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
782
361
|
body
|
|
783
362
|
);
|
|
784
|
-
document.body.append(panel);
|
|
363
|
+
(opts.host ?? document.body).append(panel);
|
|
785
364
|
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
786
365
|
let closed = false;
|
|
787
366
|
const close = () => {
|
|
@@ -821,7 +400,7 @@ function closeAnchoredPanel() {
|
|
|
821
400
|
}
|
|
822
401
|
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
823
402
|
const a = anchor.getBoundingClientRect();
|
|
824
|
-
const
|
|
403
|
+
const GAP = 10;
|
|
825
404
|
const CAP = window.innerHeight * 0.7;
|
|
826
405
|
const MIN_H = 140;
|
|
827
406
|
panel.style.width = `${width}px`;
|
|
@@ -829,7 +408,7 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
829
408
|
const centred = a.left + a.width / 2 - width / 2;
|
|
830
409
|
panel.style.left = `${Math.round(Math.min(Math.max(8, centred), window.innerWidth - width - 8))}px`;
|
|
831
410
|
} else {
|
|
832
|
-
const leftOf = a.left - width -
|
|
411
|
+
const leftOf = a.left - width - GAP;
|
|
833
412
|
const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
|
|
834
413
|
panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
|
|
835
414
|
}
|
|
@@ -850,15 +429,15 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
850
429
|
return;
|
|
851
430
|
}
|
|
852
431
|
}
|
|
853
|
-
const roomBelow = bottomLimit - (a.bottom +
|
|
854
|
-
const roomAbove = a.top -
|
|
432
|
+
const roomBelow = bottomLimit - (a.bottom + GAP);
|
|
433
|
+
const roomAbove = a.top - GAP - 8;
|
|
855
434
|
if (roomAbove > roomBelow) {
|
|
856
435
|
panel.style.top = "auto";
|
|
857
|
-
panel.style.bottom = `${Math.round(window.innerHeight - (a.top -
|
|
436
|
+
panel.style.bottom = `${Math.round(window.innerHeight - (a.top - GAP))}px`;
|
|
858
437
|
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomAbove)))}px`;
|
|
859
438
|
} else {
|
|
860
439
|
panel.style.bottom = "auto";
|
|
861
|
-
panel.style.top = `${Math.round(Math.max(8, a.bottom +
|
|
440
|
+
panel.style.top = `${Math.round(Math.max(8, a.bottom + GAP))}px`;
|
|
862
441
|
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomBelow)))}px`;
|
|
863
442
|
}
|
|
864
443
|
}
|
|
@@ -871,164 +450,6 @@ function isValidGameId(gameId) {
|
|
|
871
450
|
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
|
|
872
451
|
}
|
|
873
452
|
|
|
874
|
-
// src/property-names.ts
|
|
875
|
-
var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
|
|
876
|
-
var PROPERTY_NAME_HINT = "Lower case letters, digits and underscores. Cannot start with a digit. No hyphens or spaces.";
|
|
877
|
-
function propertyNameify(text) {
|
|
878
|
-
const trimmed = text.trim();
|
|
879
|
-
const deliberateLeading = trimmed.startsWith("_");
|
|
880
|
-
let out = trimmed.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
881
|
-
if (out === "") return "";
|
|
882
|
-
if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;
|
|
883
|
-
if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;
|
|
884
|
-
return out;
|
|
885
|
-
}
|
|
886
|
-
function isValidPropertyName(name) {
|
|
887
|
-
return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);
|
|
888
|
-
}
|
|
889
|
-
function isCaseOnlyPropertyName(name) {
|
|
890
|
-
return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// src/property-name-field.ts
|
|
894
|
-
var refSyncs = /* @__PURE__ */ new WeakMap();
|
|
895
|
-
function propertyNameProblem(name) {
|
|
896
|
-
if (isValidPropertyName(name)) return void 0;
|
|
897
|
-
const suggestion = propertyNameify(name);
|
|
898
|
-
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
899
|
-
if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
|
|
900
|
-
return `Cannot be used: "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
|
|
901
|
-
}
|
|
902
|
-
if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
|
|
903
|
-
if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
|
|
904
|
-
if (name !== name.toLowerCase()) {
|
|
905
|
-
return `Cannot be used: expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
|
|
906
|
-
}
|
|
907
|
-
return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
|
|
908
|
-
}
|
|
909
|
-
function bindPropertyName(input, onCommit, opts = {}) {
|
|
910
|
-
const hint = opts.hint;
|
|
911
|
-
const sync = () => {
|
|
912
|
-
const v = input.value.trim();
|
|
913
|
-
const problem = v === "" ? void 0 : propertyNameProblem(v);
|
|
914
|
-
input.classList.toggle("illegal", !!problem);
|
|
915
|
-
if (problem) input.title = problem;
|
|
916
|
-
else if (input.title.startsWith("Cannot be used")) {
|
|
917
|
-
if (hint) input.title = hint;
|
|
918
|
-
else input.removeAttribute("title");
|
|
919
|
-
}
|
|
920
|
-
};
|
|
921
|
-
input.addEventListener("input", () => {
|
|
922
|
-
onCommit(input.value);
|
|
923
|
-
sync();
|
|
924
|
-
});
|
|
925
|
-
input.addEventListener("keydown", (e) => {
|
|
926
|
-
if (e.key !== "Tab" || e.shiftKey) return;
|
|
927
|
-
const typed = input.value.trim();
|
|
928
|
-
if (typed === "" || isValidPropertyName(typed)) return;
|
|
929
|
-
const coerced = propertyNameify(typed);
|
|
930
|
-
if (!coerced || coerced === input.value) return;
|
|
931
|
-
e.preventDefault();
|
|
932
|
-
input.value = coerced;
|
|
933
|
-
onCommit(coerced);
|
|
934
|
-
sync();
|
|
935
|
-
});
|
|
936
|
-
sync();
|
|
937
|
-
}
|
|
938
|
-
function firstIllegalPropertyName(host) {
|
|
939
|
-
return host.querySelector("input.illegal");
|
|
940
|
-
}
|
|
941
|
-
function nearest(name, known) {
|
|
942
|
-
const n = name.toLowerCase();
|
|
943
|
-
const byPrefix = known.filter((k) => k.toLowerCase().startsWith(n) || n.startsWith(k.toLowerCase()));
|
|
944
|
-
if (byPrefix.length === 1) return byPrefix[0];
|
|
945
|
-
let best;
|
|
946
|
-
let bestScore = 3;
|
|
947
|
-
for (const k of known) {
|
|
948
|
-
const a = k.toLowerCase();
|
|
949
|
-
if (Math.abs(a.length - n.length) > 2) continue;
|
|
950
|
-
let prev = Array.from({ length: n.length + 1 }, (_, i) => i);
|
|
951
|
-
for (let i = 1; i <= a.length; i++) {
|
|
952
|
-
const row = [i];
|
|
953
|
-
for (let j = 1; j <= n.length; j++) {
|
|
954
|
-
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === n[j - 1] ? 0 : 1));
|
|
955
|
-
}
|
|
956
|
-
prev = row;
|
|
957
|
-
}
|
|
958
|
-
const d = prev[n.length];
|
|
959
|
-
if (d < bestScore) {
|
|
960
|
-
bestScore = d;
|
|
961
|
-
best = k;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return best;
|
|
965
|
-
}
|
|
966
|
-
function propertyRefProblem(name, opts) {
|
|
967
|
-
const scope = opts.scope ? `@${opts.scope}` : "that scope";
|
|
968
|
-
const folded = name.toLowerCase();
|
|
969
|
-
if (!isValidPropertyName(folded)) {
|
|
970
|
-
const suggestion = propertyNameify(name);
|
|
971
|
-
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
972
|
-
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Cannot be used: "${folded}" is a keyword in expressions.${tail}`;
|
|
973
|
-
if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
|
|
974
|
-
if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
|
|
975
|
-
return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
|
|
976
|
-
}
|
|
977
|
-
const known = opts.known();
|
|
978
|
-
if (known.some((k) => k.toLowerCase() === folded)) return void 0;
|
|
979
|
-
const near = nearest(folded, known);
|
|
980
|
-
if (near) return `No ${scope} property is called "${name}". Did you mean "${near}"?`;
|
|
981
|
-
if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
|
|
982
|
-
const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
|
|
983
|
-
const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
|
|
984
|
-
return `No ${scope} property is called "${name}". Declared: ${shown}${more}.`;
|
|
985
|
-
}
|
|
986
|
-
function bindPropertyRef(input, onCommit, opts) {
|
|
987
|
-
const list = document.createElement("datalist");
|
|
988
|
-
list.id = `shell-propref-${Math.random().toString(36).slice(2, 9)}`;
|
|
989
|
-
input.setAttribute("list", list.id);
|
|
990
|
-
const attach = () => {
|
|
991
|
-
if (!list.isConnected && input.parentNode) input.after(list);
|
|
992
|
-
};
|
|
993
|
-
const sync = () => {
|
|
994
|
-
attach();
|
|
995
|
-
const known = opts.known();
|
|
996
|
-
list.replaceChildren(...known.map((k) => {
|
|
997
|
-
const o = document.createElement("option");
|
|
998
|
-
o.value = k;
|
|
999
|
-
return o;
|
|
1000
|
-
}));
|
|
1001
|
-
const v = input.value.trim();
|
|
1002
|
-
const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
|
|
1003
|
-
input.classList.toggle("illegal", !!problem);
|
|
1004
|
-
if (problem) input.title = problem;
|
|
1005
|
-
else if (input.title.startsWith("Cannot be used") || input.title.startsWith("No ")) {
|
|
1006
|
-
if (opts.hint) input.title = opts.hint;
|
|
1007
|
-
else input.removeAttribute("title");
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
refSyncs.set(input, sync);
|
|
1011
|
-
input.addEventListener("input", () => {
|
|
1012
|
-
onCommit(input.value);
|
|
1013
|
-
sync();
|
|
1014
|
-
});
|
|
1015
|
-
input.addEventListener("keydown", (e) => {
|
|
1016
|
-
if (e.key !== "Tab" || e.shiftKey) return;
|
|
1017
|
-
const typed = input.value.trim();
|
|
1018
|
-
if (typed === "" || isValidPropertyName(typed.toLowerCase())) return;
|
|
1019
|
-
const coerced = propertyNameify(typed);
|
|
1020
|
-
if (!coerced || coerced === input.value) return;
|
|
1021
|
-
e.preventDefault();
|
|
1022
|
-
input.value = coerced;
|
|
1023
|
-
onCommit(coerced);
|
|
1024
|
-
sync();
|
|
1025
|
-
});
|
|
1026
|
-
sync();
|
|
1027
|
-
}
|
|
1028
|
-
function revalidatePropertyRefs(host) {
|
|
1029
|
-
for (const input of host.querySelectorAll("input")) refSyncs.get(input)?.();
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
453
|
// src/id-editor.ts
|
|
1033
454
|
function openGameIdEditor(opts) {
|
|
1034
455
|
const slugify = opts.slugify ?? gameIdify;
|
|
@@ -1045,9 +466,9 @@ function openGameIdEditor(opts) {
|
|
|
1045
466
|
const input = el("input", "shell-id-input");
|
|
1046
467
|
input.type = "text";
|
|
1047
468
|
input.value = opts.value;
|
|
1048
|
-
input.placeholder = opts.derived || "
|
|
469
|
+
input.placeholder = opts.derived || "game-id";
|
|
1049
470
|
input.spellcheck = false;
|
|
1050
|
-
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.");
|
|
1051
472
|
const actions = el("div", "shell-id-actions");
|
|
1052
473
|
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
1053
474
|
const set = el("button", "shell-id-set", "Set");
|
|
@@ -1109,12 +530,13 @@ function foldVc(shards, keys) {
|
|
|
1109
530
|
return out;
|
|
1110
531
|
}
|
|
1111
532
|
function vcBadgeFor(s) {
|
|
1112
|
-
|
|
1113
|
-
if (s?.
|
|
1114
|
-
if (s?.
|
|
1115
|
-
if (s?.
|
|
1116
|
-
if (s?.
|
|
1117
|
-
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.");
|
|
1118
540
|
return null;
|
|
1119
541
|
}
|
|
1120
542
|
function paintVcBadges(root, shards) {
|
|
@@ -1122,7 +544,7 @@ function paintVcBadges(root, shards) {
|
|
|
1122
544
|
host.querySelector(":scope > .vc-badge")?.remove();
|
|
1123
545
|
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
1124
546
|
if (!badge) return;
|
|
1125
|
-
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));
|
|
1126
548
|
host.append(span);
|
|
1127
549
|
});
|
|
1128
550
|
}
|
|
@@ -1145,8 +567,8 @@ function lockNotice(holders) {
|
|
|
1145
567
|
return el(
|
|
1146
568
|
"div",
|
|
1147
569
|
{ className: "vc-lock" },
|
|
1148
|
-
el("span", { className: "vc-lock-glyph",
|
|
1149
|
-
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.` })
|
|
1150
572
|
);
|
|
1151
573
|
}
|
|
1152
574
|
|
|
@@ -1154,41 +576,36 @@ function lockNotice(holders) {
|
|
|
1154
576
|
function askIdentity(opts = {}) {
|
|
1155
577
|
return new Promise((resolve) => {
|
|
1156
578
|
const seed = opts.current ?? opts.suggested;
|
|
1157
|
-
const name = el("input", "shell-ident-input");
|
|
579
|
+
const name = el("input", "field shell-ident-input");
|
|
1158
580
|
name.placeholder = "Your name";
|
|
1159
581
|
name.value = seed?.name ?? "";
|
|
1160
|
-
const email = el("input", "shell-ident-input");
|
|
1161
|
-
email.placeholder = "
|
|
582
|
+
const email = el("input", "field shell-ident-input");
|
|
583
|
+
email.placeholder = "Optional";
|
|
1162
584
|
email.type = "email";
|
|
1163
585
|
email.value = seed?.email ?? "";
|
|
1164
|
-
const skip = el("button", "shell-ident-btn", "Skip");
|
|
1165
|
-
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");
|
|
1166
588
|
skip.type = "button";
|
|
1167
589
|
save.type = "button";
|
|
1168
|
-
const dialog = el(
|
|
1169
|
-
"dialog",
|
|
1170
|
-
"shell-ident",
|
|
1171
|
-
el("div", "shell-ident-title", "Who is working here?"),
|
|
1172
|
-
el(
|
|
1173
|
-
"p",
|
|
1174
|
-
"shell-ident-hint",
|
|
1175
|
-
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
1176
|
-
),
|
|
1177
|
-
el("div", "shell-ident-fields", name, email),
|
|
1178
|
-
el("div", "shell-ident-actions", skip, save)
|
|
1179
|
-
);
|
|
1180
|
-
document.body.append(dialog);
|
|
1181
590
|
let done = false;
|
|
1182
591
|
const finish = (keep) => {
|
|
1183
592
|
if (done) return;
|
|
1184
593
|
done = true;
|
|
1185
594
|
const typed = name.value.trim();
|
|
1186
595
|
const value = email.value.trim();
|
|
1187
|
-
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
1188
|
-
dialog.close();
|
|
1189
|
-
dialog.remove();
|
|
596
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : opts.neverAskAgainOnSkip === true ? { name: "" } : void 0;
|
|
1190
597
|
resolve(answer);
|
|
598
|
+
frame.close();
|
|
1191
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);
|
|
1192
609
|
skip.addEventListener("click", () => finish(false));
|
|
1193
610
|
save.addEventListener("click", () => finish(true));
|
|
1194
611
|
name.addEventListener("keydown", (e) => {
|
|
@@ -1197,11 +614,19 @@ function askIdentity(opts = {}) {
|
|
|
1197
614
|
email.addEventListener("keydown", (e) => {
|
|
1198
615
|
if (e.key === "Enter") finish(true);
|
|
1199
616
|
});
|
|
1200
|
-
|
|
1201
|
-
dialog.showModal();
|
|
617
|
+
frame.open();
|
|
1202
618
|
name.focus();
|
|
1203
619
|
});
|
|
1204
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
|
+
}
|
|
1205
630
|
|
|
1206
631
|
// src/notes-editor.ts
|
|
1207
632
|
var EDITOR_ONLY = "";
|
|
@@ -1214,10 +639,10 @@ var CLASS_LABEL = {
|
|
|
1214
639
|
};
|
|
1215
640
|
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
1216
641
|
var PLACEHOLDER = {
|
|
1217
|
-
vo: "
|
|
1218
|
-
loc: "
|
|
642
|
+
vo: "Direction for the voice actor or director",
|
|
643
|
+
loc: "Context for translators"
|
|
1219
644
|
};
|
|
1220
|
-
var placeholder = (cls) => cls === EDITOR_ONLY ? "
|
|
645
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
|
|
1221
646
|
function openNotesEditor(opts) {
|
|
1222
647
|
const { notes } = opts;
|
|
1223
648
|
const seed = /* @__PURE__ */ new Map();
|
|
@@ -1271,7 +696,7 @@ ${line.text}` : line.text);
|
|
|
1271
696
|
{ className: "shell-notes-class" },
|
|
1272
697
|
el("label", "shell-notes-caption", label(cls))
|
|
1273
698
|
);
|
|
1274
|
-
const area = el("textarea", "shell-notes-text");
|
|
699
|
+
const area = el("textarea", "field shell-notes-text");
|
|
1275
700
|
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
1276
701
|
area.rows = 4;
|
|
1277
702
|
area.placeholder = placeholder(cls);
|
|
@@ -1297,29 +722,24 @@ ${line.text}` : line.text);
|
|
|
1297
722
|
}
|
|
1298
723
|
};
|
|
1299
724
|
render();
|
|
1300
|
-
const done = el("button", "shell-notes-btn
|
|
1301
|
-
|
|
1302
|
-
"dialog",
|
|
1303
|
-
{ className: "shell-notes" },
|
|
1304
|
-
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
1305
|
-
body,
|
|
1306
|
-
el("div", { className: "shell-notes-actions" }, done)
|
|
1307
|
-
);
|
|
1308
|
-
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
1309
|
-
document.body.append(dialog);
|
|
725
|
+
const done = el("button", "btn primary shell-notes-btn", "Done");
|
|
726
|
+
done.type = "button";
|
|
1310
727
|
const before = JSON.stringify(notes.own);
|
|
1311
728
|
let closed = false;
|
|
1312
|
-
const
|
|
729
|
+
const commit = () => {
|
|
1313
730
|
if (closed) return;
|
|
1314
731
|
closed = true;
|
|
1315
732
|
const next = value();
|
|
1316
733
|
if (JSON.stringify(next) !== before) opts.save(next);
|
|
1317
|
-
dialog.close();
|
|
1318
|
-
dialog.remove();
|
|
1319
734
|
};
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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();
|
|
1323
743
|
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
1324
744
|
}
|
|
1325
745
|
|
|
@@ -1391,7 +811,7 @@ function openComments(opts) {
|
|
|
1391
811
|
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1392
812
|
);
|
|
1393
813
|
if (opts.deleteMessage && message.deleted !== true) {
|
|
1394
|
-
const bin = el("button", { className: "shell-cmt-del",
|
|
814
|
+
const bin = el("button", { className: "shell-cmt-del" }, iconNode("close", 12));
|
|
1395
815
|
bin.dataset.tip = "Delete this comment";
|
|
1396
816
|
bin.setAttribute("aria-label", "Delete this comment");
|
|
1397
817
|
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
@@ -1456,37 +876,65 @@ var clock = (ms) => {
|
|
|
1456
876
|
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1457
877
|
function mountJobProgress(host, opts) {
|
|
1458
878
|
const units = opts.units ?? "runs";
|
|
879
|
+
const pinned = opts.indeterminate === true;
|
|
880
|
+
const cancellableByDefault = opts.cancellable ?? opts.onCancel !== void 0;
|
|
1459
881
|
const fill = el("i", { className: "job-fill" });
|
|
1460
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");
|
|
1461
886
|
const what = el("span", { className: "job-label" });
|
|
1462
887
|
const count = el("span", { className: "job-count" });
|
|
1463
888
|
const time = el("span", { className: "job-time" });
|
|
1464
889
|
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
890
|
+
cancel.type = "button";
|
|
1465
891
|
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1466
892
|
strip.hidden = true;
|
|
1467
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
|
+
};
|
|
1468
905
|
const view = {
|
|
1469
906
|
element: strip,
|
|
1470
907
|
get visible() {
|
|
1471
908
|
return !strip.hidden;
|
|
1472
909
|
},
|
|
1473
|
-
|
|
910
|
+
get indeterminate() {
|
|
911
|
+
return indeterminate;
|
|
912
|
+
},
|
|
913
|
+
begin(label2, run) {
|
|
914
|
+
runPinned = run?.indeterminate ?? pinned;
|
|
915
|
+
const cancellable = run?.cancellable ?? cancellableByDefault;
|
|
1474
916
|
what.textContent = label2 ?? "Running\u2026";
|
|
1475
917
|
count.textContent = "";
|
|
1476
918
|
time.textContent = "";
|
|
1477
|
-
|
|
919
|
+
cancel.hidden = !cancellable;
|
|
1478
920
|
cancel.disabled = false;
|
|
1479
921
|
cancel.textContent = "Cancel";
|
|
922
|
+
setMode(true);
|
|
1480
923
|
strip.hidden = false;
|
|
1481
924
|
},
|
|
1482
925
|
update(done, total, elapsedMs) {
|
|
1483
926
|
if (strip.hidden) strip.hidden = false;
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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)) {
|
|
1488
936
|
const remaining = elapsedMs / done * (total - done);
|
|
1489
|
-
time.textContent = `${clock(elapsedMs)} elapsed
|
|
937
|
+
time.textContent = `${clock(elapsedMs)} elapsed, about ${clock(remaining)} left`;
|
|
1490
938
|
} else {
|
|
1491
939
|
time.textContent = clock(elapsedMs);
|
|
1492
940
|
}
|
|
@@ -1498,8 +946,10 @@ function mountJobProgress(host, opts) {
|
|
|
1498
946
|
cancel.addEventListener("click", () => {
|
|
1499
947
|
cancel.disabled = true;
|
|
1500
948
|
cancel.textContent = "Stopping\u2026";
|
|
1501
|
-
opts.onCancel();
|
|
949
|
+
opts.onCancel?.();
|
|
1502
950
|
});
|
|
951
|
+
cancel.hidden = !cancellableByDefault;
|
|
952
|
+
setMode(true);
|
|
1503
953
|
return view;
|
|
1504
954
|
}
|
|
1505
955
|
|
|
@@ -1511,7 +961,7 @@ function createSaveController(opts) {
|
|
|
1511
961
|
let auto = true;
|
|
1512
962
|
let dirty = false;
|
|
1513
963
|
let dirtySince = 0;
|
|
1514
|
-
let
|
|
964
|
+
let timer2;
|
|
1515
965
|
let inFlight;
|
|
1516
966
|
const setStatus = (next) => {
|
|
1517
967
|
if (next === status) return;
|
|
@@ -1519,17 +969,17 @@ function createSaveController(opts) {
|
|
|
1519
969
|
opts.onStatus?.(status);
|
|
1520
970
|
};
|
|
1521
971
|
const clearTimer = () => {
|
|
1522
|
-
if (
|
|
1523
|
-
clearTimeout(
|
|
1524
|
-
|
|
972
|
+
if (timer2 === void 0) return;
|
|
973
|
+
clearTimeout(timer2);
|
|
974
|
+
timer2 = void 0;
|
|
1525
975
|
};
|
|
1526
976
|
const arm = () => {
|
|
1527
977
|
clearTimer();
|
|
1528
978
|
if (!auto) return;
|
|
1529
979
|
const deadline = dirtySince + maxWaitMs;
|
|
1530
980
|
const wait = Math.max(0, Math.min(delayMs, deadline - Date.now()));
|
|
1531
|
-
|
|
1532
|
-
|
|
981
|
+
timer2 = setTimeout(() => {
|
|
982
|
+
timer2 = void 0;
|
|
1533
983
|
void run();
|
|
1534
984
|
}, wait);
|
|
1535
985
|
};
|
|
@@ -1622,18 +1072,21 @@ function staleBar(opts) {
|
|
|
1622
1072
|
className: "stale-bar-msg",
|
|
1623
1073
|
text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
|
|
1624
1074
|
}),
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
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
|
+
)
|
|
1630
1083
|
);
|
|
1631
1084
|
}
|
|
1632
1085
|
|
|
1633
1086
|
// src/toast.ts
|
|
1634
1087
|
var DURATION = { info: 4e3, ok: 4e3, error: 7e3 };
|
|
1635
1088
|
var node;
|
|
1636
|
-
var
|
|
1089
|
+
var timer = 0;
|
|
1637
1090
|
var seq = 0;
|
|
1638
1091
|
function mount() {
|
|
1639
1092
|
if (node && node.isConnected) return node;
|
|
@@ -1650,8 +1103,8 @@ function toast(message, kind = "info", opts = {}) {
|
|
|
1650
1103
|
t.textContent = message;
|
|
1651
1104
|
t.className = `shell-toast ${kind}`;
|
|
1652
1105
|
t.hidden = false;
|
|
1653
|
-
window.clearTimeout(
|
|
1654
|
-
|
|
1106
|
+
window.clearTimeout(timer);
|
|
1107
|
+
timer = window.setTimeout(() => {
|
|
1655
1108
|
closeWithExit(t, () => {
|
|
1656
1109
|
if (seq === id) t.hidden = true;
|
|
1657
1110
|
});
|
|
@@ -1659,7 +1112,7 @@ function toast(message, kind = "info", opts = {}) {
|
|
|
1659
1112
|
}
|
|
1660
1113
|
function dismissToast() {
|
|
1661
1114
|
if (!node || node.hidden) return;
|
|
1662
|
-
window.clearTimeout(
|
|
1115
|
+
window.clearTimeout(timer);
|
|
1663
1116
|
const id = seq;
|
|
1664
1117
|
const t = node;
|
|
1665
1118
|
closeWithExit(t, () => {
|
|
@@ -1668,14 +1121,14 @@ function dismissToast() {
|
|
|
1668
1121
|
}
|
|
1669
1122
|
|
|
1670
1123
|
// src/stepper.ts
|
|
1124
|
+
var STEP_ICON = 12;
|
|
1671
1125
|
function renderStepperBar(host, opts) {
|
|
1672
1126
|
const { items } = opts;
|
|
1673
1127
|
const close = opts.onClose === void 0 ? null : el("button", {
|
|
1674
1128
|
className: "stepbar-nav stepbar-close",
|
|
1675
|
-
text: icon.close,
|
|
1676
1129
|
tip: opts.closeTip ?? "Close",
|
|
1677
1130
|
onClick: opts.onClose
|
|
1678
|
-
});
|
|
1131
|
+
}, iconNode("close", STEP_ICON));
|
|
1679
1132
|
const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
|
|
1680
1133
|
if (items.length === 0) {
|
|
1681
1134
|
if (opts.empty === void 0) {
|
|
@@ -1699,17 +1152,15 @@ function renderStepperBar(host, opts) {
|
|
|
1699
1152
|
const nav = items.length > 1 ? [
|
|
1700
1153
|
el("button", {
|
|
1701
1154
|
className: "stepbar-nav",
|
|
1702
|
-
text: icon.back,
|
|
1703
1155
|
tip: opts.tips?.prev ?? "Previous",
|
|
1704
1156
|
onClick: () => step(-1)
|
|
1705
|
-
}),
|
|
1157
|
+
}, iconNode("back", STEP_ICON)),
|
|
1706
1158
|
el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
|
|
1707
1159
|
el("button", {
|
|
1708
1160
|
className: "stepbar-nav",
|
|
1709
|
-
text: icon.forward,
|
|
1710
1161
|
tip: opts.tips?.next ?? "Next",
|
|
1711
1162
|
onClick: () => step(1)
|
|
1712
|
-
})
|
|
1163
|
+
}, iconNode("forward", STEP_ICON))
|
|
1713
1164
|
] : [];
|
|
1714
1165
|
host.replaceChildren(
|
|
1715
1166
|
el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
|
|
@@ -1743,11 +1194,11 @@ function createNavHistory(opts) {
|
|
|
1743
1194
|
}
|
|
1744
1195
|
};
|
|
1745
1196
|
return {
|
|
1746
|
-
visit(
|
|
1197
|
+
visit(place) {
|
|
1747
1198
|
ahead = [];
|
|
1748
1199
|
const top = past[past.length - 1];
|
|
1749
|
-
if (top !== void 0 && opts.same(top,
|
|
1750
|
-
else past.push(
|
|
1200
|
+
if (top !== void 0 && opts.same(top, place)) past[past.length - 1] = place;
|
|
1201
|
+
else past.push(place);
|
|
1751
1202
|
if (past.length > cap) past = past.slice(past.length - cap);
|
|
1752
1203
|
},
|
|
1753
1204
|
back(current2) {
|
|
@@ -1765,18 +1216,18 @@ function createNavHistory(opts) {
|
|
|
1765
1216
|
};
|
|
1766
1217
|
}
|
|
1767
1218
|
function historyNav(onBack, onForward) {
|
|
1768
|
-
const btn = (
|
|
1219
|
+
const btn = (name, tip, onClick) => {
|
|
1769
1220
|
const b = document.createElement("button");
|
|
1770
1221
|
b.type = "button";
|
|
1771
1222
|
b.className = "shell-histnav-btn";
|
|
1772
|
-
b.
|
|
1773
|
-
b.dataset["tip"] =
|
|
1774
|
-
b.setAttribute("aria-label",
|
|
1223
|
+
b.append(iconNode(name));
|
|
1224
|
+
b.dataset["tip"] = tip;
|
|
1225
|
+
b.setAttribute("aria-label", tip);
|
|
1775
1226
|
b.addEventListener("click", onClick);
|
|
1776
1227
|
return b;
|
|
1777
1228
|
};
|
|
1778
|
-
const back = btn("
|
|
1779
|
-
const forward = btn("
|
|
1229
|
+
const back = btn("arrowLeft", "Back", onBack);
|
|
1230
|
+
const forward = btn("arrowRight", "Forward", onForward);
|
|
1780
1231
|
const el2 = document.createElement("span");
|
|
1781
1232
|
el2.className = "shell-histnav";
|
|
1782
1233
|
el2.append(back, forward);
|
|
@@ -1789,6 +1240,6 @@ function historyNav(onBack, onForward) {
|
|
|
1789
1240
|
};
|
|
1790
1241
|
}
|
|
1791
1242
|
|
|
1792
|
-
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 };
|
|
1793
1244
|
//# sourceMappingURL=index.js.map
|
|
1794
1245
|
//# sourceMappingURL=index.js.map
|