@wildwinter/app-shell 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES.md +62 -0
- package/README.md +8 -0
- package/dist/about.css +17 -38
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +9 -9
- 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-DS4XXAZS.js +56 -0
- package/dist/chunk-DS4XXAZS.js.map +1 -0
- package/dist/chunk-EHR2KBZC.js +82 -0
- package/dist/chunk-EHR2KBZC.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-OPZPFOPS.js +66 -0
- package/dist/chunk-OPZPFOPS.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-R3NXPHRZ.js +534 -0
- package/dist/chunk-R3NXPHRZ.js.map +1 -0
- package/dist/chunk-SDOKZTAR.js +87 -0
- package/dist/chunk-SDOKZTAR.js.map +1 -0
- package/dist/chunk-SEWJLUOD.js +70 -0
- package/dist/chunk-SEWJLUOD.js.map +1 -0
- package/dist/chunk-UG2P777F.js +587 -0
- package/dist/chunk-UG2P777F.js.map +1 -0
- package/dist/chunk-YRFJLJUH.js +55 -0
- package/dist/chunk-YRFJLJUH.js.map +1 -0
- package/dist/chunk-YWMSQFXX.js +146 -0
- package/dist/chunk-YWMSQFXX.js.map +1 -0
- package/dist/comments.css +16 -14
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +2 -55
- 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 +214 -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 +56 -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 +1553 -260
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +385 -152
- package/dist/index.d.ts +385 -152
- package/dist/index.js +264 -823
- package/dist/index.js.map +1 -1
- package/dist/job.css +24 -1
- package/dist/job.css.map +1 -1
- package/dist/keys.cjs +155 -0
- package/dist/keys.cjs.map +1 -0
- package/dist/keys.css +41 -0
- package/dist/keys.css.map +1 -0
- package/dist/keys.d.cts +57 -0
- package/dist/keys.d.ts +57 -0
- package/dist/keys.js +3 -0
- package/dist/keys.js.map +1 -0
- 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 +9 -52
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +23 -5
- package/dist/pane-shell.css.map +1 -1
- package/dist/problems.cjs +61 -0
- package/dist/problems.cjs.map +1 -0
- package/dist/problems.d.cts +57 -0
- package/dist/problems.d.ts +57 -0
- package/dist/problems.js +3 -0
- package/dist/problems.js.map +1 -0
- 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/save.css +1 -1
- package/dist/save.css.map +1 -1
- package/dist/settings.css +62 -18
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +4 -1
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +47 -3
- package/dist/stepper.css.map +1 -1
- package/dist/toast.css +5 -5
- package/dist/toast.css.map +1 -1
- package/dist/tokens.css +20 -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 +8 -4
- 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/tooltip.css +3 -3
- package/dist/tooltip.css.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 +4 -3
- 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 +72 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -149,6 +149,204 @@ function initTooltips(opts = {}) {
|
|
|
149
149
|
window.addEventListener("blur", () => hide());
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
// src/icons.ts
|
|
153
|
+
var icon = {
|
|
154
|
+
// --- dismissing and revealing ---------------------------------------------
|
|
155
|
+
/** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
|
|
156
|
+
close: "\u2715",
|
|
157
|
+
/** The overflow menu on a document or a row: "more about this". */
|
|
158
|
+
more: "\u22EF",
|
|
159
|
+
/** Collapsed disclosure, pointing at what would open. */
|
|
160
|
+
collapsed: "\u25B8",
|
|
161
|
+
/** Expanded disclosure. */
|
|
162
|
+
expanded: "\u25BE",
|
|
163
|
+
/** A dropdown's affordance, where it is not a real <select>. */
|
|
164
|
+
dropdown: "\u25BE",
|
|
165
|
+
// --- moving through things ------------------------------------------------
|
|
166
|
+
/** Back / previous, in a breadcrumb or a stepper. */
|
|
167
|
+
back: "\u2039",
|
|
168
|
+
/** Forward / next. */
|
|
169
|
+
forward: "\u203A",
|
|
170
|
+
/** Up one, in a list. */
|
|
171
|
+
up: "\u2191",
|
|
172
|
+
/** Down one. */
|
|
173
|
+
down: "\u2193",
|
|
174
|
+
// --- state ----------------------------------------------------------------
|
|
175
|
+
/** Done, applied, agreed. */
|
|
176
|
+
tick: "\u2713",
|
|
177
|
+
/** Somebody else holds this: the version-control lock. */
|
|
178
|
+
locked: "\u2298",
|
|
179
|
+
/** Read-only on disk with no other holder: the resting state of everything
|
|
180
|
+
* in a lock-based working copy that nobody has checked out yet. */
|
|
181
|
+
readOnly: "\u25CB",
|
|
182
|
+
/** YOU hold this one: checked out / opened by the current user, still yours to
|
|
183
|
+
* edit. Shares its glyph with `note`, which the table permits: an entry is a
|
|
184
|
+
* MEANING, and two meanings may draw the same shape. */
|
|
185
|
+
checkedOut: "\u270E",
|
|
186
|
+
/** Tracked, with local changes not yet committed. */
|
|
187
|
+
modified: "\u25CF",
|
|
188
|
+
/** Not in version control yet: new since the last commit. */
|
|
189
|
+
untracked: "+",
|
|
190
|
+
/** Something needs attention but is not fatal. */
|
|
191
|
+
warning: "\u203C",
|
|
192
|
+
/** A live thing: the running position, an active marker. */
|
|
193
|
+
dot: "\u25CF",
|
|
194
|
+
/** Play, in the sense of "run this". */
|
|
195
|
+
play: "\u25B6",
|
|
196
|
+
/** Start the thing again from the top: a play session, a sweep. Named for the
|
|
197
|
+
* act, not the arrow, which is the table's rule. */
|
|
198
|
+
restart: "\u27F2",
|
|
199
|
+
/** The three ways a collection can be shown. Named by what you get rather than
|
|
200
|
+
* by the glyph, so a second app can pick the same three without copying
|
|
201
|
+
* somebody's taste in symbols. */
|
|
202
|
+
viewCards: "\u25A6",
|
|
203
|
+
viewTable: "\u2630",
|
|
204
|
+
viewNode: "\u25C8",
|
|
205
|
+
// --- handling -------------------------------------------------------------
|
|
206
|
+
/** The drag grip on a reorderable row. */
|
|
207
|
+
grip: "\u283F",
|
|
208
|
+
/** Add one of whatever this list holds. */
|
|
209
|
+
add: "+",
|
|
210
|
+
// --- annotation -----------------------------------------------------------
|
|
211
|
+
/** Documentation notes: the pencil. Both apps had chosen it independently,
|
|
212
|
+
* which is the happiest way for a vocabulary entry to arrive. */
|
|
213
|
+
note: "\u270E",
|
|
214
|
+
/** A comment thread. Speech, not a pencil: documentation states the reason,
|
|
215
|
+
* comments are the conversation, and the two must never look alike. */
|
|
216
|
+
comment: "\u275D"
|
|
217
|
+
};
|
|
218
|
+
var STROKE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.571" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">';
|
|
219
|
+
var FILL = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">';
|
|
220
|
+
var stroked = (inner) => `${STROKE}${inner}</svg>`;
|
|
221
|
+
var filled = (inner) => `${FILL}${inner}</svg>`;
|
|
222
|
+
var DISC = filled('<circle cx="12" cy="12" r="6"/>');
|
|
223
|
+
var DISC_LARGE = filled('<circle cx="12" cy="12" r="9"/>');
|
|
224
|
+
var DRAWN = {
|
|
225
|
+
// --- dismissing and revealing ---------------------------------------------
|
|
226
|
+
/** Close a panel, a dialog, a popover. (x) */
|
|
227
|
+
close: stroked('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>'),
|
|
228
|
+
/** The overflow menu on a document or a row: "more about this". (ellipsis) */
|
|
229
|
+
more: stroked('<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>'),
|
|
230
|
+
/** Collapsed disclosure, pointing at what would open. (chevron-right) */
|
|
231
|
+
collapsed: stroked('<path d="m9 18 6-6-6-6"/>'),
|
|
232
|
+
/** Expanded disclosure. (chevron-down) */
|
|
233
|
+
expanded: stroked('<path d="m6 9 6 6 6-6"/>'),
|
|
234
|
+
/** A dropdown's affordance, where it is not a real <select>. (chevron-down) */
|
|
235
|
+
dropdown: stroked('<path d="m6 9 6 6 6-6"/>'),
|
|
236
|
+
// --- moving through things ------------------------------------------------
|
|
237
|
+
/** Back / previous, in a breadcrumb or a stepper. (chevron-left) */
|
|
238
|
+
back: stroked('<path d="m15 18-6-6 6-6"/>'),
|
|
239
|
+
/** Forward / next. (chevron-right) */
|
|
240
|
+
forward: stroked('<path d="m9 18 6-6-6-6"/>'),
|
|
241
|
+
/** Up one, in a list. (arrow-up) */
|
|
242
|
+
up: stroked('<path d="m5 12 7-7 7 7"/><path d="M12 19V5"/>'),
|
|
243
|
+
/** Down one. (arrow-down) */
|
|
244
|
+
down: stroked('<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>'),
|
|
245
|
+
/** History back. (arrow-left) */
|
|
246
|
+
arrowLeft: stroked('<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>'),
|
|
247
|
+
/** History forward; "go there". (arrow-right) */
|
|
248
|
+
arrowRight: stroked('<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>'),
|
|
249
|
+
// --- state ----------------------------------------------------------------
|
|
250
|
+
/** Done, applied, agreed. (check) */
|
|
251
|
+
tick: stroked('<path d="M20 6 9 17l-5-5"/>'),
|
|
252
|
+
/** Somebody else holds this: the version-control lock. (lock) */
|
|
253
|
+
locked: stroked('<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>'),
|
|
254
|
+
/** Read-only on disk with no other holder. (lock-open) An open lock rather
|
|
255
|
+
* than a struck-out eye: this state is not a prohibition. Nobody holds the
|
|
256
|
+
* file, saving checks it out, and it is the resting state of everything the
|
|
257
|
+
* author has not touched yet under a lock-based VCS. eye-off would say "you
|
|
258
|
+
* cannot", which is false here; the open lock says "not held", beside
|
|
259
|
+
* `locked`, which is the shut one, so the two read as a pair. */
|
|
260
|
+
readOnly: stroked('<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>'),
|
|
261
|
+
/** YOU hold this one: checked out, still yours to edit. (pencil) */
|
|
262
|
+
checkedOut: stroked('<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>'),
|
|
263
|
+
/** Tracked, with local changes not yet committed. (a disc; see DISC) */
|
|
264
|
+
modified: DISC,
|
|
265
|
+
/** Not in version control yet: new since the last commit. (plus) */
|
|
266
|
+
untracked: stroked('<path d="M5 12h14"/><path d="M12 5v14"/>'),
|
|
267
|
+
/** Something needs attention but is not fatal. (triangle-alert) */
|
|
268
|
+
warning: stroked('<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>'),
|
|
269
|
+
/** A live thing: the running position, an active marker. (a disc) */
|
|
270
|
+
dot: DISC,
|
|
271
|
+
/** Play, in the sense of "run this". (play) */
|
|
272
|
+
play: stroked('<path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/>'),
|
|
273
|
+
/** Start the thing again from the top: a play session, a sweep. (rotate-ccw) */
|
|
274
|
+
restart: stroked('<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/>'),
|
|
275
|
+
/** The three ways a collection can be shown, named by what you get. */
|
|
276
|
+
/** Cards. (layout-grid) */
|
|
277
|
+
viewCards: stroked('<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>'),
|
|
278
|
+
/** A table. (rows-3: a ruled box, which is a table. `list` is bullets, and
|
|
279
|
+
* the view is not a list.) */
|
|
280
|
+
viewTable: stroked('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M21 9H3"/><path d="M21 15H3"/>'),
|
|
281
|
+
/** A node graph. (waypoints: four nodes on edges reads as a graph.
|
|
282
|
+
* `git-fork` reads as version control, which this family also draws, and
|
|
283
|
+
* a picture that means two things is the fault this table exists to
|
|
284
|
+
* prevent.) */
|
|
285
|
+
viewNode: stroked('<path d="m10.586 5.414-5.172 5.172"/><path d="m18.586 13.414-5.172 5.172"/><path d="M6 12h12"/><circle cx="12" cy="20" r="2"/><circle cx="12" cy="4" r="2"/><circle cx="20" cy="12" r="2"/><circle cx="4" cy="12" r="2"/>'),
|
|
286
|
+
/** Record: the disc, in whatever colour the button is. (a disc; see DISC_LARGE) */
|
|
287
|
+
record: DISC_LARGE,
|
|
288
|
+
// --- handling -------------------------------------------------------------
|
|
289
|
+
/** The drag grip on a reorderable row. (grip-vertical) */
|
|
290
|
+
grip: stroked('<circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/>'),
|
|
291
|
+
/** Add one of whatever this list holds. (plus) */
|
|
292
|
+
add: stroked('<path d="M5 12h14"/><path d="M12 5v14"/>'),
|
|
293
|
+
/** Connect: to a server, a live link, a running game. (plug) */
|
|
294
|
+
connect: stroked('<path d="M12 22v-5"/><path d="M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/>'),
|
|
295
|
+
/** Search. (search) */
|
|
296
|
+
search: stroked('<path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/>'),
|
|
297
|
+
/** Keep this window on top. (pin) */
|
|
298
|
+
pin: stroked('<path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 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"/>'),
|
|
299
|
+
/** Settings. (settings) */
|
|
300
|
+
settings: stroked('<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/>'),
|
|
301
|
+
// --- annotation -----------------------------------------------------------
|
|
302
|
+
/** Documentation notes: a document with lines on it. (file-text) The glyph
|
|
303
|
+
* table drew a pencil and shared it with `checkedOut`; the drawn set gives
|
|
304
|
+
* the two meanings two shapes, and notes get the document, which is what
|
|
305
|
+
* the shell's own noteFilled/noteOutline pair had always drawn. */
|
|
306
|
+
note: stroked('<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'),
|
|
307
|
+
/** A comment thread. Speech, not a document: documentation states the
|
|
308
|
+
* reason, comments are the conversation, and the two must never look
|
|
309
|
+
* alike. (message-square) */
|
|
310
|
+
comment: stroked('<path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/>')
|
|
311
|
+
};
|
|
312
|
+
var iconSvg = {
|
|
313
|
+
...DRAWN,
|
|
314
|
+
/** A document with lines: notes exist on this thing. (legacy, 16 grid) */
|
|
315
|
+
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>',
|
|
316
|
+
/** The same document, empty: nothing written here yet. (legacy, 16 grid) */
|
|
317
|
+
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>'
|
|
318
|
+
};
|
|
319
|
+
var ICON_NAMES = Object.keys(DRAWN);
|
|
320
|
+
var ICON_SIZE = 14;
|
|
321
|
+
function iconHtml(name, size = ICON_SIZE) {
|
|
322
|
+
return DRAWN[name].replace("<svg ", `<svg width="${size}" height="${size}" data-icon="${name}" `);
|
|
323
|
+
}
|
|
324
|
+
var templates = /* @__PURE__ */ new Map();
|
|
325
|
+
function iconNode(name, size = ICON_SIZE) {
|
|
326
|
+
let tpl = templates.get(name);
|
|
327
|
+
if (!tpl) {
|
|
328
|
+
const holder = document.createElement("template");
|
|
329
|
+
holder.innerHTML = DRAWN[name];
|
|
330
|
+
tpl = holder.content.firstElementChild;
|
|
331
|
+
tpl.setAttribute("data-icon", name);
|
|
332
|
+
templates.set(name, tpl);
|
|
333
|
+
}
|
|
334
|
+
const node2 = tpl.cloneNode(true);
|
|
335
|
+
node2.setAttribute("width", String(size));
|
|
336
|
+
node2.setAttribute("height", String(size));
|
|
337
|
+
return node2;
|
|
338
|
+
}
|
|
339
|
+
var BY_GLYPH = /* @__PURE__ */ new Map();
|
|
340
|
+
for (const [name, glyph] of Object.entries(icon)) {
|
|
341
|
+
if (!BY_GLYPH.has(glyph)) BY_GLYPH.set(glyph, name);
|
|
342
|
+
}
|
|
343
|
+
function iconNameOfGlyph(glyph) {
|
|
344
|
+
return BY_GLYPH.get(glyph);
|
|
345
|
+
}
|
|
346
|
+
function isIconName(s) {
|
|
347
|
+
return Object.prototype.hasOwnProperty.call(DRAWN, s);
|
|
348
|
+
}
|
|
349
|
+
|
|
152
350
|
// src/dom.ts
|
|
153
351
|
function el(tag, classNameOrProps, ...children) {
|
|
154
352
|
const node2 = document.createElement(tag);
|
|
@@ -169,12 +367,14 @@ function el(tag, classNameOrProps, ...children) {
|
|
|
169
367
|
checkTooltipHost();
|
|
170
368
|
return node2;
|
|
171
369
|
}
|
|
172
|
-
function iconBtn(
|
|
370
|
+
function iconBtn(name, title, onClick, disabled = false, danger = false) {
|
|
173
371
|
const b = el("button", `shell-icon${danger ? " danger" : ""}`);
|
|
174
372
|
b.type = "button";
|
|
175
|
-
b.textContent = glyph;
|
|
176
373
|
b.dataset["tip"] = title;
|
|
177
374
|
b.setAttribute("aria-label", title);
|
|
375
|
+
const word = isIconName(name) ? name : iconNameOfGlyph(name);
|
|
376
|
+
if (word) b.append(iconNode(word));
|
|
377
|
+
else b.textContent = name;
|
|
178
378
|
b.disabled = disabled;
|
|
179
379
|
b.addEventListener("click", onClick);
|
|
180
380
|
return b;
|
|
@@ -195,6 +395,7 @@ function moveItem(arr, i, delta) {
|
|
|
195
395
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
196
396
|
return true;
|
|
197
397
|
}
|
|
398
|
+
var CHIP_ICON = 10;
|
|
198
399
|
function tagChips(holder, onChange) {
|
|
199
400
|
const wrap = el("div", "shell-tags");
|
|
200
401
|
const input = el("input", "shell-tag-input");
|
|
@@ -202,18 +403,18 @@ function tagChips(holder, onChange) {
|
|
|
202
403
|
input.placeholder = "Add value";
|
|
203
404
|
input.spellcheck = false;
|
|
204
405
|
const makeChip = (v) => {
|
|
205
|
-
const
|
|
206
|
-
const x = el("button", "shell-tag-x", "
|
|
406
|
+
const chip2 = el("span", "shell-tag", v);
|
|
407
|
+
const x = el("button", "shell-tag-x", iconNode("close", CHIP_ICON));
|
|
207
408
|
x.type = "button";
|
|
208
|
-
x.dataset["tip"] = `
|
|
209
|
-
x.setAttribute("aria-label", `
|
|
409
|
+
x.dataset["tip"] = `Remove ${v}`;
|
|
410
|
+
x.setAttribute("aria-label", `Remove ${v}`);
|
|
210
411
|
x.addEventListener("click", () => {
|
|
211
412
|
holder.values = (holder.values ?? []).filter((o) => o !== v);
|
|
212
|
-
|
|
413
|
+
chip2.remove();
|
|
213
414
|
onChange?.();
|
|
214
415
|
});
|
|
215
|
-
|
|
216
|
-
return
|
|
416
|
+
chip2.append(x);
|
|
417
|
+
return chip2;
|
|
217
418
|
};
|
|
218
419
|
const commit = () => {
|
|
219
420
|
const v = input.value.trim();
|
|
@@ -235,6 +436,153 @@ function tagChips(holder, onChange) {
|
|
|
235
436
|
wrap.append(input);
|
|
236
437
|
return wrap;
|
|
237
438
|
}
|
|
439
|
+
function stageChips(holder, onChange) {
|
|
440
|
+
const wrap = el("div", "shell-tags shell-stages");
|
|
441
|
+
const input = el("input", "shell-tag-input");
|
|
442
|
+
input.type = "text";
|
|
443
|
+
input.placeholder = "Add stage";
|
|
444
|
+
input.spellcheck = false;
|
|
445
|
+
const control = (name, tip2, disabled, onClick) => {
|
|
446
|
+
const b = el("button", "shell-tag-x shell-tag-move", iconNode(name, CHIP_ICON));
|
|
447
|
+
b.type = "button";
|
|
448
|
+
b.dataset["tip"] = tip2;
|
|
449
|
+
b.setAttribute("aria-label", tip2);
|
|
450
|
+
b.disabled = disabled;
|
|
451
|
+
b.addEventListener("click", onClick);
|
|
452
|
+
return b;
|
|
453
|
+
};
|
|
454
|
+
const rebuild = () => {
|
|
455
|
+
for (const c of Array.from(wrap.children)) if (c !== input) c.remove();
|
|
456
|
+
const stages = holder.stages ?? [];
|
|
457
|
+
stages.forEach((v, i) => {
|
|
458
|
+
const chip2 = el("span", "shell-tag", `${i + 1}. ${v}`);
|
|
459
|
+
chip2.dataset["stage"] = v;
|
|
460
|
+
chip2.append(
|
|
461
|
+
control(
|
|
462
|
+
"back",
|
|
463
|
+
`Move ${v} earlier`,
|
|
464
|
+
i === 0,
|
|
465
|
+
() => {
|
|
466
|
+
if (moveItem(stages, i, -1)) {
|
|
467
|
+
rebuild();
|
|
468
|
+
onChange?.();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
control(
|
|
473
|
+
"forward",
|
|
474
|
+
`Move ${v} later`,
|
|
475
|
+
i === stages.length - 1,
|
|
476
|
+
() => {
|
|
477
|
+
if (moveItem(stages, i, 1)) {
|
|
478
|
+
rebuild();
|
|
479
|
+
onChange?.();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
const x = el("button", "shell-tag-x", iconNode("close", CHIP_ICON));
|
|
485
|
+
x.type = "button";
|
|
486
|
+
x.dataset["tip"] = `Remove ${v}`;
|
|
487
|
+
x.setAttribute("aria-label", `Remove ${v}`);
|
|
488
|
+
x.addEventListener("click", () => {
|
|
489
|
+
holder.stages = (holder.stages ?? []).filter((o) => o !== v);
|
|
490
|
+
rebuild();
|
|
491
|
+
onChange?.();
|
|
492
|
+
});
|
|
493
|
+
chip2.append(x);
|
|
494
|
+
wrap.insertBefore(chip2, input);
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
const commit = () => {
|
|
498
|
+
const v = input.value.trim();
|
|
499
|
+
if (v && !(holder.stages ?? []).includes(v)) {
|
|
500
|
+
(holder.stages ??= []).push(v);
|
|
501
|
+
rebuild();
|
|
502
|
+
onChange?.();
|
|
503
|
+
}
|
|
504
|
+
input.value = "";
|
|
505
|
+
};
|
|
506
|
+
input.addEventListener("keydown", (e) => {
|
|
507
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
508
|
+
e.preventDefault();
|
|
509
|
+
commit();
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
input.addEventListener("blur", commit);
|
|
513
|
+
wrap.append(input);
|
|
514
|
+
rebuild();
|
|
515
|
+
return wrap;
|
|
516
|
+
}
|
|
517
|
+
var reorderDragId = null;
|
|
518
|
+
var clearDropMarks = (host) => host.querySelectorAll(".drop-before, .drop-after").forEach((e) => e.classList.remove("drop-before", "drop-after"));
|
|
519
|
+
function wireReorder(el2, id, axis, onMove) {
|
|
520
|
+
el2.draggable = true;
|
|
521
|
+
el2.dataset["reorderId"] = id;
|
|
522
|
+
const host = () => el2.parentElement ?? el2;
|
|
523
|
+
el2.addEventListener("dragstart", (e) => {
|
|
524
|
+
reorderDragId = id;
|
|
525
|
+
el2.classList.add("dragging");
|
|
526
|
+
if (e.dataTransfer) {
|
|
527
|
+
e.dataTransfer.effectAllowed = "move";
|
|
528
|
+
e.dataTransfer.setData("text/plain", id);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
el2.addEventListener("dragend", () => {
|
|
532
|
+
reorderDragId = null;
|
|
533
|
+
el2.classList.remove("dragging");
|
|
534
|
+
clearDropMarks(host());
|
|
535
|
+
});
|
|
536
|
+
el2.addEventListener("dragover", (e) => {
|
|
537
|
+
if (!reorderDragId || reorderDragId === id) return;
|
|
538
|
+
e.preventDefault();
|
|
539
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
540
|
+
const r = el2.getBoundingClientRect();
|
|
541
|
+
const before = axis === "y" ? e.clientY < r.top + r.height / 2 : e.clientX < r.left + r.width / 2;
|
|
542
|
+
clearDropMarks(host());
|
|
543
|
+
el2.classList.add(before ? "drop-before" : "drop-after");
|
|
544
|
+
});
|
|
545
|
+
el2.addEventListener("dragleave", () => el2.classList.remove("drop-before", "drop-after"));
|
|
546
|
+
el2.addEventListener("drop", (e) => {
|
|
547
|
+
e.preventDefault();
|
|
548
|
+
const before = el2.classList.contains("drop-before");
|
|
549
|
+
clearDropMarks(host());
|
|
550
|
+
if (reorderDragId && reorderDragId !== id) onMove(reorderDragId, before, id);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function metaLine(parts) {
|
|
554
|
+
const row = el("span", "shell-meta");
|
|
555
|
+
for (const part of parts) {
|
|
556
|
+
if (part === null || part === void 0 || part === "") continue;
|
|
557
|
+
const seg = el("span", "shell-meta-part");
|
|
558
|
+
seg.append(part);
|
|
559
|
+
row.append(seg);
|
|
560
|
+
}
|
|
561
|
+
return row;
|
|
562
|
+
}
|
|
563
|
+
function breadcrumb(parts) {
|
|
564
|
+
const trail = el("nav", "shell-crumbs");
|
|
565
|
+
trail.setAttribute("aria-label", "Where this is");
|
|
566
|
+
parts.forEach((part, i) => {
|
|
567
|
+
const crumb = typeof part === "string" ? { label: part } : part;
|
|
568
|
+
const last = i === parts.length - 1;
|
|
569
|
+
if (i > 0) {
|
|
570
|
+
const sep = iconNode("forward", 12);
|
|
571
|
+
sep.classList.add("shell-crumb-sep");
|
|
572
|
+
trail.append(sep);
|
|
573
|
+
}
|
|
574
|
+
if (crumb.onClick && !last) {
|
|
575
|
+
const b = el("button", { className: "shell-crumb shell-crumb-link", text: crumb.label, onClick: crumb.onClick });
|
|
576
|
+
b.type = "button";
|
|
577
|
+
trail.append(b);
|
|
578
|
+
} else {
|
|
579
|
+
const s = el("span", "shell-crumb", crumb.label);
|
|
580
|
+
if (last) s.setAttribute("aria-current", "location");
|
|
581
|
+
trail.append(s);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
return trail;
|
|
585
|
+
}
|
|
238
586
|
|
|
239
587
|
// src/colour.ts
|
|
240
588
|
var PALETTE_SIZE = 12;
|
|
@@ -256,78 +604,148 @@ function colourFor(name) {
|
|
|
256
604
|
return PALETTE[colourIndex(name)];
|
|
257
605
|
}
|
|
258
606
|
|
|
259
|
-
// src/
|
|
260
|
-
var
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* MEANING, and two meanings may draw the same shape. */
|
|
292
|
-
checkedOut: "\u270E",
|
|
293
|
-
/** Tracked, with local changes not yet committed. */
|
|
294
|
-
modified: "\u25CF",
|
|
295
|
-
/** Not in version control yet: new since the last commit. */
|
|
296
|
-
untracked: "+",
|
|
297
|
-
/** Something needs attention but is not fatal. */
|
|
298
|
-
warning: "\u203C",
|
|
299
|
-
/** A live thing: the running position, an active marker. */
|
|
300
|
-
dot: "\u25CF",
|
|
301
|
-
/** Play, in the sense of "run this". */
|
|
302
|
-
play: "\u25B6",
|
|
303
|
-
/** Start the thing again from the top: a play session, a sweep. Named for the
|
|
304
|
-
* act, not the arrow, which is the table's rule. */
|
|
305
|
-
restart: "\u27F2",
|
|
306
|
-
/** The three ways a collection can be shown. Named by what you get rather than
|
|
307
|
-
* by the glyph, so a second app can pick the same three without copying
|
|
308
|
-
* somebody's taste in symbols. */
|
|
309
|
-
viewCards: "\u25A6",
|
|
310
|
-
viewTable: "\u2630",
|
|
311
|
-
viewNode: "\u25C8",
|
|
312
|
-
// --- handling -------------------------------------------------------------
|
|
313
|
-
/** The drag grip on a reorderable row. */
|
|
314
|
-
grip: "\u283F",
|
|
315
|
-
/** Add one of whatever this list holds. */
|
|
316
|
-
add: "+",
|
|
317
|
-
// --- annotation -----------------------------------------------------------
|
|
318
|
-
/** Documentation notes: the pencil. Both apps had chosen it independently,
|
|
319
|
-
* which is the happiest way for a vocabulary entry to arrive. */
|
|
320
|
-
note: "\u270E",
|
|
321
|
-
/** A comment thread. Speech, not a pencil: documentation states the reason,
|
|
322
|
-
* comments are the conversation, and the two must never look alike. */
|
|
323
|
-
comment: "\u275D"
|
|
607
|
+
// src/keys.ts
|
|
608
|
+
var override;
|
|
609
|
+
function setKeyPlatform(platform) {
|
|
610
|
+
override = platform;
|
|
611
|
+
}
|
|
612
|
+
function keyPlatform() {
|
|
613
|
+
if (override) return override;
|
|
614
|
+
if (typeof navigator !== "undefined") {
|
|
615
|
+
const s = `${navigator.platform ?? ""} ${navigator.userAgent ?? ""}`;
|
|
616
|
+
if (/mac|iphone|ipad/i.test(s)) return "mac";
|
|
617
|
+
if (/win/i.test(s)) return "win";
|
|
618
|
+
if (s.trim()) return "linux";
|
|
619
|
+
}
|
|
620
|
+
const p = typeof process !== "undefined" ? process.platform : void 0;
|
|
621
|
+
return p === "darwin" ? "mac" : p === "win32" ? "win" : "linux";
|
|
622
|
+
}
|
|
623
|
+
var MAC_ORDER = ["ctrl", "alt", "shift", "mod"];
|
|
624
|
+
var OTHER_ORDER = ["mod", "ctrl", "alt", "shift"];
|
|
625
|
+
var MAC_MOD = { ctrl: "\u2303", alt: "\u2325", shift: "\u21E7", mod: "\u2318" };
|
|
626
|
+
var OTHER_MOD = { ctrl: "Ctrl", alt: "Alt", shift: "Shift", mod: "Ctrl" };
|
|
627
|
+
var MODIFIER_ALIASES = {
|
|
628
|
+
mod: "mod",
|
|
629
|
+
cmd: "mod",
|
|
630
|
+
command: "mod",
|
|
631
|
+
meta: "mod",
|
|
632
|
+
super: "mod",
|
|
633
|
+
ctrl: "ctrl",
|
|
634
|
+
control: "ctrl",
|
|
635
|
+
alt: "alt",
|
|
636
|
+
option: "alt",
|
|
637
|
+
opt: "alt",
|
|
638
|
+
shift: "shift"
|
|
324
639
|
};
|
|
325
|
-
var
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
640
|
+
var NAMED = {
|
|
641
|
+
enter: { mac: "\u21A9", other: "Enter" },
|
|
642
|
+
return: { mac: "\u21A9", other: "Enter" },
|
|
643
|
+
esc: { mac: "esc", other: "Esc" },
|
|
644
|
+
escape: { mac: "esc", other: "Esc" },
|
|
645
|
+
up: { mac: "\u2191", other: "Up" },
|
|
646
|
+
down: { mac: "\u2193", other: "Down" },
|
|
647
|
+
left: { mac: "\u2190", other: "Left" },
|
|
648
|
+
right: { mac: "\u2192", other: "Right" },
|
|
649
|
+
backspace: { mac: "\u232B", other: "Backspace" },
|
|
650
|
+
delete: { mac: "\u2326", other: "Del" },
|
|
651
|
+
del: { mac: "\u2326", other: "Del" },
|
|
652
|
+
tab: { mac: "\u21E5", other: "Tab" },
|
|
653
|
+
space: { mac: "Space", other: "Space" },
|
|
654
|
+
home: { mac: "\u2196", other: "Home" },
|
|
655
|
+
end: { mac: "\u2198", other: "End" },
|
|
656
|
+
pageup: { mac: "\u21DE", other: "PgUp" },
|
|
657
|
+
pagedown: { mac: "\u21DF", other: "PgDn" }
|
|
330
658
|
};
|
|
659
|
+
var PORTABLE_MODS = /* @__PURE__ */ new Set(["mod", "ctrl", "alt", "shift"]);
|
|
660
|
+
function parse(combo) {
|
|
661
|
+
const parts = combo.split("+");
|
|
662
|
+
const mods = [];
|
|
663
|
+
let key;
|
|
664
|
+
for (let i = 0; i < parts.length; i++) {
|
|
665
|
+
const raw = parts[i] ?? "";
|
|
666
|
+
if (raw === "" && i > 0) {
|
|
667
|
+
key = "+";
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const mod = MODIFIER_ALIASES[raw.toLowerCase()];
|
|
671
|
+
if (mod) {
|
|
672
|
+
if (!mods.includes(mod)) mods.push(mod);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
key = raw;
|
|
676
|
+
}
|
|
677
|
+
return { mods, key };
|
|
678
|
+
}
|
|
679
|
+
function isKeyCombo(value) {
|
|
680
|
+
const plus = value.length > 2 && value.endsWith("++");
|
|
681
|
+
const parts = (plus ? value.slice(0, -2) : value).split("+");
|
|
682
|
+
let keys = plus ? 1 : 0;
|
|
683
|
+
for (const raw of parts) {
|
|
684
|
+
if (raw === "") return false;
|
|
685
|
+
const word = raw.toLowerCase();
|
|
686
|
+
if (PORTABLE_MODS.has(word)) continue;
|
|
687
|
+
if (raw.length === 1 || word in NAMED) {
|
|
688
|
+
keys++;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
return keys === 1;
|
|
694
|
+
}
|
|
695
|
+
function keyLegend(key, mac) {
|
|
696
|
+
const named = NAMED[key.toLowerCase()];
|
|
697
|
+
if (named) return mac ? named.mac : named.other;
|
|
698
|
+
return key.length === 1 ? key.toUpperCase() : key;
|
|
699
|
+
}
|
|
700
|
+
function keyLabel(combo, platform = keyPlatform()) {
|
|
701
|
+
const mac = platform === "mac";
|
|
702
|
+
const { mods, key } = parse(combo);
|
|
703
|
+
const order = mac ? MAC_ORDER : OTHER_ORDER;
|
|
704
|
+
const words = order.filter((m) => mods.includes(m)).map((m) => (mac ? MAC_MOD : OTHER_MOD)[m]);
|
|
705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
706
|
+
const modWords = words.filter((w) => seen.has(w) ? false : (seen.add(w), true));
|
|
707
|
+
const legend = key === void 0 ? [] : [keyLegend(key, mac)];
|
|
708
|
+
return mac ? [...modWords, ...legend].join("") : [...modWords, ...legend].join("+");
|
|
709
|
+
}
|
|
710
|
+
function keyLegends(combo, platform = keyPlatform()) {
|
|
711
|
+
if (platform === "mac") return [keyLabel(combo, platform)];
|
|
712
|
+
const { mods, key } = parse(combo);
|
|
713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
714
|
+
const words = OTHER_ORDER.filter((m) => mods.includes(m)).map((m) => OTHER_MOD[m]).filter((w) => seen.has(w) ? false : (seen.add(w), true));
|
|
715
|
+
return key === void 0 ? words : [...words, keyLegend(key, false)];
|
|
716
|
+
}
|
|
717
|
+
function chip(legend) {
|
|
718
|
+
const k = document.createElement("kbd");
|
|
719
|
+
k.className = "shell-kbd";
|
|
720
|
+
k.textContent = legend;
|
|
721
|
+
return k;
|
|
722
|
+
}
|
|
723
|
+
function keyHint(combo, opts = {}) {
|
|
724
|
+
const platform = opts.platform ?? keyPlatform();
|
|
725
|
+
const legends = combo.trim().split(/\s+/).flatMap((c) => keyLegends(c, platform));
|
|
726
|
+
if (legends.length === 1) return chip(legends[0]);
|
|
727
|
+
const group = document.createElement("span");
|
|
728
|
+
group.className = "shell-keys";
|
|
729
|
+
for (const legend of legends) group.append(chip(legend));
|
|
730
|
+
return group;
|
|
731
|
+
}
|
|
732
|
+
function hintBar(items, opts = {}) {
|
|
733
|
+
const bar = document.createElement("div");
|
|
734
|
+
bar.className = "shell-hintbar";
|
|
735
|
+
for (const item of items) {
|
|
736
|
+
const hint = document.createElement("span");
|
|
737
|
+
hint.className = "shell-hint";
|
|
738
|
+
const label2 = document.createElement("span");
|
|
739
|
+
label2.className = "shell-hint-label";
|
|
740
|
+
label2.textContent = item.label;
|
|
741
|
+
hint.append(keyHint(item.keys, opts), label2);
|
|
742
|
+
bar.append(hint);
|
|
743
|
+
}
|
|
744
|
+
return bar;
|
|
745
|
+
}
|
|
746
|
+
function tipWithKey(text, combo, platform = keyPlatform()) {
|
|
747
|
+
return `${text} (${keyLabel(combo, platform)})`;
|
|
748
|
+
}
|
|
331
749
|
|
|
332
750
|
// src/pane-shell.ts
|
|
333
751
|
var DEFAULT_MAX = (w) => Math.min(640, w - 420);
|
|
@@ -341,6 +759,8 @@ function mountPaneShell(host, opts) {
|
|
|
341
759
|
inspector: offered("inspector") && (opts.initial?.open?.inspector ?? true)
|
|
342
760
|
};
|
|
343
761
|
const width = { ...opts.initial?.width ?? {} };
|
|
762
|
+
const held = { nav: false, inspector: false };
|
|
763
|
+
const showing = (side) => open[side] && !held[side];
|
|
344
764
|
const el2 = (tag, cls) => {
|
|
345
765
|
const n = document.createElement(tag);
|
|
346
766
|
if (cls) n.className = cls;
|
|
@@ -371,18 +791,29 @@ function mountPaneShell(host, opts) {
|
|
|
371
791
|
host.append(root);
|
|
372
792
|
if (!offered("nav")) navToggle.remove();
|
|
373
793
|
if (!offered("inspector")) inspToggle.remove();
|
|
794
|
+
const defaultTip = (side, isOpen) => {
|
|
795
|
+
const text = `${isOpen ? "Hide" : "Show"} ${cfg[side].label ?? side}`;
|
|
796
|
+
const hint = cfg[side].shortcutHint;
|
|
797
|
+
if (!hint) return text;
|
|
798
|
+
return isKeyCombo(hint) ? tipWithKey(text, hint) : `${text} (${hint})`;
|
|
799
|
+
};
|
|
800
|
+
const tipFor = opts.tipFor ?? defaultTip;
|
|
374
801
|
function applyToggleGlyph(side, button) {
|
|
375
802
|
const isOpen = open[side];
|
|
376
|
-
button.
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
button.
|
|
380
|
-
button.setAttribute("aria-label", button.title);
|
|
803
|
+
button.replaceChildren(iconNode(side === "nav" ? isOpen ? "back" : "forward" : isOpen ? "forward" : "back"));
|
|
804
|
+
const tip2 = tipFor(side, isOpen);
|
|
805
|
+
button.dataset["tip"] = tip2;
|
|
806
|
+
button.setAttribute("aria-label", tip2);
|
|
381
807
|
button.setAttribute("aria-pressed", String(isOpen));
|
|
382
808
|
}
|
|
809
|
+
ensureTooltipHost();
|
|
383
810
|
function apply() {
|
|
384
|
-
|
|
385
|
-
|
|
811
|
+
for (const side of ["nav", "inspector"]) {
|
|
812
|
+
const collapsed = !showing(side);
|
|
813
|
+
panes.classList.toggle(collapseClass[side], collapsed);
|
|
814
|
+
const also = opts.collapseAlso?.[side];
|
|
815
|
+
if (also) panes.classList.toggle(also, collapsed);
|
|
816
|
+
}
|
|
386
817
|
panes.style.setProperty(openVar.nav, width.nav !== void 0 ? `${width.nav}px` : cfg.nav.defaultWidth);
|
|
387
818
|
panes.style.setProperty(openVar.inspector, width.inspector !== void 0 ? `${width.inspector}px` : cfg.inspector.defaultWidth);
|
|
388
819
|
applyToggleGlyph("nav", navToggle);
|
|
@@ -401,6 +832,11 @@ function mountPaneShell(host, opts) {
|
|
|
401
832
|
function togglePane(side) {
|
|
402
833
|
if (offered(side)) setPaneOpen(side, !open[side]);
|
|
403
834
|
}
|
|
835
|
+
function holdClosed(side, next) {
|
|
836
|
+
if (held[side] === next) return;
|
|
837
|
+
held[side] = next;
|
|
838
|
+
apply();
|
|
839
|
+
}
|
|
404
840
|
function resetWidths() {
|
|
405
841
|
delete width.nav;
|
|
406
842
|
delete width.inspector;
|
|
@@ -410,9 +846,11 @@ function mountPaneShell(host, opts) {
|
|
|
410
846
|
navToggle.addEventListener("click", () => togglePane("nav"));
|
|
411
847
|
inspToggle.addEventListener("click", () => togglePane("inspector"));
|
|
412
848
|
function beginResize(side, startEvent) {
|
|
413
|
-
if (cfg[side].resizable === false || !
|
|
849
|
+
if (cfg[side].resizable === false || !showing(side)) return;
|
|
414
850
|
startEvent.preventDefault();
|
|
415
851
|
const pane = side === "nav" ? navPane : inspPane;
|
|
852
|
+
const seam = side === "nav" ? navResizer : inspResizer;
|
|
853
|
+
seam.classList.add("dragging");
|
|
416
854
|
const min = cfg[side].minWidth ?? 160;
|
|
417
855
|
const max = cfg[side].maxWidth ?? DEFAULT_MAX;
|
|
418
856
|
const startX = startEvent.clientX;
|
|
@@ -428,6 +866,7 @@ function mountPaneShell(host, opts) {
|
|
|
428
866
|
window.removeEventListener("pointermove", onMove);
|
|
429
867
|
window.removeEventListener("pointerup", onUp);
|
|
430
868
|
document.body.classList.remove("pane-resizing");
|
|
869
|
+
seam.classList.remove("dragging");
|
|
431
870
|
width[side] = latest;
|
|
432
871
|
persist();
|
|
433
872
|
};
|
|
@@ -457,7 +896,8 @@ function mountPaneShell(host, opts) {
|
|
|
457
896
|
inspector: inspInner,
|
|
458
897
|
togglePane,
|
|
459
898
|
setPaneOpen,
|
|
460
|
-
isOpen:
|
|
899
|
+
isOpen: showing,
|
|
900
|
+
holdClosed,
|
|
461
901
|
resetWidths,
|
|
462
902
|
state: snapshot,
|
|
463
903
|
destroy: () => {
|
|
@@ -471,7 +911,8 @@ var EDIT_MENU = {
|
|
|
471
911
|
undo: { label: "Undo", accelerator: "CmdOrCtrl+Z" },
|
|
472
912
|
redo: { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z" },
|
|
473
913
|
duplicate: { label: "Duplicate", accelerator: "CmdOrCtrl+D" },
|
|
474
|
-
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" }
|
|
914
|
+
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" },
|
|
915
|
+
replace: { label: "Replace\u2026", acceleratorMac: "Cmd+Alt+F", acceleratorOther: "Ctrl+H" }
|
|
475
916
|
};
|
|
476
917
|
var GO_MENU = {
|
|
477
918
|
back: { label: "Back", acceleratorMac: "Ctrl+Cmd+Left", acceleratorOther: "Alt+Left" },
|
|
@@ -482,6 +923,91 @@ var PANE_MENU = {
|
|
|
482
923
|
showInspector: { label: "Show Inspector", accelerator: "CmdOrCtrl+2" },
|
|
483
924
|
resetView: { label: "Reset View" }
|
|
484
925
|
};
|
|
926
|
+
var FILE_MENU = {
|
|
927
|
+
newProject: { label: "New Project\u2026", accelerator: "CmdOrCtrl+N" },
|
|
928
|
+
openProject: { label: "Open Project\u2026", accelerator: "CmdOrCtrl+O" },
|
|
929
|
+
openRecent: { label: "Open Recent" },
|
|
930
|
+
noRecents: { label: "No Recent Projects" },
|
|
931
|
+
clearRecents: { label: "Clear Recents" },
|
|
932
|
+
closeProject: { label: "Close Project" },
|
|
933
|
+
save: { label: "Save", accelerator: "CmdOrCtrl+S" },
|
|
934
|
+
saveAs: { label: "Save As\u2026", accelerator: "Shift+CmdOrCtrl+S" },
|
|
935
|
+
projectSettings: { label: "Project Settings\u2026", accelerator: "CmdOrCtrl+," }
|
|
936
|
+
};
|
|
937
|
+
var PLAY_MENU = {
|
|
938
|
+
liveLink: { label: "Live Link" }
|
|
939
|
+
};
|
|
940
|
+
var REVIEW_MENU = {
|
|
941
|
+
reviewFeedback: { label: "Review Feedback", accelerator: "CmdOrCtrl+Shift+R" },
|
|
942
|
+
nextFeedback: { label: "Next Feedback", accelerator: "F8" },
|
|
943
|
+
previousFeedback: { label: "Previous Feedback", accelerator: "Shift+F8" },
|
|
944
|
+
coverageTest: { label: "Coverage Test\u2026", accelerator: "Shift+CmdOrCtrl+C" },
|
|
945
|
+
findPropertyUsage: { label: "Find Property Usage\u2026" },
|
|
946
|
+
showResolvedComments: { label: "Show Resolved Comments" }
|
|
947
|
+
};
|
|
948
|
+
var PUBLISH_MENU = {
|
|
949
|
+
playableHtml: { label: "Publish Playable HTML\u2026" },
|
|
950
|
+
bundle: { label: "Publish Bundle", accelerator: "Shift+CmdOrCtrl+B" },
|
|
951
|
+
autoRebuild: { label: "Auto Rebuild" }
|
|
952
|
+
};
|
|
953
|
+
var VIEW_MENU = {
|
|
954
|
+
projectOverview: { label: "Project Overview" },
|
|
955
|
+
upALevel: { label: "Up a Level", accelerator: "CmdOrCtrl+[" },
|
|
956
|
+
colourTheme: { label: "Colour Theme" }
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/problems.ts
|
|
960
|
+
function problemName(p, dangling = false) {
|
|
961
|
+
if (p.title) return `\u201C${p.title}\u201D`;
|
|
962
|
+
if (dangling && p.where) return `(id ${p.where})`;
|
|
963
|
+
return "This";
|
|
964
|
+
}
|
|
965
|
+
function describeProblem(problem, table = {}) {
|
|
966
|
+
const entry = problem.code ? table[problem.code] : void 0;
|
|
967
|
+
if (entry) {
|
|
968
|
+
const copy = entry(problem);
|
|
969
|
+
const text = copy.dangling && !problem.title && problem.where && !copy.text.includes(`(id ${problem.where})`) ? `${copy.text} (id ${problem.where})` : copy.text;
|
|
970
|
+
return copy.next ? { text, next: copy.next } : { text };
|
|
971
|
+
}
|
|
972
|
+
const message = problem.message.replace(/\s*\[[^\]]*\]\s*$/, "").trim();
|
|
973
|
+
return { text: problem.title ? `${problem.title}: ${message}` : message };
|
|
974
|
+
}
|
|
975
|
+
function problemLine(problem, table) {
|
|
976
|
+
const { text, next } = describeProblem(problem, table);
|
|
977
|
+
return next ? `${text} ${next}` : text;
|
|
978
|
+
}
|
|
979
|
+
var defaultProblemCopy = {
|
|
980
|
+
"duplicate-gameid": (p) => ({
|
|
981
|
+
text: `The Game ID on ${problemName(p)} is already used elsewhere.`,
|
|
982
|
+
next: "Each one must be unique; change one of them."
|
|
983
|
+
}),
|
|
984
|
+
"invalid-gameid": (p) => ({
|
|
985
|
+
text: `The Game ID on ${problemName(p)} isn't valid.`,
|
|
986
|
+
next: "Use lowercase letters, digits and hyphens."
|
|
987
|
+
}),
|
|
988
|
+
"dangling-reference": (p) => ({
|
|
989
|
+
text: `${problemName(p, true)} points at something that no longer exists.`,
|
|
990
|
+
next: "Choose where it goes.",
|
|
991
|
+
dangling: true
|
|
992
|
+
}),
|
|
993
|
+
"unknown-property": (p) => ({
|
|
994
|
+
text: `${problemName(p)} uses a property that isn't set up yet.`,
|
|
995
|
+
next: "Declare it in the project settings, or fix the name."
|
|
996
|
+
}),
|
|
997
|
+
"missing-name": (p) => ({ text: `${problemName(p)} needs a name.` }),
|
|
998
|
+
"empty": (p) => ({
|
|
999
|
+
text: `${problemName(p)} is empty.`,
|
|
1000
|
+
next: "Add something inside it, or remove it."
|
|
1001
|
+
}),
|
|
1002
|
+
"stale-build": (p) => ({
|
|
1003
|
+
text: `Your playable build${p.path ? ` (${p.path})` : ""} is out of date.`,
|
|
1004
|
+
next: "It refreshes the next time you publish."
|
|
1005
|
+
}),
|
|
1006
|
+
"merge-conflict": (p) => ({
|
|
1007
|
+
text: `${p.path ?? "This file"} still has an unresolved merge conflict in it.`,
|
|
1008
|
+
next: "Resolve it in your version control tool, then reopen the project."
|
|
1009
|
+
})
|
|
1010
|
+
};
|
|
485
1011
|
|
|
486
1012
|
// src/settings.ts
|
|
487
1013
|
function expandableRow(opts) {
|
|
@@ -525,6 +1051,22 @@ function revealRow(within, name) {
|
|
|
525
1051
|
row.addEventListener("animationend", () => row.classList.remove("landed"), { once: true });
|
|
526
1052
|
return true;
|
|
527
1053
|
}
|
|
1054
|
+
function revealRowWhenReady(within, name, tries = 12) {
|
|
1055
|
+
return new Promise((resolve) => {
|
|
1056
|
+
const attempt = (left) => {
|
|
1057
|
+
if (revealRow(within, name)) {
|
|
1058
|
+
resolve(true);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (left <= 0) {
|
|
1062
|
+
resolve(false);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
requestAnimationFrame(() => attempt(left - 1));
|
|
1066
|
+
};
|
|
1067
|
+
attempt(tries);
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
528
1070
|
function focusNewRow(listEl) {
|
|
529
1071
|
const last = listEl?.lastElementChild;
|
|
530
1072
|
if (!(last instanceof HTMLElement)) return;
|
|
@@ -532,6 +1074,7 @@ function focusNewRow(listEl) {
|
|
|
532
1074
|
const name = last.querySelector("input.set-name") ?? last.querySelector("input");
|
|
533
1075
|
name?.focus();
|
|
534
1076
|
}
|
|
1077
|
+
var DUP_MESSAGE = "Another row already has this name.";
|
|
535
1078
|
function dupGuard() {
|
|
536
1079
|
const items = [];
|
|
537
1080
|
const check = () => {
|
|
@@ -546,9 +1089,9 @@ function dupGuard() {
|
|
|
546
1089
|
const dup = !!k && (counts.get(k) ?? 0) > 1;
|
|
547
1090
|
it.input.classList.toggle("invalid", dup);
|
|
548
1091
|
if (dup) {
|
|
549
|
-
it.input.
|
|
1092
|
+
it.input.dataset["tip"] = DUP_MESSAGE;
|
|
550
1093
|
any = true;
|
|
551
|
-
} else if (it.input.
|
|
1094
|
+
} else if (it.input.dataset["tip"] === DUP_MESSAGE) delete it.input.dataset["tip"];
|
|
552
1095
|
}
|
|
553
1096
|
return any;
|
|
554
1097
|
};
|
|
@@ -594,7 +1137,9 @@ function mountSettingsDialog(opts) {
|
|
|
594
1137
|
const tab = el("button", "settings-tab", s.label);
|
|
595
1138
|
tab.type = "button";
|
|
596
1139
|
tab.dataset["tab"] = s.id;
|
|
597
|
-
tab.addEventListener("click", () =>
|
|
1140
|
+
tab.addEventListener("click", () => {
|
|
1141
|
+
if (!tab.classList.contains("is-disabled")) showTab(s.id);
|
|
1142
|
+
});
|
|
598
1143
|
tabs.append(tab);
|
|
599
1144
|
tabFor.set(s.id, tab);
|
|
600
1145
|
const panel = el("section", "settings-panel");
|
|
@@ -608,6 +1153,25 @@ function mountSettingsDialog(opts) {
|
|
|
608
1153
|
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
609
1154
|
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
610
1155
|
}
|
|
1156
|
+
const usable = (s) => !tabFor.get(s.id)?.classList.contains("is-disabled");
|
|
1157
|
+
function refreshTabs() {
|
|
1158
|
+
for (const s of opts.sections) {
|
|
1159
|
+
const tab = tabFor.get(s.id);
|
|
1160
|
+
const reason = s.disabled?.() ?? null;
|
|
1161
|
+
tab.classList.toggle("is-disabled", reason !== null);
|
|
1162
|
+
if (reason !== null) {
|
|
1163
|
+
tab.dataset["tip"] = reason;
|
|
1164
|
+
tab.setAttribute("aria-disabled", "true");
|
|
1165
|
+
} else {
|
|
1166
|
+
delete tab.dataset["tip"];
|
|
1167
|
+
tab.removeAttribute("aria-disabled");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (!opts.sections.some((s) => s.id === active2 && usable(s))) {
|
|
1171
|
+
const first = opts.sections.find(usable);
|
|
1172
|
+
if (first) showTab(first.id);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
611
1175
|
form.append(
|
|
612
1176
|
el("h2", "settings-title", opts.title),
|
|
613
1177
|
el("div", "settings-body", tabs, panels),
|
|
@@ -623,14 +1187,15 @@ function mountSettingsDialog(opts) {
|
|
|
623
1187
|
e.preventDefault();
|
|
624
1188
|
error.hidden = true;
|
|
625
1189
|
for (const s of opts.sections) {
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1190
|
+
const found = handles.get(s.id)?.firstInvalid?.();
|
|
1191
|
+
if (!found) continue;
|
|
1192
|
+
const bad = found instanceof HTMLElement ? found : found.el;
|
|
1193
|
+
const message = (found instanceof HTMLElement ? void 0 : found.message) ?? bad.dataset["tip"];
|
|
1194
|
+
showTab(s.id);
|
|
1195
|
+
bad.focus();
|
|
1196
|
+
error.textContent = message || "Fix the highlighted fields first.";
|
|
1197
|
+
error.hidden = false;
|
|
1198
|
+
return;
|
|
634
1199
|
}
|
|
635
1200
|
void (async () => {
|
|
636
1201
|
await opts.onSave();
|
|
@@ -647,25 +1212,94 @@ function mountSettingsDialog(opts) {
|
|
|
647
1212
|
}
|
|
648
1213
|
error.hidden = true;
|
|
649
1214
|
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
1215
|
+
refreshTabs();
|
|
650
1216
|
dialog.showModal();
|
|
651
1217
|
},
|
|
1218
|
+
refreshTabs,
|
|
652
1219
|
destroy() {
|
|
653
1220
|
dialog.remove();
|
|
654
1221
|
}
|
|
655
1222
|
};
|
|
656
1223
|
}
|
|
657
1224
|
|
|
1225
|
+
// src/util.ts
|
|
1226
|
+
function plural(n, noun, pluralForm = `${noun}s`) {
|
|
1227
|
+
return `${n} ${n === 1 ? noun : pluralForm}`;
|
|
1228
|
+
}
|
|
1229
|
+
function debounce(fn, ms) {
|
|
1230
|
+
let timer3;
|
|
1231
|
+
let pending;
|
|
1232
|
+
const run = () => {
|
|
1233
|
+
timer3 = void 0;
|
|
1234
|
+
if (pending === void 0) return;
|
|
1235
|
+
const args = pending;
|
|
1236
|
+
pending = void 0;
|
|
1237
|
+
fn(...args);
|
|
1238
|
+
};
|
|
1239
|
+
const debounced = ((...args) => {
|
|
1240
|
+
pending = args;
|
|
1241
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1242
|
+
timer3 = setTimeout(run, ms);
|
|
1243
|
+
});
|
|
1244
|
+
debounced.cancel = () => {
|
|
1245
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1246
|
+
timer3 = void 0;
|
|
1247
|
+
pending = void 0;
|
|
1248
|
+
};
|
|
1249
|
+
debounced.flush = () => {
|
|
1250
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1251
|
+
run();
|
|
1252
|
+
};
|
|
1253
|
+
return debounced;
|
|
1254
|
+
}
|
|
1255
|
+
function isEditableTarget(t) {
|
|
1256
|
+
if (!(t instanceof Element)) return false;
|
|
1257
|
+
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) return true;
|
|
1258
|
+
if (t instanceof HTMLElement && t.isContentEditable === true) return true;
|
|
1259
|
+
const attr = t.getAttribute("contenteditable");
|
|
1260
|
+
return attr !== null && attr !== "false";
|
|
1261
|
+
}
|
|
1262
|
+
function formatCount(n) {
|
|
1263
|
+
return n.toLocaleString();
|
|
1264
|
+
}
|
|
1265
|
+
var copyTimers = /* @__PURE__ */ new WeakMap();
|
|
1266
|
+
function copyWithFeedback(el2, text, ms = 1e3) {
|
|
1267
|
+
const write = typeof navigator !== "undefined" && navigator.clipboard ? navigator.clipboard.writeText(text).catch(() => {
|
|
1268
|
+
}) : Promise.resolve();
|
|
1269
|
+
el2.classList.add("copied");
|
|
1270
|
+
const prior = copyTimers.get(el2);
|
|
1271
|
+
if (prior !== void 0) clearTimeout(prior);
|
|
1272
|
+
copyTimers.set(el2, setTimeout(() => {
|
|
1273
|
+
el2.classList.remove("copied");
|
|
1274
|
+
copyTimers.delete(el2);
|
|
1275
|
+
}, ms));
|
|
1276
|
+
return write;
|
|
1277
|
+
}
|
|
1278
|
+
function relativeTime(date, now = Date.now()) {
|
|
1279
|
+
const then = date instanceof Date ? date.getTime() : new Date(date).getTime();
|
|
1280
|
+
if (!Number.isFinite(then)) return "";
|
|
1281
|
+
const mins = Math.floor((now - then) / 6e4);
|
|
1282
|
+
if (mins < 1) return "just now";
|
|
1283
|
+
if (mins < 60) return `${plural(mins, "minute")} ago`;
|
|
1284
|
+
const hours = Math.floor(mins / 60);
|
|
1285
|
+
if (hours === 1) return "an hour ago";
|
|
1286
|
+
if (hours < 24) return `${hours} hours ago`;
|
|
1287
|
+
const days = Math.floor(hours / 24);
|
|
1288
|
+
if (days === 1) return "yesterday";
|
|
1289
|
+
if (days < 7) return `${days} days ago`;
|
|
1290
|
+
return new Date(then).toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" });
|
|
1291
|
+
}
|
|
1292
|
+
|
|
658
1293
|
// src/tool-window-web.ts
|
|
659
|
-
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>`;
|
|
660
1294
|
function pinButton(opts) {
|
|
661
1295
|
const b = el("button", "swin-pin");
|
|
662
1296
|
b.type = "button";
|
|
663
|
-
b.
|
|
1297
|
+
b.append(iconNode("pin"));
|
|
664
1298
|
let pinned = opts.pinned;
|
|
665
1299
|
const reflect = () => {
|
|
666
1300
|
b.classList.toggle("on", pinned);
|
|
667
1301
|
b.setAttribute("aria-pressed", String(pinned));
|
|
668
|
-
const label2 = pinned ? "Pinned on top
|
|
1302
|
+
const label2 = pinned ? "Pinned on top. Click to unpin." : "Keep on top";
|
|
669
1303
|
b.dataset["tip"] = label2;
|
|
670
1304
|
b.setAttribute("aria-label", label2);
|
|
671
1305
|
};
|
|
@@ -680,6 +1314,61 @@ function pinButton(opts) {
|
|
|
680
1314
|
reflect();
|
|
681
1315
|
} };
|
|
682
1316
|
}
|
|
1317
|
+
var escSlot;
|
|
1318
|
+
var escWired = false;
|
|
1319
|
+
function wireEsc() {
|
|
1320
|
+
if (escWired) return;
|
|
1321
|
+
escWired = true;
|
|
1322
|
+
window.addEventListener("keydown", (e) => {
|
|
1323
|
+
if (e.key !== "Escape" || !escSlot) return;
|
|
1324
|
+
if (e.defaultPrevented) return;
|
|
1325
|
+
if (!escSlot.fromFields && isEditableTarget(e.target)) return;
|
|
1326
|
+
if (escSlot.onEscape?.() === true) return;
|
|
1327
|
+
escSlot.onClose();
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
function toolWindowHead(opts) {
|
|
1331
|
+
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
1332
|
+
const pin = opts.pin ?? pinButton({ pinned: opts.pinned ?? true, onToggle: opts.onPin ?? (() => {
|
|
1333
|
+
}) });
|
|
1334
|
+
if (opts.esc !== false) {
|
|
1335
|
+
escSlot = { onClose: opts.onClose, onEscape: opts.onEscape, fromFields: opts.esc === "always" };
|
|
1336
|
+
wireEsc();
|
|
1337
|
+
}
|
|
1338
|
+
return el(
|
|
1339
|
+
"header",
|
|
1340
|
+
{ className: `swin-head${opts.className ? ` ${opts.className}` : ""}` },
|
|
1341
|
+
...opts.title !== void 0 ? [el("span", { className: "swin-title", text: opts.title })] : [],
|
|
1342
|
+
...opts.tabs ? [opts.tabs] : [],
|
|
1343
|
+
...keep(opts.lead),
|
|
1344
|
+
el("span", { className: "swin-spacer" }),
|
|
1345
|
+
...keep(opts.trail),
|
|
1346
|
+
pin.el,
|
|
1347
|
+
el("button", { className: "swin-close", tip: "Close (Esc)", onClick: opts.onClose }, iconNode("close"))
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
function followButton(opts) {
|
|
1351
|
+
const what = opts.what ?? "line";
|
|
1352
|
+
const b = el("button", "btn swin-follow", "Follow in the editor");
|
|
1353
|
+
b.type = "button";
|
|
1354
|
+
let on = opts.on;
|
|
1355
|
+
const reflect = () => {
|
|
1356
|
+
b.classList.toggle("on", on);
|
|
1357
|
+
b.setAttribute("aria-pressed", String(on));
|
|
1358
|
+
const label2 = on ? `The editor opens each ${what} as it plays. Click to stop.` : `Open each ${what} in the editor as it plays`;
|
|
1359
|
+
b.dataset["tip"] = label2;
|
|
1360
|
+
};
|
|
1361
|
+
reflect();
|
|
1362
|
+
b.addEventListener("click", () => {
|
|
1363
|
+
on = !on;
|
|
1364
|
+
reflect();
|
|
1365
|
+
opts.onToggle(on);
|
|
1366
|
+
});
|
|
1367
|
+
return { el: b, set: (next) => {
|
|
1368
|
+
on = next;
|
|
1369
|
+
reflect();
|
|
1370
|
+
} };
|
|
1371
|
+
}
|
|
683
1372
|
|
|
684
1373
|
// src/exit.ts
|
|
685
1374
|
function closeWithExit(element, done) {
|
|
@@ -704,61 +1393,232 @@ function closeWithExit(element, done) {
|
|
|
704
1393
|
const timer3 = setTimeout(finish, seconds * 1e3 + 120);
|
|
705
1394
|
}
|
|
706
1395
|
|
|
1396
|
+
// src/dialog.ts
|
|
1397
|
+
function dialogFrame(opts) {
|
|
1398
|
+
const dialog = el("dialog", `shell-dialog ${opts.className ?? ""}`.trim());
|
|
1399
|
+
const body = el("div", "shell-dialog-body");
|
|
1400
|
+
const actions = el("div", "shell-dialog-actions");
|
|
1401
|
+
dialog.append(el("h2", "shell-dialog-title", opts.title));
|
|
1402
|
+
if (opts.sub !== void 0) dialog.append(el("p", "shell-dialog-sub", opts.sub));
|
|
1403
|
+
dialog.append(body, actions);
|
|
1404
|
+
dialog.setAttribute("aria-label", opts.title);
|
|
1405
|
+
document.body.append(dialog);
|
|
1406
|
+
let state = "closed";
|
|
1407
|
+
let opener = null;
|
|
1408
|
+
const teardown = () => {
|
|
1409
|
+
dialog.remove();
|
|
1410
|
+
if (opener instanceof HTMLElement && opener.isConnected) opener.focus();
|
|
1411
|
+
opener = null;
|
|
1412
|
+
state = "closed";
|
|
1413
|
+
opts.onClose?.();
|
|
1414
|
+
};
|
|
1415
|
+
const close = () => {
|
|
1416
|
+
if (state !== "open") return;
|
|
1417
|
+
state = "closing";
|
|
1418
|
+
closeWithExit(dialog, () => {
|
|
1419
|
+
if (dialog.open) dialog.close();
|
|
1420
|
+
teardown();
|
|
1421
|
+
});
|
|
1422
|
+
};
|
|
1423
|
+
dialog.addEventListener("cancel", (e) => {
|
|
1424
|
+
e.preventDefault();
|
|
1425
|
+
close();
|
|
1426
|
+
});
|
|
1427
|
+
dialog.addEventListener("close", () => {
|
|
1428
|
+
if (state === "open") teardown();
|
|
1429
|
+
});
|
|
1430
|
+
return {
|
|
1431
|
+
dialog,
|
|
1432
|
+
body,
|
|
1433
|
+
actions,
|
|
1434
|
+
open() {
|
|
1435
|
+
if (state !== "closed") return;
|
|
1436
|
+
opener = document.activeElement;
|
|
1437
|
+
state = "open";
|
|
1438
|
+
dialog.showModal();
|
|
1439
|
+
},
|
|
1440
|
+
close,
|
|
1441
|
+
isOpen: () => state === "open"
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
|
|
707
1445
|
// src/confirm.ts
|
|
708
1446
|
function confirmDialog(opts) {
|
|
709
1447
|
return new Promise((resolve) => {
|
|
710
|
-
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
711
|
-
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
712
|
-
cancel.type = "button";
|
|
713
|
-
danger.type = "button";
|
|
714
|
-
const dlg = el(
|
|
715
|
-
"dialog",
|
|
716
|
-
"confirm-dialog",
|
|
717
|
-
el("div", "confirm-title", opts.title),
|
|
718
|
-
el("div", "confirm-body", opts.body),
|
|
719
|
-
el("div", "confirm-actions", cancel, danger)
|
|
720
|
-
);
|
|
721
|
-
dlg.setAttribute("aria-label", opts.title);
|
|
722
|
-
document.body.append(dlg);
|
|
723
1448
|
let done = false;
|
|
724
1449
|
const finish = (ok) => {
|
|
725
1450
|
if (done) return;
|
|
726
1451
|
done = true;
|
|
727
1452
|
resolve(ok);
|
|
728
|
-
|
|
729
|
-
dlg.close();
|
|
730
|
-
dlg.remove();
|
|
731
|
-
});
|
|
1453
|
+
frame.close();
|
|
732
1454
|
};
|
|
1455
|
+
const frame = dialogFrame({
|
|
1456
|
+
title: opts.title,
|
|
1457
|
+
className: "confirm-dialog",
|
|
1458
|
+
onClose: () => finish(false)
|
|
1459
|
+
// Esc, or closed by someone else
|
|
1460
|
+
});
|
|
1461
|
+
const cancel = el("button", "btn confirm-btn cancel", "Cancel");
|
|
1462
|
+
const danger = el("button", "btn danger confirm-btn", opts.confirmLabel);
|
|
1463
|
+
cancel.type = "button";
|
|
1464
|
+
danger.type = "button";
|
|
1465
|
+
if (opts.body !== void 0) frame.body.append(el("div", "confirm-body", opts.body));
|
|
1466
|
+
if (opts.bodyNode) frame.body.append(opts.bodyNode);
|
|
1467
|
+
frame.actions.append(cancel, danger);
|
|
733
1468
|
cancel.addEventListener("click", () => finish(false));
|
|
734
1469
|
danger.addEventListener("click", () => finish(true));
|
|
735
|
-
|
|
736
|
-
e.
|
|
737
|
-
finish(false);
|
|
738
|
-
});
|
|
739
|
-
dlg.addEventListener("mousedown", (e) => {
|
|
740
|
-
if (e.target === dlg) finish(false);
|
|
1470
|
+
frame.dialog.addEventListener("mousedown", (e) => {
|
|
1471
|
+
if (e.target === frame.dialog) finish(false);
|
|
741
1472
|
});
|
|
742
|
-
|
|
1473
|
+
frame.open();
|
|
743
1474
|
cancel.focus();
|
|
744
1475
|
});
|
|
745
1476
|
}
|
|
746
1477
|
|
|
1478
|
+
// src/floating.ts
|
|
1479
|
+
function followOnScroll(reposition) {
|
|
1480
|
+
const onMove = () => reposition();
|
|
1481
|
+
window.addEventListener("scroll", onMove, true);
|
|
1482
|
+
window.addEventListener("resize", onMove);
|
|
1483
|
+
return () => {
|
|
1484
|
+
window.removeEventListener("scroll", onMove, true);
|
|
1485
|
+
window.removeEventListener("resize", onMove);
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function createFloating(className, opts = {}) {
|
|
1489
|
+
const el2 = document.createElement("div");
|
|
1490
|
+
el2.className = className;
|
|
1491
|
+
el2.style.display = "none";
|
|
1492
|
+
(opts.host ?? document.body).appendChild(el2);
|
|
1493
|
+
let open = false;
|
|
1494
|
+
let detach = null;
|
|
1495
|
+
let outside = null;
|
|
1496
|
+
let closeToken = 0;
|
|
1497
|
+
const close = () => {
|
|
1498
|
+
if (!open) return;
|
|
1499
|
+
open = false;
|
|
1500
|
+
detach?.();
|
|
1501
|
+
detach = null;
|
|
1502
|
+
if (outside) {
|
|
1503
|
+
document.removeEventListener("mousedown", outside, true);
|
|
1504
|
+
outside = null;
|
|
1505
|
+
}
|
|
1506
|
+
const myToken = ++closeToken;
|
|
1507
|
+
closeWithExit(el2, () => {
|
|
1508
|
+
if (myToken !== closeToken) return;
|
|
1509
|
+
el2.style.display = "none";
|
|
1510
|
+
el2.replaceChildren();
|
|
1511
|
+
el2.classList.remove("closing");
|
|
1512
|
+
});
|
|
1513
|
+
};
|
|
1514
|
+
const show2 = (reposition) => {
|
|
1515
|
+
closeToken++;
|
|
1516
|
+
el2.classList.remove("closing");
|
|
1517
|
+
open = true;
|
|
1518
|
+
el2.style.display = "block";
|
|
1519
|
+
reposition();
|
|
1520
|
+
detach ??= followOnScroll(reposition);
|
|
1521
|
+
};
|
|
1522
|
+
const dismissOnOutside = (onOutside, isInside) => {
|
|
1523
|
+
if (outside) return;
|
|
1524
|
+
outside = (e) => {
|
|
1525
|
+
const t = e.target;
|
|
1526
|
+
if (!el2.contains(t) && !(isInside?.(t) ?? false)) onOutside();
|
|
1527
|
+
};
|
|
1528
|
+
document.addEventListener("mousedown", outside, true);
|
|
1529
|
+
};
|
|
1530
|
+
return { el: el2, show: show2, dismissOnOutside, close, isOpen: () => open };
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// src/context-menu.ts
|
|
1534
|
+
var EDGE2 = 6;
|
|
1535
|
+
var current = null;
|
|
1536
|
+
function floating(node2, onDismiss) {
|
|
1537
|
+
let gone = false;
|
|
1538
|
+
const dismiss = () => {
|
|
1539
|
+
if (gone) return;
|
|
1540
|
+
gone = true;
|
|
1541
|
+
if (current === dismiss) current = null;
|
|
1542
|
+
window.removeEventListener("pointerdown", onAway, true);
|
|
1543
|
+
window.removeEventListener("keydown", onKey, true);
|
|
1544
|
+
closeWithExit(node2, () => node2.remove());
|
|
1545
|
+
onDismiss?.();
|
|
1546
|
+
};
|
|
1547
|
+
const onAway = (e) => {
|
|
1548
|
+
if (!node2.contains(e.target)) dismiss();
|
|
1549
|
+
};
|
|
1550
|
+
const onKey = (e) => {
|
|
1551
|
+
if (e.key === "Escape") {
|
|
1552
|
+
e.preventDefault();
|
|
1553
|
+
dismiss();
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
window.addEventListener("pointerdown", onAway, true);
|
|
1557
|
+
window.addEventListener("keydown", onKey, true);
|
|
1558
|
+
current?.();
|
|
1559
|
+
current = dismiss;
|
|
1560
|
+
return { dismiss };
|
|
1561
|
+
}
|
|
1562
|
+
function placeBelow(node2, anchor) {
|
|
1563
|
+
node2.style.left = `${anchor.left}px`;
|
|
1564
|
+
node2.style.top = `${anchor.bottom + 4}px`;
|
|
1565
|
+
const r = node2.getBoundingClientRect();
|
|
1566
|
+
if (r.right > window.innerWidth) node2.style.left = `${Math.max(EDGE2, window.innerWidth - r.width - EDGE2)}px`;
|
|
1567
|
+
if (r.bottom > window.innerHeight) node2.style.top = `${Math.max(EDGE2, anchor.top - r.height - 4)}px`;
|
|
1568
|
+
}
|
|
1569
|
+
function openPopover(anchor, build, onClose, opts = {}) {
|
|
1570
|
+
const pop = el("div", "popover");
|
|
1571
|
+
let closed = false;
|
|
1572
|
+
const finish = () => {
|
|
1573
|
+
if (closed) return;
|
|
1574
|
+
closed = true;
|
|
1575
|
+
onClose?.();
|
|
1576
|
+
};
|
|
1577
|
+
const { dismiss } = floating(pop, finish);
|
|
1578
|
+
pop.append(build(dismiss));
|
|
1579
|
+
(opts.host ?? document.body).append(pop);
|
|
1580
|
+
placeBelow(pop, anchor.getBoundingClientRect());
|
|
1581
|
+
}
|
|
1582
|
+
function openContextMenu(x, y, items) {
|
|
1583
|
+
const menu = el("div", "ctxmenu");
|
|
1584
|
+
menu.setAttribute("role", "menu");
|
|
1585
|
+
const { dismiss } = floating(menu);
|
|
1586
|
+
for (const item of items) {
|
|
1587
|
+
const button = el("button", {
|
|
1588
|
+
className: `ctxmenu-item${item.danger ? " danger" : ""}`,
|
|
1589
|
+
text: item.label,
|
|
1590
|
+
onClick: () => {
|
|
1591
|
+
if (item.disabled === true) return;
|
|
1592
|
+
dismiss();
|
|
1593
|
+
item.onClick();
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
button.type = "button";
|
|
1597
|
+
button.setAttribute("role", "menuitem");
|
|
1598
|
+
if (item.disabled === true) button.disabled = true;
|
|
1599
|
+
menu.append(button);
|
|
1600
|
+
}
|
|
1601
|
+
menu.style.left = `${x}px`;
|
|
1602
|
+
menu.style.top = `${y}px`;
|
|
1603
|
+
document.body.append(menu);
|
|
1604
|
+
const r = menu.getBoundingClientRect();
|
|
1605
|
+
if (r.right > window.innerWidth) menu.style.left = `${Math.max(EDGE2, window.innerWidth - r.width - EDGE2)}px`;
|
|
1606
|
+
if (r.bottom > window.innerHeight) menu.style.top = `${Math.max(EDGE2, window.innerHeight - r.height - EDGE2)}px`;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
747
1609
|
// src/about.ts
|
|
748
1610
|
function showAbout(opts) {
|
|
749
1611
|
return new Promise((resolve) => {
|
|
750
|
-
const
|
|
1612
|
+
const frame = dialogFrame({ title: opts.appName, className: "shell-about", onClose: () => resolve() });
|
|
1613
|
+
frame.dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
751
1614
|
if (opts.wordmark !== void 0) {
|
|
752
1615
|
const mark = el("div", "shell-about-mark");
|
|
753
1616
|
mark.innerHTML = opts.wordmark;
|
|
754
|
-
dialog.
|
|
1617
|
+
frame.dialog.prepend(mark);
|
|
755
1618
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
);
|
|
760
|
-
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
761
|
-
if (opts.credits !== void 0) dialog.append(el("p", "shell-about-credits", opts.credits));
|
|
1619
|
+
frame.body.append(el("div", "shell-about-version", `Version ${opts.version}`));
|
|
1620
|
+
if (opts.blurb !== void 0) frame.body.append(el("p", "shell-about-blurb", opts.blurb));
|
|
1621
|
+
if (opts.credits !== void 0) frame.body.append(el("p", "shell-about-credits", opts.credits));
|
|
762
1622
|
if (opts.links?.length) {
|
|
763
1623
|
const row = el("div", "shell-about-links");
|
|
764
1624
|
for (const link of opts.links) {
|
|
@@ -771,39 +1631,28 @@ function showAbout(opts) {
|
|
|
771
1631
|
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
772
1632
|
row.append(a);
|
|
773
1633
|
}
|
|
774
|
-
|
|
1634
|
+
frame.body.append(row);
|
|
775
1635
|
}
|
|
776
|
-
const close = el("button", "shell-about-close", "Close");
|
|
1636
|
+
const close = el("button", "btn shell-about-close", "Close");
|
|
777
1637
|
close.type = "button";
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
let done = false;
|
|
782
|
-
const finish = () => {
|
|
783
|
-
if (done) return;
|
|
784
|
-
done = true;
|
|
785
|
-
dialog.close();
|
|
786
|
-
dialog.remove();
|
|
787
|
-
resolve();
|
|
788
|
-
};
|
|
789
|
-
close.addEventListener("click", finish);
|
|
790
|
-
dialog.addEventListener("close", finish);
|
|
791
|
-
dialog.showModal();
|
|
1638
|
+
close.addEventListener("click", () => frame.close());
|
|
1639
|
+
frame.actions.append(close);
|
|
1640
|
+
frame.open();
|
|
792
1641
|
close.focus();
|
|
793
1642
|
});
|
|
794
1643
|
}
|
|
795
1644
|
|
|
796
1645
|
// src/anchored.ts
|
|
797
|
-
var
|
|
1646
|
+
var current2 = null;
|
|
798
1647
|
function openAnchoredPanel(opts) {
|
|
799
|
-
if (
|
|
800
|
-
const prev =
|
|
801
|
-
|
|
1648
|
+
if (current2) {
|
|
1649
|
+
const prev = current2;
|
|
1650
|
+
current2 = null;
|
|
802
1651
|
prev.close();
|
|
803
1652
|
if (prev.anchor === opts.anchor) return null;
|
|
804
1653
|
}
|
|
805
1654
|
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
806
|
-
const closeBtn = el("button", "shell-anchored-close", "
|
|
1655
|
+
const closeBtn = el("button", "shell-anchored-close", iconNode("close", 12));
|
|
807
1656
|
closeBtn.type = "button";
|
|
808
1657
|
closeBtn.setAttribute("aria-label", "Close");
|
|
809
1658
|
const body = el("div", "shell-anchored-body");
|
|
@@ -811,13 +1660,13 @@ function openAnchoredPanel(opts) {
|
|
|
811
1660
|
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
812
1661
|
body
|
|
813
1662
|
);
|
|
814
|
-
document.body.append(panel);
|
|
1663
|
+
(opts.host ?? document.body).append(panel);
|
|
815
1664
|
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
816
1665
|
let closed = false;
|
|
817
1666
|
const close = () => {
|
|
818
1667
|
if (closed) return;
|
|
819
1668
|
closed = true;
|
|
820
|
-
if (
|
|
1669
|
+
if (current2?.panel === panel) current2 = null;
|
|
821
1670
|
document.removeEventListener("pointerdown", onDown, true);
|
|
822
1671
|
document.removeEventListener("keydown", onKey, true);
|
|
823
1672
|
closeWithExit(panel, () => {
|
|
@@ -841,12 +1690,12 @@ function openAnchoredPanel(opts) {
|
|
|
841
1690
|
document.addEventListener("pointerdown", onDown, true);
|
|
842
1691
|
document.addEventListener("keydown", onKey, true);
|
|
843
1692
|
}, 0);
|
|
844
|
-
|
|
1693
|
+
current2 = { anchor: opts.anchor, panel, close };
|
|
845
1694
|
return { panel, body, close };
|
|
846
1695
|
}
|
|
847
1696
|
function closeAnchoredPanel() {
|
|
848
|
-
const open =
|
|
849
|
-
|
|
1697
|
+
const open = current2;
|
|
1698
|
+
current2 = null;
|
|
850
1699
|
open?.close();
|
|
851
1700
|
}
|
|
852
1701
|
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
@@ -903,7 +1752,7 @@ function isValidGameId(gameId) {
|
|
|
903
1752
|
|
|
904
1753
|
// src/property-names.ts
|
|
905
1754
|
var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
|
|
906
|
-
var PROPERTY_NAME_HINT = "Lower case letters, digits and underscores.
|
|
1755
|
+
var PROPERTY_NAME_HINT = "Lower case letters, digits, and underscores. It can't start with a digit. No hyphens or spaces.";
|
|
907
1756
|
function propertyNameify(text) {
|
|
908
1757
|
const trimmed = text.trim();
|
|
909
1758
|
const deliberateLeading = trimmed.startsWith("_");
|
|
@@ -927,14 +1776,14 @@ function propertyNameProblem(name) {
|
|
|
927
1776
|
const suggestion = propertyNameify(name);
|
|
928
1777
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
929
1778
|
if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
|
|
930
|
-
return `
|
|
1779
|
+
return `Can't be used. "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
|
|
931
1780
|
}
|
|
932
|
-
if (name.includes("-")) return `
|
|
933
|
-
if (/^[0-9]/.test(name)) return `
|
|
1781
|
+
if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
|
|
1782
|
+
if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
|
|
934
1783
|
if (name !== name.toLowerCase()) {
|
|
935
|
-
return `
|
|
1784
|
+
return `Can't be used. Expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
|
|
936
1785
|
}
|
|
937
|
-
return `
|
|
1786
|
+
return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
|
|
938
1787
|
}
|
|
939
1788
|
function bindPropertyName(input, onCommit, opts = {}) {
|
|
940
1789
|
const hint = opts.hint;
|
|
@@ -943,7 +1792,7 @@ function bindPropertyName(input, onCommit, opts = {}) {
|
|
|
943
1792
|
const problem = v === "" ? void 0 : propertyNameProblem(v);
|
|
944
1793
|
input.classList.toggle("illegal", !!problem);
|
|
945
1794
|
if (problem) input.title = problem;
|
|
946
|
-
else if (input.title.startsWith("
|
|
1795
|
+
else if (input.title.startsWith("Can't be used")) {
|
|
947
1796
|
if (hint) input.title = hint;
|
|
948
1797
|
else input.removeAttribute("title");
|
|
949
1798
|
}
|
|
@@ -999,10 +1848,10 @@ function propertyRefProblem(name, opts) {
|
|
|
999
1848
|
if (!isValidPropertyName(folded)) {
|
|
1000
1849
|
const suggestion = propertyNameify(name);
|
|
1001
1850
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
1002
|
-
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `
|
|
1003
|
-
if (name.includes("-")) return `
|
|
1004
|
-
if (/^[0-9]/.test(name)) return `
|
|
1005
|
-
return `
|
|
1851
|
+
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Can't be used. "${folded}" is a keyword in expressions.${tail}`;
|
|
1852
|
+
if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
|
|
1853
|
+
if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
|
|
1854
|
+
return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
|
|
1006
1855
|
}
|
|
1007
1856
|
const known = opts.known();
|
|
1008
1857
|
if (known.some((k) => k.toLowerCase() === folded)) return void 0;
|
|
@@ -1011,7 +1860,7 @@ function propertyRefProblem(name, opts) {
|
|
|
1011
1860
|
if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
|
|
1012
1861
|
const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
|
|
1013
1862
|
const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
|
|
1014
|
-
return `No ${scope} property is called "${name}".
|
|
1863
|
+
return `No ${scope} property is called "${name}". Pick one of ${shown}${more}.`;
|
|
1015
1864
|
}
|
|
1016
1865
|
function bindPropertyRef(input, onCommit, opts) {
|
|
1017
1866
|
const list = document.createElement("datalist");
|
|
@@ -1032,7 +1881,7 @@ function bindPropertyRef(input, onCommit, opts) {
|
|
|
1032
1881
|
const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
|
|
1033
1882
|
input.classList.toggle("illegal", !!problem);
|
|
1034
1883
|
if (problem) input.title = problem;
|
|
1035
|
-
else if (input.title.startsWith("
|
|
1884
|
+
else if (input.title.startsWith("Can't be used") || input.title.startsWith("No ")) {
|
|
1036
1885
|
if (opts.hint) input.title = opts.hint;
|
|
1037
1886
|
else input.removeAttribute("title");
|
|
1038
1887
|
}
|
|
@@ -1075,9 +1924,9 @@ function openGameIdEditor(opts) {
|
|
|
1075
1924
|
const input = el("input", "shell-id-input");
|
|
1076
1925
|
input.type = "text";
|
|
1077
1926
|
input.value = opts.value;
|
|
1078
|
-
input.placeholder = opts.derived || "
|
|
1927
|
+
input.placeholder = opts.derived || "game-id";
|
|
1079
1928
|
input.spellcheck = false;
|
|
1080
|
-
const hint = el("div", "shell-id-hint", "Lower case, digits, hyphens.
|
|
1929
|
+
const hint = el("div", "shell-id-hint", "Lower case letters, digits, and hyphens. Leave it empty to derive one from the name.");
|
|
1081
1930
|
const actions = el("div", "shell-id-actions");
|
|
1082
1931
|
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
1083
1932
|
const set = el("button", "shell-id-set", "Set");
|
|
@@ -1139,12 +1988,13 @@ function foldVc(shards, keys) {
|
|
|
1139
1988
|
return out;
|
|
1140
1989
|
}
|
|
1141
1990
|
function vcBadgeFor(s) {
|
|
1142
|
-
|
|
1143
|
-
if (s?.
|
|
1144
|
-
if (s?.
|
|
1145
|
-
if (s?.
|
|
1146
|
-
if (s?.
|
|
1147
|
-
if (s
|
|
1991
|
+
const badge = (name, cls, title) => ({ name, glyph: icon[name] ?? "", cls, title });
|
|
1992
|
+
if (s?.lockedBy?.length) return badge("locked", "vc-locked", `Locked by ${s.lockedBy.join(", ")}`);
|
|
1993
|
+
if (s?.outOfDate) return badge("down", "vc-stale", "Out of date. A newer version is on the server.");
|
|
1994
|
+
if (s?.checkedOutByMe) return badge("checkedOut", "vc-mine", "Checked out by you");
|
|
1995
|
+
if (s?.dirty) return badge("modified", "vc-dirty", "Modified. Local changes aren't committed yet.");
|
|
1996
|
+
if (s?.untracked) return badge("untracked", "vc-new", "New. Not in version control yet.");
|
|
1997
|
+
if (s && !s.writable) return badge("readOnly", "vc-frozen", "Read-only on disk. Saving checks it out.");
|
|
1148
1998
|
return null;
|
|
1149
1999
|
}
|
|
1150
2000
|
function paintVcBadges(root, shards) {
|
|
@@ -1152,7 +2002,7 @@ function paintVcBadges(root, shards) {
|
|
|
1152
2002
|
host.querySelector(":scope > .vc-badge")?.remove();
|
|
1153
2003
|
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
1154
2004
|
if (!badge) return;
|
|
1155
|
-
const span = el("span", { className: `vc-badge ${badge.cls}`,
|
|
2005
|
+
const span = el("span", { className: `vc-badge ${badge.cls}`, tip: badge.title }, iconNode(badge.name, 12));
|
|
1156
2006
|
host.append(span);
|
|
1157
2007
|
});
|
|
1158
2008
|
}
|
|
@@ -1175,8 +2025,8 @@ function lockNotice(holders) {
|
|
|
1175
2025
|
return el(
|
|
1176
2026
|
"div",
|
|
1177
2027
|
{ className: "vc-lock" },
|
|
1178
|
-
el("span", { className: "vc-lock-glyph",
|
|
1179
|
-
el("span", { text: `Locked by ${holders.join(", ")} -
|
|
2028
|
+
el("span", { className: "vc-lock-glyph" }, iconNode("locked")),
|
|
2029
|
+
el("span", { text: `Locked by ${holders.join(", ")}. Read-only until they release it.` })
|
|
1180
2030
|
);
|
|
1181
2031
|
}
|
|
1182
2032
|
|
|
@@ -1184,41 +2034,36 @@ function lockNotice(holders) {
|
|
|
1184
2034
|
function askIdentity(opts = {}) {
|
|
1185
2035
|
return new Promise((resolve) => {
|
|
1186
2036
|
const seed = opts.current ?? opts.suggested;
|
|
1187
|
-
const name = el("input", "shell-ident-input");
|
|
2037
|
+
const name = el("input", "field shell-ident-input");
|
|
1188
2038
|
name.placeholder = "Your name";
|
|
1189
2039
|
name.value = seed?.name ?? "";
|
|
1190
|
-
const email = el("input", "shell-ident-input");
|
|
1191
|
-
email.placeholder = "
|
|
2040
|
+
const email = el("input", "field shell-ident-input");
|
|
2041
|
+
email.placeholder = "Optional";
|
|
1192
2042
|
email.type = "email";
|
|
1193
2043
|
email.value = seed?.email ?? "";
|
|
1194
|
-
const skip = el("button", "shell-ident-btn", "Skip");
|
|
1195
|
-
const save = el("button", "shell-ident-btn
|
|
2044
|
+
const skip = el("button", "btn shell-ident-btn", "Skip");
|
|
2045
|
+
const save = el("button", "btn primary shell-ident-btn", opts.mode === "welcome" ? "Continue" : "Save");
|
|
1196
2046
|
skip.type = "button";
|
|
1197
2047
|
save.type = "button";
|
|
1198
|
-
const dialog = el(
|
|
1199
|
-
"dialog",
|
|
1200
|
-
"shell-ident",
|
|
1201
|
-
el("div", "shell-ident-title", "Who is working here?"),
|
|
1202
|
-
el(
|
|
1203
|
-
"p",
|
|
1204
|
-
"shell-ident-hint",
|
|
1205
|
-
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
1206
|
-
),
|
|
1207
|
-
el("div", "shell-ident-fields", name, email),
|
|
1208
|
-
el("div", "shell-ident-actions", skip, save)
|
|
1209
|
-
);
|
|
1210
|
-
document.body.append(dialog);
|
|
1211
2048
|
let done = false;
|
|
1212
2049
|
const finish = (keep) => {
|
|
1213
2050
|
if (done) return;
|
|
1214
2051
|
done = true;
|
|
1215
2052
|
const typed = name.value.trim();
|
|
1216
2053
|
const value = email.value.trim();
|
|
1217
|
-
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
1218
|
-
dialog.close();
|
|
1219
|
-
dialog.remove();
|
|
2054
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : opts.neverAskAgainOnSkip === true ? { name: "" } : void 0;
|
|
1220
2055
|
resolve(answer);
|
|
2056
|
+
frame.close();
|
|
1221
2057
|
};
|
|
2058
|
+
const frame = dialogFrame({
|
|
2059
|
+
title: titleFor(opts),
|
|
2060
|
+
sub: subFor(opts),
|
|
2061
|
+
className: "shell-ident",
|
|
2062
|
+
onClose: () => finish(false)
|
|
2063
|
+
// Escape, or closed by someone else: a skip
|
|
2064
|
+
});
|
|
2065
|
+
frame.body.append(el("div", "shell-ident-fields", labelled("Name", name), labelled("Email", email)));
|
|
2066
|
+
frame.actions.append(skip, save);
|
|
1222
2067
|
skip.addEventListener("click", () => finish(false));
|
|
1223
2068
|
save.addEventListener("click", () => finish(true));
|
|
1224
2069
|
name.addEventListener("keydown", (e) => {
|
|
@@ -1227,11 +2072,19 @@ function askIdentity(opts = {}) {
|
|
|
1227
2072
|
email.addEventListener("keydown", (e) => {
|
|
1228
2073
|
if (e.key === "Enter") finish(true);
|
|
1229
2074
|
});
|
|
1230
|
-
|
|
1231
|
-
dialog.showModal();
|
|
2075
|
+
frame.open();
|
|
1232
2076
|
name.focus();
|
|
1233
2077
|
});
|
|
1234
2078
|
}
|
|
2079
|
+
function titleFor(opts) {
|
|
2080
|
+
if (opts.mode === "welcome") return opts.appName ? `Welcome to ${opts.appName}` : "Welcome";
|
|
2081
|
+
if (opts.mode === "edit") return "User information";
|
|
2082
|
+
return "Who is working here?";
|
|
2083
|
+
}
|
|
2084
|
+
function subFor(opts) {
|
|
2085
|
+
const why = "Your name goes on the comments you write. It's kept in this app, not in the project.";
|
|
2086
|
+
return opts.mode === "edit" ? why : `${why} You can change it later.`;
|
|
2087
|
+
}
|
|
1235
2088
|
|
|
1236
2089
|
// src/notes-editor.ts
|
|
1237
2090
|
var EDITOR_ONLY = "";
|
|
@@ -1244,10 +2097,10 @@ var CLASS_LABEL = {
|
|
|
1244
2097
|
};
|
|
1245
2098
|
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
1246
2099
|
var PLACEHOLDER = {
|
|
1247
|
-
vo: "
|
|
1248
|
-
loc: "
|
|
2100
|
+
vo: "Direction for the voice actor or director",
|
|
2101
|
+
loc: "Context for translators"
|
|
1249
2102
|
};
|
|
1250
|
-
var placeholder = (cls) => cls === EDITOR_ONLY ? "
|
|
2103
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
|
|
1251
2104
|
function openNotesEditor(opts) {
|
|
1252
2105
|
const { notes } = opts;
|
|
1253
2106
|
const seed = /* @__PURE__ */ new Map();
|
|
@@ -1301,7 +2154,7 @@ ${line.text}` : line.text);
|
|
|
1301
2154
|
{ className: "shell-notes-class" },
|
|
1302
2155
|
el("label", "shell-notes-caption", label(cls))
|
|
1303
2156
|
);
|
|
1304
|
-
const area = el("textarea", "shell-notes-text");
|
|
2157
|
+
const area = el("textarea", "field shell-notes-text");
|
|
1305
2158
|
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
1306
2159
|
area.rows = 4;
|
|
1307
2160
|
area.placeholder = placeholder(cls);
|
|
@@ -1327,29 +2180,24 @@ ${line.text}` : line.text);
|
|
|
1327
2180
|
}
|
|
1328
2181
|
};
|
|
1329
2182
|
render();
|
|
1330
|
-
const done = el("button", "shell-notes-btn
|
|
1331
|
-
|
|
1332
|
-
"dialog",
|
|
1333
|
-
{ className: "shell-notes" },
|
|
1334
|
-
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
1335
|
-
body,
|
|
1336
|
-
el("div", { className: "shell-notes-actions" }, done)
|
|
1337
|
-
);
|
|
1338
|
-
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
1339
|
-
document.body.append(dialog);
|
|
2183
|
+
const done = el("button", "btn primary shell-notes-btn", "Done");
|
|
2184
|
+
done.type = "button";
|
|
1340
2185
|
const before = JSON.stringify(notes.own);
|
|
1341
2186
|
let closed = false;
|
|
1342
|
-
const
|
|
2187
|
+
const commit = () => {
|
|
1343
2188
|
if (closed) return;
|
|
1344
2189
|
closed = true;
|
|
1345
2190
|
const next = value();
|
|
1346
2191
|
if (JSON.stringify(next) !== before) opts.save(next);
|
|
1347
|
-
dialog.close();
|
|
1348
|
-
dialog.remove();
|
|
1349
2192
|
};
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
2193
|
+
const frame = dialogFrame({ title: `Notes: ${opts.subject}`, className: "shell-notes", onClose: commit });
|
|
2194
|
+
frame.body.append(body);
|
|
2195
|
+
frame.actions.append(done);
|
|
2196
|
+
done.addEventListener("click", () => {
|
|
2197
|
+
commit();
|
|
2198
|
+
frame.close();
|
|
2199
|
+
});
|
|
2200
|
+
frame.open();
|
|
1353
2201
|
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
1354
2202
|
}
|
|
1355
2203
|
|
|
@@ -1421,7 +2269,7 @@ function openComments(opts) {
|
|
|
1421
2269
|
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1422
2270
|
);
|
|
1423
2271
|
if (opts.deleteMessage && message.deleted !== true) {
|
|
1424
|
-
const bin = el("button", { className: "shell-cmt-del",
|
|
2272
|
+
const bin = el("button", { className: "shell-cmt-del" }, iconNode("close", 12));
|
|
1425
2273
|
bin.dataset.tip = "Delete this comment";
|
|
1426
2274
|
bin.setAttribute("aria-label", "Delete this comment");
|
|
1427
2275
|
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
@@ -1486,37 +2334,65 @@ var clock = (ms) => {
|
|
|
1486
2334
|
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1487
2335
|
function mountJobProgress(host, opts) {
|
|
1488
2336
|
const units = opts.units ?? "runs";
|
|
2337
|
+
const pinned = opts.indeterminate === true;
|
|
2338
|
+
const cancellableByDefault = opts.cancellable ?? opts.onCancel !== void 0;
|
|
1489
2339
|
const fill = el("i", { className: "job-fill" });
|
|
1490
2340
|
const bar = el("div", { className: "job-bar" }, fill);
|
|
2341
|
+
bar.setAttribute("role", "progressbar");
|
|
2342
|
+
bar.setAttribute("aria-valuemin", "0");
|
|
2343
|
+
bar.setAttribute("aria-valuemax", "100");
|
|
1491
2344
|
const what = el("span", { className: "job-label" });
|
|
1492
2345
|
const count = el("span", { className: "job-count" });
|
|
1493
2346
|
const time = el("span", { className: "job-time" });
|
|
1494
2347
|
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
2348
|
+
cancel.type = "button";
|
|
1495
2349
|
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1496
2350
|
strip.hidden = true;
|
|
1497
2351
|
host.append(strip);
|
|
2352
|
+
let runPinned = pinned;
|
|
2353
|
+
let indeterminate = false;
|
|
2354
|
+
const setMode = (band) => {
|
|
2355
|
+
indeterminate = band;
|
|
2356
|
+
strip.classList.toggle("indeterminate", band);
|
|
2357
|
+
count.hidden = band;
|
|
2358
|
+
if (band) {
|
|
2359
|
+
fill.style.width = "";
|
|
2360
|
+
bar.removeAttribute("aria-valuenow");
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
1498
2363
|
const view = {
|
|
1499
2364
|
element: strip,
|
|
1500
2365
|
get visible() {
|
|
1501
2366
|
return !strip.hidden;
|
|
1502
2367
|
},
|
|
1503
|
-
|
|
2368
|
+
get indeterminate() {
|
|
2369
|
+
return indeterminate;
|
|
2370
|
+
},
|
|
2371
|
+
begin(label2, run) {
|
|
2372
|
+
runPinned = run?.indeterminate ?? pinned;
|
|
2373
|
+
const cancellable = run?.cancellable ?? cancellableByDefault;
|
|
1504
2374
|
what.textContent = label2 ?? "Running\u2026";
|
|
1505
2375
|
count.textContent = "";
|
|
1506
2376
|
time.textContent = "";
|
|
1507
|
-
|
|
2377
|
+
cancel.hidden = !cancellable;
|
|
1508
2378
|
cancel.disabled = false;
|
|
1509
2379
|
cancel.textContent = "Cancel";
|
|
2380
|
+
setMode(true);
|
|
1510
2381
|
strip.hidden = false;
|
|
1511
2382
|
},
|
|
1512
2383
|
update(done, total, elapsedMs) {
|
|
1513
2384
|
if (strip.hidden) strip.hidden = false;
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
2385
|
+
const band = runPinned || !(total > 0);
|
|
2386
|
+
setMode(band);
|
|
2387
|
+
if (!band) {
|
|
2388
|
+
const pct = Math.min(100, done / total * 100);
|
|
2389
|
+
fill.style.width = `${pct}%`;
|
|
2390
|
+
bar.setAttribute("aria-valuenow", String(Math.round(pct)));
|
|
2391
|
+
count.textContent = `${done} / ${total} ${units}`.trimEnd();
|
|
2392
|
+
}
|
|
2393
|
+
if (!band && estimable(done, total, elapsedMs)) {
|
|
1518
2394
|
const remaining = elapsedMs / done * (total - done);
|
|
1519
|
-
time.textContent = `${clock(elapsedMs)} elapsed
|
|
2395
|
+
time.textContent = `${clock(elapsedMs)} elapsed, about ${clock(remaining)} left`;
|
|
1520
2396
|
} else {
|
|
1521
2397
|
time.textContent = clock(elapsedMs);
|
|
1522
2398
|
}
|
|
@@ -1528,8 +2404,10 @@ function mountJobProgress(host, opts) {
|
|
|
1528
2404
|
cancel.addEventListener("click", () => {
|
|
1529
2405
|
cancel.disabled = true;
|
|
1530
2406
|
cancel.textContent = "Stopping\u2026";
|
|
1531
|
-
opts.onCancel();
|
|
2407
|
+
opts.onCancel?.();
|
|
1532
2408
|
});
|
|
2409
|
+
cancel.hidden = !cancellableByDefault;
|
|
2410
|
+
setMode(true);
|
|
1533
2411
|
return view;
|
|
1534
2412
|
}
|
|
1535
2413
|
|
|
@@ -1655,11 +2533,14 @@ function staleBar(opts) {
|
|
|
1655
2533
|
className: "stale-bar-msg",
|
|
1656
2534
|
text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
|
|
1657
2535
|
}),
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
2536
|
+
// The icon is a node beside the word, not a character inside it; the
|
|
2537
|
+
// button's flex gap sets the space (stale.css).
|
|
2538
|
+
el(
|
|
2539
|
+
"button",
|
|
2540
|
+
{ className: "stale-bar-go", onClick: () => opts.onRestart() },
|
|
2541
|
+
iconNode("restart", 12),
|
|
2542
|
+
label2
|
|
2543
|
+
)
|
|
1663
2544
|
);
|
|
1664
2545
|
}
|
|
1665
2546
|
|
|
@@ -1701,14 +2582,14 @@ function dismissToast() {
|
|
|
1701
2582
|
}
|
|
1702
2583
|
|
|
1703
2584
|
// src/stepper.ts
|
|
2585
|
+
var STEP_ICON = 12;
|
|
1704
2586
|
function renderStepperBar(host, opts) {
|
|
1705
2587
|
const { items } = opts;
|
|
1706
2588
|
const close = opts.onClose === void 0 ? null : el("button", {
|
|
1707
2589
|
className: "stepbar-nav stepbar-close",
|
|
1708
|
-
text: icon.close,
|
|
1709
2590
|
tip: opts.closeTip ?? "Close",
|
|
1710
2591
|
onClick: opts.onClose
|
|
1711
|
-
});
|
|
2592
|
+
}, iconNode("close", STEP_ICON));
|
|
1712
2593
|
const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
|
|
1713
2594
|
if (items.length === 0) {
|
|
1714
2595
|
if (opts.empty === void 0) {
|
|
@@ -1732,17 +2613,15 @@ function renderStepperBar(host, opts) {
|
|
|
1732
2613
|
const nav = items.length > 1 ? [
|
|
1733
2614
|
el("button", {
|
|
1734
2615
|
className: "stepbar-nav",
|
|
1735
|
-
text: icon.back,
|
|
1736
2616
|
tip: opts.tips?.prev ?? "Previous",
|
|
1737
2617
|
onClick: () => step(-1)
|
|
1738
|
-
}),
|
|
2618
|
+
}, iconNode("back", STEP_ICON)),
|
|
1739
2619
|
el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
|
|
1740
2620
|
el("button", {
|
|
1741
2621
|
className: "stepbar-nav",
|
|
1742
|
-
text: icon.forward,
|
|
1743
2622
|
tip: opts.tips?.next ?? "Next",
|
|
1744
2623
|
onClick: () => step(1)
|
|
1745
|
-
})
|
|
2624
|
+
}, iconNode("forward", STEP_ICON))
|
|
1746
2625
|
] : [];
|
|
1747
2626
|
host.replaceChildren(
|
|
1748
2627
|
el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
|
|
@@ -1751,7 +2630,7 @@ function renderStepperBar(host, opts) {
|
|
|
1751
2630
|
"button",
|
|
1752
2631
|
{ className: "stepbar-cur", tip: opts.tips?.go ?? "Go to this", onClick: () => opts.onGo(index) },
|
|
1753
2632
|
item.kind === void 0 ? null : el("span", { className: `stepbar-cat${item.kindClass ? ` ${item.kindClass}` : ""}`, text: item.kind }),
|
|
1754
|
-
item.where === void 0 ? null : el("span", { className: "stepbar-where", text: item.where }),
|
|
2633
|
+
item.where === void 0 ? null : typeof item.where === "string" ? el("span", { className: "stepbar-where", text: item.where }) : el("span", "stepbar-where", item.where),
|
|
1755
2634
|
el("span", { className: "stepbar-msg", text: item.text })
|
|
1756
2635
|
),
|
|
1757
2636
|
...actions,
|
|
@@ -1759,19 +2638,383 @@ function renderStepperBar(host, opts) {
|
|
|
1759
2638
|
);
|
|
1760
2639
|
}
|
|
1761
2640
|
|
|
2641
|
+
// src/updater-view.ts
|
|
2642
|
+
var liveProgress = null;
|
|
2643
|
+
var mb = (n) => (n / (1024 * 1024)).toFixed(1);
|
|
2644
|
+
function feedUpdaterDownloadProgress(p) {
|
|
2645
|
+
if (!liveProgress) return;
|
|
2646
|
+
const pct = Math.max(0, Math.min(100, p.percent));
|
|
2647
|
+
liveProgress.bar.style.width = `${pct}%`;
|
|
2648
|
+
liveProgress.label.textContent = `${pct.toFixed(0)}%, ${mb(p.transferred)} of ${mb(p.total)} MB (${mb(p.bytesPerSecond)} MB/s)`;
|
|
2649
|
+
}
|
|
2650
|
+
function showUpdaterDialog(opts) {
|
|
2651
|
+
return new Promise((resolve) => {
|
|
2652
|
+
const defaultId = opts.defaultId ?? 0;
|
|
2653
|
+
const cancelId = opts.cancelId ?? opts.buttons.length - 1;
|
|
2654
|
+
let done = false;
|
|
2655
|
+
const finish = (idx) => {
|
|
2656
|
+
if (done) return;
|
|
2657
|
+
done = true;
|
|
2658
|
+
liveProgress = null;
|
|
2659
|
+
resolve(idx);
|
|
2660
|
+
frame.close();
|
|
2661
|
+
};
|
|
2662
|
+
const frame = dialogFrame({
|
|
2663
|
+
title: opts.message,
|
|
2664
|
+
className: "updater-dialog",
|
|
2665
|
+
// Esc is the cancel button, not a further answer: main is waiting on an index.
|
|
2666
|
+
onClose: () => finish(cancelId)
|
|
2667
|
+
});
|
|
2668
|
+
if (opts.detail) frame.body.append(el("p", "updater-detail", opts.detail));
|
|
2669
|
+
if (opts.progress) {
|
|
2670
|
+
const track = el("div", "updater-progress-track");
|
|
2671
|
+
const bar = el("div", "updater-progress-bar");
|
|
2672
|
+
track.append(bar);
|
|
2673
|
+
const label2 = el("p", "updater-progress-label", "Starting download\u2026");
|
|
2674
|
+
frame.body.append(track, label2);
|
|
2675
|
+
liveProgress = { bar, label: label2 };
|
|
2676
|
+
}
|
|
2677
|
+
if (opts.links?.length) {
|
|
2678
|
+
const row = el("p", "updater-links");
|
|
2679
|
+
for (const link of opts.links) {
|
|
2680
|
+
const a = el("a", void 0, link.label);
|
|
2681
|
+
a.href = link.url;
|
|
2682
|
+
a.addEventListener("click", (e) => {
|
|
2683
|
+
e.preventDefault();
|
|
2684
|
+
opts.openExternal?.(link.url);
|
|
2685
|
+
});
|
|
2686
|
+
row.append(a);
|
|
2687
|
+
}
|
|
2688
|
+
frame.body.append(row);
|
|
2689
|
+
}
|
|
2690
|
+
opts.buttons.forEach((label2, i) => {
|
|
2691
|
+
const b = el("button", i === defaultId ? "btn primary" : "btn", label2);
|
|
2692
|
+
b.type = "button";
|
|
2693
|
+
b.addEventListener("click", () => finish(i));
|
|
2694
|
+
frame.actions.append(b);
|
|
2695
|
+
if (i === defaultId) queueMicrotask(() => b.focus());
|
|
2696
|
+
});
|
|
2697
|
+
frame.open();
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// src/welcome.ts
|
|
2702
|
+
function mountWelcome(host, opts) {
|
|
2703
|
+
const caption = (text) => el("h2", "welcome-caption", text);
|
|
2704
|
+
const group = (label2, ...body) => el("section", "welcome-group", caption(label2), ...body);
|
|
2705
|
+
const actions = el("div", "welcome-actions");
|
|
2706
|
+
for (const a of opts.actions) {
|
|
2707
|
+
const b = el("button", { className: `btn${a.primary ? " primary" : ""}`, text: a.label, onClick: a.onClick });
|
|
2708
|
+
b.type = "button";
|
|
2709
|
+
actions.append(b);
|
|
2710
|
+
}
|
|
2711
|
+
const groups = (opts.groups ?? []).map((g) => group(
|
|
2712
|
+
g.caption,
|
|
2713
|
+
g.note !== void 0 ? el("p", "welcome-note", g.note) : null,
|
|
2714
|
+
el("div", "welcome-list", ...g.items.map((item) => {
|
|
2715
|
+
const b = el(
|
|
2716
|
+
"button",
|
|
2717
|
+
{ className: "welcome-item", onClick: item.onOpen },
|
|
2718
|
+
el("span", "welcome-item-name", item.name),
|
|
2719
|
+
item.hint !== void 0 ? el("span", "welcome-item-hint", item.hint) : null
|
|
2720
|
+
);
|
|
2721
|
+
b.type = "button";
|
|
2722
|
+
return b;
|
|
2723
|
+
}))
|
|
2724
|
+
));
|
|
2725
|
+
const recentsHost = el("div", "welcome-recents-slot");
|
|
2726
|
+
const drawRecents = (recents) => {
|
|
2727
|
+
const shown = recents.slice(0, opts.maxRecents ?? 5);
|
|
2728
|
+
recentsHost.replaceChildren();
|
|
2729
|
+
if (shown.length === 0) return;
|
|
2730
|
+
recentsHost.append(group(
|
|
2731
|
+
opts.recentsCaption ?? "Recent",
|
|
2732
|
+
el("div", "welcome-recents", ...shown.map((r) => {
|
|
2733
|
+
const b = el(
|
|
2734
|
+
"button",
|
|
2735
|
+
{ className: "welcome-recent", onClick: r.onOpen },
|
|
2736
|
+
el("span", "welcome-recent-name", r.name),
|
|
2737
|
+
el("span", "welcome-recent-path", r.path)
|
|
2738
|
+
);
|
|
2739
|
+
b.type = "button";
|
|
2740
|
+
return b;
|
|
2741
|
+
}))
|
|
2742
|
+
));
|
|
2743
|
+
};
|
|
2744
|
+
drawRecents(opts.recents);
|
|
2745
|
+
const error = el("p", "welcome-error");
|
|
2746
|
+
const setError = (text) => {
|
|
2747
|
+
error.textContent = text ?? "";
|
|
2748
|
+
error.hidden = text === void 0 || text === "";
|
|
2749
|
+
};
|
|
2750
|
+
setError(opts.error);
|
|
2751
|
+
const root = el("div", "welcome", el(
|
|
2752
|
+
"div",
|
|
2753
|
+
"welcome-card",
|
|
2754
|
+
el("h1", "welcome-title", opts.title),
|
|
2755
|
+
el("p", "welcome-sub", opts.sub),
|
|
2756
|
+
actions,
|
|
2757
|
+
opts.tourLine ? el("div", "welcome-line", opts.tourLine) : null,
|
|
2758
|
+
...groups,
|
|
2759
|
+
recentsHost,
|
|
2760
|
+
error
|
|
2761
|
+
));
|
|
2762
|
+
host.replaceChildren(root);
|
|
2763
|
+
return { el: root, setRecents: drawRecents, setError };
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/link-status.ts
|
|
2767
|
+
function linkStatusClass(s) {
|
|
2768
|
+
if (s.state === "off" || s.state === "error") return "off";
|
|
2769
|
+
if (s.state === "listening") return "listening";
|
|
2770
|
+
return s.build === "stale" ? "stale" : "live";
|
|
2771
|
+
}
|
|
2772
|
+
function linkStatusTip(s) {
|
|
2773
|
+
switch (s.state) {
|
|
2774
|
+
case "off":
|
|
2775
|
+
return "Live link is off. Click to start listening.";
|
|
2776
|
+
case "error":
|
|
2777
|
+
return `Live link failed (${s.message}). Click to retry.`;
|
|
2778
|
+
case "listening":
|
|
2779
|
+
return "Live link is listening for a game. Click to stop.";
|
|
2780
|
+
case "connected": {
|
|
2781
|
+
const who = s.project !== void 0 ? ` to ${s.project}` : "";
|
|
2782
|
+
const build = s.build === "stale" ? " on a different build, so save or rebuild to re-sync" : s.build === "match" ? " and in sync" : "";
|
|
2783
|
+
const note = s.note ? ` ${s.note}` : "";
|
|
2784
|
+
return `Live link is connected${who}${build}.${note} Click to stop.`;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
function linkAddress(s) {
|
|
2789
|
+
if (s.state !== "listening" && s.state !== "connected") return void 0;
|
|
2790
|
+
return s.address ?? `ws://127.0.0.1:${s.port}`;
|
|
2791
|
+
}
|
|
2792
|
+
function mountLinkStatus(host, opts) {
|
|
2793
|
+
const wrap = el("div", "linkstatus");
|
|
2794
|
+
wrap.hidden = true;
|
|
2795
|
+
const url = el("button", "linkstatus-url");
|
|
2796
|
+
url.type = "button";
|
|
2797
|
+
url.hidden = true;
|
|
2798
|
+
const toggle = el("button", "linkstatus-toggle off");
|
|
2799
|
+
toggle.type = "button";
|
|
2800
|
+
toggle.append(iconNode("connect"));
|
|
2801
|
+
if (opts.label !== void 0) wrap.append(el("span", "linkstatus-word", opts.label));
|
|
2802
|
+
wrap.append(...opts.lead ?? [], url, toggle);
|
|
2803
|
+
host.append(wrap);
|
|
2804
|
+
let status = { state: "off" };
|
|
2805
|
+
const render = () => {
|
|
2806
|
+
toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;
|
|
2807
|
+
const tip2 = linkStatusTip(status);
|
|
2808
|
+
toggle.dataset["tip"] = tip2;
|
|
2809
|
+
toggle.setAttribute("aria-label", tip2);
|
|
2810
|
+
const addr = linkAddress(status);
|
|
2811
|
+
if (addr !== void 0) {
|
|
2812
|
+
url.textContent = addr;
|
|
2813
|
+
url.dataset["tip"] = "Click to copy the live link address";
|
|
2814
|
+
url.hidden = false;
|
|
2815
|
+
} else url.hidden = true;
|
|
2816
|
+
};
|
|
2817
|
+
const apply = (s) => {
|
|
2818
|
+
status = s;
|
|
2819
|
+
render();
|
|
2820
|
+
};
|
|
2821
|
+
render();
|
|
2822
|
+
toggle.addEventListener("click", () => {
|
|
2823
|
+
const out = opts.onToggle(status);
|
|
2824
|
+
void Promise.resolve(out).then((next) => {
|
|
2825
|
+
if (next) apply(next);
|
|
2826
|
+
});
|
|
2827
|
+
});
|
|
2828
|
+
url.addEventListener("click", () => {
|
|
2829
|
+
const addr = url.textContent ?? "";
|
|
2830
|
+
if (!addr) return;
|
|
2831
|
+
void copyWithFeedback(url, addr);
|
|
2832
|
+
opts.onCopy?.(addr);
|
|
2833
|
+
});
|
|
2834
|
+
return {
|
|
2835
|
+
el: wrap,
|
|
2836
|
+
apply,
|
|
2837
|
+
status: () => status,
|
|
2838
|
+
setVisible(on) {
|
|
2839
|
+
wrap.hidden = !on;
|
|
2840
|
+
},
|
|
2841
|
+
toggle() {
|
|
2842
|
+
toggle.click();
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/property-list.ts
|
|
2848
|
+
var PROPERTY_TYPES = [
|
|
2849
|
+
["number", "Number"],
|
|
2850
|
+
["boolean", "True / False"],
|
|
2851
|
+
["string", "Text"],
|
|
2852
|
+
["enum", "List"],
|
|
2853
|
+
["flags", "Flags"],
|
|
2854
|
+
["quality", "Quality"]
|
|
2855
|
+
];
|
|
2856
|
+
var option = (value, label2, selected) => {
|
|
2857
|
+
const o = el("option", void 0, label2);
|
|
2858
|
+
o.value = value;
|
|
2859
|
+
o.selected = selected;
|
|
2860
|
+
return o;
|
|
2861
|
+
};
|
|
2862
|
+
var NONE = "(none)";
|
|
2863
|
+
function defaultControl(p, onChange, opts = {}) {
|
|
2864
|
+
const typed = opts.typed === true;
|
|
2865
|
+
const write = (raw) => {
|
|
2866
|
+
if (raw === "") {
|
|
2867
|
+
if (typed) delete p.default;
|
|
2868
|
+
else p.default = "";
|
|
2869
|
+
} else if (typed && p.type === "boolean") p.default = raw === "true";
|
|
2870
|
+
else if (typed && p.type === "number") p.default = Number(raw);
|
|
2871
|
+
else p.default = raw;
|
|
2872
|
+
onChange?.();
|
|
2873
|
+
};
|
|
2874
|
+
const current3 = p.default === void 0 || p.default === null ? "" : String(p.default);
|
|
2875
|
+
if (p.type === "boolean") {
|
|
2876
|
+
const sel = el("select", "field set-default");
|
|
2877
|
+
sel.append(option("", NONE, current3 === ""), option("true", "True", current3 === "true"), option("false", "False", current3 === "false"));
|
|
2878
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2879
|
+
return sel;
|
|
2880
|
+
}
|
|
2881
|
+
if (p.type === "enum") {
|
|
2882
|
+
const sel = el("select", "field set-default");
|
|
2883
|
+
sel.append(option("", NONE, current3 === ""));
|
|
2884
|
+
for (const v of p.values ?? []) sel.append(option(v, v, current3 === v));
|
|
2885
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2886
|
+
return sel;
|
|
2887
|
+
}
|
|
2888
|
+
if (p.type === "quality") {
|
|
2889
|
+
const sel = el("select", "field set-default");
|
|
2890
|
+
sel.append(option("", "(first stage)", current3 === ""));
|
|
2891
|
+
for (const v of p.stages ?? []) sel.append(option(v, v, current3 === v));
|
|
2892
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2893
|
+
return sel;
|
|
2894
|
+
}
|
|
2895
|
+
if (p.type === "flags") {
|
|
2896
|
+
const s = el("span", "set-flagnote", "Starts empty");
|
|
2897
|
+
s.dataset["tip"] = "A flags property starts with none set.";
|
|
2898
|
+
return s;
|
|
2899
|
+
}
|
|
2900
|
+
const input = el("input", "field set-default");
|
|
2901
|
+
input.type = p.type === "number" ? "number" : "text";
|
|
2902
|
+
input.placeholder = "Default";
|
|
2903
|
+
input.value = current3;
|
|
2904
|
+
input.addEventListener("input", () => write(input.value));
|
|
2905
|
+
return input;
|
|
2906
|
+
}
|
|
2907
|
+
function mountPropertyList(host, decls, opts = {}) {
|
|
2908
|
+
const changed = () => opts.onChange?.();
|
|
2909
|
+
const guard = dupGuard();
|
|
2910
|
+
const types = opts.types ?? PROPERTY_TYPES;
|
|
2911
|
+
const list = el("div", "set-list");
|
|
2912
|
+
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
2913
|
+
const row = (p, i) => {
|
|
2914
|
+
const name = el("input", "field set-name");
|
|
2915
|
+
name.type = "text";
|
|
2916
|
+
name.placeholder = "Property name";
|
|
2917
|
+
name.value = p.name;
|
|
2918
|
+
name.spellcheck = false;
|
|
2919
|
+
name.dataset["tip"] = PROPERTY_NAME_HINT;
|
|
2920
|
+
bindPropertyName(name, (v) => {
|
|
2921
|
+
p.name = v;
|
|
2922
|
+
changed();
|
|
2923
|
+
}, { hint: PROPERTY_NAME_HINT });
|
|
2924
|
+
guard.track(name);
|
|
2925
|
+
const type = el("select", "field set-type");
|
|
2926
|
+
for (const [v, label2] of types) type.append(option(v, label2, v === p.type));
|
|
2927
|
+
type.addEventListener("change", () => {
|
|
2928
|
+
p.type = type.value;
|
|
2929
|
+
if (opts.typed) delete p.default;
|
|
2930
|
+
else p.default = "";
|
|
2931
|
+
if (p.type === "enum" || p.type === "flags") p.values ??= [];
|
|
2932
|
+
else delete p.values;
|
|
2933
|
+
if (p.type === "quality") p.stages ??= [];
|
|
2934
|
+
else delete p.stages;
|
|
2935
|
+
render();
|
|
2936
|
+
changed();
|
|
2937
|
+
});
|
|
2938
|
+
let def = defaultControl(p, changed, opts);
|
|
2939
|
+
const refreshDefault = () => {
|
|
2940
|
+
const fresh = defaultControl(p, changed, opts);
|
|
2941
|
+
def.replaceWith(fresh);
|
|
2942
|
+
def = fresh;
|
|
2943
|
+
changed();
|
|
2944
|
+
};
|
|
2945
|
+
const up = iconBtn("up", "Move up", () => {
|
|
2946
|
+
if (moveItem(decls, i, -1)) {
|
|
2947
|
+
render();
|
|
2948
|
+
changed();
|
|
2949
|
+
}
|
|
2950
|
+
}, i === 0);
|
|
2951
|
+
const down = iconBtn("down", "Move down", () => {
|
|
2952
|
+
if (moveItem(decls, i, 1)) {
|
|
2953
|
+
render();
|
|
2954
|
+
changed();
|
|
2955
|
+
}
|
|
2956
|
+
}, i === decls.length - 1);
|
|
2957
|
+
const del = iconBtn("close", "Delete property", () => {
|
|
2958
|
+
decls.splice(i, 1);
|
|
2959
|
+
render();
|
|
2960
|
+
changed();
|
|
2961
|
+
}, false, true);
|
|
2962
|
+
const details = keep(opts.extraDetails?.(p, i));
|
|
2963
|
+
if (p.type === "enum" || p.type === "flags") details.push(labelled("Values", tagChips(p, refreshDefault)));
|
|
2964
|
+
if (p.type === "quality") details.push(labelled("Stages (in order)", stageChips(p, refreshDefault)));
|
|
2965
|
+
if (opts.purpose !== false) {
|
|
2966
|
+
const purpose = el("input", "field set-purpose");
|
|
2967
|
+
purpose.type = "text";
|
|
2968
|
+
purpose.placeholder = "What this property is for";
|
|
2969
|
+
purpose.value = p.purpose ?? "";
|
|
2970
|
+
purpose.addEventListener("input", () => {
|
|
2971
|
+
if (purpose.value.trim()) p.purpose = purpose.value;
|
|
2972
|
+
else delete p.purpose;
|
|
2973
|
+
changed();
|
|
2974
|
+
});
|
|
2975
|
+
details.push(labelled("Purpose", purpose));
|
|
2976
|
+
}
|
|
2977
|
+
return expandableRow({ line: [name, type, def, ...keep(opts.extraLine?.(p, i)), up, down, del], details, name: p.name });
|
|
2978
|
+
};
|
|
2979
|
+
const render = () => {
|
|
2980
|
+
guard.reset();
|
|
2981
|
+
list.replaceChildren();
|
|
2982
|
+
if (decls.length === 0) list.append(el("p", "empty", opts.emptyText ?? "No properties yet."));
|
|
2983
|
+
else decls.forEach((p, i) => list.append(row(p, i)));
|
|
2984
|
+
guard.check();
|
|
2985
|
+
};
|
|
2986
|
+
render();
|
|
2987
|
+
const add = el("button", "btn set-add", opts.addLabel ?? "+ Add property");
|
|
2988
|
+
add.type = "button";
|
|
2989
|
+
add.addEventListener("click", () => {
|
|
2990
|
+
decls.push(opts.newDecl ? opts.newDecl() : { name: "", type: types[0]?.[0] ?? "string", ...opts.typed ? {} : { default: "" } });
|
|
2991
|
+
render();
|
|
2992
|
+
changed();
|
|
2993
|
+
focusNewRow(list);
|
|
2994
|
+
});
|
|
2995
|
+
host.append(list, add);
|
|
2996
|
+
return {
|
|
2997
|
+
firstDuplicate: () => guard.firstDuplicate(),
|
|
2998
|
+
firstIllegalName: () => firstIllegalPropertyName(host),
|
|
2999
|
+
// The Save gate takes both faults; the field's own rollover says which.
|
|
3000
|
+
firstInvalid: () => guard.firstDuplicate() ?? firstIllegalPropertyName(host),
|
|
3001
|
+
render
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
|
|
1762
3005
|
// src/nav-history.ts
|
|
1763
3006
|
function createNavHistory(opts) {
|
|
1764
3007
|
const cap = opts.cap ?? 100;
|
|
1765
3008
|
const usable = opts.usable ?? (() => true);
|
|
1766
3009
|
let past = [];
|
|
1767
3010
|
let ahead = [];
|
|
1768
|
-
const step = (from, to,
|
|
3011
|
+
const step = (from, to, current3) => {
|
|
1769
3012
|
for (; ; ) {
|
|
1770
3013
|
const p = from.pop();
|
|
1771
3014
|
if (p === void 0) return void 0;
|
|
1772
3015
|
if (!usable(p)) continue;
|
|
1773
|
-
if (opts.same(p,
|
|
1774
|
-
to.push(
|
|
3016
|
+
if (opts.same(p, current3)) continue;
|
|
3017
|
+
to.push(current3);
|
|
1775
3018
|
return p;
|
|
1776
3019
|
}
|
|
1777
3020
|
};
|
|
@@ -1783,11 +3026,11 @@ function createNavHistory(opts) {
|
|
|
1783
3026
|
else past.push(place2);
|
|
1784
3027
|
if (past.length > cap) past = past.slice(past.length - cap);
|
|
1785
3028
|
},
|
|
1786
|
-
back(
|
|
1787
|
-
return step(past, ahead,
|
|
3029
|
+
back(current3) {
|
|
3030
|
+
return step(past, ahead, current3);
|
|
1788
3031
|
},
|
|
1789
|
-
forward(
|
|
1790
|
-
return step(ahead, past,
|
|
3032
|
+
forward(current3) {
|
|
3033
|
+
return step(ahead, past, current3);
|
|
1791
3034
|
},
|
|
1792
3035
|
canBack() {
|
|
1793
3036
|
return past.length > 0;
|
|
@@ -1798,18 +3041,18 @@ function createNavHistory(opts) {
|
|
|
1798
3041
|
};
|
|
1799
3042
|
}
|
|
1800
3043
|
function historyNav(onBack, onForward) {
|
|
1801
|
-
const btn = (
|
|
3044
|
+
const btn = (name, tip2, onClick) => {
|
|
1802
3045
|
const b = document.createElement("button");
|
|
1803
3046
|
b.type = "button";
|
|
1804
3047
|
b.className = "shell-histnav-btn";
|
|
1805
|
-
b.
|
|
3048
|
+
b.append(iconNode(name));
|
|
1806
3049
|
b.dataset["tip"] = tip2;
|
|
1807
3050
|
b.setAttribute("aria-label", tip2);
|
|
1808
3051
|
b.addEventListener("click", onClick);
|
|
1809
3052
|
return b;
|
|
1810
3053
|
};
|
|
1811
|
-
const back = btn("
|
|
1812
|
-
const forward = btn("
|
|
3054
|
+
const back = btn("arrowLeft", "Back", onBack);
|
|
3055
|
+
const forward = btn("arrowRight", "Forward", onForward);
|
|
1813
3056
|
const el2 = document.createElement("span");
|
|
1814
3057
|
el2.className = "shell-histnav";
|
|
1815
3058
|
el2.append(back, forward);
|
|
@@ -1823,67 +3066,117 @@ function historyNav(onBack, onForward) {
|
|
|
1823
3066
|
}
|
|
1824
3067
|
|
|
1825
3068
|
exports.EDIT_MENU = EDIT_MENU;
|
|
3069
|
+
exports.FILE_MENU = FILE_MENU;
|
|
1826
3070
|
exports.GO_MENU = GO_MENU;
|
|
3071
|
+
exports.ICON_NAMES = ICON_NAMES;
|
|
3072
|
+
exports.ICON_SIZE = ICON_SIZE;
|
|
1827
3073
|
exports.JOB_PROGRESS = JOB_PROGRESS;
|
|
1828
3074
|
exports.PALETTE = PALETTE;
|
|
1829
3075
|
exports.PALETTE_SIZE = PALETTE_SIZE;
|
|
1830
3076
|
exports.PANE_MENU = PANE_MENU;
|
|
3077
|
+
exports.PLAY_MENU = PLAY_MENU;
|
|
1831
3078
|
exports.PROPERTY_NAME_HINT = PROPERTY_NAME_HINT;
|
|
3079
|
+
exports.PROPERTY_TYPES = PROPERTY_TYPES;
|
|
3080
|
+
exports.PUBLISH_MENU = PUBLISH_MENU;
|
|
1832
3081
|
exports.RESERVED_PROPERTY_NAMES = RESERVED_PROPERTY_NAMES;
|
|
3082
|
+
exports.REVIEW_MENU = REVIEW_MENU;
|
|
3083
|
+
exports.VIEW_MENU = VIEW_MENU;
|
|
1833
3084
|
exports.askIdentity = askIdentity;
|
|
1834
3085
|
exports.bindPropertyName = bindPropertyName;
|
|
1835
3086
|
exports.bindPropertyRef = bindPropertyRef;
|
|
3087
|
+
exports.breadcrumb = breadcrumb;
|
|
1836
3088
|
exports.closeAnchoredPanel = closeAnchoredPanel;
|
|
1837
3089
|
exports.closeWithExit = closeWithExit;
|
|
1838
3090
|
exports.colourFor = colourFor;
|
|
1839
3091
|
exports.colourIndex = colourIndex;
|
|
1840
3092
|
exports.confirmDialog = confirmDialog;
|
|
3093
|
+
exports.copyWithFeedback = copyWithFeedback;
|
|
3094
|
+
exports.createFloating = createFloating;
|
|
1841
3095
|
exports.createNavHistory = createNavHistory;
|
|
1842
3096
|
exports.createSaveController = createSaveController;
|
|
3097
|
+
exports.debounce = debounce;
|
|
3098
|
+
exports.defaultControl = defaultControl;
|
|
3099
|
+
exports.defaultProblemCopy = defaultProblemCopy;
|
|
3100
|
+
exports.describeProblem = describeProblem;
|
|
3101
|
+
exports.dialogFrame = dialogFrame;
|
|
1843
3102
|
exports.dismissToast = dismissToast;
|
|
1844
3103
|
exports.dupGuard = dupGuard;
|
|
1845
3104
|
exports.el = el;
|
|
1846
3105
|
exports.expandableRow = expandableRow;
|
|
3106
|
+
exports.feedUpdaterDownloadProgress = feedUpdaterDownloadProgress;
|
|
1847
3107
|
exports.firstIllegalPropertyName = firstIllegalPropertyName;
|
|
1848
3108
|
exports.focusNewRow = focusNewRow;
|
|
1849
3109
|
exports.foldVc = foldVc;
|
|
3110
|
+
exports.followButton = followButton;
|
|
3111
|
+
exports.formatCount = formatCount;
|
|
1850
3112
|
exports.gameIdify = gameIdify;
|
|
1851
3113
|
exports.hideTip = hideTip;
|
|
3114
|
+
exports.hintBar = hintBar;
|
|
1852
3115
|
exports.historyNav = historyNav;
|
|
1853
3116
|
exports.icon = icon;
|
|
1854
3117
|
exports.iconBtn = iconBtn;
|
|
3118
|
+
exports.iconHtml = iconHtml;
|
|
3119
|
+
exports.iconNameOfGlyph = iconNameOfGlyph;
|
|
3120
|
+
exports.iconNode = iconNode;
|
|
1855
3121
|
exports.iconSvg = iconSvg;
|
|
1856
3122
|
exports.initTooltips = initTooltips;
|
|
1857
3123
|
exports.isCaseOnlyPropertyName = isCaseOnlyPropertyName;
|
|
3124
|
+
exports.isEditableTarget = isEditableTarget;
|
|
3125
|
+
exports.isIconName = isIconName;
|
|
3126
|
+
exports.isKeyCombo = isKeyCombo;
|
|
1858
3127
|
exports.isValidGameId = isValidGameId;
|
|
1859
3128
|
exports.isValidPropertyName = isValidPropertyName;
|
|
3129
|
+
exports.keyHint = keyHint;
|
|
3130
|
+
exports.keyLabel = keyLabel;
|
|
3131
|
+
exports.keyLegends = keyLegends;
|
|
3132
|
+
exports.keyPlatform = keyPlatform;
|
|
1860
3133
|
exports.labelled = labelled;
|
|
3134
|
+
exports.linkAddress = linkAddress;
|
|
3135
|
+
exports.linkStatusClass = linkStatusClass;
|
|
3136
|
+
exports.linkStatusTip = linkStatusTip;
|
|
1861
3137
|
exports.lockControls = lockControls;
|
|
1862
3138
|
exports.lockNotice = lockNotice;
|
|
3139
|
+
exports.metaLine = metaLine;
|
|
1863
3140
|
exports.mountJobProgress = mountJobProgress;
|
|
3141
|
+
exports.mountLinkStatus = mountLinkStatus;
|
|
1864
3142
|
exports.mountPaneShell = mountPaneShell;
|
|
3143
|
+
exports.mountPropertyList = mountPropertyList;
|
|
1865
3144
|
exports.mountSettingsDialog = mountSettingsDialog;
|
|
3145
|
+
exports.mountWelcome = mountWelcome;
|
|
1866
3146
|
exports.moveItem = moveItem;
|
|
1867
3147
|
exports.openAnchoredPanel = openAnchoredPanel;
|
|
1868
3148
|
exports.openComments = openComments;
|
|
3149
|
+
exports.openContextMenu = openContextMenu;
|
|
1869
3150
|
exports.openGameIdEditor = openGameIdEditor;
|
|
1870
3151
|
exports.openNotesEditor = openNotesEditor;
|
|
3152
|
+
exports.openPopover = openPopover;
|
|
1871
3153
|
exports.paintVcBadges = paintVcBadges;
|
|
1872
3154
|
exports.pinButton = pinButton;
|
|
1873
3155
|
exports.placeAnchored = placeAnchored;
|
|
3156
|
+
exports.plural = plural;
|
|
3157
|
+
exports.problemLine = problemLine;
|
|
3158
|
+
exports.problemName = problemName;
|
|
1874
3159
|
exports.propertyNameProblem = propertyNameProblem;
|
|
1875
3160
|
exports.propertyNameify = propertyNameify;
|
|
1876
3161
|
exports.propertyRefProblem = propertyRefProblem;
|
|
3162
|
+
exports.relativeTime = relativeTime;
|
|
1877
3163
|
exports.renderStepperBar = renderStepperBar;
|
|
1878
3164
|
exports.revalidatePropertyRefs = revalidatePropertyRefs;
|
|
1879
3165
|
exports.revealRow = revealRow;
|
|
3166
|
+
exports.revealRowWhenReady = revealRowWhenReady;
|
|
1880
3167
|
exports.saveIndicator = saveIndicator;
|
|
3168
|
+
exports.setKeyPlatform = setKeyPlatform;
|
|
1881
3169
|
exports.showAbout = showAbout;
|
|
3170
|
+
exports.showUpdaterDialog = showUpdaterDialog;
|
|
3171
|
+
exports.stageChips = stageChips;
|
|
1882
3172
|
exports.staleBar = staleBar;
|
|
1883
3173
|
exports.tagChips = tagChips;
|
|
1884
3174
|
exports.tipAt = tipAt;
|
|
1885
3175
|
exports.tipBold = tipBold;
|
|
3176
|
+
exports.tipWithKey = tipWithKey;
|
|
1886
3177
|
exports.toast = toast;
|
|
3178
|
+
exports.toolWindowHead = toolWindowHead;
|
|
1887
3179
|
exports.vcBadgeFor = vcBadgeFor;
|
|
3180
|
+
exports.wireReorder = wireReorder;
|
|
1888
3181
|
//# sourceMappingURL=index.cjs.map
|
|
1889
3182
|
//# sourceMappingURL=index.cjs.map
|