@wildwinter/app-shell 0.4.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/about.css +76 -0
- package/dist/about.css.map +1 -0
- package/dist/anchored.css +50 -0
- package/dist/anchored.css.map +1 -0
- package/dist/app-store.cjs +74 -0
- package/dist/app-store.cjs.map +1 -0
- package/dist/app-store.d.cts +75 -0
- package/dist/app-store.d.ts +75 -0
- package/dist/app-store.js +72 -0
- package/dist/app-store.js.map +1 -0
- package/dist/chunk-MDGOIT3O.js +58 -0
- package/dist/chunk-MDGOIT3O.js.map +1 -0
- package/dist/chunk-R6RO5TXB.js +16 -0
- package/dist/chunk-R6RO5TXB.js.map +1 -0
- package/dist/comments.css +109 -0
- package/dist/comments.css.map +1 -0
- package/dist/confirm.css +62 -0
- package/dist/confirm.css.map +1 -0
- package/dist/identity.css +62 -0
- package/dist/identity.css.map +1 -0
- package/dist/index.cjs +926 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +371 -3
- package/dist/index.d.ts +371 -3
- package/dist/index.js +896 -8
- package/dist/index.js.map +1 -1
- package/dist/job.cjs +61 -0
- package/dist/job.cjs.map +1 -0
- package/dist/job.css +70 -0
- package/dist/job.css.map +1 -0
- package/dist/job.d.cts +56 -0
- package/dist/job.d.ts +56 -0
- package/dist/job.js +3 -0
- package/dist/job.js.map +1 -0
- package/dist/menu.cjs +7 -0
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +20 -1
- package/dist/menu.d.ts +20 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +107 -0
- package/dist/notes-editor.css.map +1 -0
- package/dist/session.cjs +52 -0
- package/dist/session.cjs.map +1 -0
- package/dist/session.d.cts +79 -0
- package/dist/session.d.ts +79 -0
- package/dist/session.js +50 -0
- package/dist/session.js.map +1 -0
- package/dist/tool-window.cjs +79 -0
- package/dist/tool-window.cjs.map +1 -0
- package/dist/tool-window.css +60 -0
- package/dist/tool-window.css.map +1 -0
- package/dist/tool-window.d.cts +63 -0
- package/dist/tool-window.d.ts +63 -0
- package/dist/tool-window.js +73 -0
- package/dist/tool-window.js.map +1 -0
- package/dist/tooltip.css +22 -0
- package/dist/tooltip.css.map +1 -0
- package/dist/updater.cjs +315 -0
- package/dist/updater.cjs.map +1 -0
- package/dist/updater.d.cts +53 -0
- package/dist/updater.d.ts +53 -0
- package/dist/updater.js +306 -0
- package/dist/updater.js.map +1 -0
- package/dist/vc-status.cjs +77 -0
- package/dist/vc-status.cjs.map +1 -0
- package/dist/vc-status.d.cts +40 -0
- package/dist/vc-status.d.ts +40 -0
- package/dist/vc-status.js +71 -0
- package/dist/vc-status.js.map +1 -0
- package/dist/vc.css +41 -0
- package/dist/vc.css.map +1 -0
- package/package.json +90 -1
- package/dist/chunk-KQT3XYRS.js +0 -10
- package/dist/chunk-KQT3XYRS.js.map +0 -1
- package/dist/pane-shell.d.cts +0 -2
- package/dist/pane-shell.d.ts +0 -2
- package/dist/settings.d.cts +0 -2
- package/dist/settings.d.ts +0 -2
- package/dist/tokens.d.cts +0 -2
- package/dist/tokens.d.ts +0 -2
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,10 @@ function el(tag, classNameOrProps, ...children) {
|
|
|
7
7
|
if (props.className) node.className = props.className;
|
|
8
8
|
if (props.text !== void 0 && props.text !== null) node.textContent = props.text;
|
|
9
9
|
if (props.title !== void 0) node.title = props.title;
|
|
10
|
+
if (props.tip !== void 0) {
|
|
11
|
+
node.dataset["tip"] = props.tip;
|
|
12
|
+
node.setAttribute("aria-label", props.tip);
|
|
13
|
+
}
|
|
10
14
|
if (props.onClick) node.addEventListener("click", props.onClick);
|
|
11
15
|
for (const child of children) {
|
|
12
16
|
if (child === null || child === void 0) continue;
|
|
@@ -18,15 +22,15 @@ function iconBtn(glyph, title, onClick, disabled = false, danger = false) {
|
|
|
18
22
|
const b = el("button", `shell-icon${danger ? " danger" : ""}`);
|
|
19
23
|
b.type = "button";
|
|
20
24
|
b.textContent = glyph;
|
|
21
|
-
b.
|
|
25
|
+
b.dataset["tip"] = title;
|
|
22
26
|
b.setAttribute("aria-label", title);
|
|
23
27
|
b.disabled = disabled;
|
|
24
28
|
b.addEventListener("click", onClick);
|
|
25
29
|
return b;
|
|
26
30
|
}
|
|
27
|
-
function labelled(
|
|
31
|
+
function labelled(label2, control) {
|
|
28
32
|
const w = el("label", "shell-labelled");
|
|
29
|
-
w.append(el("span", "shell-fieldcap",
|
|
33
|
+
w.append(el("span", "shell-fieldcap", label2), control);
|
|
30
34
|
return w;
|
|
31
35
|
}
|
|
32
36
|
function moveItem(arr, i, delta) {
|
|
@@ -96,6 +100,62 @@ function colourFor(name) {
|
|
|
96
100
|
return PALETTE[colourIndex(name)];
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
// src/icons.ts
|
|
104
|
+
var icon = {
|
|
105
|
+
// --- dismissing and revealing ---------------------------------------------
|
|
106
|
+
/** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
|
|
107
|
+
close: "\u2715",
|
|
108
|
+
/** The overflow menu on a document or a row: "more about this". */
|
|
109
|
+
more: "\u22EF",
|
|
110
|
+
/** Collapsed disclosure, pointing at what would open. */
|
|
111
|
+
collapsed: "\u25B8",
|
|
112
|
+
/** Expanded disclosure. */
|
|
113
|
+
expanded: "\u25BE",
|
|
114
|
+
/** A dropdown's affordance, where it is not a real <select>. */
|
|
115
|
+
dropdown: "\u25BE",
|
|
116
|
+
// --- moving through things ------------------------------------------------
|
|
117
|
+
/** Back / previous, in a breadcrumb or a stepper. */
|
|
118
|
+
back: "\u2039",
|
|
119
|
+
/** Forward / next. */
|
|
120
|
+
forward: "\u203A",
|
|
121
|
+
/** Up one, in a list. */
|
|
122
|
+
up: "\u2191",
|
|
123
|
+
/** Down one. */
|
|
124
|
+
down: "\u2193",
|
|
125
|
+
// --- state ----------------------------------------------------------------
|
|
126
|
+
/** Done, applied, agreed. */
|
|
127
|
+
tick: "\u2713",
|
|
128
|
+
/** Somebody else holds this: the version-control lock. */
|
|
129
|
+
locked: "\u2298",
|
|
130
|
+
/** Read-only on disk with no other holder: the resting state of everything
|
|
131
|
+
* in a lock-based working copy that nobody has checked out yet. */
|
|
132
|
+
readOnly: "\u25CB",
|
|
133
|
+
/** Something needs attention but is not fatal. */
|
|
134
|
+
warning: "\u203C",
|
|
135
|
+
/** A live thing: the running position, an active marker. */
|
|
136
|
+
dot: "\u25CF",
|
|
137
|
+
/** Play, in the sense of "run this". */
|
|
138
|
+
play: "\u25B6",
|
|
139
|
+
// --- handling -------------------------------------------------------------
|
|
140
|
+
/** The drag grip on a reorderable row. */
|
|
141
|
+
grip: "\u283F",
|
|
142
|
+
/** Add one of whatever this list holds. */
|
|
143
|
+
add: "+",
|
|
144
|
+
// --- annotation -----------------------------------------------------------
|
|
145
|
+
/** Documentation notes: the pencil. Both apps had chosen it independently,
|
|
146
|
+
* which is the happiest way for a vocabulary entry to arrive. */
|
|
147
|
+
note: "\u270E",
|
|
148
|
+
/** A comment thread. Speech, not a pencil: documentation states the reason,
|
|
149
|
+
* comments are the conversation, and the two must never look alike. */
|
|
150
|
+
comment: "\u275D"
|
|
151
|
+
};
|
|
152
|
+
var iconSvg = {
|
|
153
|
+
/** A document with lines: notes exist on this thing. */
|
|
154
|
+
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>',
|
|
155
|
+
/** The same document, empty: nothing written here yet. */
|
|
156
|
+
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>'
|
|
157
|
+
};
|
|
158
|
+
|
|
99
159
|
// src/pane-shell.ts
|
|
100
160
|
var DEFAULT_MAX = (w) => Math.min(640, w - 420);
|
|
101
161
|
var openVar = { nav: "--nav-open-w", inspector: "--insp-open-w" };
|
|
@@ -228,6 +288,12 @@ function mountPaneShell(host, opts) {
|
|
|
228
288
|
}
|
|
229
289
|
|
|
230
290
|
// src/menu.ts
|
|
291
|
+
var EDIT_MENU = {
|
|
292
|
+
undo: { label: "Undo", accelerator: "CmdOrCtrl+Z" },
|
|
293
|
+
redo: { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z" },
|
|
294
|
+
duplicate: { label: "Duplicate", accelerator: "CmdOrCtrl+D" },
|
|
295
|
+
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" }
|
|
296
|
+
};
|
|
231
297
|
var PANE_MENU = {
|
|
232
298
|
showNav: { label: "Show Navigator", accelerator: "CmdOrCtrl+1" },
|
|
233
299
|
showInspector: { label: "Show Inspector", accelerator: "CmdOrCtrl+2" },
|
|
@@ -321,7 +387,7 @@ function mountSettingsDialog(opts) {
|
|
|
321
387
|
const panelFor = /* @__PURE__ */ new Map();
|
|
322
388
|
const tabFor = /* @__PURE__ */ new Map();
|
|
323
389
|
const handles = /* @__PURE__ */ new Map();
|
|
324
|
-
let
|
|
390
|
+
let active2 = opts.sections[0]?.id ?? "";
|
|
325
391
|
let lastGroup;
|
|
326
392
|
for (const s of opts.sections) {
|
|
327
393
|
if (s.group && s.group !== lastGroup) {
|
|
@@ -341,7 +407,7 @@ function mountSettingsDialog(opts) {
|
|
|
341
407
|
panelFor.set(s.id, panel);
|
|
342
408
|
}
|
|
343
409
|
function showTab(id) {
|
|
344
|
-
|
|
410
|
+
active2 = id;
|
|
345
411
|
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
346
412
|
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
347
413
|
}
|
|
@@ -383,7 +449,7 @@ function mountSettingsDialog(opts) {
|
|
|
383
449
|
handles.set(s.id, s.mount(panel));
|
|
384
450
|
}
|
|
385
451
|
error.hidden = true;
|
|
386
|
-
showTab(tabId ??
|
|
452
|
+
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
387
453
|
dialog.showModal();
|
|
388
454
|
},
|
|
389
455
|
destroy() {
|
|
@@ -392,20 +458,874 @@ function mountSettingsDialog(opts) {
|
|
|
392
458
|
};
|
|
393
459
|
}
|
|
394
460
|
|
|
461
|
+
// src/tool-window-web.ts
|
|
462
|
+
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>`;
|
|
463
|
+
function pinButton(opts) {
|
|
464
|
+
const b = el("button", "swin-pin");
|
|
465
|
+
b.type = "button";
|
|
466
|
+
b.innerHTML = PIN_ICON;
|
|
467
|
+
let pinned = opts.pinned;
|
|
468
|
+
const reflect = () => {
|
|
469
|
+
b.classList.toggle("on", pinned);
|
|
470
|
+
b.setAttribute("aria-pressed", String(pinned));
|
|
471
|
+
b.title = pinned ? "Pinned on top: click to unpin" : "Click to keep on top";
|
|
472
|
+
};
|
|
473
|
+
reflect();
|
|
474
|
+
b.addEventListener("click", () => {
|
|
475
|
+
pinned = !pinned;
|
|
476
|
+
reflect();
|
|
477
|
+
opts.onToggle(pinned);
|
|
478
|
+
});
|
|
479
|
+
return b;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/confirm.ts
|
|
483
|
+
function confirmDialog(opts) {
|
|
484
|
+
return new Promise((resolve) => {
|
|
485
|
+
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
486
|
+
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
487
|
+
cancel.type = "button";
|
|
488
|
+
danger.type = "button";
|
|
489
|
+
const dlg = el(
|
|
490
|
+
"dialog",
|
|
491
|
+
"confirm-dialog",
|
|
492
|
+
el("div", "confirm-title", opts.title),
|
|
493
|
+
el("div", "confirm-body", opts.body),
|
|
494
|
+
el("div", "confirm-actions", cancel, danger)
|
|
495
|
+
);
|
|
496
|
+
dlg.setAttribute("aria-label", opts.title);
|
|
497
|
+
document.body.append(dlg);
|
|
498
|
+
let done = false;
|
|
499
|
+
const finish = (ok) => {
|
|
500
|
+
if (done) return;
|
|
501
|
+
done = true;
|
|
502
|
+
resolve(ok);
|
|
503
|
+
dlg.classList.add("closing");
|
|
504
|
+
const dur = parseFloat(getComputedStyle(dlg).animationDuration) || 0;
|
|
505
|
+
const teardown = () => {
|
|
506
|
+
dlg.close();
|
|
507
|
+
dlg.remove();
|
|
508
|
+
};
|
|
509
|
+
if (dur === 0) {
|
|
510
|
+
teardown();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const timer2 = setTimeout(teardown, dur * 1e3 + 120);
|
|
514
|
+
dlg.addEventListener("animationend", (e) => {
|
|
515
|
+
if (e.target !== dlg) return;
|
|
516
|
+
clearTimeout(timer2);
|
|
517
|
+
teardown();
|
|
518
|
+
});
|
|
519
|
+
};
|
|
520
|
+
cancel.addEventListener("click", () => finish(false));
|
|
521
|
+
danger.addEventListener("click", () => finish(true));
|
|
522
|
+
dlg.addEventListener("cancel", (e) => {
|
|
523
|
+
e.preventDefault();
|
|
524
|
+
finish(false);
|
|
525
|
+
});
|
|
526
|
+
dlg.addEventListener("mousedown", (e) => {
|
|
527
|
+
if (e.target === dlg) finish(false);
|
|
528
|
+
});
|
|
529
|
+
dlg.showModal();
|
|
530
|
+
cancel.focus();
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/about.ts
|
|
535
|
+
function showAbout(opts) {
|
|
536
|
+
return new Promise((resolve) => {
|
|
537
|
+
const dialog = el("dialog", "shell-about");
|
|
538
|
+
if (opts.wordmark !== void 0) {
|
|
539
|
+
const mark = el("div", "shell-about-mark");
|
|
540
|
+
mark.innerHTML = opts.wordmark;
|
|
541
|
+
dialog.append(mark);
|
|
542
|
+
}
|
|
543
|
+
dialog.append(
|
|
544
|
+
el("div", "shell-about-name", opts.appName),
|
|
545
|
+
el("div", "shell-about-version", `Version ${opts.version}`)
|
|
546
|
+
);
|
|
547
|
+
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
548
|
+
if (opts.credits !== void 0) dialog.append(el("p", "shell-about-credits", opts.credits));
|
|
549
|
+
if (opts.links?.length) {
|
|
550
|
+
const row = el("div", "shell-about-links");
|
|
551
|
+
for (const link of opts.links) {
|
|
552
|
+
if (!opts.onOpenLink) {
|
|
553
|
+
row.append(el("span", "shell-about-link", link.label));
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const a = el("button", "shell-about-link", link.label);
|
|
557
|
+
a.type = "button";
|
|
558
|
+
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
559
|
+
row.append(a);
|
|
560
|
+
}
|
|
561
|
+
dialog.append(row);
|
|
562
|
+
}
|
|
563
|
+
const close = el("button", "shell-about-close", "Close");
|
|
564
|
+
close.type = "button";
|
|
565
|
+
dialog.append(el("div", "shell-about-actions", close));
|
|
566
|
+
dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
567
|
+
document.body.append(dialog);
|
|
568
|
+
let done = false;
|
|
569
|
+
const finish = () => {
|
|
570
|
+
if (done) return;
|
|
571
|
+
done = true;
|
|
572
|
+
dialog.close();
|
|
573
|
+
dialog.remove();
|
|
574
|
+
resolve();
|
|
575
|
+
};
|
|
576
|
+
close.addEventListener("click", finish);
|
|
577
|
+
dialog.addEventListener("close", finish);
|
|
578
|
+
dialog.showModal();
|
|
579
|
+
close.focus();
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/anchored.ts
|
|
584
|
+
var current = null;
|
|
585
|
+
function openAnchoredPanel(opts) {
|
|
586
|
+
if (current) {
|
|
587
|
+
const prev = current;
|
|
588
|
+
current = null;
|
|
589
|
+
prev.close();
|
|
590
|
+
if (prev.anchor === opts.anchor) return null;
|
|
591
|
+
}
|
|
592
|
+
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
593
|
+
const closeBtn = el("button", "shell-anchored-close", "\u2715");
|
|
594
|
+
closeBtn.type = "button";
|
|
595
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
596
|
+
const body = el("div", "shell-anchored-body");
|
|
597
|
+
panel.append(
|
|
598
|
+
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
599
|
+
body
|
|
600
|
+
);
|
|
601
|
+
document.body.append(panel);
|
|
602
|
+
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
603
|
+
let closed = false;
|
|
604
|
+
const close = () => {
|
|
605
|
+
if (closed) return;
|
|
606
|
+
closed = true;
|
|
607
|
+
if (current?.panel === panel) current = null;
|
|
608
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
609
|
+
document.removeEventListener("keydown", onKey, true);
|
|
610
|
+
panel.remove();
|
|
611
|
+
opts.onClose?.();
|
|
612
|
+
};
|
|
613
|
+
closeBtn.addEventListener("click", close);
|
|
614
|
+
const onDown = (e) => {
|
|
615
|
+
const target = e.target;
|
|
616
|
+
if (panel.contains(target) || opts.anchor.contains(target)) return;
|
|
617
|
+
if (opts.ignoreDown !== void 0 && target instanceof Element && target.closest(opts.ignoreDown)) return;
|
|
618
|
+
close();
|
|
619
|
+
};
|
|
620
|
+
const onKey = (e) => {
|
|
621
|
+
if (e.key !== "Escape") return;
|
|
622
|
+
if (opts.deferEscape !== void 0 && document.querySelector(opts.deferEscape)) return;
|
|
623
|
+
close();
|
|
624
|
+
};
|
|
625
|
+
setTimeout(() => {
|
|
626
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
627
|
+
document.addEventListener("keydown", onKey, true);
|
|
628
|
+
}, 0);
|
|
629
|
+
current = { anchor: opts.anchor, panel, close };
|
|
630
|
+
return { panel, body, close };
|
|
631
|
+
}
|
|
632
|
+
function closeAnchoredPanel() {
|
|
633
|
+
const open = current;
|
|
634
|
+
current = null;
|
|
635
|
+
open?.close();
|
|
636
|
+
}
|
|
637
|
+
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
638
|
+
const a = anchor.getBoundingClientRect();
|
|
639
|
+
const GAP2 = 10;
|
|
640
|
+
const CAP = window.innerHeight * 0.7;
|
|
641
|
+
const MIN_H = 140;
|
|
642
|
+
panel.style.width = `${width}px`;
|
|
643
|
+
const leftOf = a.left - width - GAP2;
|
|
644
|
+
const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
|
|
645
|
+
panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
|
|
646
|
+
let bottomLimit = window.innerHeight - 8;
|
|
647
|
+
for (const selector of keepClear) {
|
|
648
|
+
const bar = document.querySelector(selector);
|
|
649
|
+
if (!bar) continue;
|
|
650
|
+
const r = bar.getBoundingClientRect();
|
|
651
|
+
if (r.height > 0) bottomLimit = Math.min(bottomLimit, r.top - 6);
|
|
652
|
+
}
|
|
653
|
+
const roomBelow = bottomLimit - (a.bottom + GAP2);
|
|
654
|
+
const roomAbove = a.top - GAP2 - 8;
|
|
655
|
+
if (roomAbove > roomBelow) {
|
|
656
|
+
panel.style.top = "auto";
|
|
657
|
+
panel.style.bottom = `${Math.round(window.innerHeight - (a.top - GAP2))}px`;
|
|
658
|
+
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomAbove)))}px`;
|
|
659
|
+
} else {
|
|
660
|
+
panel.style.bottom = "auto";
|
|
661
|
+
panel.style.top = `${Math.round(Math.max(8, a.bottom + GAP2))}px`;
|
|
662
|
+
panel.style.maxHeight = `${Math.round(Math.max(MIN_H, Math.min(CAP, roomBelow)))}px`;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/vc-view.ts
|
|
667
|
+
function foldVc(shards, keys) {
|
|
668
|
+
if (!keys) return void 0;
|
|
669
|
+
let out;
|
|
670
|
+
for (const key of keys.split(" ")) {
|
|
671
|
+
const s = shards.get(key);
|
|
672
|
+
if (!s) continue;
|
|
673
|
+
out ??= { key, writable: true };
|
|
674
|
+
if (!s.writable) out.writable = false;
|
|
675
|
+
if (s.lockedBy?.length) out.lockedBy = [.../* @__PURE__ */ new Set([...out.lockedBy ?? [], ...s.lockedBy])];
|
|
676
|
+
if (s.outOfDate) out.outOfDate = true;
|
|
677
|
+
}
|
|
678
|
+
return out;
|
|
679
|
+
}
|
|
680
|
+
function vcBadgeFor(s) {
|
|
681
|
+
if (s?.lockedBy?.length) return { glyph: icon.locked, cls: "vc-locked", title: `Locked by ${s.lockedBy.join(", ")}` };
|
|
682
|
+
if (s?.outOfDate) return { glyph: icon.down, cls: "vc-stale", title: "Out of date - a newer version is on the server" };
|
|
683
|
+
if (s && !s.writable) return { glyph: icon.readOnly, cls: "vc-frozen", title: "Read-only on disk - saving checks it out" };
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
function paintVcBadges(root, shards) {
|
|
687
|
+
root.querySelectorAll("[data-vc]").forEach((host) => {
|
|
688
|
+
host.querySelector(":scope > .vc-badge")?.remove();
|
|
689
|
+
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
690
|
+
if (!badge) return;
|
|
691
|
+
const span = el("span", { className: `vc-badge ${badge.cls}`, text: badge.glyph, tip: badge.title });
|
|
692
|
+
host.append(span);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
function lockControls(host, off, staysLive) {
|
|
696
|
+
host.classList.toggle("vc-readonly", off);
|
|
697
|
+
if (off) {
|
|
698
|
+
host.querySelectorAll("input, textarea, select, button").forEach((c) => {
|
|
699
|
+
if (c.disabled || staysLive !== "" && c.matches(staysLive)) return;
|
|
700
|
+
c.disabled = true;
|
|
701
|
+
c.classList.add("vc-off");
|
|
702
|
+
});
|
|
703
|
+
} else {
|
|
704
|
+
host.querySelectorAll(".vc-off").forEach((c) => {
|
|
705
|
+
c.disabled = false;
|
|
706
|
+
c.classList.remove("vc-off");
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function lockNotice(holders) {
|
|
711
|
+
return el(
|
|
712
|
+
"div",
|
|
713
|
+
{ className: "vc-lock" },
|
|
714
|
+
el("span", { className: "vc-lock-glyph", text: icon.locked }),
|
|
715
|
+
el("span", { text: `Locked by ${holders.join(", ")} - read-only until they release it.` })
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/identity.ts
|
|
720
|
+
function askIdentity(opts = {}) {
|
|
721
|
+
return new Promise((resolve) => {
|
|
722
|
+
const seed = opts.current ?? opts.suggested;
|
|
723
|
+
const name = el("input", "shell-ident-input");
|
|
724
|
+
name.placeholder = "Your name";
|
|
725
|
+
name.value = seed?.name ?? "";
|
|
726
|
+
const email = el("input", "shell-ident-input");
|
|
727
|
+
email.placeholder = "Email (optional)";
|
|
728
|
+
email.type = "email";
|
|
729
|
+
email.value = seed?.email ?? "";
|
|
730
|
+
const skip = el("button", "shell-ident-btn", "Skip");
|
|
731
|
+
const save = el("button", "shell-ident-btn primary", "Save");
|
|
732
|
+
skip.type = "button";
|
|
733
|
+
save.type = "button";
|
|
734
|
+
const dialog = el(
|
|
735
|
+
"dialog",
|
|
736
|
+
"shell-ident",
|
|
737
|
+
el("div", "shell-ident-title", "Who is working here?"),
|
|
738
|
+
el(
|
|
739
|
+
"p",
|
|
740
|
+
"shell-ident-hint",
|
|
741
|
+
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
742
|
+
),
|
|
743
|
+
el("div", "shell-ident-fields", name, email),
|
|
744
|
+
el("div", "shell-ident-actions", skip, save)
|
|
745
|
+
);
|
|
746
|
+
document.body.append(dialog);
|
|
747
|
+
let done = false;
|
|
748
|
+
const finish = (keep) => {
|
|
749
|
+
if (done) return;
|
|
750
|
+
done = true;
|
|
751
|
+
const typed = name.value.trim();
|
|
752
|
+
const value = email.value.trim();
|
|
753
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
754
|
+
dialog.close();
|
|
755
|
+
dialog.remove();
|
|
756
|
+
resolve(answer);
|
|
757
|
+
};
|
|
758
|
+
skip.addEventListener("click", () => finish(false));
|
|
759
|
+
save.addEventListener("click", () => finish(true));
|
|
760
|
+
name.addEventListener("keydown", (e) => {
|
|
761
|
+
if (e.key === "Enter") finish(true);
|
|
762
|
+
});
|
|
763
|
+
email.addEventListener("keydown", (e) => {
|
|
764
|
+
if (e.key === "Enter") finish(true);
|
|
765
|
+
});
|
|
766
|
+
dialog.addEventListener("close", () => finish(false));
|
|
767
|
+
dialog.showModal();
|
|
768
|
+
name.focus();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/notes-editor.ts
|
|
773
|
+
var EDITOR_ONLY = "";
|
|
774
|
+
var UNTYPED_KEY = "__untyped";
|
|
775
|
+
var CLASS_LABEL = {
|
|
776
|
+
everyone: "Everyone",
|
|
777
|
+
writing: "Writing",
|
|
778
|
+
vo: "Voice (VO)",
|
|
779
|
+
loc: "Localisers"
|
|
780
|
+
};
|
|
781
|
+
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
782
|
+
var PLACEHOLDER = {
|
|
783
|
+
vo: "direction for the voice actor / director",
|
|
784
|
+
loc: "context for translators"
|
|
785
|
+
};
|
|
786
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "an internal note - never exported" : PLACEHOLDER[cls] ?? "intent / rationale - why this is here";
|
|
787
|
+
function openNotesEditor(opts) {
|
|
788
|
+
const { notes } = opts;
|
|
789
|
+
const seed = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const line of notes.own) {
|
|
791
|
+
const key = line.type ?? EDITOR_ONLY;
|
|
792
|
+
seed.set(key, seed.has(key) ? `${seed.get(key)}
|
|
793
|
+
${line.text}` : line.text);
|
|
794
|
+
}
|
|
795
|
+
const shown = [...seed.keys()];
|
|
796
|
+
if (shown.length === 0) shown.push(notes.classes[0] ?? EDITOR_ONLY);
|
|
797
|
+
const areas = /* @__PURE__ */ new Map();
|
|
798
|
+
const value = () => {
|
|
799
|
+
const out = [];
|
|
800
|
+
for (const cls of shown) {
|
|
801
|
+
const area = areas.get(cls);
|
|
802
|
+
if (!area) continue;
|
|
803
|
+
for (const raw of area.value.split("\n")) {
|
|
804
|
+
const text = raw.trim();
|
|
805
|
+
if (text !== "") out.push(cls === EDITOR_ONLY ? { text } : { type: cls, text });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return out;
|
|
809
|
+
};
|
|
810
|
+
const body = el("div", "shell-notes-body");
|
|
811
|
+
const render = () => {
|
|
812
|
+
body.replaceChildren();
|
|
813
|
+
if (notes.inherited.length > 0) {
|
|
814
|
+
const from = el(
|
|
815
|
+
"div",
|
|
816
|
+
{ className: "shell-notes-inherited" },
|
|
817
|
+
el("div", "shell-notes-caption", "Inherited")
|
|
818
|
+
);
|
|
819
|
+
for (const level of notes.inherited) {
|
|
820
|
+
from.append(el(
|
|
821
|
+
"div",
|
|
822
|
+
{ className: "shell-notes-level" },
|
|
823
|
+
el("span", "shell-notes-level-label", level.label),
|
|
824
|
+
...level.lines.map((line) => el(
|
|
825
|
+
"div",
|
|
826
|
+
{ className: "shell-notes-line" },
|
|
827
|
+
...line.type !== void 0 && line.type !== "everyone" ? [el("span", "shell-notes-cls", label(line.type))] : [],
|
|
828
|
+
el("span", "", line.text)
|
|
829
|
+
))
|
|
830
|
+
));
|
|
831
|
+
}
|
|
832
|
+
body.append(from);
|
|
833
|
+
}
|
|
834
|
+
for (const cls of shown) {
|
|
835
|
+
const block = el(
|
|
836
|
+
"div",
|
|
837
|
+
{ className: "shell-notes-class" },
|
|
838
|
+
el("label", "shell-notes-caption", label(cls))
|
|
839
|
+
);
|
|
840
|
+
const area = el("textarea", "shell-notes-text");
|
|
841
|
+
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
842
|
+
area.rows = 4;
|
|
843
|
+
area.placeholder = placeholder(cls);
|
|
844
|
+
area.spellcheck = true;
|
|
845
|
+
areas.set(cls, area);
|
|
846
|
+
block.append(area);
|
|
847
|
+
body.append(block);
|
|
848
|
+
}
|
|
849
|
+
body.append(el("p", "shell-notes-hint", "One note per line."));
|
|
850
|
+
const remaining = [...notes.classes, EDITOR_ONLY].filter((c) => !shown.includes(c));
|
|
851
|
+
if (remaining.length > 0) {
|
|
852
|
+
const add = el("select", "shell-notes-add");
|
|
853
|
+
add.append(new Option("Add a note for\u2026", ""));
|
|
854
|
+
for (const cls of remaining) add.append(new Option(label(cls), cls === EDITOR_ONLY ? UNTYPED_KEY : cls));
|
|
855
|
+
add.addEventListener("change", () => {
|
|
856
|
+
if (add.value === "") return;
|
|
857
|
+
const cls = add.value === UNTYPED_KEY ? EDITOR_ONLY : add.value;
|
|
858
|
+
shown.push(cls);
|
|
859
|
+
render();
|
|
860
|
+
areas.get(cls)?.focus();
|
|
861
|
+
});
|
|
862
|
+
body.append(add);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
render();
|
|
866
|
+
const done = el("button", "shell-notes-btn primary", "Done");
|
|
867
|
+
const dialog = el(
|
|
868
|
+
"dialog",
|
|
869
|
+
{ className: "shell-notes" },
|
|
870
|
+
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
871
|
+
body,
|
|
872
|
+
el("div", { className: "shell-notes-actions" }, done)
|
|
873
|
+
);
|
|
874
|
+
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
875
|
+
document.body.append(dialog);
|
|
876
|
+
const before = JSON.stringify(notes.own);
|
|
877
|
+
let closed = false;
|
|
878
|
+
const close = () => {
|
|
879
|
+
if (closed) return;
|
|
880
|
+
closed = true;
|
|
881
|
+
const next = value();
|
|
882
|
+
if (JSON.stringify(next) !== before) opts.save(next);
|
|
883
|
+
dialog.close();
|
|
884
|
+
dialog.remove();
|
|
885
|
+
};
|
|
886
|
+
done.addEventListener("click", close);
|
|
887
|
+
dialog.addEventListener("close", close);
|
|
888
|
+
dialog.showModal();
|
|
889
|
+
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/comments.ts
|
|
893
|
+
var stamp = (ts) => {
|
|
894
|
+
const when = new Date(ts);
|
|
895
|
+
if (Number.isNaN(when.getTime())) return ts;
|
|
896
|
+
return when.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
|
|
897
|
+
};
|
|
898
|
+
var gist = (thread) => {
|
|
899
|
+
const first = thread.messages[0]?.body ?? "";
|
|
900
|
+
return first.length > 60 ? `${first.slice(0, 57)}\u2026` : first;
|
|
901
|
+
};
|
|
902
|
+
function openComments(opts) {
|
|
903
|
+
const visible = opts.threads.filter((t) => opts.showResolved || t.resolved !== true);
|
|
904
|
+
const panel = openAnchoredPanel({
|
|
905
|
+
anchor: opts.anchor,
|
|
906
|
+
className: "comments",
|
|
907
|
+
title: "Comments",
|
|
908
|
+
width: 320,
|
|
909
|
+
// Below its button (the topline), unlike Patterpad's inspector rows which
|
|
910
|
+
// want it to the left; and never over the problems bar, which an author may
|
|
911
|
+
// be stepping through while reading the thread.
|
|
912
|
+
prefer: "below",
|
|
913
|
+
keepClear: [".problembar"]
|
|
914
|
+
});
|
|
915
|
+
if (!panel) return;
|
|
916
|
+
const { body, close } = panel;
|
|
917
|
+
let open = visible.length === 1 ? visible[0] : void 0;
|
|
918
|
+
let composing = visible.length === 0;
|
|
919
|
+
const render = () => {
|
|
920
|
+
body.replaceChildren();
|
|
921
|
+
if (!composing && open === void 0) {
|
|
922
|
+
for (const thread2 of visible) {
|
|
923
|
+
const row = el(
|
|
924
|
+
"button",
|
|
925
|
+
{ className: `shell-cmt-row${thread2.resolved === true ? " resolved" : ""}` },
|
|
926
|
+
el("span", { className: "shell-cmt-row-author", text: thread2.messages[0]?.author ?? "Someone" }),
|
|
927
|
+
el("span", { className: "shell-cmt-row-gist", text: gist(thread2) }),
|
|
928
|
+
...thread2.messages.length > 1 ? [el("span", { className: "shell-cmt-row-count", text: `${thread2.messages.length}` })] : []
|
|
929
|
+
);
|
|
930
|
+
row.addEventListener("click", () => {
|
|
931
|
+
open = thread2;
|
|
932
|
+
render();
|
|
933
|
+
});
|
|
934
|
+
body.append(row);
|
|
935
|
+
}
|
|
936
|
+
const fresh = el("button", { className: "shell-cmt-new", text: "New comment" });
|
|
937
|
+
fresh.addEventListener("click", () => {
|
|
938
|
+
open = void 0;
|
|
939
|
+
composing = true;
|
|
940
|
+
render();
|
|
941
|
+
});
|
|
942
|
+
body.append(fresh);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const thread = open;
|
|
946
|
+
if (thread) {
|
|
947
|
+
const list = el("div", { className: `shell-cmt-list${thread.resolved === true ? " resolved" : ""}` });
|
|
948
|
+
for (const message of thread.messages) {
|
|
949
|
+
list.append(el(
|
|
950
|
+
"div",
|
|
951
|
+
{ className: "shell-cmt-msg" },
|
|
952
|
+
el(
|
|
953
|
+
"div",
|
|
954
|
+
{ className: "shell-cmt-msg-head" },
|
|
955
|
+
el("span", { className: "shell-cmt-author", text: message.author === "" ? "Someone" : message.author }),
|
|
956
|
+
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
957
|
+
),
|
|
958
|
+
el("div", { className: "shell-cmt-body", text: message.body })
|
|
959
|
+
));
|
|
960
|
+
}
|
|
961
|
+
body.append(list);
|
|
962
|
+
}
|
|
963
|
+
const input = el("textarea", { className: "shell-cmt-input" });
|
|
964
|
+
input.rows = 2;
|
|
965
|
+
input.placeholder = thread ? "Reply\u2026" : "Comment\u2026";
|
|
966
|
+
input.addEventListener("keydown", (e) => {
|
|
967
|
+
e.stopPropagation();
|
|
968
|
+
if (e.key !== "Enter" || e.shiftKey) return;
|
|
969
|
+
e.preventDefault();
|
|
970
|
+
submit(true);
|
|
971
|
+
});
|
|
972
|
+
body.append(input);
|
|
973
|
+
const submit = (andClose) => {
|
|
974
|
+
const text = input.value.trim();
|
|
975
|
+
if (text === "") return;
|
|
976
|
+
const id = thread?.id ?? opts.newThreadId();
|
|
977
|
+
opts.post(id, text);
|
|
978
|
+
if (andClose) close();
|
|
979
|
+
else {
|
|
980
|
+
input.value = "";
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
const actions = el("div", { className: "shell-cmt-actions" });
|
|
984
|
+
const post = el("button", { className: "shell-cmt-btn primary", text: thread ? "Reply" : "Comment" });
|
|
985
|
+
post.addEventListener("click", () => submit(false));
|
|
986
|
+
actions.append(post);
|
|
987
|
+
if (thread) {
|
|
988
|
+
const resolved = thread.resolved === true;
|
|
989
|
+
const toggle = el("button", { className: "shell-cmt-btn", text: resolved ? "Reopen" : "Mark complete" });
|
|
990
|
+
toggle.addEventListener("click", () => {
|
|
991
|
+
opts.setResolved(thread.id, !resolved);
|
|
992
|
+
if (!resolved) close();
|
|
993
|
+
else render();
|
|
994
|
+
});
|
|
995
|
+
actions.append(toggle);
|
|
996
|
+
}
|
|
997
|
+
body.append(actions);
|
|
998
|
+
if (!thread) input.focus();
|
|
999
|
+
};
|
|
1000
|
+
render();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/job-view.ts
|
|
1004
|
+
var clock = (ms) => {
|
|
1005
|
+
const total = Math.round(ms / 1e3);
|
|
1006
|
+
const m = Math.floor(total / 60);
|
|
1007
|
+
const s = total % 60;
|
|
1008
|
+
return m > 0 ? `${m}m ${String(s).padStart(2, "0")}s` : `${s}s`;
|
|
1009
|
+
};
|
|
1010
|
+
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1011
|
+
function mountJobProgress(host, opts) {
|
|
1012
|
+
const units = opts.units ?? "runs";
|
|
1013
|
+
const fill = el("i", { className: "job-fill" });
|
|
1014
|
+
const bar = el("div", { className: "job-bar" }, fill);
|
|
1015
|
+
const what = el("span", { className: "job-label" });
|
|
1016
|
+
const count = el("span", { className: "job-count" });
|
|
1017
|
+
const time = el("span", { className: "job-time" });
|
|
1018
|
+
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
1019
|
+
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1020
|
+
strip.hidden = true;
|
|
1021
|
+
host.append(strip);
|
|
1022
|
+
const view = {
|
|
1023
|
+
element: strip,
|
|
1024
|
+
get visible() {
|
|
1025
|
+
return !strip.hidden;
|
|
1026
|
+
},
|
|
1027
|
+
begin(label2) {
|
|
1028
|
+
what.textContent = label2 ?? "Running\u2026";
|
|
1029
|
+
count.textContent = "";
|
|
1030
|
+
time.textContent = "";
|
|
1031
|
+
fill.style.width = "0%";
|
|
1032
|
+
cancel.disabled = false;
|
|
1033
|
+
cancel.textContent = "Cancel";
|
|
1034
|
+
strip.hidden = false;
|
|
1035
|
+
},
|
|
1036
|
+
update(done, total, elapsedMs) {
|
|
1037
|
+
if (strip.hidden) strip.hidden = false;
|
|
1038
|
+
const pct = total > 0 ? Math.min(100, done / total * 100) : 0;
|
|
1039
|
+
fill.style.width = `${pct}%`;
|
|
1040
|
+
count.textContent = total > 0 ? `${done} / ${total} ${units}` : `${done} ${units}`;
|
|
1041
|
+
if (estimable(done, total, elapsedMs)) {
|
|
1042
|
+
const remaining = elapsedMs / done * (total - done);
|
|
1043
|
+
time.textContent = `${clock(elapsedMs)} elapsed \xB7 about ${clock(remaining)} left`;
|
|
1044
|
+
} else {
|
|
1045
|
+
time.textContent = clock(elapsedMs);
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
end() {
|
|
1049
|
+
strip.hidden = true;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
cancel.addEventListener("click", () => {
|
|
1053
|
+
cancel.disabled = true;
|
|
1054
|
+
cancel.textContent = "Stopping\u2026";
|
|
1055
|
+
opts.onCancel();
|
|
1056
|
+
});
|
|
1057
|
+
return view;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/job.ts
|
|
1061
|
+
var JOB_PROGRESS = "job:progress";
|
|
1062
|
+
|
|
1063
|
+
// src/tooltip.ts
|
|
1064
|
+
var B_OPEN = "";
|
|
1065
|
+
var B_CLOSE = "";
|
|
1066
|
+
function tipBold(s) {
|
|
1067
|
+
return `${B_OPEN}${s}${B_CLOSE}`;
|
|
1068
|
+
}
|
|
1069
|
+
function renderTip(host, text) {
|
|
1070
|
+
host.textContent = "";
|
|
1071
|
+
const re = /\u0002([^\u0003]*)\u0003/g;
|
|
1072
|
+
let last = 0;
|
|
1073
|
+
let m;
|
|
1074
|
+
while ((m = re.exec(text)) !== null) {
|
|
1075
|
+
if (m.index > last) host.append(text.slice(last, m.index));
|
|
1076
|
+
const strong = document.createElement("strong");
|
|
1077
|
+
strong.textContent = m[1] ?? "";
|
|
1078
|
+
host.append(strong);
|
|
1079
|
+
last = m.index + m[0].length;
|
|
1080
|
+
}
|
|
1081
|
+
if (last < text.length) host.append(text.slice(last));
|
|
1082
|
+
}
|
|
1083
|
+
var inited = false;
|
|
1084
|
+
var suppressed;
|
|
1085
|
+
var SHOW_DELAY = 350;
|
|
1086
|
+
var EDGE = 6;
|
|
1087
|
+
var GAP = 7;
|
|
1088
|
+
var tip = null;
|
|
1089
|
+
var timer = 0;
|
|
1090
|
+
var active = null;
|
|
1091
|
+
var activeKey = null;
|
|
1092
|
+
function ensureEl() {
|
|
1093
|
+
if (tip) return tip;
|
|
1094
|
+
const el2 = document.createElement("div");
|
|
1095
|
+
el2.className = "tooltip";
|
|
1096
|
+
el2.setAttribute("role", "tooltip");
|
|
1097
|
+
el2.hidden = true;
|
|
1098
|
+
document.body.appendChild(el2);
|
|
1099
|
+
tip = el2;
|
|
1100
|
+
return el2;
|
|
1101
|
+
}
|
|
1102
|
+
var rectOf = (anchor) => anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor;
|
|
1103
|
+
function place(anchor) {
|
|
1104
|
+
const t = ensureEl();
|
|
1105
|
+
const r = rectOf(anchor);
|
|
1106
|
+
const tr = t.getBoundingClientRect();
|
|
1107
|
+
let top = r.top - tr.height - GAP;
|
|
1108
|
+
if (top < EDGE) top = r.top + r.height + GAP;
|
|
1109
|
+
let left = r.left + r.width / 2 - tr.width / 2;
|
|
1110
|
+
left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));
|
|
1111
|
+
t.style.left = `${Math.round(left)}px`;
|
|
1112
|
+
t.style.top = `${Math.round(top)}px`;
|
|
1113
|
+
}
|
|
1114
|
+
function show(anchor) {
|
|
1115
|
+
const text = anchor.dataset["tip"];
|
|
1116
|
+
if (!text) return;
|
|
1117
|
+
paint(anchor, text);
|
|
1118
|
+
}
|
|
1119
|
+
function paint(anchor, text) {
|
|
1120
|
+
if (suppressed?.()) return;
|
|
1121
|
+
const t = ensureEl();
|
|
1122
|
+
const host = (anchor instanceof HTMLElement ? anchor.closest("dialog[open]") : null) ?? document.body;
|
|
1123
|
+
if (t.parentElement !== host) host.appendChild(t);
|
|
1124
|
+
active = anchor instanceof HTMLElement ? anchor : null;
|
|
1125
|
+
renderTip(t, text);
|
|
1126
|
+
t.hidden = false;
|
|
1127
|
+
t.classList.remove("show");
|
|
1128
|
+
void t.offsetWidth;
|
|
1129
|
+
place(anchor);
|
|
1130
|
+
t.classList.add("show");
|
|
1131
|
+
}
|
|
1132
|
+
function hide() {
|
|
1133
|
+
if (timer) {
|
|
1134
|
+
window.clearTimeout(timer);
|
|
1135
|
+
timer = 0;
|
|
1136
|
+
}
|
|
1137
|
+
active = null;
|
|
1138
|
+
activeKey = null;
|
|
1139
|
+
if (tip) {
|
|
1140
|
+
tip.hidden = true;
|
|
1141
|
+
tip.classList.remove("show");
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function schedule(anchor) {
|
|
1145
|
+
if (anchor === active) return;
|
|
1146
|
+
if (timer) window.clearTimeout(timer);
|
|
1147
|
+
timer = window.setTimeout(() => show(anchor), SHOW_DELAY);
|
|
1148
|
+
}
|
|
1149
|
+
function tipAt(key, rect, text) {
|
|
1150
|
+
if (key === activeKey) {
|
|
1151
|
+
if (tip && !tip.hidden) {
|
|
1152
|
+
renderTip(tip, text);
|
|
1153
|
+
place(rect);
|
|
1154
|
+
}
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
hide();
|
|
1158
|
+
activeKey = key;
|
|
1159
|
+
timer = window.setTimeout(() => {
|
|
1160
|
+
timer = 0;
|
|
1161
|
+
paint(rect, text);
|
|
1162
|
+
}, SHOW_DELAY);
|
|
1163
|
+
}
|
|
1164
|
+
function hideTip() {
|
|
1165
|
+
hide();
|
|
1166
|
+
}
|
|
1167
|
+
var tipAncestor = (n) => n instanceof Element ? n.closest("[data-tip]") : null;
|
|
1168
|
+
function initTooltips(opts = {}) {
|
|
1169
|
+
if (inited) return;
|
|
1170
|
+
inited = true;
|
|
1171
|
+
suppressed = opts.suppressed;
|
|
1172
|
+
document.addEventListener("pointerover", (e) => {
|
|
1173
|
+
const el2 = tipAncestor(e.target);
|
|
1174
|
+
if (el2) schedule(el2);
|
|
1175
|
+
});
|
|
1176
|
+
document.addEventListener("pointerout", (e) => {
|
|
1177
|
+
const el2 = tipAncestor(e.target);
|
|
1178
|
+
if (!el2) return;
|
|
1179
|
+
const to = e.relatedTarget;
|
|
1180
|
+
if (to instanceof Node && el2.contains(to)) return;
|
|
1181
|
+
hide();
|
|
1182
|
+
});
|
|
1183
|
+
document.addEventListener("focusin", (e) => {
|
|
1184
|
+
const el2 = tipAncestor(e.target);
|
|
1185
|
+
if (el2) schedule(el2);
|
|
1186
|
+
});
|
|
1187
|
+
document.addEventListener("focusout", () => hide());
|
|
1188
|
+
document.addEventListener("pointerdown", () => hide(), true);
|
|
1189
|
+
document.addEventListener("scroll", () => {
|
|
1190
|
+
if (active) hide();
|
|
1191
|
+
}, true);
|
|
1192
|
+
document.addEventListener("keydown", (e) => {
|
|
1193
|
+
if (e.key === "Escape") hide();
|
|
1194
|
+
});
|
|
1195
|
+
window.addEventListener("blur", () => hide());
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/save.ts
|
|
1199
|
+
function createSaveController(opts) {
|
|
1200
|
+
const delayMs = opts.delayMs ?? 700;
|
|
1201
|
+
const maxWaitMs = opts.maxWaitMs ?? 5e3;
|
|
1202
|
+
let status = "saved";
|
|
1203
|
+
let auto = true;
|
|
1204
|
+
let dirty = false;
|
|
1205
|
+
let dirtySince = 0;
|
|
1206
|
+
let timer2;
|
|
1207
|
+
let inFlight;
|
|
1208
|
+
const setStatus = (next) => {
|
|
1209
|
+
if (next === status) return;
|
|
1210
|
+
status = next;
|
|
1211
|
+
opts.onStatus?.(status);
|
|
1212
|
+
};
|
|
1213
|
+
const clearTimer = () => {
|
|
1214
|
+
if (timer2 === void 0) return;
|
|
1215
|
+
clearTimeout(timer2);
|
|
1216
|
+
timer2 = void 0;
|
|
1217
|
+
};
|
|
1218
|
+
const arm = () => {
|
|
1219
|
+
clearTimer();
|
|
1220
|
+
if (!auto) return;
|
|
1221
|
+
const deadline = dirtySince + maxWaitMs;
|
|
1222
|
+
const wait = Math.max(0, Math.min(delayMs, deadline - Date.now()));
|
|
1223
|
+
timer2 = setTimeout(() => {
|
|
1224
|
+
timer2 = void 0;
|
|
1225
|
+
void run();
|
|
1226
|
+
}, wait);
|
|
1227
|
+
};
|
|
1228
|
+
const run = async () => {
|
|
1229
|
+
if (inFlight) return inFlight;
|
|
1230
|
+
if (!dirty) {
|
|
1231
|
+
setStatus("saved");
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
clearTimer();
|
|
1235
|
+
dirty = false;
|
|
1236
|
+
setStatus("saving");
|
|
1237
|
+
inFlight = (async () => {
|
|
1238
|
+
let ok = false;
|
|
1239
|
+
try {
|
|
1240
|
+
ok = await opts.write();
|
|
1241
|
+
} catch {
|
|
1242
|
+
ok = false;
|
|
1243
|
+
}
|
|
1244
|
+
if (!ok) {
|
|
1245
|
+
if (!dirty) {
|
|
1246
|
+
dirty = true;
|
|
1247
|
+
dirtySince = dirtySince || Date.now();
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
setStatus(dirty ? "unsaved" : "saved");
|
|
1251
|
+
if (dirty) arm();
|
|
1252
|
+
})();
|
|
1253
|
+
try {
|
|
1254
|
+
await inFlight;
|
|
1255
|
+
} finally {
|
|
1256
|
+
inFlight = void 0;
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
return {
|
|
1260
|
+
get status() {
|
|
1261
|
+
return status;
|
|
1262
|
+
},
|
|
1263
|
+
get pending() {
|
|
1264
|
+
return dirty || inFlight !== void 0;
|
|
1265
|
+
},
|
|
1266
|
+
touch() {
|
|
1267
|
+
if (!dirty) dirtySince = Date.now();
|
|
1268
|
+
dirty = true;
|
|
1269
|
+
if (!inFlight) setStatus("unsaved");
|
|
1270
|
+
arm();
|
|
1271
|
+
},
|
|
1272
|
+
async flush() {
|
|
1273
|
+
if (inFlight) await inFlight;
|
|
1274
|
+
if (dirty) await run();
|
|
1275
|
+
else setStatus("saved");
|
|
1276
|
+
},
|
|
1277
|
+
cancel() {
|
|
1278
|
+
clearTimer();
|
|
1279
|
+
dirty = false;
|
|
1280
|
+
setStatus(inFlight ? "saving" : "saved");
|
|
1281
|
+
},
|
|
1282
|
+
setAuto(on) {
|
|
1283
|
+
auto = on;
|
|
1284
|
+
if (on) {
|
|
1285
|
+
if (dirty) arm();
|
|
1286
|
+
} else clearTimer();
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
exports.EDIT_MENU = EDIT_MENU;
|
|
1292
|
+
exports.JOB_PROGRESS = JOB_PROGRESS;
|
|
395
1293
|
exports.PALETTE = PALETTE;
|
|
396
1294
|
exports.PALETTE_SIZE = PALETTE_SIZE;
|
|
397
1295
|
exports.PANE_MENU = PANE_MENU;
|
|
1296
|
+
exports.askIdentity = askIdentity;
|
|
1297
|
+
exports.closeAnchoredPanel = closeAnchoredPanel;
|
|
398
1298
|
exports.colourFor = colourFor;
|
|
399
1299
|
exports.colourIndex = colourIndex;
|
|
1300
|
+
exports.confirmDialog = confirmDialog;
|
|
1301
|
+
exports.createSaveController = createSaveController;
|
|
400
1302
|
exports.dupGuard = dupGuard;
|
|
401
1303
|
exports.el = el;
|
|
402
1304
|
exports.expandableRow = expandableRow;
|
|
403
1305
|
exports.focusNewRow = focusNewRow;
|
|
1306
|
+
exports.foldVc = foldVc;
|
|
1307
|
+
exports.hideTip = hideTip;
|
|
1308
|
+
exports.icon = icon;
|
|
404
1309
|
exports.iconBtn = iconBtn;
|
|
1310
|
+
exports.iconSvg = iconSvg;
|
|
1311
|
+
exports.initTooltips = initTooltips;
|
|
405
1312
|
exports.labelled = labelled;
|
|
1313
|
+
exports.lockControls = lockControls;
|
|
1314
|
+
exports.lockNotice = lockNotice;
|
|
1315
|
+
exports.mountJobProgress = mountJobProgress;
|
|
406
1316
|
exports.mountPaneShell = mountPaneShell;
|
|
407
1317
|
exports.mountSettingsDialog = mountSettingsDialog;
|
|
408
1318
|
exports.moveItem = moveItem;
|
|
1319
|
+
exports.openAnchoredPanel = openAnchoredPanel;
|
|
1320
|
+
exports.openComments = openComments;
|
|
1321
|
+
exports.openNotesEditor = openNotesEditor;
|
|
1322
|
+
exports.paintVcBadges = paintVcBadges;
|
|
1323
|
+
exports.pinButton = pinButton;
|
|
1324
|
+
exports.placeAnchored = placeAnchored;
|
|
1325
|
+
exports.showAbout = showAbout;
|
|
409
1326
|
exports.tagChips = tagChips;
|
|
1327
|
+
exports.tipAt = tipAt;
|
|
1328
|
+
exports.tipBold = tipBold;
|
|
1329
|
+
exports.vcBadgeFor = vcBadgeFor;
|
|
410
1330
|
//# sourceMappingURL=index.cjs.map
|
|
411
1331
|
//# sourceMappingURL=index.cjs.map
|