@wildwinter/app-shell 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES.md +62 -0
- package/README.md +21 -0
- package/dist/about.css +13 -34
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +5 -5
- package/dist/anchored.css.map +1 -1
- package/dist/app-store.cjs +15 -0
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +25 -1
- package/dist/app-store.d.ts +25 -1
- package/dist/app-store.js +14 -1
- package/dist/app-store.js.map +1 -1
- package/dist/chunk-2ECJUTSX.js +97 -0
- package/dist/chunk-2ECJUTSX.js.map +1 -0
- package/dist/chunk-2NMVIM54.js +55 -0
- package/dist/chunk-2NMVIM54.js.map +1 -0
- package/dist/chunk-C6UVQYKI.js +70 -0
- package/dist/chunk-C6UVQYKI.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-K2AOLQW4.js +82 -0
- package/dist/chunk-K2AOLQW4.js.map +1 -0
- package/dist/chunk-KGAMP5LW.js +87 -0
- package/dist/chunk-KGAMP5LW.js.map +1 -0
- package/dist/chunk-L5HOBEKL.js +534 -0
- package/dist/chunk-L5HOBEKL.js.map +1 -0
- package/dist/chunk-P4G4ERG6.js +60 -0
- package/dist/chunk-P4G4ERG6.js.map +1 -0
- package/dist/chunk-P6WLAEKZ.js +71 -0
- package/dist/chunk-P6WLAEKZ.js.map +1 -0
- package/dist/chunk-S4KWQLYN.js +66 -0
- package/dist/chunk-S4KWQLYN.js.map +1 -0
- package/dist/chunk-XZYZXY7I.js +554 -0
- package/dist/chunk-XZYZXY7I.js.map +1 -0
- package/dist/comments.css +5 -3
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +1 -54
- package/dist/confirm.css.map +1 -1
- package/dist/context-menu.cjs +250 -0
- package/dist/context-menu.cjs.map +1 -0
- package/dist/context-menu.css +64 -0
- package/dist/context-menu.css.map +1 -0
- package/dist/context-menu.d.cts +25 -0
- package/dist/context-menu.d.ts +25 -0
- package/dist/context-menu.js +5 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/controls.css +155 -0
- package/dist/controls.css.map +1 -0
- package/dist/dialog.cjs +222 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.css +55 -0
- package/dist/dialog.css.map +1 -0
- package/dist/dialog.d.cts +28 -0
- package/dist/dialog.d.ts +28 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/floating.cjs +83 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +25 -0
- package/dist/floating.d.ts +25 -0
- package/dist/floating.js +4 -0
- package/dist/floating.js.map +1 -0
- package/dist/identity.css +15 -50
- package/dist/identity.css.map +1 -1
- package/dist/index.cjs +1322 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -131
- package/dist/index.d.ts +349 -131
- package/dist/index.js +259 -808
- package/dist/index.js.map +1 -1
- package/dist/job.css +23 -0
- package/dist/job.css.map +1 -1
- package/dist/link-status.cjs +430 -0
- package/dist/link-status.cjs.map +1 -0
- package/dist/link-status.css +76 -0
- package/dist/link-status.css.map +1 -0
- package/dist/link-status.d.cts +53 -0
- package/dist/link-status.d.ts +53 -0
- package/dist/link-status.js +5 -0
- package/dist/link-status.js.map +1 -0
- package/dist/menu.cjs +62 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +88 -1
- package/dist/menu.d.ts +88 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +3 -46
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +16 -3
- package/dist/pane-shell.css.map +1 -1
- package/dist/property-list-D3VZCJzi.d.cts +162 -0
- package/dist/property-list-D3VZCJzi.d.ts +162 -0
- package/dist/property-list.cjs +784 -0
- package/dist/property-list.cjs.map +1 -0
- package/dist/property-list.d.cts +1 -0
- package/dist/property-list.d.ts +1 -0
- package/dist/property-list.js +4 -0
- package/dist/property-list.js.map +1 -0
- package/dist/settings.css +63 -8
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +3 -0
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +35 -0
- package/dist/stepper.css.map +1 -1
- package/dist/tokens.css +19 -0
- package/dist/tokens.css.map +1 -1
- package/dist/tool-window.cjs +63 -0
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.css +6 -3
- package/dist/tool-window.css.map +1 -1
- package/dist/tool-window.d.cts +67 -1
- package/dist/tool-window.d.ts +67 -1
- package/dist/tool-window.js +63 -1
- package/dist/tool-window.js.map +1 -1
- package/dist/updater-view.cjs +283 -0
- package/dist/updater-view.cjs.map +1 -0
- package/dist/updater-view.d.cts +23 -0
- package/dist/updater-view.d.ts +23 -0
- package/dist/updater-view.js +6 -0
- package/dist/updater-view.js.map +1 -0
- package/dist/updater.cjs +12 -16
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.css +44 -0
- package/dist/updater.css.map +1 -0
- package/dist/updater.js +12 -16
- package/dist/updater.js.map +1 -1
- package/dist/util.cjs +78 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +37 -0
- package/dist/util.d.ts +37 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/dist/vc.css +3 -2
- package/dist/vc.css.map +1 -1
- package/dist/welcome.cjs +215 -0
- package/dist/welcome.cjs.map +1 -0
- package/dist/welcome.css +112 -0
- package/dist/welcome.css.map +1 -0
- package/dist/welcome.d.cts +55 -0
- package/dist/welcome.d.ts +55 -0
- package/dist/welcome.js +4 -0
- package/dist/welcome.js.map +1 -0
- package/package.json +58 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
package/dist/index.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");
|
|
@@ -203,10 +404,10 @@ function tagChips(holder, onChange) {
|
|
|
203
404
|
input.spellcheck = false;
|
|
204
405
|
const makeChip = (v) => {
|
|
205
406
|
const chip = el("span", "shell-tag", v);
|
|
206
|
-
const x = el("button", "shell-tag-x", "
|
|
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
|
chip.remove();
|
|
@@ -235,6 +436,120 @@ 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 chip = el("span", "shell-tag", `${i + 1}. ${v}`);
|
|
459
|
+
chip.dataset["stage"] = v;
|
|
460
|
+
chip.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
|
+
chip.append(x);
|
|
494
|
+
wrap.insertBefore(chip, 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
|
+
}
|
|
238
553
|
|
|
239
554
|
// src/colour.ts
|
|
240
555
|
var PALETTE_SIZE = 12;
|
|
@@ -256,79 +571,6 @@ function colourFor(name) {
|
|
|
256
571
|
return PALETTE[colourIndex(name)];
|
|
257
572
|
}
|
|
258
573
|
|
|
259
|
-
// src/icons.ts
|
|
260
|
-
var icon = {
|
|
261
|
-
// --- dismissing and revealing ---------------------------------------------
|
|
262
|
-
/** Close a panel, a dialog, a popover. Patterpad's, twelve times over. */
|
|
263
|
-
close: "\u2715",
|
|
264
|
-
/** The overflow menu on a document or a row: "more about this". */
|
|
265
|
-
more: "\u22EF",
|
|
266
|
-
/** Collapsed disclosure, pointing at what would open. */
|
|
267
|
-
collapsed: "\u25B8",
|
|
268
|
-
/** Expanded disclosure. */
|
|
269
|
-
expanded: "\u25BE",
|
|
270
|
-
/** A dropdown's affordance, where it is not a real <select>. */
|
|
271
|
-
dropdown: "\u25BE",
|
|
272
|
-
// --- moving through things ------------------------------------------------
|
|
273
|
-
/** Back / previous, in a breadcrumb or a stepper. */
|
|
274
|
-
back: "\u2039",
|
|
275
|
-
/** Forward / next. */
|
|
276
|
-
forward: "\u203A",
|
|
277
|
-
/** Up one, in a list. */
|
|
278
|
-
up: "\u2191",
|
|
279
|
-
/** Down one. */
|
|
280
|
-
down: "\u2193",
|
|
281
|
-
// --- state ----------------------------------------------------------------
|
|
282
|
-
/** Done, applied, agreed. */
|
|
283
|
-
tick: "\u2713",
|
|
284
|
-
/** Somebody else holds this: the version-control lock. */
|
|
285
|
-
locked: "\u2298",
|
|
286
|
-
/** Read-only on disk with no other holder: the resting state of everything
|
|
287
|
-
* in a lock-based working copy that nobody has checked out yet. */
|
|
288
|
-
readOnly: "\u25CB",
|
|
289
|
-
/** YOU hold this one: checked out / opened by the current user, still yours to
|
|
290
|
-
* edit. Shares its glyph with `note`, which the table permits: an entry is a
|
|
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"
|
|
324
|
-
};
|
|
325
|
-
var iconSvg = {
|
|
326
|
-
/** A document with lines: notes exist on this thing. */
|
|
327
|
-
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>',
|
|
328
|
-
/** The same document, empty: nothing written here yet. */
|
|
329
|
-
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>'
|
|
330
|
-
};
|
|
331
|
-
|
|
332
574
|
// src/pane-shell.ts
|
|
333
575
|
var DEFAULT_MAX = (w) => Math.min(640, w - 420);
|
|
334
576
|
var openVar = { nav: "--nav-open-w", inspector: "--insp-open-w" };
|
|
@@ -341,6 +583,8 @@ function mountPaneShell(host, opts) {
|
|
|
341
583
|
inspector: offered("inspector") && (opts.initial?.open?.inspector ?? true)
|
|
342
584
|
};
|
|
343
585
|
const width = { ...opts.initial?.width ?? {} };
|
|
586
|
+
const held = { nav: false, inspector: false };
|
|
587
|
+
const showing = (side) => open[side] && !held[side];
|
|
344
588
|
const el2 = (tag, cls) => {
|
|
345
589
|
const n = document.createElement(tag);
|
|
346
590
|
if (cls) n.className = cls;
|
|
@@ -371,18 +615,28 @@ function mountPaneShell(host, opts) {
|
|
|
371
615
|
host.append(root);
|
|
372
616
|
if (!offered("nav")) navToggle.remove();
|
|
373
617
|
if (!offered("inspector")) inspToggle.remove();
|
|
374
|
-
|
|
375
|
-
const isOpen = open[side];
|
|
376
|
-
button.textContent = side === "nav" ? isOpen ? "\u2039" : "\u203A" : isOpen ? "\u203A" : "\u2039";
|
|
618
|
+
const defaultTip = (side, isOpen) => {
|
|
377
619
|
const noun = cfg[side].label ?? side;
|
|
378
620
|
const hint = cfg[side].shortcutHint ? ` (${cfg[side].shortcutHint})` : "";
|
|
379
|
-
|
|
380
|
-
|
|
621
|
+
return `${isOpen ? "Hide" : "Show"} ${noun}${hint}`;
|
|
622
|
+
};
|
|
623
|
+
const tipFor = opts.tipFor ?? defaultTip;
|
|
624
|
+
function applyToggleGlyph(side, button) {
|
|
625
|
+
const isOpen = open[side];
|
|
626
|
+
button.replaceChildren(iconNode(side === "nav" ? isOpen ? "back" : "forward" : isOpen ? "forward" : "back"));
|
|
627
|
+
const tip2 = tipFor(side, isOpen);
|
|
628
|
+
button.dataset["tip"] = tip2;
|
|
629
|
+
button.setAttribute("aria-label", tip2);
|
|
381
630
|
button.setAttribute("aria-pressed", String(isOpen));
|
|
382
631
|
}
|
|
632
|
+
ensureTooltipHost();
|
|
383
633
|
function apply() {
|
|
384
|
-
|
|
385
|
-
|
|
634
|
+
for (const side of ["nav", "inspector"]) {
|
|
635
|
+
const collapsed = !showing(side);
|
|
636
|
+
panes.classList.toggle(collapseClass[side], collapsed);
|
|
637
|
+
const also = opts.collapseAlso?.[side];
|
|
638
|
+
if (also) panes.classList.toggle(also, collapsed);
|
|
639
|
+
}
|
|
386
640
|
panes.style.setProperty(openVar.nav, width.nav !== void 0 ? `${width.nav}px` : cfg.nav.defaultWidth);
|
|
387
641
|
panes.style.setProperty(openVar.inspector, width.inspector !== void 0 ? `${width.inspector}px` : cfg.inspector.defaultWidth);
|
|
388
642
|
applyToggleGlyph("nav", navToggle);
|
|
@@ -401,6 +655,11 @@ function mountPaneShell(host, opts) {
|
|
|
401
655
|
function togglePane(side) {
|
|
402
656
|
if (offered(side)) setPaneOpen(side, !open[side]);
|
|
403
657
|
}
|
|
658
|
+
function holdClosed(side, next) {
|
|
659
|
+
if (held[side] === next) return;
|
|
660
|
+
held[side] = next;
|
|
661
|
+
apply();
|
|
662
|
+
}
|
|
404
663
|
function resetWidths() {
|
|
405
664
|
delete width.nav;
|
|
406
665
|
delete width.inspector;
|
|
@@ -410,9 +669,11 @@ function mountPaneShell(host, opts) {
|
|
|
410
669
|
navToggle.addEventListener("click", () => togglePane("nav"));
|
|
411
670
|
inspToggle.addEventListener("click", () => togglePane("inspector"));
|
|
412
671
|
function beginResize(side, startEvent) {
|
|
413
|
-
if (cfg[side].resizable === false || !
|
|
672
|
+
if (cfg[side].resizable === false || !showing(side)) return;
|
|
414
673
|
startEvent.preventDefault();
|
|
415
674
|
const pane = side === "nav" ? navPane : inspPane;
|
|
675
|
+
const seam = side === "nav" ? navResizer : inspResizer;
|
|
676
|
+
seam.classList.add("dragging");
|
|
416
677
|
const min = cfg[side].minWidth ?? 160;
|
|
417
678
|
const max = cfg[side].maxWidth ?? DEFAULT_MAX;
|
|
418
679
|
const startX = startEvent.clientX;
|
|
@@ -428,6 +689,7 @@ function mountPaneShell(host, opts) {
|
|
|
428
689
|
window.removeEventListener("pointermove", onMove);
|
|
429
690
|
window.removeEventListener("pointerup", onUp);
|
|
430
691
|
document.body.classList.remove("pane-resizing");
|
|
692
|
+
seam.classList.remove("dragging");
|
|
431
693
|
width[side] = latest;
|
|
432
694
|
persist();
|
|
433
695
|
};
|
|
@@ -457,7 +719,8 @@ function mountPaneShell(host, opts) {
|
|
|
457
719
|
inspector: inspInner,
|
|
458
720
|
togglePane,
|
|
459
721
|
setPaneOpen,
|
|
460
|
-
isOpen:
|
|
722
|
+
isOpen: showing,
|
|
723
|
+
holdClosed,
|
|
461
724
|
resetWidths,
|
|
462
725
|
state: snapshot,
|
|
463
726
|
destroy: () => {
|
|
@@ -471,7 +734,8 @@ var EDIT_MENU = {
|
|
|
471
734
|
undo: { label: "Undo", accelerator: "CmdOrCtrl+Z" },
|
|
472
735
|
redo: { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z" },
|
|
473
736
|
duplicate: { label: "Duplicate", accelerator: "CmdOrCtrl+D" },
|
|
474
|
-
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" }
|
|
737
|
+
find: { label: "Find\u2026", accelerator: "CmdOrCtrl+F" },
|
|
738
|
+
replace: { label: "Replace\u2026", acceleratorMac: "Cmd+Alt+F", acceleratorOther: "Ctrl+H" }
|
|
475
739
|
};
|
|
476
740
|
var GO_MENU = {
|
|
477
741
|
back: { label: "Back", acceleratorMac: "Ctrl+Cmd+Left", acceleratorOther: "Alt+Left" },
|
|
@@ -482,10 +746,43 @@ var PANE_MENU = {
|
|
|
482
746
|
showInspector: { label: "Show Inspector", accelerator: "CmdOrCtrl+2" },
|
|
483
747
|
resetView: { label: "Reset View" }
|
|
484
748
|
};
|
|
749
|
+
var FILE_MENU = {
|
|
750
|
+
newProject: { label: "New Project\u2026", accelerator: "CmdOrCtrl+N" },
|
|
751
|
+
openProject: { label: "Open Project\u2026", accelerator: "CmdOrCtrl+O" },
|
|
752
|
+
openRecent: { label: "Open Recent" },
|
|
753
|
+
noRecents: { label: "No Recent Projects" },
|
|
754
|
+
clearRecents: { label: "Clear Recents" },
|
|
755
|
+
closeProject: { label: "Close Project" },
|
|
756
|
+
save: { label: "Save", accelerator: "CmdOrCtrl+S" },
|
|
757
|
+
saveAs: { label: "Save As\u2026", accelerator: "Shift+CmdOrCtrl+S" },
|
|
758
|
+
projectSettings: { label: "Project Settings\u2026", accelerator: "CmdOrCtrl+," }
|
|
759
|
+
};
|
|
760
|
+
var PLAY_MENU = {
|
|
761
|
+
liveLink: { label: "Live Link" }
|
|
762
|
+
};
|
|
763
|
+
var REVIEW_MENU = {
|
|
764
|
+
reviewFeedback: { label: "Review Feedback", accelerator: "CmdOrCtrl+Shift+R" },
|
|
765
|
+
nextFeedback: { label: "Next Feedback", accelerator: "F8" },
|
|
766
|
+
previousFeedback: { label: "Previous Feedback", accelerator: "Shift+F8" },
|
|
767
|
+
coverageTest: { label: "Coverage Test\u2026", accelerator: "Shift+CmdOrCtrl+C" },
|
|
768
|
+
findPropertyUsage: { label: "Find Property Usage\u2026" },
|
|
769
|
+
showResolvedComments: { label: "Show Resolved Comments" }
|
|
770
|
+
};
|
|
771
|
+
var PUBLISH_MENU = {
|
|
772
|
+
playableHtml: { label: "Publish Playable HTML\u2026" },
|
|
773
|
+
bundle: { label: "Publish Bundle", accelerator: "Shift+CmdOrCtrl+B" },
|
|
774
|
+
autoRebuild: { label: "Auto Rebuild" }
|
|
775
|
+
};
|
|
776
|
+
var VIEW_MENU = {
|
|
777
|
+
projectOverview: { label: "Project Overview" },
|
|
778
|
+
upALevel: { label: "Up a Level", accelerator: "CmdOrCtrl+[" },
|
|
779
|
+
colourTheme: { label: "Colour Theme" }
|
|
780
|
+
};
|
|
485
781
|
|
|
486
782
|
// src/settings.ts
|
|
487
783
|
function expandableRow(opts) {
|
|
488
784
|
const row = el("div", "set-row");
|
|
785
|
+
if (opts.name !== void 0) row.dataset.name = opts.name;
|
|
489
786
|
const line = el("div", "set-rowline");
|
|
490
787
|
if (opts.details && opts.details.length) {
|
|
491
788
|
const details = el("div", "set-details");
|
|
@@ -511,6 +808,35 @@ function expandableRow(opts) {
|
|
|
511
808
|
}
|
|
512
809
|
return row;
|
|
513
810
|
}
|
|
811
|
+
function revealRow(within, name) {
|
|
812
|
+
const row = [...within.querySelectorAll(".set-row")].find((r) => r.dataset.name === name);
|
|
813
|
+
if (!row) return false;
|
|
814
|
+
const toggle = row.querySelector(".set-rowline > .set-expand");
|
|
815
|
+
if (toggle && toggle.getAttribute("aria-expanded") !== "true") toggle.click();
|
|
816
|
+
const still = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
817
|
+
row.scrollIntoView({ block: "center", behavior: still ? "auto" : "smooth" });
|
|
818
|
+
row.classList.remove("landed");
|
|
819
|
+
void row.offsetWidth;
|
|
820
|
+
row.classList.add("landed");
|
|
821
|
+
row.addEventListener("animationend", () => row.classList.remove("landed"), { once: true });
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
function revealRowWhenReady(within, name, tries = 12) {
|
|
825
|
+
return new Promise((resolve) => {
|
|
826
|
+
const attempt = (left) => {
|
|
827
|
+
if (revealRow(within, name)) {
|
|
828
|
+
resolve(true);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (left <= 0) {
|
|
832
|
+
resolve(false);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
requestAnimationFrame(() => attempt(left - 1));
|
|
836
|
+
};
|
|
837
|
+
attempt(tries);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
514
840
|
function focusNewRow(listEl) {
|
|
515
841
|
const last = listEl?.lastElementChild;
|
|
516
842
|
if (!(last instanceof HTMLElement)) return;
|
|
@@ -518,6 +844,7 @@ function focusNewRow(listEl) {
|
|
|
518
844
|
const name = last.querySelector("input.set-name") ?? last.querySelector("input");
|
|
519
845
|
name?.focus();
|
|
520
846
|
}
|
|
847
|
+
var DUP_MESSAGE = "Another row already has this name.";
|
|
521
848
|
function dupGuard() {
|
|
522
849
|
const items = [];
|
|
523
850
|
const check = () => {
|
|
@@ -532,9 +859,9 @@ function dupGuard() {
|
|
|
532
859
|
const dup = !!k && (counts.get(k) ?? 0) > 1;
|
|
533
860
|
it.input.classList.toggle("invalid", dup);
|
|
534
861
|
if (dup) {
|
|
535
|
-
it.input.
|
|
862
|
+
it.input.dataset["tip"] = DUP_MESSAGE;
|
|
536
863
|
any = true;
|
|
537
|
-
} else if (it.input.
|
|
864
|
+
} else if (it.input.dataset["tip"] === DUP_MESSAGE) delete it.input.dataset["tip"];
|
|
538
865
|
}
|
|
539
866
|
return any;
|
|
540
867
|
};
|
|
@@ -580,7 +907,9 @@ function mountSettingsDialog(opts) {
|
|
|
580
907
|
const tab = el("button", "settings-tab", s.label);
|
|
581
908
|
tab.type = "button";
|
|
582
909
|
tab.dataset["tab"] = s.id;
|
|
583
|
-
tab.addEventListener("click", () =>
|
|
910
|
+
tab.addEventListener("click", () => {
|
|
911
|
+
if (!tab.classList.contains("is-disabled")) showTab(s.id);
|
|
912
|
+
});
|
|
584
913
|
tabs.append(tab);
|
|
585
914
|
tabFor.set(s.id, tab);
|
|
586
915
|
const panel = el("section", "settings-panel");
|
|
@@ -594,6 +923,25 @@ function mountSettingsDialog(opts) {
|
|
|
594
923
|
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
595
924
|
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
596
925
|
}
|
|
926
|
+
const usable = (s) => !tabFor.get(s.id)?.classList.contains("is-disabled");
|
|
927
|
+
function refreshTabs() {
|
|
928
|
+
for (const s of opts.sections) {
|
|
929
|
+
const tab = tabFor.get(s.id);
|
|
930
|
+
const reason = s.disabled?.() ?? null;
|
|
931
|
+
tab.classList.toggle("is-disabled", reason !== null);
|
|
932
|
+
if (reason !== null) {
|
|
933
|
+
tab.dataset["tip"] = reason;
|
|
934
|
+
tab.setAttribute("aria-disabled", "true");
|
|
935
|
+
} else {
|
|
936
|
+
delete tab.dataset["tip"];
|
|
937
|
+
tab.removeAttribute("aria-disabled");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (!opts.sections.some((s) => s.id === active2 && usable(s))) {
|
|
941
|
+
const first = opts.sections.find(usable);
|
|
942
|
+
if (first) showTab(first.id);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
597
945
|
form.append(
|
|
598
946
|
el("h2", "settings-title", opts.title),
|
|
599
947
|
el("div", "settings-body", tabs, panels),
|
|
@@ -609,14 +957,15 @@ function mountSettingsDialog(opts) {
|
|
|
609
957
|
e.preventDefault();
|
|
610
958
|
error.hidden = true;
|
|
611
959
|
for (const s of opts.sections) {
|
|
612
|
-
const
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
960
|
+
const found = handles.get(s.id)?.firstInvalid?.();
|
|
961
|
+
if (!found) continue;
|
|
962
|
+
const bad = found instanceof HTMLElement ? found : found.el;
|
|
963
|
+
const message = (found instanceof HTMLElement ? void 0 : found.message) ?? bad.dataset["tip"];
|
|
964
|
+
showTab(s.id);
|
|
965
|
+
bad.focus();
|
|
966
|
+
error.textContent = message || "Fix the highlighted fields first.";
|
|
967
|
+
error.hidden = false;
|
|
968
|
+
return;
|
|
620
969
|
}
|
|
621
970
|
void (async () => {
|
|
622
971
|
await opts.onSave();
|
|
@@ -633,25 +982,94 @@ function mountSettingsDialog(opts) {
|
|
|
633
982
|
}
|
|
634
983
|
error.hidden = true;
|
|
635
984
|
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
985
|
+
refreshTabs();
|
|
636
986
|
dialog.showModal();
|
|
637
987
|
},
|
|
988
|
+
refreshTabs,
|
|
638
989
|
destroy() {
|
|
639
990
|
dialog.remove();
|
|
640
991
|
}
|
|
641
992
|
};
|
|
642
993
|
}
|
|
643
994
|
|
|
995
|
+
// src/util.ts
|
|
996
|
+
function plural(n, noun, pluralForm = `${noun}s`) {
|
|
997
|
+
return `${n} ${n === 1 ? noun : pluralForm}`;
|
|
998
|
+
}
|
|
999
|
+
function debounce(fn, ms) {
|
|
1000
|
+
let timer3;
|
|
1001
|
+
let pending;
|
|
1002
|
+
const run = () => {
|
|
1003
|
+
timer3 = void 0;
|
|
1004
|
+
if (pending === void 0) return;
|
|
1005
|
+
const args = pending;
|
|
1006
|
+
pending = void 0;
|
|
1007
|
+
fn(...args);
|
|
1008
|
+
};
|
|
1009
|
+
const debounced = ((...args) => {
|
|
1010
|
+
pending = args;
|
|
1011
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1012
|
+
timer3 = setTimeout(run, ms);
|
|
1013
|
+
});
|
|
1014
|
+
debounced.cancel = () => {
|
|
1015
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1016
|
+
timer3 = void 0;
|
|
1017
|
+
pending = void 0;
|
|
1018
|
+
};
|
|
1019
|
+
debounced.flush = () => {
|
|
1020
|
+
if (timer3 !== void 0) clearTimeout(timer3);
|
|
1021
|
+
run();
|
|
1022
|
+
};
|
|
1023
|
+
return debounced;
|
|
1024
|
+
}
|
|
1025
|
+
function isEditableTarget(t) {
|
|
1026
|
+
if (!(t instanceof Element)) return false;
|
|
1027
|
+
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) return true;
|
|
1028
|
+
if (t instanceof HTMLElement && t.isContentEditable === true) return true;
|
|
1029
|
+
const attr = t.getAttribute("contenteditable");
|
|
1030
|
+
return attr !== null && attr !== "false";
|
|
1031
|
+
}
|
|
1032
|
+
function formatCount(n) {
|
|
1033
|
+
return n.toLocaleString();
|
|
1034
|
+
}
|
|
1035
|
+
var copyTimers = /* @__PURE__ */ new WeakMap();
|
|
1036
|
+
function copyWithFeedback(el2, text, ms = 1e3) {
|
|
1037
|
+
const write = typeof navigator !== "undefined" && navigator.clipboard ? navigator.clipboard.writeText(text).catch(() => {
|
|
1038
|
+
}) : Promise.resolve();
|
|
1039
|
+
el2.classList.add("copied");
|
|
1040
|
+
const prior = copyTimers.get(el2);
|
|
1041
|
+
if (prior !== void 0) clearTimeout(prior);
|
|
1042
|
+
copyTimers.set(el2, setTimeout(() => {
|
|
1043
|
+
el2.classList.remove("copied");
|
|
1044
|
+
copyTimers.delete(el2);
|
|
1045
|
+
}, ms));
|
|
1046
|
+
return write;
|
|
1047
|
+
}
|
|
1048
|
+
function relativeTime(date, now = Date.now()) {
|
|
1049
|
+
const then = date instanceof Date ? date.getTime() : new Date(date).getTime();
|
|
1050
|
+
if (!Number.isFinite(then)) return "";
|
|
1051
|
+
const mins = Math.floor((now - then) / 6e4);
|
|
1052
|
+
if (mins < 1) return "just now";
|
|
1053
|
+
if (mins < 60) return `${plural(mins, "minute")} ago`;
|
|
1054
|
+
const hours = Math.floor(mins / 60);
|
|
1055
|
+
if (hours === 1) return "an hour ago";
|
|
1056
|
+
if (hours < 24) return `${hours} hours ago`;
|
|
1057
|
+
const days = Math.floor(hours / 24);
|
|
1058
|
+
if (days === 1) return "yesterday";
|
|
1059
|
+
if (days < 7) return `${days} days ago`;
|
|
1060
|
+
return new Date(then).toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" });
|
|
1061
|
+
}
|
|
1062
|
+
|
|
644
1063
|
// src/tool-window-web.ts
|
|
645
|
-
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>`;
|
|
646
1064
|
function pinButton(opts) {
|
|
647
1065
|
const b = el("button", "swin-pin");
|
|
648
1066
|
b.type = "button";
|
|
649
|
-
b.
|
|
1067
|
+
b.append(iconNode("pin"));
|
|
650
1068
|
let pinned = opts.pinned;
|
|
651
1069
|
const reflect = () => {
|
|
652
1070
|
b.classList.toggle("on", pinned);
|
|
653
1071
|
b.setAttribute("aria-pressed", String(pinned));
|
|
654
|
-
const label2 = pinned ? "Pinned on top
|
|
1072
|
+
const label2 = pinned ? "Pinned on top. Click to unpin." : "Keep on top";
|
|
655
1073
|
b.dataset["tip"] = label2;
|
|
656
1074
|
b.setAttribute("aria-label", label2);
|
|
657
1075
|
};
|
|
@@ -666,6 +1084,61 @@ function pinButton(opts) {
|
|
|
666
1084
|
reflect();
|
|
667
1085
|
} };
|
|
668
1086
|
}
|
|
1087
|
+
var escSlot;
|
|
1088
|
+
var escWired = false;
|
|
1089
|
+
function wireEsc() {
|
|
1090
|
+
if (escWired) return;
|
|
1091
|
+
escWired = true;
|
|
1092
|
+
window.addEventListener("keydown", (e) => {
|
|
1093
|
+
if (e.key !== "Escape" || !escSlot) return;
|
|
1094
|
+
if (e.defaultPrevented) return;
|
|
1095
|
+
if (!escSlot.fromFields && isEditableTarget(e.target)) return;
|
|
1096
|
+
if (escSlot.onEscape?.() === true) return;
|
|
1097
|
+
escSlot.onClose();
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function toolWindowHead(opts) {
|
|
1101
|
+
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
1102
|
+
const pin = opts.pin ?? pinButton({ pinned: opts.pinned ?? true, onToggle: opts.onPin ?? (() => {
|
|
1103
|
+
}) });
|
|
1104
|
+
if (opts.esc !== false) {
|
|
1105
|
+
escSlot = { onClose: opts.onClose, onEscape: opts.onEscape, fromFields: opts.esc === "always" };
|
|
1106
|
+
wireEsc();
|
|
1107
|
+
}
|
|
1108
|
+
return el(
|
|
1109
|
+
"header",
|
|
1110
|
+
{ className: `swin-head${opts.className ? ` ${opts.className}` : ""}` },
|
|
1111
|
+
...opts.title !== void 0 ? [el("span", { className: "swin-title", text: opts.title })] : [],
|
|
1112
|
+
...opts.tabs ? [opts.tabs] : [],
|
|
1113
|
+
...keep(opts.lead),
|
|
1114
|
+
el("span", { className: "swin-spacer" }),
|
|
1115
|
+
...keep(opts.trail),
|
|
1116
|
+
pin.el,
|
|
1117
|
+
el("button", { className: "swin-close", tip: "Close (Esc)", onClick: opts.onClose }, iconNode("close"))
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
function followButton(opts) {
|
|
1121
|
+
const what = opts.what ?? "line";
|
|
1122
|
+
const b = el("button", "btn swin-follow", "Follow in the editor");
|
|
1123
|
+
b.type = "button";
|
|
1124
|
+
let on = opts.on;
|
|
1125
|
+
const reflect = () => {
|
|
1126
|
+
b.classList.toggle("on", on);
|
|
1127
|
+
b.setAttribute("aria-pressed", String(on));
|
|
1128
|
+
const label2 = on ? `The editor opens each ${what} as it plays. Click to stop.` : `Open each ${what} in the editor as it plays`;
|
|
1129
|
+
b.dataset["tip"] = label2;
|
|
1130
|
+
};
|
|
1131
|
+
reflect();
|
|
1132
|
+
b.addEventListener("click", () => {
|
|
1133
|
+
on = !on;
|
|
1134
|
+
reflect();
|
|
1135
|
+
opts.onToggle(on);
|
|
1136
|
+
});
|
|
1137
|
+
return { el: b, set: (next) => {
|
|
1138
|
+
on = next;
|
|
1139
|
+
reflect();
|
|
1140
|
+
} };
|
|
1141
|
+
}
|
|
669
1142
|
|
|
670
1143
|
// src/exit.ts
|
|
671
1144
|
function closeWithExit(element, done) {
|
|
@@ -690,61 +1163,232 @@ function closeWithExit(element, done) {
|
|
|
690
1163
|
const timer3 = setTimeout(finish, seconds * 1e3 + 120);
|
|
691
1164
|
}
|
|
692
1165
|
|
|
1166
|
+
// src/dialog.ts
|
|
1167
|
+
function dialogFrame(opts) {
|
|
1168
|
+
const dialog = el("dialog", `shell-dialog ${opts.className ?? ""}`.trim());
|
|
1169
|
+
const body = el("div", "shell-dialog-body");
|
|
1170
|
+
const actions = el("div", "shell-dialog-actions");
|
|
1171
|
+
dialog.append(el("h2", "shell-dialog-title", opts.title));
|
|
1172
|
+
if (opts.sub !== void 0) dialog.append(el("p", "shell-dialog-sub", opts.sub));
|
|
1173
|
+
dialog.append(body, actions);
|
|
1174
|
+
dialog.setAttribute("aria-label", opts.title);
|
|
1175
|
+
document.body.append(dialog);
|
|
1176
|
+
let state = "closed";
|
|
1177
|
+
let opener = null;
|
|
1178
|
+
const teardown = () => {
|
|
1179
|
+
dialog.remove();
|
|
1180
|
+
if (opener instanceof HTMLElement && opener.isConnected) opener.focus();
|
|
1181
|
+
opener = null;
|
|
1182
|
+
state = "closed";
|
|
1183
|
+
opts.onClose?.();
|
|
1184
|
+
};
|
|
1185
|
+
const close = () => {
|
|
1186
|
+
if (state !== "open") return;
|
|
1187
|
+
state = "closing";
|
|
1188
|
+
closeWithExit(dialog, () => {
|
|
1189
|
+
if (dialog.open) dialog.close();
|
|
1190
|
+
teardown();
|
|
1191
|
+
});
|
|
1192
|
+
};
|
|
1193
|
+
dialog.addEventListener("cancel", (e) => {
|
|
1194
|
+
e.preventDefault();
|
|
1195
|
+
close();
|
|
1196
|
+
});
|
|
1197
|
+
dialog.addEventListener("close", () => {
|
|
1198
|
+
if (state === "open") teardown();
|
|
1199
|
+
});
|
|
1200
|
+
return {
|
|
1201
|
+
dialog,
|
|
1202
|
+
body,
|
|
1203
|
+
actions,
|
|
1204
|
+
open() {
|
|
1205
|
+
if (state !== "closed") return;
|
|
1206
|
+
opener = document.activeElement;
|
|
1207
|
+
state = "open";
|
|
1208
|
+
dialog.showModal();
|
|
1209
|
+
},
|
|
1210
|
+
close,
|
|
1211
|
+
isOpen: () => state === "open"
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
693
1215
|
// src/confirm.ts
|
|
694
1216
|
function confirmDialog(opts) {
|
|
695
1217
|
return new Promise((resolve) => {
|
|
696
|
-
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
697
|
-
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
698
|
-
cancel.type = "button";
|
|
699
|
-
danger.type = "button";
|
|
700
|
-
const dlg = el(
|
|
701
|
-
"dialog",
|
|
702
|
-
"confirm-dialog",
|
|
703
|
-
el("div", "confirm-title", opts.title),
|
|
704
|
-
el("div", "confirm-body", opts.body),
|
|
705
|
-
el("div", "confirm-actions", cancel, danger)
|
|
706
|
-
);
|
|
707
|
-
dlg.setAttribute("aria-label", opts.title);
|
|
708
|
-
document.body.append(dlg);
|
|
709
1218
|
let done = false;
|
|
710
1219
|
const finish = (ok) => {
|
|
711
1220
|
if (done) return;
|
|
712
1221
|
done = true;
|
|
713
1222
|
resolve(ok);
|
|
714
|
-
|
|
715
|
-
dlg.close();
|
|
716
|
-
dlg.remove();
|
|
717
|
-
});
|
|
1223
|
+
frame.close();
|
|
718
1224
|
};
|
|
1225
|
+
const frame = dialogFrame({
|
|
1226
|
+
title: opts.title,
|
|
1227
|
+
className: "confirm-dialog",
|
|
1228
|
+
onClose: () => finish(false)
|
|
1229
|
+
// Esc, or closed by someone else
|
|
1230
|
+
});
|
|
1231
|
+
const cancel = el("button", "btn confirm-btn cancel", "Cancel");
|
|
1232
|
+
const danger = el("button", "btn danger confirm-btn", opts.confirmLabel);
|
|
1233
|
+
cancel.type = "button";
|
|
1234
|
+
danger.type = "button";
|
|
1235
|
+
if (opts.body !== void 0) frame.body.append(el("div", "confirm-body", opts.body));
|
|
1236
|
+
if (opts.bodyNode) frame.body.append(opts.bodyNode);
|
|
1237
|
+
frame.actions.append(cancel, danger);
|
|
719
1238
|
cancel.addEventListener("click", () => finish(false));
|
|
720
1239
|
danger.addEventListener("click", () => finish(true));
|
|
721
|
-
|
|
722
|
-
e.
|
|
723
|
-
finish(false);
|
|
1240
|
+
frame.dialog.addEventListener("mousedown", (e) => {
|
|
1241
|
+
if (e.target === frame.dialog) finish(false);
|
|
724
1242
|
});
|
|
725
|
-
|
|
726
|
-
if (e.target === dlg) finish(false);
|
|
727
|
-
});
|
|
728
|
-
dlg.showModal();
|
|
1243
|
+
frame.open();
|
|
729
1244
|
cancel.focus();
|
|
730
1245
|
});
|
|
731
1246
|
}
|
|
732
1247
|
|
|
1248
|
+
// src/floating.ts
|
|
1249
|
+
function followOnScroll(reposition) {
|
|
1250
|
+
const onMove = () => reposition();
|
|
1251
|
+
window.addEventListener("scroll", onMove, true);
|
|
1252
|
+
window.addEventListener("resize", onMove);
|
|
1253
|
+
return () => {
|
|
1254
|
+
window.removeEventListener("scroll", onMove, true);
|
|
1255
|
+
window.removeEventListener("resize", onMove);
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function createFloating(className, opts = {}) {
|
|
1259
|
+
const el2 = document.createElement("div");
|
|
1260
|
+
el2.className = className;
|
|
1261
|
+
el2.style.display = "none";
|
|
1262
|
+
(opts.host ?? document.body).appendChild(el2);
|
|
1263
|
+
let open = false;
|
|
1264
|
+
let detach = null;
|
|
1265
|
+
let outside = null;
|
|
1266
|
+
let closeToken = 0;
|
|
1267
|
+
const close = () => {
|
|
1268
|
+
if (!open) return;
|
|
1269
|
+
open = false;
|
|
1270
|
+
detach?.();
|
|
1271
|
+
detach = null;
|
|
1272
|
+
if (outside) {
|
|
1273
|
+
document.removeEventListener("mousedown", outside, true);
|
|
1274
|
+
outside = null;
|
|
1275
|
+
}
|
|
1276
|
+
const myToken = ++closeToken;
|
|
1277
|
+
closeWithExit(el2, () => {
|
|
1278
|
+
if (myToken !== closeToken) return;
|
|
1279
|
+
el2.style.display = "none";
|
|
1280
|
+
el2.replaceChildren();
|
|
1281
|
+
el2.classList.remove("closing");
|
|
1282
|
+
});
|
|
1283
|
+
};
|
|
1284
|
+
const show2 = (reposition) => {
|
|
1285
|
+
closeToken++;
|
|
1286
|
+
el2.classList.remove("closing");
|
|
1287
|
+
open = true;
|
|
1288
|
+
el2.style.display = "block";
|
|
1289
|
+
reposition();
|
|
1290
|
+
detach ??= followOnScroll(reposition);
|
|
1291
|
+
};
|
|
1292
|
+
const dismissOnOutside = (onOutside, isInside) => {
|
|
1293
|
+
if (outside) return;
|
|
1294
|
+
outside = (e) => {
|
|
1295
|
+
const t = e.target;
|
|
1296
|
+
if (!el2.contains(t) && !(isInside?.(t) ?? false)) onOutside();
|
|
1297
|
+
};
|
|
1298
|
+
document.addEventListener("mousedown", outside, true);
|
|
1299
|
+
};
|
|
1300
|
+
return { el: el2, show: show2, dismissOnOutside, close, isOpen: () => open };
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/context-menu.ts
|
|
1304
|
+
var EDGE2 = 6;
|
|
1305
|
+
var current = null;
|
|
1306
|
+
function floating(node2, onDismiss) {
|
|
1307
|
+
let gone = false;
|
|
1308
|
+
const dismiss = () => {
|
|
1309
|
+
if (gone) return;
|
|
1310
|
+
gone = true;
|
|
1311
|
+
if (current === dismiss) current = null;
|
|
1312
|
+
window.removeEventListener("pointerdown", onAway, true);
|
|
1313
|
+
window.removeEventListener("keydown", onKey, true);
|
|
1314
|
+
closeWithExit(node2, () => node2.remove());
|
|
1315
|
+
onDismiss?.();
|
|
1316
|
+
};
|
|
1317
|
+
const onAway = (e) => {
|
|
1318
|
+
if (!node2.contains(e.target)) dismiss();
|
|
1319
|
+
};
|
|
1320
|
+
const onKey = (e) => {
|
|
1321
|
+
if (e.key === "Escape") {
|
|
1322
|
+
e.preventDefault();
|
|
1323
|
+
dismiss();
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
window.addEventListener("pointerdown", onAway, true);
|
|
1327
|
+
window.addEventListener("keydown", onKey, true);
|
|
1328
|
+
current?.();
|
|
1329
|
+
current = dismiss;
|
|
1330
|
+
return { dismiss };
|
|
1331
|
+
}
|
|
1332
|
+
function placeBelow(node2, anchor) {
|
|
1333
|
+
node2.style.left = `${anchor.left}px`;
|
|
1334
|
+
node2.style.top = `${anchor.bottom + 4}px`;
|
|
1335
|
+
const r = node2.getBoundingClientRect();
|
|
1336
|
+
if (r.right > window.innerWidth) node2.style.left = `${Math.max(EDGE2, window.innerWidth - r.width - EDGE2)}px`;
|
|
1337
|
+
if (r.bottom > window.innerHeight) node2.style.top = `${Math.max(EDGE2, anchor.top - r.height - 4)}px`;
|
|
1338
|
+
}
|
|
1339
|
+
function openPopover(anchor, build, onClose, opts = {}) {
|
|
1340
|
+
const pop = el("div", "popover");
|
|
1341
|
+
let closed = false;
|
|
1342
|
+
const finish = () => {
|
|
1343
|
+
if (closed) return;
|
|
1344
|
+
closed = true;
|
|
1345
|
+
onClose?.();
|
|
1346
|
+
};
|
|
1347
|
+
const { dismiss } = floating(pop, finish);
|
|
1348
|
+
pop.append(build(dismiss));
|
|
1349
|
+
(opts.host ?? document.body).append(pop);
|
|
1350
|
+
placeBelow(pop, anchor.getBoundingClientRect());
|
|
1351
|
+
}
|
|
1352
|
+
function openContextMenu(x, y, items) {
|
|
1353
|
+
const menu = el("div", "ctxmenu");
|
|
1354
|
+
menu.setAttribute("role", "menu");
|
|
1355
|
+
const { dismiss } = floating(menu);
|
|
1356
|
+
for (const item of items) {
|
|
1357
|
+
const button = el("button", {
|
|
1358
|
+
className: `ctxmenu-item${item.danger ? " danger" : ""}`,
|
|
1359
|
+
text: item.label,
|
|
1360
|
+
onClick: () => {
|
|
1361
|
+
if (item.disabled === true) return;
|
|
1362
|
+
dismiss();
|
|
1363
|
+
item.onClick();
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
button.type = "button";
|
|
1367
|
+
button.setAttribute("role", "menuitem");
|
|
1368
|
+
if (item.disabled === true) button.disabled = true;
|
|
1369
|
+
menu.append(button);
|
|
1370
|
+
}
|
|
1371
|
+
menu.style.left = `${x}px`;
|
|
1372
|
+
menu.style.top = `${y}px`;
|
|
1373
|
+
document.body.append(menu);
|
|
1374
|
+
const r = menu.getBoundingClientRect();
|
|
1375
|
+
if (r.right > window.innerWidth) menu.style.left = `${Math.max(EDGE2, window.innerWidth - r.width - EDGE2)}px`;
|
|
1376
|
+
if (r.bottom > window.innerHeight) menu.style.top = `${Math.max(EDGE2, window.innerHeight - r.height - EDGE2)}px`;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
733
1379
|
// src/about.ts
|
|
734
1380
|
function showAbout(opts) {
|
|
735
1381
|
return new Promise((resolve) => {
|
|
736
|
-
const
|
|
1382
|
+
const frame = dialogFrame({ title: opts.appName, className: "shell-about", onClose: () => resolve() });
|
|
1383
|
+
frame.dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
737
1384
|
if (opts.wordmark !== void 0) {
|
|
738
1385
|
const mark = el("div", "shell-about-mark");
|
|
739
1386
|
mark.innerHTML = opts.wordmark;
|
|
740
|
-
dialog.
|
|
1387
|
+
frame.dialog.prepend(mark);
|
|
741
1388
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
);
|
|
746
|
-
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
747
|
-
if (opts.credits !== void 0) dialog.append(el("p", "shell-about-credits", opts.credits));
|
|
1389
|
+
frame.body.append(el("div", "shell-about-version", `Version ${opts.version}`));
|
|
1390
|
+
if (opts.blurb !== void 0) frame.body.append(el("p", "shell-about-blurb", opts.blurb));
|
|
1391
|
+
if (opts.credits !== void 0) frame.body.append(el("p", "shell-about-credits", opts.credits));
|
|
748
1392
|
if (opts.links?.length) {
|
|
749
1393
|
const row = el("div", "shell-about-links");
|
|
750
1394
|
for (const link of opts.links) {
|
|
@@ -757,39 +1401,28 @@ function showAbout(opts) {
|
|
|
757
1401
|
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
758
1402
|
row.append(a);
|
|
759
1403
|
}
|
|
760
|
-
|
|
1404
|
+
frame.body.append(row);
|
|
761
1405
|
}
|
|
762
|
-
const close = el("button", "shell-about-close", "Close");
|
|
1406
|
+
const close = el("button", "btn shell-about-close", "Close");
|
|
763
1407
|
close.type = "button";
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
let done = false;
|
|
768
|
-
const finish = () => {
|
|
769
|
-
if (done) return;
|
|
770
|
-
done = true;
|
|
771
|
-
dialog.close();
|
|
772
|
-
dialog.remove();
|
|
773
|
-
resolve();
|
|
774
|
-
};
|
|
775
|
-
close.addEventListener("click", finish);
|
|
776
|
-
dialog.addEventListener("close", finish);
|
|
777
|
-
dialog.showModal();
|
|
1408
|
+
close.addEventListener("click", () => frame.close());
|
|
1409
|
+
frame.actions.append(close);
|
|
1410
|
+
frame.open();
|
|
778
1411
|
close.focus();
|
|
779
1412
|
});
|
|
780
1413
|
}
|
|
781
1414
|
|
|
782
1415
|
// src/anchored.ts
|
|
783
|
-
var
|
|
1416
|
+
var current2 = null;
|
|
784
1417
|
function openAnchoredPanel(opts) {
|
|
785
|
-
if (
|
|
786
|
-
const prev =
|
|
787
|
-
|
|
1418
|
+
if (current2) {
|
|
1419
|
+
const prev = current2;
|
|
1420
|
+
current2 = null;
|
|
788
1421
|
prev.close();
|
|
789
1422
|
if (prev.anchor === opts.anchor) return null;
|
|
790
1423
|
}
|
|
791
1424
|
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
792
|
-
const closeBtn = el("button", "shell-anchored-close", "
|
|
1425
|
+
const closeBtn = el("button", "shell-anchored-close", iconNode("close", 12));
|
|
793
1426
|
closeBtn.type = "button";
|
|
794
1427
|
closeBtn.setAttribute("aria-label", "Close");
|
|
795
1428
|
const body = el("div", "shell-anchored-body");
|
|
@@ -797,13 +1430,13 @@ function openAnchoredPanel(opts) {
|
|
|
797
1430
|
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
798
1431
|
body
|
|
799
1432
|
);
|
|
800
|
-
document.body.append(panel);
|
|
1433
|
+
(opts.host ?? document.body).append(panel);
|
|
801
1434
|
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
802
1435
|
let closed = false;
|
|
803
1436
|
const close = () => {
|
|
804
1437
|
if (closed) return;
|
|
805
1438
|
closed = true;
|
|
806
|
-
if (
|
|
1439
|
+
if (current2?.panel === panel) current2 = null;
|
|
807
1440
|
document.removeEventListener("pointerdown", onDown, true);
|
|
808
1441
|
document.removeEventListener("keydown", onKey, true);
|
|
809
1442
|
closeWithExit(panel, () => {
|
|
@@ -827,12 +1460,12 @@ function openAnchoredPanel(opts) {
|
|
|
827
1460
|
document.addEventListener("pointerdown", onDown, true);
|
|
828
1461
|
document.addEventListener("keydown", onKey, true);
|
|
829
1462
|
}, 0);
|
|
830
|
-
|
|
1463
|
+
current2 = { anchor: opts.anchor, panel, close };
|
|
831
1464
|
return { panel, body, close };
|
|
832
1465
|
}
|
|
833
1466
|
function closeAnchoredPanel() {
|
|
834
|
-
const open =
|
|
835
|
-
|
|
1467
|
+
const open = current2;
|
|
1468
|
+
current2 = null;
|
|
836
1469
|
open?.close();
|
|
837
1470
|
}
|
|
838
1471
|
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
@@ -889,7 +1522,7 @@ function isValidGameId(gameId) {
|
|
|
889
1522
|
|
|
890
1523
|
// src/property-names.ts
|
|
891
1524
|
var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
|
|
892
|
-
var PROPERTY_NAME_HINT = "Lower case letters, digits and underscores.
|
|
1525
|
+
var PROPERTY_NAME_HINT = "Lower case letters, digits, and underscores. It can't start with a digit. No hyphens or spaces.";
|
|
893
1526
|
function propertyNameify(text) {
|
|
894
1527
|
const trimmed = text.trim();
|
|
895
1528
|
const deliberateLeading = trimmed.startsWith("_");
|
|
@@ -913,14 +1546,14 @@ function propertyNameProblem(name) {
|
|
|
913
1546
|
const suggestion = propertyNameify(name);
|
|
914
1547
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
915
1548
|
if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
|
|
916
|
-
return `
|
|
1549
|
+
return `Can't be used. "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
|
|
917
1550
|
}
|
|
918
|
-
if (name.includes("-")) return `
|
|
919
|
-
if (/^[0-9]/.test(name)) return `
|
|
1551
|
+
if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
|
|
1552
|
+
if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
|
|
920
1553
|
if (name !== name.toLowerCase()) {
|
|
921
|
-
return `
|
|
1554
|
+
return `Can't be used. Expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
|
|
922
1555
|
}
|
|
923
|
-
return `
|
|
1556
|
+
return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
|
|
924
1557
|
}
|
|
925
1558
|
function bindPropertyName(input, onCommit, opts = {}) {
|
|
926
1559
|
const hint = opts.hint;
|
|
@@ -929,7 +1562,7 @@ function bindPropertyName(input, onCommit, opts = {}) {
|
|
|
929
1562
|
const problem = v === "" ? void 0 : propertyNameProblem(v);
|
|
930
1563
|
input.classList.toggle("illegal", !!problem);
|
|
931
1564
|
if (problem) input.title = problem;
|
|
932
|
-
else if (input.title.startsWith("
|
|
1565
|
+
else if (input.title.startsWith("Can't be used")) {
|
|
933
1566
|
if (hint) input.title = hint;
|
|
934
1567
|
else input.removeAttribute("title");
|
|
935
1568
|
}
|
|
@@ -985,10 +1618,10 @@ function propertyRefProblem(name, opts) {
|
|
|
985
1618
|
if (!isValidPropertyName(folded)) {
|
|
986
1619
|
const suggestion = propertyNameify(name);
|
|
987
1620
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
988
|
-
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `
|
|
989
|
-
if (name.includes("-")) return `
|
|
990
|
-
if (/^[0-9]/.test(name)) return `
|
|
991
|
-
return `
|
|
1621
|
+
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Can't be used. "${folded}" is a keyword in expressions.${tail}`;
|
|
1622
|
+
if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
|
|
1623
|
+
if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
|
|
1624
|
+
return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
|
|
992
1625
|
}
|
|
993
1626
|
const known = opts.known();
|
|
994
1627
|
if (known.some((k) => k.toLowerCase() === folded)) return void 0;
|
|
@@ -997,7 +1630,7 @@ function propertyRefProblem(name, opts) {
|
|
|
997
1630
|
if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
|
|
998
1631
|
const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
|
|
999
1632
|
const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
|
|
1000
|
-
return `No ${scope} property is called "${name}".
|
|
1633
|
+
return `No ${scope} property is called "${name}". Pick one of ${shown}${more}.`;
|
|
1001
1634
|
}
|
|
1002
1635
|
function bindPropertyRef(input, onCommit, opts) {
|
|
1003
1636
|
const list = document.createElement("datalist");
|
|
@@ -1018,7 +1651,7 @@ function bindPropertyRef(input, onCommit, opts) {
|
|
|
1018
1651
|
const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
|
|
1019
1652
|
input.classList.toggle("illegal", !!problem);
|
|
1020
1653
|
if (problem) input.title = problem;
|
|
1021
|
-
else if (input.title.startsWith("
|
|
1654
|
+
else if (input.title.startsWith("Can't be used") || input.title.startsWith("No ")) {
|
|
1022
1655
|
if (opts.hint) input.title = opts.hint;
|
|
1023
1656
|
else input.removeAttribute("title");
|
|
1024
1657
|
}
|
|
@@ -1061,9 +1694,9 @@ function openGameIdEditor(opts) {
|
|
|
1061
1694
|
const input = el("input", "shell-id-input");
|
|
1062
1695
|
input.type = "text";
|
|
1063
1696
|
input.value = opts.value;
|
|
1064
|
-
input.placeholder = opts.derived || "
|
|
1697
|
+
input.placeholder = opts.derived || "game-id";
|
|
1065
1698
|
input.spellcheck = false;
|
|
1066
|
-
const hint = el("div", "shell-id-hint", "Lower case, digits, hyphens.
|
|
1699
|
+
const hint = el("div", "shell-id-hint", "Lower case letters, digits, and hyphens. Leave it empty to derive one from the name.");
|
|
1067
1700
|
const actions = el("div", "shell-id-actions");
|
|
1068
1701
|
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
1069
1702
|
const set = el("button", "shell-id-set", "Set");
|
|
@@ -1125,12 +1758,13 @@ function foldVc(shards, keys) {
|
|
|
1125
1758
|
return out;
|
|
1126
1759
|
}
|
|
1127
1760
|
function vcBadgeFor(s) {
|
|
1128
|
-
|
|
1129
|
-
if (s?.
|
|
1130
|
-
if (s?.
|
|
1131
|
-
if (s?.
|
|
1132
|
-
if (s?.
|
|
1133
|
-
if (s
|
|
1761
|
+
const badge = (name, cls, title) => ({ name, glyph: icon[name] ?? "", cls, title });
|
|
1762
|
+
if (s?.lockedBy?.length) return badge("locked", "vc-locked", `Locked by ${s.lockedBy.join(", ")}`);
|
|
1763
|
+
if (s?.outOfDate) return badge("down", "vc-stale", "Out of date. A newer version is on the server.");
|
|
1764
|
+
if (s?.checkedOutByMe) return badge("checkedOut", "vc-mine", "Checked out by you");
|
|
1765
|
+
if (s?.dirty) return badge("modified", "vc-dirty", "Modified. Local changes aren't committed yet.");
|
|
1766
|
+
if (s?.untracked) return badge("untracked", "vc-new", "New. Not in version control yet.");
|
|
1767
|
+
if (s && !s.writable) return badge("readOnly", "vc-frozen", "Read-only on disk. Saving checks it out.");
|
|
1134
1768
|
return null;
|
|
1135
1769
|
}
|
|
1136
1770
|
function paintVcBadges(root, shards) {
|
|
@@ -1138,7 +1772,7 @@ function paintVcBadges(root, shards) {
|
|
|
1138
1772
|
host.querySelector(":scope > .vc-badge")?.remove();
|
|
1139
1773
|
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
1140
1774
|
if (!badge) return;
|
|
1141
|
-
const span = el("span", { className: `vc-badge ${badge.cls}`,
|
|
1775
|
+
const span = el("span", { className: `vc-badge ${badge.cls}`, tip: badge.title }, iconNode(badge.name, 12));
|
|
1142
1776
|
host.append(span);
|
|
1143
1777
|
});
|
|
1144
1778
|
}
|
|
@@ -1161,8 +1795,8 @@ function lockNotice(holders) {
|
|
|
1161
1795
|
return el(
|
|
1162
1796
|
"div",
|
|
1163
1797
|
{ className: "vc-lock" },
|
|
1164
|
-
el("span", { className: "vc-lock-glyph",
|
|
1165
|
-
el("span", { text: `Locked by ${holders.join(", ")} -
|
|
1798
|
+
el("span", { className: "vc-lock-glyph" }, iconNode("locked")),
|
|
1799
|
+
el("span", { text: `Locked by ${holders.join(", ")}. Read-only until they release it.` })
|
|
1166
1800
|
);
|
|
1167
1801
|
}
|
|
1168
1802
|
|
|
@@ -1170,41 +1804,36 @@ function lockNotice(holders) {
|
|
|
1170
1804
|
function askIdentity(opts = {}) {
|
|
1171
1805
|
return new Promise((resolve) => {
|
|
1172
1806
|
const seed = opts.current ?? opts.suggested;
|
|
1173
|
-
const name = el("input", "shell-ident-input");
|
|
1807
|
+
const name = el("input", "field shell-ident-input");
|
|
1174
1808
|
name.placeholder = "Your name";
|
|
1175
1809
|
name.value = seed?.name ?? "";
|
|
1176
|
-
const email = el("input", "shell-ident-input");
|
|
1177
|
-
email.placeholder = "
|
|
1810
|
+
const email = el("input", "field shell-ident-input");
|
|
1811
|
+
email.placeholder = "Optional";
|
|
1178
1812
|
email.type = "email";
|
|
1179
1813
|
email.value = seed?.email ?? "";
|
|
1180
|
-
const skip = el("button", "shell-ident-btn", "Skip");
|
|
1181
|
-
const save = el("button", "shell-ident-btn
|
|
1814
|
+
const skip = el("button", "btn shell-ident-btn", "Skip");
|
|
1815
|
+
const save = el("button", "btn primary shell-ident-btn", opts.mode === "welcome" ? "Continue" : "Save");
|
|
1182
1816
|
skip.type = "button";
|
|
1183
1817
|
save.type = "button";
|
|
1184
|
-
const dialog = el(
|
|
1185
|
-
"dialog",
|
|
1186
|
-
"shell-ident",
|
|
1187
|
-
el("div", "shell-ident-title", "Who is working here?"),
|
|
1188
|
-
el(
|
|
1189
|
-
"p",
|
|
1190
|
-
"shell-ident-hint",
|
|
1191
|
-
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
1192
|
-
),
|
|
1193
|
-
el("div", "shell-ident-fields", name, email),
|
|
1194
|
-
el("div", "shell-ident-actions", skip, save)
|
|
1195
|
-
);
|
|
1196
|
-
document.body.append(dialog);
|
|
1197
1818
|
let done = false;
|
|
1198
1819
|
const finish = (keep) => {
|
|
1199
1820
|
if (done) return;
|
|
1200
1821
|
done = true;
|
|
1201
1822
|
const typed = name.value.trim();
|
|
1202
1823
|
const value = email.value.trim();
|
|
1203
|
-
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
1204
|
-
dialog.close();
|
|
1205
|
-
dialog.remove();
|
|
1824
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : opts.neverAskAgainOnSkip === true ? { name: "" } : void 0;
|
|
1206
1825
|
resolve(answer);
|
|
1826
|
+
frame.close();
|
|
1207
1827
|
};
|
|
1828
|
+
const frame = dialogFrame({
|
|
1829
|
+
title: titleFor(opts),
|
|
1830
|
+
sub: subFor(opts),
|
|
1831
|
+
className: "shell-ident",
|
|
1832
|
+
onClose: () => finish(false)
|
|
1833
|
+
// Escape, or closed by someone else: a skip
|
|
1834
|
+
});
|
|
1835
|
+
frame.body.append(el("div", "shell-ident-fields", labelled("Name", name), labelled("Email", email)));
|
|
1836
|
+
frame.actions.append(skip, save);
|
|
1208
1837
|
skip.addEventListener("click", () => finish(false));
|
|
1209
1838
|
save.addEventListener("click", () => finish(true));
|
|
1210
1839
|
name.addEventListener("keydown", (e) => {
|
|
@@ -1213,11 +1842,19 @@ function askIdentity(opts = {}) {
|
|
|
1213
1842
|
email.addEventListener("keydown", (e) => {
|
|
1214
1843
|
if (e.key === "Enter") finish(true);
|
|
1215
1844
|
});
|
|
1216
|
-
|
|
1217
|
-
dialog.showModal();
|
|
1845
|
+
frame.open();
|
|
1218
1846
|
name.focus();
|
|
1219
1847
|
});
|
|
1220
1848
|
}
|
|
1849
|
+
function titleFor(opts) {
|
|
1850
|
+
if (opts.mode === "welcome") return opts.appName ? `Welcome to ${opts.appName}` : "Welcome";
|
|
1851
|
+
if (opts.mode === "edit") return "User information";
|
|
1852
|
+
return "Who is working here?";
|
|
1853
|
+
}
|
|
1854
|
+
function subFor(opts) {
|
|
1855
|
+
const why = "Your name goes on the comments you write. It's kept in this app, not in the project.";
|
|
1856
|
+
return opts.mode === "edit" ? why : `${why} You can change it later.`;
|
|
1857
|
+
}
|
|
1221
1858
|
|
|
1222
1859
|
// src/notes-editor.ts
|
|
1223
1860
|
var EDITOR_ONLY = "";
|
|
@@ -1230,10 +1867,10 @@ var CLASS_LABEL = {
|
|
|
1230
1867
|
};
|
|
1231
1868
|
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
1232
1869
|
var PLACEHOLDER = {
|
|
1233
|
-
vo: "
|
|
1234
|
-
loc: "
|
|
1870
|
+
vo: "Direction for the voice actor or director",
|
|
1871
|
+
loc: "Context for translators"
|
|
1235
1872
|
};
|
|
1236
|
-
var placeholder = (cls) => cls === EDITOR_ONLY ? "
|
|
1873
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
|
|
1237
1874
|
function openNotesEditor(opts) {
|
|
1238
1875
|
const { notes } = opts;
|
|
1239
1876
|
const seed = /* @__PURE__ */ new Map();
|
|
@@ -1287,7 +1924,7 @@ ${line.text}` : line.text);
|
|
|
1287
1924
|
{ className: "shell-notes-class" },
|
|
1288
1925
|
el("label", "shell-notes-caption", label(cls))
|
|
1289
1926
|
);
|
|
1290
|
-
const area = el("textarea", "shell-notes-text");
|
|
1927
|
+
const area = el("textarea", "field shell-notes-text");
|
|
1291
1928
|
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
1292
1929
|
area.rows = 4;
|
|
1293
1930
|
area.placeholder = placeholder(cls);
|
|
@@ -1313,29 +1950,24 @@ ${line.text}` : line.text);
|
|
|
1313
1950
|
}
|
|
1314
1951
|
};
|
|
1315
1952
|
render();
|
|
1316
|
-
const done = el("button", "shell-notes-btn
|
|
1317
|
-
|
|
1318
|
-
"dialog",
|
|
1319
|
-
{ className: "shell-notes" },
|
|
1320
|
-
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
1321
|
-
body,
|
|
1322
|
-
el("div", { className: "shell-notes-actions" }, done)
|
|
1323
|
-
);
|
|
1324
|
-
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
1325
|
-
document.body.append(dialog);
|
|
1953
|
+
const done = el("button", "btn primary shell-notes-btn", "Done");
|
|
1954
|
+
done.type = "button";
|
|
1326
1955
|
const before = JSON.stringify(notes.own);
|
|
1327
1956
|
let closed = false;
|
|
1328
|
-
const
|
|
1957
|
+
const commit = () => {
|
|
1329
1958
|
if (closed) return;
|
|
1330
1959
|
closed = true;
|
|
1331
1960
|
const next = value();
|
|
1332
1961
|
if (JSON.stringify(next) !== before) opts.save(next);
|
|
1333
|
-
dialog.close();
|
|
1334
|
-
dialog.remove();
|
|
1335
1962
|
};
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1963
|
+
const frame = dialogFrame({ title: `Notes: ${opts.subject}`, className: "shell-notes", onClose: commit });
|
|
1964
|
+
frame.body.append(body);
|
|
1965
|
+
frame.actions.append(done);
|
|
1966
|
+
done.addEventListener("click", () => {
|
|
1967
|
+
commit();
|
|
1968
|
+
frame.close();
|
|
1969
|
+
});
|
|
1970
|
+
frame.open();
|
|
1339
1971
|
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
1340
1972
|
}
|
|
1341
1973
|
|
|
@@ -1407,7 +2039,7 @@ function openComments(opts) {
|
|
|
1407
2039
|
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1408
2040
|
);
|
|
1409
2041
|
if (opts.deleteMessage && message.deleted !== true) {
|
|
1410
|
-
const bin = el("button", { className: "shell-cmt-del",
|
|
2042
|
+
const bin = el("button", { className: "shell-cmt-del" }, iconNode("close", 12));
|
|
1411
2043
|
bin.dataset.tip = "Delete this comment";
|
|
1412
2044
|
bin.setAttribute("aria-label", "Delete this comment");
|
|
1413
2045
|
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
@@ -1472,37 +2104,65 @@ var clock = (ms) => {
|
|
|
1472
2104
|
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1473
2105
|
function mountJobProgress(host, opts) {
|
|
1474
2106
|
const units = opts.units ?? "runs";
|
|
2107
|
+
const pinned = opts.indeterminate === true;
|
|
2108
|
+
const cancellableByDefault = opts.cancellable ?? opts.onCancel !== void 0;
|
|
1475
2109
|
const fill = el("i", { className: "job-fill" });
|
|
1476
2110
|
const bar = el("div", { className: "job-bar" }, fill);
|
|
2111
|
+
bar.setAttribute("role", "progressbar");
|
|
2112
|
+
bar.setAttribute("aria-valuemin", "0");
|
|
2113
|
+
bar.setAttribute("aria-valuemax", "100");
|
|
1477
2114
|
const what = el("span", { className: "job-label" });
|
|
1478
2115
|
const count = el("span", { className: "job-count" });
|
|
1479
2116
|
const time = el("span", { className: "job-time" });
|
|
1480
2117
|
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
2118
|
+
cancel.type = "button";
|
|
1481
2119
|
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1482
2120
|
strip.hidden = true;
|
|
1483
2121
|
host.append(strip);
|
|
2122
|
+
let runPinned = pinned;
|
|
2123
|
+
let indeterminate = false;
|
|
2124
|
+
const setMode = (band) => {
|
|
2125
|
+
indeterminate = band;
|
|
2126
|
+
strip.classList.toggle("indeterminate", band);
|
|
2127
|
+
count.hidden = band;
|
|
2128
|
+
if (band) {
|
|
2129
|
+
fill.style.width = "";
|
|
2130
|
+
bar.removeAttribute("aria-valuenow");
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
1484
2133
|
const view = {
|
|
1485
2134
|
element: strip,
|
|
1486
2135
|
get visible() {
|
|
1487
2136
|
return !strip.hidden;
|
|
1488
2137
|
},
|
|
1489
|
-
|
|
2138
|
+
get indeterminate() {
|
|
2139
|
+
return indeterminate;
|
|
2140
|
+
},
|
|
2141
|
+
begin(label2, run) {
|
|
2142
|
+
runPinned = run?.indeterminate ?? pinned;
|
|
2143
|
+
const cancellable = run?.cancellable ?? cancellableByDefault;
|
|
1490
2144
|
what.textContent = label2 ?? "Running\u2026";
|
|
1491
2145
|
count.textContent = "";
|
|
1492
2146
|
time.textContent = "";
|
|
1493
|
-
|
|
2147
|
+
cancel.hidden = !cancellable;
|
|
1494
2148
|
cancel.disabled = false;
|
|
1495
2149
|
cancel.textContent = "Cancel";
|
|
2150
|
+
setMode(true);
|
|
1496
2151
|
strip.hidden = false;
|
|
1497
2152
|
},
|
|
1498
2153
|
update(done, total, elapsedMs) {
|
|
1499
2154
|
if (strip.hidden) strip.hidden = false;
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
2155
|
+
const band = runPinned || !(total > 0);
|
|
2156
|
+
setMode(band);
|
|
2157
|
+
if (!band) {
|
|
2158
|
+
const pct = Math.min(100, done / total * 100);
|
|
2159
|
+
fill.style.width = `${pct}%`;
|
|
2160
|
+
bar.setAttribute("aria-valuenow", String(Math.round(pct)));
|
|
2161
|
+
count.textContent = `${done} / ${total} ${units}`.trimEnd();
|
|
2162
|
+
}
|
|
2163
|
+
if (!band && estimable(done, total, elapsedMs)) {
|
|
1504
2164
|
const remaining = elapsedMs / done * (total - done);
|
|
1505
|
-
time.textContent = `${clock(elapsedMs)} elapsed
|
|
2165
|
+
time.textContent = `${clock(elapsedMs)} elapsed, about ${clock(remaining)} left`;
|
|
1506
2166
|
} else {
|
|
1507
2167
|
time.textContent = clock(elapsedMs);
|
|
1508
2168
|
}
|
|
@@ -1514,8 +2174,10 @@ function mountJobProgress(host, opts) {
|
|
|
1514
2174
|
cancel.addEventListener("click", () => {
|
|
1515
2175
|
cancel.disabled = true;
|
|
1516
2176
|
cancel.textContent = "Stopping\u2026";
|
|
1517
|
-
opts.onCancel();
|
|
2177
|
+
opts.onCancel?.();
|
|
1518
2178
|
});
|
|
2179
|
+
cancel.hidden = !cancellableByDefault;
|
|
2180
|
+
setMode(true);
|
|
1519
2181
|
return view;
|
|
1520
2182
|
}
|
|
1521
2183
|
|
|
@@ -1641,11 +2303,14 @@ function staleBar(opts) {
|
|
|
1641
2303
|
className: "stale-bar-msg",
|
|
1642
2304
|
text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
|
|
1643
2305
|
}),
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2306
|
+
// The icon is a node beside the word, not a character inside it; the
|
|
2307
|
+
// button's flex gap sets the space (stale.css).
|
|
2308
|
+
el(
|
|
2309
|
+
"button",
|
|
2310
|
+
{ className: "stale-bar-go", onClick: () => opts.onRestart() },
|
|
2311
|
+
iconNode("restart", 12),
|
|
2312
|
+
label2
|
|
2313
|
+
)
|
|
1649
2314
|
);
|
|
1650
2315
|
}
|
|
1651
2316
|
|
|
@@ -1687,14 +2352,14 @@ function dismissToast() {
|
|
|
1687
2352
|
}
|
|
1688
2353
|
|
|
1689
2354
|
// src/stepper.ts
|
|
2355
|
+
var STEP_ICON = 12;
|
|
1690
2356
|
function renderStepperBar(host, opts) {
|
|
1691
2357
|
const { items } = opts;
|
|
1692
2358
|
const close = opts.onClose === void 0 ? null : el("button", {
|
|
1693
2359
|
className: "stepbar-nav stepbar-close",
|
|
1694
|
-
text: icon.close,
|
|
1695
2360
|
tip: opts.closeTip ?? "Close",
|
|
1696
2361
|
onClick: opts.onClose
|
|
1697
|
-
});
|
|
2362
|
+
}, iconNode("close", STEP_ICON));
|
|
1698
2363
|
const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
|
|
1699
2364
|
if (items.length === 0) {
|
|
1700
2365
|
if (opts.empty === void 0) {
|
|
@@ -1718,17 +2383,15 @@ function renderStepperBar(host, opts) {
|
|
|
1718
2383
|
const nav = items.length > 1 ? [
|
|
1719
2384
|
el("button", {
|
|
1720
2385
|
className: "stepbar-nav",
|
|
1721
|
-
text: icon.back,
|
|
1722
2386
|
tip: opts.tips?.prev ?? "Previous",
|
|
1723
2387
|
onClick: () => step(-1)
|
|
1724
|
-
}),
|
|
2388
|
+
}, iconNode("back", STEP_ICON)),
|
|
1725
2389
|
el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
|
|
1726
2390
|
el("button", {
|
|
1727
2391
|
className: "stepbar-nav",
|
|
1728
|
-
text: icon.forward,
|
|
1729
2392
|
tip: opts.tips?.next ?? "Next",
|
|
1730
2393
|
onClick: () => step(1)
|
|
1731
|
-
})
|
|
2394
|
+
}, iconNode("forward", STEP_ICON))
|
|
1732
2395
|
] : [];
|
|
1733
2396
|
host.replaceChildren(
|
|
1734
2397
|
el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
|
|
@@ -1745,19 +2408,383 @@ function renderStepperBar(host, opts) {
|
|
|
1745
2408
|
);
|
|
1746
2409
|
}
|
|
1747
2410
|
|
|
2411
|
+
// src/updater-view.ts
|
|
2412
|
+
var liveProgress = null;
|
|
2413
|
+
var mb = (n) => (n / (1024 * 1024)).toFixed(1);
|
|
2414
|
+
function feedUpdaterDownloadProgress(p) {
|
|
2415
|
+
if (!liveProgress) return;
|
|
2416
|
+
const pct = Math.max(0, Math.min(100, p.percent));
|
|
2417
|
+
liveProgress.bar.style.width = `${pct}%`;
|
|
2418
|
+
liveProgress.label.textContent = `${pct.toFixed(0)}%, ${mb(p.transferred)} of ${mb(p.total)} MB (${mb(p.bytesPerSecond)} MB/s)`;
|
|
2419
|
+
}
|
|
2420
|
+
function showUpdaterDialog(opts) {
|
|
2421
|
+
return new Promise((resolve) => {
|
|
2422
|
+
const defaultId = opts.defaultId ?? 0;
|
|
2423
|
+
const cancelId = opts.cancelId ?? opts.buttons.length - 1;
|
|
2424
|
+
let done = false;
|
|
2425
|
+
const finish = (idx) => {
|
|
2426
|
+
if (done) return;
|
|
2427
|
+
done = true;
|
|
2428
|
+
liveProgress = null;
|
|
2429
|
+
resolve(idx);
|
|
2430
|
+
frame.close();
|
|
2431
|
+
};
|
|
2432
|
+
const frame = dialogFrame({
|
|
2433
|
+
title: opts.message,
|
|
2434
|
+
className: "updater-dialog",
|
|
2435
|
+
// Esc is the cancel button, not a further answer: main is waiting on an index.
|
|
2436
|
+
onClose: () => finish(cancelId)
|
|
2437
|
+
});
|
|
2438
|
+
if (opts.detail) frame.body.append(el("p", "updater-detail", opts.detail));
|
|
2439
|
+
if (opts.progress) {
|
|
2440
|
+
const track = el("div", "updater-progress-track");
|
|
2441
|
+
const bar = el("div", "updater-progress-bar");
|
|
2442
|
+
track.append(bar);
|
|
2443
|
+
const label2 = el("p", "updater-progress-label", "Starting download\u2026");
|
|
2444
|
+
frame.body.append(track, label2);
|
|
2445
|
+
liveProgress = { bar, label: label2 };
|
|
2446
|
+
}
|
|
2447
|
+
if (opts.links?.length) {
|
|
2448
|
+
const row = el("p", "updater-links");
|
|
2449
|
+
for (const link of opts.links) {
|
|
2450
|
+
const a = el("a", void 0, link.label);
|
|
2451
|
+
a.href = link.url;
|
|
2452
|
+
a.addEventListener("click", (e) => {
|
|
2453
|
+
e.preventDefault();
|
|
2454
|
+
opts.openExternal?.(link.url);
|
|
2455
|
+
});
|
|
2456
|
+
row.append(a);
|
|
2457
|
+
}
|
|
2458
|
+
frame.body.append(row);
|
|
2459
|
+
}
|
|
2460
|
+
opts.buttons.forEach((label2, i) => {
|
|
2461
|
+
const b = el("button", i === defaultId ? "btn primary" : "btn", label2);
|
|
2462
|
+
b.type = "button";
|
|
2463
|
+
b.addEventListener("click", () => finish(i));
|
|
2464
|
+
frame.actions.append(b);
|
|
2465
|
+
if (i === defaultId) queueMicrotask(() => b.focus());
|
|
2466
|
+
});
|
|
2467
|
+
frame.open();
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// src/welcome.ts
|
|
2472
|
+
function mountWelcome(host, opts) {
|
|
2473
|
+
const caption = (text) => el("h2", "welcome-caption", text);
|
|
2474
|
+
const group = (label2, ...body) => el("section", "welcome-group", caption(label2), ...body);
|
|
2475
|
+
const actions = el("div", "welcome-actions");
|
|
2476
|
+
for (const a of opts.actions) {
|
|
2477
|
+
const b = el("button", { className: `btn${a.primary ? " primary" : ""}`, text: a.label, onClick: a.onClick });
|
|
2478
|
+
b.type = "button";
|
|
2479
|
+
actions.append(b);
|
|
2480
|
+
}
|
|
2481
|
+
const groups = (opts.groups ?? []).map((g) => group(
|
|
2482
|
+
g.caption,
|
|
2483
|
+
g.note !== void 0 ? el("p", "welcome-note", g.note) : null,
|
|
2484
|
+
el("div", "welcome-list", ...g.items.map((item) => {
|
|
2485
|
+
const b = el(
|
|
2486
|
+
"button",
|
|
2487
|
+
{ className: "welcome-item", onClick: item.onOpen },
|
|
2488
|
+
el("span", "welcome-item-name", item.name),
|
|
2489
|
+
item.hint !== void 0 ? el("span", "welcome-item-hint", item.hint) : null
|
|
2490
|
+
);
|
|
2491
|
+
b.type = "button";
|
|
2492
|
+
return b;
|
|
2493
|
+
}))
|
|
2494
|
+
));
|
|
2495
|
+
const recentsHost = el("div", "welcome-recents-slot");
|
|
2496
|
+
const drawRecents = (recents) => {
|
|
2497
|
+
const shown = recents.slice(0, opts.maxRecents ?? 5);
|
|
2498
|
+
recentsHost.replaceChildren();
|
|
2499
|
+
if (shown.length === 0) return;
|
|
2500
|
+
recentsHost.append(group(
|
|
2501
|
+
opts.recentsCaption ?? "Recent",
|
|
2502
|
+
el("div", "welcome-recents", ...shown.map((r) => {
|
|
2503
|
+
const b = el(
|
|
2504
|
+
"button",
|
|
2505
|
+
{ className: "welcome-recent", onClick: r.onOpen },
|
|
2506
|
+
el("span", "welcome-recent-name", r.name),
|
|
2507
|
+
el("span", "welcome-recent-path", r.path)
|
|
2508
|
+
);
|
|
2509
|
+
b.type = "button";
|
|
2510
|
+
return b;
|
|
2511
|
+
}))
|
|
2512
|
+
));
|
|
2513
|
+
};
|
|
2514
|
+
drawRecents(opts.recents);
|
|
2515
|
+
const error = el("p", "welcome-error");
|
|
2516
|
+
const setError = (text) => {
|
|
2517
|
+
error.textContent = text ?? "";
|
|
2518
|
+
error.hidden = text === void 0 || text === "";
|
|
2519
|
+
};
|
|
2520
|
+
setError(opts.error);
|
|
2521
|
+
const root = el("div", "welcome", el(
|
|
2522
|
+
"div",
|
|
2523
|
+
"welcome-card",
|
|
2524
|
+
el("h1", "welcome-title", opts.title),
|
|
2525
|
+
el("p", "welcome-sub", opts.sub),
|
|
2526
|
+
actions,
|
|
2527
|
+
opts.tourLine ? el("div", "welcome-line", opts.tourLine) : null,
|
|
2528
|
+
...groups,
|
|
2529
|
+
recentsHost,
|
|
2530
|
+
error
|
|
2531
|
+
));
|
|
2532
|
+
host.replaceChildren(root);
|
|
2533
|
+
return { el: root, setRecents: drawRecents, setError };
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/link-status.ts
|
|
2537
|
+
function linkStatusClass(s) {
|
|
2538
|
+
if (s.state === "off" || s.state === "error") return "off";
|
|
2539
|
+
if (s.state === "listening") return "listening";
|
|
2540
|
+
return s.build === "stale" ? "stale" : "live";
|
|
2541
|
+
}
|
|
2542
|
+
function linkStatusTip(s) {
|
|
2543
|
+
switch (s.state) {
|
|
2544
|
+
case "off":
|
|
2545
|
+
return "Live link is off. Click to start listening.";
|
|
2546
|
+
case "error":
|
|
2547
|
+
return `Live link failed (${s.message}). Click to retry.`;
|
|
2548
|
+
case "listening":
|
|
2549
|
+
return "Live link is listening for a game. Click to stop.";
|
|
2550
|
+
case "connected": {
|
|
2551
|
+
const who = s.project !== void 0 ? ` to ${s.project}` : "";
|
|
2552
|
+
const build = s.build === "stale" ? " on a different build, so save or rebuild to re-sync" : s.build === "match" ? " and in sync" : "";
|
|
2553
|
+
const note = s.note ? ` ${s.note}` : "";
|
|
2554
|
+
return `Live link is connected${who}${build}.${note} Click to stop.`;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
function linkAddress(s) {
|
|
2559
|
+
if (s.state !== "listening" && s.state !== "connected") return void 0;
|
|
2560
|
+
return s.address ?? `ws://127.0.0.1:${s.port}`;
|
|
2561
|
+
}
|
|
2562
|
+
function mountLinkStatus(host, opts) {
|
|
2563
|
+
const wrap = el("div", "linkstatus");
|
|
2564
|
+
wrap.hidden = true;
|
|
2565
|
+
const url = el("button", "linkstatus-url");
|
|
2566
|
+
url.type = "button";
|
|
2567
|
+
url.hidden = true;
|
|
2568
|
+
const toggle = el("button", "linkstatus-toggle off");
|
|
2569
|
+
toggle.type = "button";
|
|
2570
|
+
toggle.append(iconNode("connect"));
|
|
2571
|
+
if (opts.label !== void 0) wrap.append(el("span", "linkstatus-word", opts.label));
|
|
2572
|
+
wrap.append(...opts.lead ?? [], url, toggle);
|
|
2573
|
+
host.append(wrap);
|
|
2574
|
+
let status = { state: "off" };
|
|
2575
|
+
const render = () => {
|
|
2576
|
+
toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;
|
|
2577
|
+
const tip2 = linkStatusTip(status);
|
|
2578
|
+
toggle.dataset["tip"] = tip2;
|
|
2579
|
+
toggle.setAttribute("aria-label", tip2);
|
|
2580
|
+
const addr = linkAddress(status);
|
|
2581
|
+
if (addr !== void 0) {
|
|
2582
|
+
url.textContent = addr;
|
|
2583
|
+
url.dataset["tip"] = "Click to copy the live link address";
|
|
2584
|
+
url.hidden = false;
|
|
2585
|
+
} else url.hidden = true;
|
|
2586
|
+
};
|
|
2587
|
+
const apply = (s) => {
|
|
2588
|
+
status = s;
|
|
2589
|
+
render();
|
|
2590
|
+
};
|
|
2591
|
+
render();
|
|
2592
|
+
toggle.addEventListener("click", () => {
|
|
2593
|
+
const out = opts.onToggle(status);
|
|
2594
|
+
void Promise.resolve(out).then((next) => {
|
|
2595
|
+
if (next) apply(next);
|
|
2596
|
+
});
|
|
2597
|
+
});
|
|
2598
|
+
url.addEventListener("click", () => {
|
|
2599
|
+
const addr = url.textContent ?? "";
|
|
2600
|
+
if (!addr) return;
|
|
2601
|
+
void copyWithFeedback(url, addr);
|
|
2602
|
+
opts.onCopy?.(addr);
|
|
2603
|
+
});
|
|
2604
|
+
return {
|
|
2605
|
+
el: wrap,
|
|
2606
|
+
apply,
|
|
2607
|
+
status: () => status,
|
|
2608
|
+
setVisible(on) {
|
|
2609
|
+
wrap.hidden = !on;
|
|
2610
|
+
},
|
|
2611
|
+
toggle() {
|
|
2612
|
+
toggle.click();
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// src/property-list.ts
|
|
2618
|
+
var PROPERTY_TYPES = [
|
|
2619
|
+
["number", "Number"],
|
|
2620
|
+
["boolean", "True / False"],
|
|
2621
|
+
["string", "Text"],
|
|
2622
|
+
["enum", "List"],
|
|
2623
|
+
["flags", "Flags"],
|
|
2624
|
+
["quality", "Quality"]
|
|
2625
|
+
];
|
|
2626
|
+
var option = (value, label2, selected) => {
|
|
2627
|
+
const o = el("option", void 0, label2);
|
|
2628
|
+
o.value = value;
|
|
2629
|
+
o.selected = selected;
|
|
2630
|
+
return o;
|
|
2631
|
+
};
|
|
2632
|
+
var NONE = "(none)";
|
|
2633
|
+
function defaultControl(p, onChange, opts = {}) {
|
|
2634
|
+
const typed = opts.typed === true;
|
|
2635
|
+
const write = (raw) => {
|
|
2636
|
+
if (raw === "") {
|
|
2637
|
+
if (typed) delete p.default;
|
|
2638
|
+
else p.default = "";
|
|
2639
|
+
} else if (typed && p.type === "boolean") p.default = raw === "true";
|
|
2640
|
+
else if (typed && p.type === "number") p.default = Number(raw);
|
|
2641
|
+
else p.default = raw;
|
|
2642
|
+
onChange?.();
|
|
2643
|
+
};
|
|
2644
|
+
const current3 = p.default === void 0 || p.default === null ? "" : String(p.default);
|
|
2645
|
+
if (p.type === "boolean") {
|
|
2646
|
+
const sel = el("select", "field set-default");
|
|
2647
|
+
sel.append(option("", NONE, current3 === ""), option("true", "True", current3 === "true"), option("false", "False", current3 === "false"));
|
|
2648
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2649
|
+
return sel;
|
|
2650
|
+
}
|
|
2651
|
+
if (p.type === "enum") {
|
|
2652
|
+
const sel = el("select", "field set-default");
|
|
2653
|
+
sel.append(option("", NONE, current3 === ""));
|
|
2654
|
+
for (const v of p.values ?? []) sel.append(option(v, v, current3 === v));
|
|
2655
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2656
|
+
return sel;
|
|
2657
|
+
}
|
|
2658
|
+
if (p.type === "quality") {
|
|
2659
|
+
const sel = el("select", "field set-default");
|
|
2660
|
+
sel.append(option("", "(first stage)", current3 === ""));
|
|
2661
|
+
for (const v of p.stages ?? []) sel.append(option(v, v, current3 === v));
|
|
2662
|
+
sel.addEventListener("change", () => write(sel.value));
|
|
2663
|
+
return sel;
|
|
2664
|
+
}
|
|
2665
|
+
if (p.type === "flags") {
|
|
2666
|
+
const s = el("span", "set-flagnote", "Starts empty");
|
|
2667
|
+
s.dataset["tip"] = "A flags property starts with none set.";
|
|
2668
|
+
return s;
|
|
2669
|
+
}
|
|
2670
|
+
const input = el("input", "field set-default");
|
|
2671
|
+
input.type = p.type === "number" ? "number" : "text";
|
|
2672
|
+
input.placeholder = "Default";
|
|
2673
|
+
input.value = current3;
|
|
2674
|
+
input.addEventListener("input", () => write(input.value));
|
|
2675
|
+
return input;
|
|
2676
|
+
}
|
|
2677
|
+
function mountPropertyList(host, decls, opts = {}) {
|
|
2678
|
+
const changed = () => opts.onChange?.();
|
|
2679
|
+
const guard = dupGuard();
|
|
2680
|
+
const types = opts.types ?? PROPERTY_TYPES;
|
|
2681
|
+
const list = el("div", "set-list");
|
|
2682
|
+
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
2683
|
+
const row = (p, i) => {
|
|
2684
|
+
const name = el("input", "field set-name");
|
|
2685
|
+
name.type = "text";
|
|
2686
|
+
name.placeholder = "Property name";
|
|
2687
|
+
name.value = p.name;
|
|
2688
|
+
name.spellcheck = false;
|
|
2689
|
+
name.dataset["tip"] = PROPERTY_NAME_HINT;
|
|
2690
|
+
bindPropertyName(name, (v) => {
|
|
2691
|
+
p.name = v;
|
|
2692
|
+
changed();
|
|
2693
|
+
}, { hint: PROPERTY_NAME_HINT });
|
|
2694
|
+
guard.track(name);
|
|
2695
|
+
const type = el("select", "field set-type");
|
|
2696
|
+
for (const [v, label2] of types) type.append(option(v, label2, v === p.type));
|
|
2697
|
+
type.addEventListener("change", () => {
|
|
2698
|
+
p.type = type.value;
|
|
2699
|
+
if (opts.typed) delete p.default;
|
|
2700
|
+
else p.default = "";
|
|
2701
|
+
if (p.type === "enum" || p.type === "flags") p.values ??= [];
|
|
2702
|
+
else delete p.values;
|
|
2703
|
+
if (p.type === "quality") p.stages ??= [];
|
|
2704
|
+
else delete p.stages;
|
|
2705
|
+
render();
|
|
2706
|
+
changed();
|
|
2707
|
+
});
|
|
2708
|
+
let def = defaultControl(p, changed, opts);
|
|
2709
|
+
const refreshDefault = () => {
|
|
2710
|
+
const fresh = defaultControl(p, changed, opts);
|
|
2711
|
+
def.replaceWith(fresh);
|
|
2712
|
+
def = fresh;
|
|
2713
|
+
changed();
|
|
2714
|
+
};
|
|
2715
|
+
const up = iconBtn("up", "Move up", () => {
|
|
2716
|
+
if (moveItem(decls, i, -1)) {
|
|
2717
|
+
render();
|
|
2718
|
+
changed();
|
|
2719
|
+
}
|
|
2720
|
+
}, i === 0);
|
|
2721
|
+
const down = iconBtn("down", "Move down", () => {
|
|
2722
|
+
if (moveItem(decls, i, 1)) {
|
|
2723
|
+
render();
|
|
2724
|
+
changed();
|
|
2725
|
+
}
|
|
2726
|
+
}, i === decls.length - 1);
|
|
2727
|
+
const del = iconBtn("close", "Delete property", () => {
|
|
2728
|
+
decls.splice(i, 1);
|
|
2729
|
+
render();
|
|
2730
|
+
changed();
|
|
2731
|
+
}, false, true);
|
|
2732
|
+
const details = keep(opts.extraDetails?.(p, i));
|
|
2733
|
+
if (p.type === "enum" || p.type === "flags") details.push(labelled("Values", tagChips(p, refreshDefault)));
|
|
2734
|
+
if (p.type === "quality") details.push(labelled("Stages (in order)", stageChips(p, refreshDefault)));
|
|
2735
|
+
if (opts.purpose !== false) {
|
|
2736
|
+
const purpose = el("input", "field set-purpose");
|
|
2737
|
+
purpose.type = "text";
|
|
2738
|
+
purpose.placeholder = "What this property is for";
|
|
2739
|
+
purpose.value = p.purpose ?? "";
|
|
2740
|
+
purpose.addEventListener("input", () => {
|
|
2741
|
+
if (purpose.value.trim()) p.purpose = purpose.value;
|
|
2742
|
+
else delete p.purpose;
|
|
2743
|
+
changed();
|
|
2744
|
+
});
|
|
2745
|
+
details.push(labelled("Purpose", purpose));
|
|
2746
|
+
}
|
|
2747
|
+
return expandableRow({ line: [name, type, def, ...keep(opts.extraLine?.(p, i)), up, down, del], details, name: p.name });
|
|
2748
|
+
};
|
|
2749
|
+
const render = () => {
|
|
2750
|
+
guard.reset();
|
|
2751
|
+
list.replaceChildren();
|
|
2752
|
+
if (decls.length === 0) list.append(el("p", "empty", opts.emptyText ?? "No properties yet."));
|
|
2753
|
+
else decls.forEach((p, i) => list.append(row(p, i)));
|
|
2754
|
+
guard.check();
|
|
2755
|
+
};
|
|
2756
|
+
render();
|
|
2757
|
+
const add = el("button", "btn set-add", opts.addLabel ?? "+ Add property");
|
|
2758
|
+
add.type = "button";
|
|
2759
|
+
add.addEventListener("click", () => {
|
|
2760
|
+
decls.push(opts.newDecl ? opts.newDecl() : { name: "", type: types[0]?.[0] ?? "string", ...opts.typed ? {} : { default: "" } });
|
|
2761
|
+
render();
|
|
2762
|
+
changed();
|
|
2763
|
+
focusNewRow(list);
|
|
2764
|
+
});
|
|
2765
|
+
host.append(list, add);
|
|
2766
|
+
return {
|
|
2767
|
+
firstDuplicate: () => guard.firstDuplicate(),
|
|
2768
|
+
firstIllegalName: () => firstIllegalPropertyName(host),
|
|
2769
|
+
// The Save gate takes both faults; the field's own rollover says which.
|
|
2770
|
+
firstInvalid: () => guard.firstDuplicate() ?? firstIllegalPropertyName(host),
|
|
2771
|
+
render
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
|
|
1748
2775
|
// src/nav-history.ts
|
|
1749
2776
|
function createNavHistory(opts) {
|
|
1750
2777
|
const cap = opts.cap ?? 100;
|
|
1751
2778
|
const usable = opts.usable ?? (() => true);
|
|
1752
2779
|
let past = [];
|
|
1753
2780
|
let ahead = [];
|
|
1754
|
-
const step = (from, to,
|
|
2781
|
+
const step = (from, to, current3) => {
|
|
1755
2782
|
for (; ; ) {
|
|
1756
2783
|
const p = from.pop();
|
|
1757
2784
|
if (p === void 0) return void 0;
|
|
1758
2785
|
if (!usable(p)) continue;
|
|
1759
|
-
if (opts.same(p,
|
|
1760
|
-
to.push(
|
|
2786
|
+
if (opts.same(p, current3)) continue;
|
|
2787
|
+
to.push(current3);
|
|
1761
2788
|
return p;
|
|
1762
2789
|
}
|
|
1763
2790
|
};
|
|
@@ -1769,11 +2796,11 @@ function createNavHistory(opts) {
|
|
|
1769
2796
|
else past.push(place2);
|
|
1770
2797
|
if (past.length > cap) past = past.slice(past.length - cap);
|
|
1771
2798
|
},
|
|
1772
|
-
back(
|
|
1773
|
-
return step(past, ahead,
|
|
2799
|
+
back(current3) {
|
|
2800
|
+
return step(past, ahead, current3);
|
|
1774
2801
|
},
|
|
1775
|
-
forward(
|
|
1776
|
-
return step(ahead, past,
|
|
2802
|
+
forward(current3) {
|
|
2803
|
+
return step(ahead, past, current3);
|
|
1777
2804
|
},
|
|
1778
2805
|
canBack() {
|
|
1779
2806
|
return past.length > 0;
|
|
@@ -1784,18 +2811,18 @@ function createNavHistory(opts) {
|
|
|
1784
2811
|
};
|
|
1785
2812
|
}
|
|
1786
2813
|
function historyNav(onBack, onForward) {
|
|
1787
|
-
const btn = (
|
|
2814
|
+
const btn = (name, tip2, onClick) => {
|
|
1788
2815
|
const b = document.createElement("button");
|
|
1789
2816
|
b.type = "button";
|
|
1790
2817
|
b.className = "shell-histnav-btn";
|
|
1791
|
-
b.
|
|
2818
|
+
b.append(iconNode(name));
|
|
1792
2819
|
b.dataset["tip"] = tip2;
|
|
1793
2820
|
b.setAttribute("aria-label", tip2);
|
|
1794
2821
|
b.addEventListener("click", onClick);
|
|
1795
2822
|
return b;
|
|
1796
2823
|
};
|
|
1797
|
-
const back = btn("
|
|
1798
|
-
const forward = btn("
|
|
2824
|
+
const back = btn("arrowLeft", "Back", onBack);
|
|
2825
|
+
const forward = btn("arrowRight", "Forward", onForward);
|
|
1799
2826
|
const el2 = document.createElement("span");
|
|
1800
2827
|
el2.className = "shell-histnav";
|
|
1801
2828
|
el2.append(back, forward);
|
|
@@ -1809,13 +2836,21 @@ function historyNav(onBack, onForward) {
|
|
|
1809
2836
|
}
|
|
1810
2837
|
|
|
1811
2838
|
exports.EDIT_MENU = EDIT_MENU;
|
|
2839
|
+
exports.FILE_MENU = FILE_MENU;
|
|
1812
2840
|
exports.GO_MENU = GO_MENU;
|
|
2841
|
+
exports.ICON_NAMES = ICON_NAMES;
|
|
2842
|
+
exports.ICON_SIZE = ICON_SIZE;
|
|
1813
2843
|
exports.JOB_PROGRESS = JOB_PROGRESS;
|
|
1814
2844
|
exports.PALETTE = PALETTE;
|
|
1815
2845
|
exports.PALETTE_SIZE = PALETTE_SIZE;
|
|
1816
2846
|
exports.PANE_MENU = PANE_MENU;
|
|
2847
|
+
exports.PLAY_MENU = PLAY_MENU;
|
|
1817
2848
|
exports.PROPERTY_NAME_HINT = PROPERTY_NAME_HINT;
|
|
2849
|
+
exports.PROPERTY_TYPES = PROPERTY_TYPES;
|
|
2850
|
+
exports.PUBLISH_MENU = PUBLISH_MENU;
|
|
1818
2851
|
exports.RESERVED_PROPERTY_NAMES = RESERVED_PROPERTY_NAMES;
|
|
2852
|
+
exports.REVIEW_MENU = REVIEW_MENU;
|
|
2853
|
+
exports.VIEW_MENU = VIEW_MENU;
|
|
1819
2854
|
exports.askIdentity = askIdentity;
|
|
1820
2855
|
exports.bindPropertyName = bindPropertyName;
|
|
1821
2856
|
exports.bindPropertyRef = bindPropertyRef;
|
|
@@ -1824,51 +2859,80 @@ exports.closeWithExit = closeWithExit;
|
|
|
1824
2859
|
exports.colourFor = colourFor;
|
|
1825
2860
|
exports.colourIndex = colourIndex;
|
|
1826
2861
|
exports.confirmDialog = confirmDialog;
|
|
2862
|
+
exports.copyWithFeedback = copyWithFeedback;
|
|
2863
|
+
exports.createFloating = createFloating;
|
|
1827
2864
|
exports.createNavHistory = createNavHistory;
|
|
1828
2865
|
exports.createSaveController = createSaveController;
|
|
2866
|
+
exports.debounce = debounce;
|
|
2867
|
+
exports.defaultControl = defaultControl;
|
|
2868
|
+
exports.dialogFrame = dialogFrame;
|
|
1829
2869
|
exports.dismissToast = dismissToast;
|
|
1830
2870
|
exports.dupGuard = dupGuard;
|
|
1831
2871
|
exports.el = el;
|
|
1832
2872
|
exports.expandableRow = expandableRow;
|
|
2873
|
+
exports.feedUpdaterDownloadProgress = feedUpdaterDownloadProgress;
|
|
1833
2874
|
exports.firstIllegalPropertyName = firstIllegalPropertyName;
|
|
1834
2875
|
exports.focusNewRow = focusNewRow;
|
|
1835
2876
|
exports.foldVc = foldVc;
|
|
2877
|
+
exports.followButton = followButton;
|
|
2878
|
+
exports.formatCount = formatCount;
|
|
1836
2879
|
exports.gameIdify = gameIdify;
|
|
1837
2880
|
exports.hideTip = hideTip;
|
|
1838
2881
|
exports.historyNav = historyNav;
|
|
1839
2882
|
exports.icon = icon;
|
|
1840
2883
|
exports.iconBtn = iconBtn;
|
|
2884
|
+
exports.iconHtml = iconHtml;
|
|
2885
|
+
exports.iconNameOfGlyph = iconNameOfGlyph;
|
|
2886
|
+
exports.iconNode = iconNode;
|
|
1841
2887
|
exports.iconSvg = iconSvg;
|
|
1842
2888
|
exports.initTooltips = initTooltips;
|
|
1843
2889
|
exports.isCaseOnlyPropertyName = isCaseOnlyPropertyName;
|
|
2890
|
+
exports.isEditableTarget = isEditableTarget;
|
|
2891
|
+
exports.isIconName = isIconName;
|
|
1844
2892
|
exports.isValidGameId = isValidGameId;
|
|
1845
2893
|
exports.isValidPropertyName = isValidPropertyName;
|
|
1846
2894
|
exports.labelled = labelled;
|
|
2895
|
+
exports.linkAddress = linkAddress;
|
|
2896
|
+
exports.linkStatusClass = linkStatusClass;
|
|
2897
|
+
exports.linkStatusTip = linkStatusTip;
|
|
1847
2898
|
exports.lockControls = lockControls;
|
|
1848
2899
|
exports.lockNotice = lockNotice;
|
|
1849
2900
|
exports.mountJobProgress = mountJobProgress;
|
|
2901
|
+
exports.mountLinkStatus = mountLinkStatus;
|
|
1850
2902
|
exports.mountPaneShell = mountPaneShell;
|
|
2903
|
+
exports.mountPropertyList = mountPropertyList;
|
|
1851
2904
|
exports.mountSettingsDialog = mountSettingsDialog;
|
|
2905
|
+
exports.mountWelcome = mountWelcome;
|
|
1852
2906
|
exports.moveItem = moveItem;
|
|
1853
2907
|
exports.openAnchoredPanel = openAnchoredPanel;
|
|
1854
2908
|
exports.openComments = openComments;
|
|
2909
|
+
exports.openContextMenu = openContextMenu;
|
|
1855
2910
|
exports.openGameIdEditor = openGameIdEditor;
|
|
1856
2911
|
exports.openNotesEditor = openNotesEditor;
|
|
2912
|
+
exports.openPopover = openPopover;
|
|
1857
2913
|
exports.paintVcBadges = paintVcBadges;
|
|
1858
2914
|
exports.pinButton = pinButton;
|
|
1859
2915
|
exports.placeAnchored = placeAnchored;
|
|
2916
|
+
exports.plural = plural;
|
|
1860
2917
|
exports.propertyNameProblem = propertyNameProblem;
|
|
1861
2918
|
exports.propertyNameify = propertyNameify;
|
|
1862
2919
|
exports.propertyRefProblem = propertyRefProblem;
|
|
2920
|
+
exports.relativeTime = relativeTime;
|
|
1863
2921
|
exports.renderStepperBar = renderStepperBar;
|
|
1864
2922
|
exports.revalidatePropertyRefs = revalidatePropertyRefs;
|
|
2923
|
+
exports.revealRow = revealRow;
|
|
2924
|
+
exports.revealRowWhenReady = revealRowWhenReady;
|
|
1865
2925
|
exports.saveIndicator = saveIndicator;
|
|
1866
2926
|
exports.showAbout = showAbout;
|
|
2927
|
+
exports.showUpdaterDialog = showUpdaterDialog;
|
|
2928
|
+
exports.stageChips = stageChips;
|
|
1867
2929
|
exports.staleBar = staleBar;
|
|
1868
2930
|
exports.tagChips = tagChips;
|
|
1869
2931
|
exports.tipAt = tipAt;
|
|
1870
2932
|
exports.tipBold = tipBold;
|
|
1871
2933
|
exports.toast = toast;
|
|
2934
|
+
exports.toolWindowHead = toolWindowHead;
|
|
1872
2935
|
exports.vcBadgeFor = vcBadgeFor;
|
|
2936
|
+
exports.wireReorder = wireReorder;
|
|
1873
2937
|
//# sourceMappingURL=index.cjs.map
|
|
1874
2938
|
//# sourceMappingURL=index.cjs.map
|