@wildwinter/app-shell 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES.md +62 -0
- package/README.md +8 -0
- package/dist/about.css +13 -34
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +5 -5
- package/dist/anchored.css.map +1 -1
- package/dist/app-store.cjs +15 -0
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +25 -1
- package/dist/app-store.d.ts +25 -1
- package/dist/app-store.js +14 -1
- package/dist/app-store.js.map +1 -1
- package/dist/chunk-2ECJUTSX.js +97 -0
- package/dist/chunk-2ECJUTSX.js.map +1 -0
- package/dist/chunk-2NMVIM54.js +55 -0
- package/dist/chunk-2NMVIM54.js.map +1 -0
- package/dist/chunk-C6UVQYKI.js +70 -0
- package/dist/chunk-C6UVQYKI.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-K2AOLQW4.js +82 -0
- package/dist/chunk-K2AOLQW4.js.map +1 -0
- package/dist/chunk-KGAMP5LW.js +87 -0
- package/dist/chunk-KGAMP5LW.js.map +1 -0
- package/dist/chunk-L5HOBEKL.js +534 -0
- package/dist/chunk-L5HOBEKL.js.map +1 -0
- package/dist/chunk-P4G4ERG6.js +60 -0
- package/dist/chunk-P4G4ERG6.js.map +1 -0
- package/dist/chunk-P6WLAEKZ.js +71 -0
- package/dist/chunk-P6WLAEKZ.js.map +1 -0
- package/dist/chunk-S4KWQLYN.js +66 -0
- package/dist/chunk-S4KWQLYN.js.map +1 -0
- package/dist/chunk-XZYZXY7I.js +554 -0
- package/dist/chunk-XZYZXY7I.js.map +1 -0
- package/dist/comments.css +5 -3
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +1 -54
- package/dist/confirm.css.map +1 -1
- package/dist/context-menu.cjs +250 -0
- package/dist/context-menu.cjs.map +1 -0
- package/dist/context-menu.css +64 -0
- package/dist/context-menu.css.map +1 -0
- package/dist/context-menu.d.cts +25 -0
- package/dist/context-menu.d.ts +25 -0
- package/dist/context-menu.js +5 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/controls.css +155 -0
- package/dist/controls.css.map +1 -0
- package/dist/dialog.cjs +222 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.css +55 -0
- package/dist/dialog.css.map +1 -0
- package/dist/dialog.d.cts +28 -0
- package/dist/dialog.d.ts +28 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/floating.cjs +83 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +25 -0
- package/dist/floating.d.ts +25 -0
- package/dist/floating.js +4 -0
- package/dist/floating.js.map +1 -0
- package/dist/identity.css +15 -50
- package/dist/identity.css.map +1 -1
- package/dist/index.cjs +1307 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -149
- package/dist/index.d.ts +349 -149
- package/dist/index.js +259 -822
- package/dist/index.js.map +1 -1
- package/dist/job.css +23 -0
- package/dist/job.css.map +1 -1
- package/dist/link-status.cjs +430 -0
- package/dist/link-status.cjs.map +1 -0
- package/dist/link-status.css +76 -0
- package/dist/link-status.css.map +1 -0
- package/dist/link-status.d.cts +53 -0
- package/dist/link-status.d.ts +53 -0
- package/dist/link-status.js +5 -0
- package/dist/link-status.js.map +1 -0
- package/dist/menu.cjs +62 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +88 -1
- package/dist/menu.d.ts +88 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +3 -46
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +16 -3
- package/dist/pane-shell.css.map +1 -1
- package/dist/property-list-D3VZCJzi.d.cts +162 -0
- package/dist/property-list-D3VZCJzi.d.ts +162 -0
- package/dist/property-list.cjs +784 -0
- package/dist/property-list.cjs.map +1 -0
- package/dist/property-list.d.cts +1 -0
- package/dist/property-list.d.ts +1 -0
- package/dist/property-list.js +4 -0
- package/dist/property-list.js.map +1 -0
- package/dist/settings.css +52 -8
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +3 -0
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +35 -0
- package/dist/stepper.css.map +1 -1
- package/dist/tokens.css +19 -0
- package/dist/tokens.css.map +1 -1
- package/dist/tool-window.cjs +63 -0
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.css +6 -3
- package/dist/tool-window.css.map +1 -1
- package/dist/tool-window.d.cts +67 -1
- package/dist/tool-window.d.ts +67 -1
- package/dist/tool-window.js +63 -1
- package/dist/tool-window.js.map +1 -1
- package/dist/updater-view.cjs +283 -0
- package/dist/updater-view.cjs.map +1 -0
- package/dist/updater-view.d.cts +23 -0
- package/dist/updater-view.d.ts +23 -0
- package/dist/updater-view.js +6 -0
- package/dist/updater-view.js.map +1 -0
- package/dist/updater.cjs +12 -16
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.css +44 -0
- package/dist/updater.css.map +1 -0
- package/dist/updater.js +12 -16
- package/dist/updater.js.map +1 -1
- package/dist/util.cjs +78 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +37 -0
- package/dist/util.d.ts +37 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/dist/vc.css +3 -2
- package/dist/vc.css.map +1 -1
- package/dist/welcome.cjs +215 -0
- package/dist/welcome.cjs.map +1 -0
- package/dist/welcome.css +112 -0
- package/dist/welcome.css.map +1 -0
- package/dist/welcome.d.cts +55 -0
- package/dist/welcome.d.ts +55 -0
- package/dist/welcome.js +4 -0
- package/dist/welcome.js.map +1 -0
- package/package.json +58 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
package/dist/index.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,6 +746,38 @@ 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) {
|
|
@@ -525,6 +821,22 @@ function revealRow(within, name) {
|
|
|
525
821
|
row.addEventListener("animationend", () => row.classList.remove("landed"), { once: true });
|
|
526
822
|
return true;
|
|
527
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
|
+
}
|
|
528
840
|
function focusNewRow(listEl) {
|
|
529
841
|
const last = listEl?.lastElementChild;
|
|
530
842
|
if (!(last instanceof HTMLElement)) return;
|
|
@@ -532,6 +844,7 @@ function focusNewRow(listEl) {
|
|
|
532
844
|
const name = last.querySelector("input.set-name") ?? last.querySelector("input");
|
|
533
845
|
name?.focus();
|
|
534
846
|
}
|
|
847
|
+
var DUP_MESSAGE = "Another row already has this name.";
|
|
535
848
|
function dupGuard() {
|
|
536
849
|
const items = [];
|
|
537
850
|
const check = () => {
|
|
@@ -546,9 +859,9 @@ function dupGuard() {
|
|
|
546
859
|
const dup = !!k && (counts.get(k) ?? 0) > 1;
|
|
547
860
|
it.input.classList.toggle("invalid", dup);
|
|
548
861
|
if (dup) {
|
|
549
|
-
it.input.
|
|
862
|
+
it.input.dataset["tip"] = DUP_MESSAGE;
|
|
550
863
|
any = true;
|
|
551
|
-
} else if (it.input.
|
|
864
|
+
} else if (it.input.dataset["tip"] === DUP_MESSAGE) delete it.input.dataset["tip"];
|
|
552
865
|
}
|
|
553
866
|
return any;
|
|
554
867
|
};
|
|
@@ -594,7 +907,9 @@ function mountSettingsDialog(opts) {
|
|
|
594
907
|
const tab = el("button", "settings-tab", s.label);
|
|
595
908
|
tab.type = "button";
|
|
596
909
|
tab.dataset["tab"] = s.id;
|
|
597
|
-
tab.addEventListener("click", () =>
|
|
910
|
+
tab.addEventListener("click", () => {
|
|
911
|
+
if (!tab.classList.contains("is-disabled")) showTab(s.id);
|
|
912
|
+
});
|
|
598
913
|
tabs.append(tab);
|
|
599
914
|
tabFor.set(s.id, tab);
|
|
600
915
|
const panel = el("section", "settings-panel");
|
|
@@ -608,6 +923,25 @@ function mountSettingsDialog(opts) {
|
|
|
608
923
|
for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
|
|
609
924
|
for (const [sid, p] of panelFor) p.hidden = sid !== id;
|
|
610
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
|
+
}
|
|
611
945
|
form.append(
|
|
612
946
|
el("h2", "settings-title", opts.title),
|
|
613
947
|
el("div", "settings-body", tabs, panels),
|
|
@@ -623,14 +957,15 @@ function mountSettingsDialog(opts) {
|
|
|
623
957
|
e.preventDefault();
|
|
624
958
|
error.hidden = true;
|
|
625
959
|
for (const s of opts.sections) {
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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;
|
|
634
969
|
}
|
|
635
970
|
void (async () => {
|
|
636
971
|
await opts.onSave();
|
|
@@ -647,25 +982,94 @@ function mountSettingsDialog(opts) {
|
|
|
647
982
|
}
|
|
648
983
|
error.hidden = true;
|
|
649
984
|
showTab(tabId ?? active2 ?? opts.sections[0]?.id ?? "");
|
|
985
|
+
refreshTabs();
|
|
650
986
|
dialog.showModal();
|
|
651
987
|
},
|
|
988
|
+
refreshTabs,
|
|
652
989
|
destroy() {
|
|
653
990
|
dialog.remove();
|
|
654
991
|
}
|
|
655
992
|
};
|
|
656
993
|
}
|
|
657
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
|
+
|
|
658
1063
|
// src/tool-window-web.ts
|
|
659
|
-
var PIN_ICON = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="17" x2="12" y2="22"/><path d="M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.3V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.7a2 2 0 0 0-1.1-1.8l-1.8-.9A2 2 0 0 1 15 10.8V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>`;
|
|
660
1064
|
function pinButton(opts) {
|
|
661
1065
|
const b = el("button", "swin-pin");
|
|
662
1066
|
b.type = "button";
|
|
663
|
-
b.
|
|
1067
|
+
b.append(iconNode("pin"));
|
|
664
1068
|
let pinned = opts.pinned;
|
|
665
1069
|
const reflect = () => {
|
|
666
1070
|
b.classList.toggle("on", pinned);
|
|
667
1071
|
b.setAttribute("aria-pressed", String(pinned));
|
|
668
|
-
const label2 = pinned ? "Pinned on top
|
|
1072
|
+
const label2 = pinned ? "Pinned on top. Click to unpin." : "Keep on top";
|
|
669
1073
|
b.dataset["tip"] = label2;
|
|
670
1074
|
b.setAttribute("aria-label", label2);
|
|
671
1075
|
};
|
|
@@ -680,6 +1084,61 @@ function pinButton(opts) {
|
|
|
680
1084
|
reflect();
|
|
681
1085
|
} };
|
|
682
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
|
+
}
|
|
683
1142
|
|
|
684
1143
|
// src/exit.ts
|
|
685
1144
|
function closeWithExit(element, done) {
|
|
@@ -704,61 +1163,232 @@ function closeWithExit(element, done) {
|
|
|
704
1163
|
const timer3 = setTimeout(finish, seconds * 1e3 + 120);
|
|
705
1164
|
}
|
|
706
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
|
+
|
|
707
1215
|
// src/confirm.ts
|
|
708
1216
|
function confirmDialog(opts) {
|
|
709
1217
|
return new Promise((resolve) => {
|
|
710
|
-
const cancel = el("button", "confirm-btn cancel", "Cancel");
|
|
711
|
-
const danger = el("button", "confirm-btn danger", opts.confirmLabel);
|
|
712
|
-
cancel.type = "button";
|
|
713
|
-
danger.type = "button";
|
|
714
|
-
const dlg = el(
|
|
715
|
-
"dialog",
|
|
716
|
-
"confirm-dialog",
|
|
717
|
-
el("div", "confirm-title", opts.title),
|
|
718
|
-
el("div", "confirm-body", opts.body),
|
|
719
|
-
el("div", "confirm-actions", cancel, danger)
|
|
720
|
-
);
|
|
721
|
-
dlg.setAttribute("aria-label", opts.title);
|
|
722
|
-
document.body.append(dlg);
|
|
723
1218
|
let done = false;
|
|
724
1219
|
const finish = (ok) => {
|
|
725
1220
|
if (done) return;
|
|
726
1221
|
done = true;
|
|
727
1222
|
resolve(ok);
|
|
728
|
-
|
|
729
|
-
dlg.close();
|
|
730
|
-
dlg.remove();
|
|
731
|
-
});
|
|
1223
|
+
frame.close();
|
|
732
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);
|
|
733
1238
|
cancel.addEventListener("click", () => finish(false));
|
|
734
1239
|
danger.addEventListener("click", () => finish(true));
|
|
735
|
-
|
|
736
|
-
e.
|
|
737
|
-
finish(false);
|
|
1240
|
+
frame.dialog.addEventListener("mousedown", (e) => {
|
|
1241
|
+
if (e.target === frame.dialog) finish(false);
|
|
738
1242
|
});
|
|
739
|
-
|
|
740
|
-
if (e.target === dlg) finish(false);
|
|
741
|
-
});
|
|
742
|
-
dlg.showModal();
|
|
1243
|
+
frame.open();
|
|
743
1244
|
cancel.focus();
|
|
744
1245
|
});
|
|
745
1246
|
}
|
|
746
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
|
+
|
|
747
1379
|
// src/about.ts
|
|
748
1380
|
function showAbout(opts) {
|
|
749
1381
|
return new Promise((resolve) => {
|
|
750
|
-
const
|
|
1382
|
+
const frame = dialogFrame({ title: opts.appName, className: "shell-about", onClose: () => resolve() });
|
|
1383
|
+
frame.dialog.setAttribute("aria-label", `About ${opts.appName}`);
|
|
751
1384
|
if (opts.wordmark !== void 0) {
|
|
752
1385
|
const mark = el("div", "shell-about-mark");
|
|
753
1386
|
mark.innerHTML = opts.wordmark;
|
|
754
|
-
dialog.
|
|
1387
|
+
frame.dialog.prepend(mark);
|
|
755
1388
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
);
|
|
760
|
-
if (opts.blurb !== void 0) dialog.append(el("p", "shell-about-blurb", opts.blurb));
|
|
761
|
-
if (opts.credits !== void 0) dialog.append(el("p", "shell-about-credits", opts.credits));
|
|
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));
|
|
762
1392
|
if (opts.links?.length) {
|
|
763
1393
|
const row = el("div", "shell-about-links");
|
|
764
1394
|
for (const link of opts.links) {
|
|
@@ -771,39 +1401,28 @@ function showAbout(opts) {
|
|
|
771
1401
|
a.addEventListener("click", () => opts.onOpenLink?.(link.url));
|
|
772
1402
|
row.append(a);
|
|
773
1403
|
}
|
|
774
|
-
|
|
1404
|
+
frame.body.append(row);
|
|
775
1405
|
}
|
|
776
|
-
const close = el("button", "shell-about-close", "Close");
|
|
1406
|
+
const close = el("button", "btn shell-about-close", "Close");
|
|
777
1407
|
close.type = "button";
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
let done = false;
|
|
782
|
-
const finish = () => {
|
|
783
|
-
if (done) return;
|
|
784
|
-
done = true;
|
|
785
|
-
dialog.close();
|
|
786
|
-
dialog.remove();
|
|
787
|
-
resolve();
|
|
788
|
-
};
|
|
789
|
-
close.addEventListener("click", finish);
|
|
790
|
-
dialog.addEventListener("close", finish);
|
|
791
|
-
dialog.showModal();
|
|
1408
|
+
close.addEventListener("click", () => frame.close());
|
|
1409
|
+
frame.actions.append(close);
|
|
1410
|
+
frame.open();
|
|
792
1411
|
close.focus();
|
|
793
1412
|
});
|
|
794
1413
|
}
|
|
795
1414
|
|
|
796
1415
|
// src/anchored.ts
|
|
797
|
-
var
|
|
1416
|
+
var current2 = null;
|
|
798
1417
|
function openAnchoredPanel(opts) {
|
|
799
|
-
if (
|
|
800
|
-
const prev =
|
|
801
|
-
|
|
1418
|
+
if (current2) {
|
|
1419
|
+
const prev = current2;
|
|
1420
|
+
current2 = null;
|
|
802
1421
|
prev.close();
|
|
803
1422
|
if (prev.anchor === opts.anchor) return null;
|
|
804
1423
|
}
|
|
805
1424
|
const panel = el("div", `shell-anchored ${opts.className ?? ""}`.trim());
|
|
806
|
-
const closeBtn = el("button", "shell-anchored-close", "
|
|
1425
|
+
const closeBtn = el("button", "shell-anchored-close", iconNode("close", 12));
|
|
807
1426
|
closeBtn.type = "button";
|
|
808
1427
|
closeBtn.setAttribute("aria-label", "Close");
|
|
809
1428
|
const body = el("div", "shell-anchored-body");
|
|
@@ -811,13 +1430,13 @@ function openAnchoredPanel(opts) {
|
|
|
811
1430
|
el("div", "shell-anchored-head", el("span", "shell-anchored-title", opts.title), closeBtn),
|
|
812
1431
|
body
|
|
813
1432
|
);
|
|
814
|
-
document.body.append(panel);
|
|
1433
|
+
(opts.host ?? document.body).append(panel);
|
|
815
1434
|
placeAnchored(panel, opts.anchor, opts.width, opts.prefer ?? "below", opts.keepClear ?? []);
|
|
816
1435
|
let closed = false;
|
|
817
1436
|
const close = () => {
|
|
818
1437
|
if (closed) return;
|
|
819
1438
|
closed = true;
|
|
820
|
-
if (
|
|
1439
|
+
if (current2?.panel === panel) current2 = null;
|
|
821
1440
|
document.removeEventListener("pointerdown", onDown, true);
|
|
822
1441
|
document.removeEventListener("keydown", onKey, true);
|
|
823
1442
|
closeWithExit(panel, () => {
|
|
@@ -841,12 +1460,12 @@ function openAnchoredPanel(opts) {
|
|
|
841
1460
|
document.addEventListener("pointerdown", onDown, true);
|
|
842
1461
|
document.addEventListener("keydown", onKey, true);
|
|
843
1462
|
}, 0);
|
|
844
|
-
|
|
1463
|
+
current2 = { anchor: opts.anchor, panel, close };
|
|
845
1464
|
return { panel, body, close };
|
|
846
1465
|
}
|
|
847
1466
|
function closeAnchoredPanel() {
|
|
848
|
-
const open =
|
|
849
|
-
|
|
1467
|
+
const open = current2;
|
|
1468
|
+
current2 = null;
|
|
850
1469
|
open?.close();
|
|
851
1470
|
}
|
|
852
1471
|
function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
@@ -903,7 +1522,7 @@ function isValidGameId(gameId) {
|
|
|
903
1522
|
|
|
904
1523
|
// src/property-names.ts
|
|
905
1524
|
var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
|
|
906
|
-
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.";
|
|
907
1526
|
function propertyNameify(text) {
|
|
908
1527
|
const trimmed = text.trim();
|
|
909
1528
|
const deliberateLeading = trimmed.startsWith("_");
|
|
@@ -927,14 +1546,14 @@ function propertyNameProblem(name) {
|
|
|
927
1546
|
const suggestion = propertyNameify(name);
|
|
928
1547
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
929
1548
|
if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
|
|
930
|
-
return `
|
|
1549
|
+
return `Can't be used. "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
|
|
931
1550
|
}
|
|
932
|
-
if (name.includes("-")) return `
|
|
933
|
-
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}`;
|
|
934
1553
|
if (name !== name.toLowerCase()) {
|
|
935
|
-
return `
|
|
1554
|
+
return `Can't be used. Expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
|
|
936
1555
|
}
|
|
937
|
-
return `
|
|
1556
|
+
return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
|
|
938
1557
|
}
|
|
939
1558
|
function bindPropertyName(input, onCommit, opts = {}) {
|
|
940
1559
|
const hint = opts.hint;
|
|
@@ -943,7 +1562,7 @@ function bindPropertyName(input, onCommit, opts = {}) {
|
|
|
943
1562
|
const problem = v === "" ? void 0 : propertyNameProblem(v);
|
|
944
1563
|
input.classList.toggle("illegal", !!problem);
|
|
945
1564
|
if (problem) input.title = problem;
|
|
946
|
-
else if (input.title.startsWith("
|
|
1565
|
+
else if (input.title.startsWith("Can't be used")) {
|
|
947
1566
|
if (hint) input.title = hint;
|
|
948
1567
|
else input.removeAttribute("title");
|
|
949
1568
|
}
|
|
@@ -999,10 +1618,10 @@ function propertyRefProblem(name, opts) {
|
|
|
999
1618
|
if (!isValidPropertyName(folded)) {
|
|
1000
1619
|
const suggestion = propertyNameify(name);
|
|
1001
1620
|
const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
|
|
1002
|
-
if (RESERVED_PROPERTY_NAMES.includes(folded)) return `
|
|
1003
|
-
if (name.includes("-")) return `
|
|
1004
|
-
if (/^[0-9]/.test(name)) return `
|
|
1005
|
-
return `
|
|
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}`;
|
|
1006
1625
|
}
|
|
1007
1626
|
const known = opts.known();
|
|
1008
1627
|
if (known.some((k) => k.toLowerCase() === folded)) return void 0;
|
|
@@ -1011,7 +1630,7 @@ function propertyRefProblem(name, opts) {
|
|
|
1011
1630
|
if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
|
|
1012
1631
|
const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
|
|
1013
1632
|
const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
|
|
1014
|
-
return `No ${scope} property is called "${name}".
|
|
1633
|
+
return `No ${scope} property is called "${name}". Pick one of ${shown}${more}.`;
|
|
1015
1634
|
}
|
|
1016
1635
|
function bindPropertyRef(input, onCommit, opts) {
|
|
1017
1636
|
const list = document.createElement("datalist");
|
|
@@ -1032,7 +1651,7 @@ function bindPropertyRef(input, onCommit, opts) {
|
|
|
1032
1651
|
const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
|
|
1033
1652
|
input.classList.toggle("illegal", !!problem);
|
|
1034
1653
|
if (problem) input.title = problem;
|
|
1035
|
-
else if (input.title.startsWith("
|
|
1654
|
+
else if (input.title.startsWith("Can't be used") || input.title.startsWith("No ")) {
|
|
1036
1655
|
if (opts.hint) input.title = opts.hint;
|
|
1037
1656
|
else input.removeAttribute("title");
|
|
1038
1657
|
}
|
|
@@ -1075,9 +1694,9 @@ function openGameIdEditor(opts) {
|
|
|
1075
1694
|
const input = el("input", "shell-id-input");
|
|
1076
1695
|
input.type = "text";
|
|
1077
1696
|
input.value = opts.value;
|
|
1078
|
-
input.placeholder = opts.derived || "
|
|
1697
|
+
input.placeholder = opts.derived || "game-id";
|
|
1079
1698
|
input.spellcheck = false;
|
|
1080
|
-
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.");
|
|
1081
1700
|
const actions = el("div", "shell-id-actions");
|
|
1082
1701
|
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
1083
1702
|
const set = el("button", "shell-id-set", "Set");
|
|
@@ -1139,12 +1758,13 @@ function foldVc(shards, keys) {
|
|
|
1139
1758
|
return out;
|
|
1140
1759
|
}
|
|
1141
1760
|
function vcBadgeFor(s) {
|
|
1142
|
-
|
|
1143
|
-
if (s?.
|
|
1144
|
-
if (s?.
|
|
1145
|
-
if (s?.
|
|
1146
|
-
if (s?.
|
|
1147
|
-
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.");
|
|
1148
1768
|
return null;
|
|
1149
1769
|
}
|
|
1150
1770
|
function paintVcBadges(root, shards) {
|
|
@@ -1152,7 +1772,7 @@ function paintVcBadges(root, shards) {
|
|
|
1152
1772
|
host.querySelector(":scope > .vc-badge")?.remove();
|
|
1153
1773
|
const badge = vcBadgeFor(foldVc(shards, host.dataset["vc"]));
|
|
1154
1774
|
if (!badge) return;
|
|
1155
|
-
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));
|
|
1156
1776
|
host.append(span);
|
|
1157
1777
|
});
|
|
1158
1778
|
}
|
|
@@ -1175,8 +1795,8 @@ function lockNotice(holders) {
|
|
|
1175
1795
|
return el(
|
|
1176
1796
|
"div",
|
|
1177
1797
|
{ className: "vc-lock" },
|
|
1178
|
-
el("span", { className: "vc-lock-glyph",
|
|
1179
|
-
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.` })
|
|
1180
1800
|
);
|
|
1181
1801
|
}
|
|
1182
1802
|
|
|
@@ -1184,41 +1804,36 @@ function lockNotice(holders) {
|
|
|
1184
1804
|
function askIdentity(opts = {}) {
|
|
1185
1805
|
return new Promise((resolve) => {
|
|
1186
1806
|
const seed = opts.current ?? opts.suggested;
|
|
1187
|
-
const name = el("input", "shell-ident-input");
|
|
1807
|
+
const name = el("input", "field shell-ident-input");
|
|
1188
1808
|
name.placeholder = "Your name";
|
|
1189
1809
|
name.value = seed?.name ?? "";
|
|
1190
|
-
const email = el("input", "shell-ident-input");
|
|
1191
|
-
email.placeholder = "
|
|
1810
|
+
const email = el("input", "field shell-ident-input");
|
|
1811
|
+
email.placeholder = "Optional";
|
|
1192
1812
|
email.type = "email";
|
|
1193
1813
|
email.value = seed?.email ?? "";
|
|
1194
|
-
const skip = el("button", "shell-ident-btn", "Skip");
|
|
1195
|
-
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");
|
|
1196
1816
|
skip.type = "button";
|
|
1197
1817
|
save.type = "button";
|
|
1198
|
-
const dialog = el(
|
|
1199
|
-
"dialog",
|
|
1200
|
-
"shell-ident",
|
|
1201
|
-
el("div", "shell-ident-title", "Who is working here?"),
|
|
1202
|
-
el(
|
|
1203
|
-
"p",
|
|
1204
|
-
"shell-ident-hint",
|
|
1205
|
-
"Stamped on the comments you write. Kept in this app, never in the project, and changeable later."
|
|
1206
|
-
),
|
|
1207
|
-
el("div", "shell-ident-fields", name, email),
|
|
1208
|
-
el("div", "shell-ident-actions", skip, save)
|
|
1209
|
-
);
|
|
1210
|
-
document.body.append(dialog);
|
|
1211
1818
|
let done = false;
|
|
1212
1819
|
const finish = (keep) => {
|
|
1213
1820
|
if (done) return;
|
|
1214
1821
|
done = true;
|
|
1215
1822
|
const typed = name.value.trim();
|
|
1216
1823
|
const value = email.value.trim();
|
|
1217
|
-
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : void 0;
|
|
1218
|
-
dialog.close();
|
|
1219
|
-
dialog.remove();
|
|
1824
|
+
const answer = keep && typed !== "" ? value === "" ? { name: typed } : { name: typed, email: value } : opts.neverAskAgainOnSkip === true ? { name: "" } : void 0;
|
|
1220
1825
|
resolve(answer);
|
|
1826
|
+
frame.close();
|
|
1221
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);
|
|
1222
1837
|
skip.addEventListener("click", () => finish(false));
|
|
1223
1838
|
save.addEventListener("click", () => finish(true));
|
|
1224
1839
|
name.addEventListener("keydown", (e) => {
|
|
@@ -1227,11 +1842,19 @@ function askIdentity(opts = {}) {
|
|
|
1227
1842
|
email.addEventListener("keydown", (e) => {
|
|
1228
1843
|
if (e.key === "Enter") finish(true);
|
|
1229
1844
|
});
|
|
1230
|
-
|
|
1231
|
-
dialog.showModal();
|
|
1845
|
+
frame.open();
|
|
1232
1846
|
name.focus();
|
|
1233
1847
|
});
|
|
1234
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
|
+
}
|
|
1235
1858
|
|
|
1236
1859
|
// src/notes-editor.ts
|
|
1237
1860
|
var EDITOR_ONLY = "";
|
|
@@ -1244,10 +1867,10 @@ var CLASS_LABEL = {
|
|
|
1244
1867
|
};
|
|
1245
1868
|
var label = (cls) => cls === EDITOR_ONLY ? "Note (editor-only)" : CLASS_LABEL[cls] ?? cls[0].toUpperCase() + cls.slice(1);
|
|
1246
1869
|
var PLACEHOLDER = {
|
|
1247
|
-
vo: "
|
|
1248
|
-
loc: "
|
|
1870
|
+
vo: "Direction for the voice actor or director",
|
|
1871
|
+
loc: "Context for translators"
|
|
1249
1872
|
};
|
|
1250
|
-
var placeholder = (cls) => cls === EDITOR_ONLY ? "
|
|
1873
|
+
var placeholder = (cls) => cls === EDITOR_ONLY ? "An internal note, never exported" : PLACEHOLDER[cls] ?? "Why this is here";
|
|
1251
1874
|
function openNotesEditor(opts) {
|
|
1252
1875
|
const { notes } = opts;
|
|
1253
1876
|
const seed = /* @__PURE__ */ new Map();
|
|
@@ -1301,7 +1924,7 @@ ${line.text}` : line.text);
|
|
|
1301
1924
|
{ className: "shell-notes-class" },
|
|
1302
1925
|
el("label", "shell-notes-caption", label(cls))
|
|
1303
1926
|
);
|
|
1304
|
-
const area = el("textarea", "shell-notes-text");
|
|
1927
|
+
const area = el("textarea", "field shell-notes-text");
|
|
1305
1928
|
area.value = areas.get(cls)?.value ?? seed.get(cls) ?? "";
|
|
1306
1929
|
area.rows = 4;
|
|
1307
1930
|
area.placeholder = placeholder(cls);
|
|
@@ -1327,29 +1950,24 @@ ${line.text}` : line.text);
|
|
|
1327
1950
|
}
|
|
1328
1951
|
};
|
|
1329
1952
|
render();
|
|
1330
|
-
const done = el("button", "shell-notes-btn
|
|
1331
|
-
|
|
1332
|
-
"dialog",
|
|
1333
|
-
{ className: "shell-notes" },
|
|
1334
|
-
el("div", { className: "shell-notes-title", text: `Notes: ${opts.subject}` }),
|
|
1335
|
-
body,
|
|
1336
|
-
el("div", { className: "shell-notes-actions" }, done)
|
|
1337
|
-
);
|
|
1338
|
-
dialog.setAttribute("aria-label", `Notes: ${opts.subject}`);
|
|
1339
|
-
document.body.append(dialog);
|
|
1953
|
+
const done = el("button", "btn primary shell-notes-btn", "Done");
|
|
1954
|
+
done.type = "button";
|
|
1340
1955
|
const before = JSON.stringify(notes.own);
|
|
1341
1956
|
let closed = false;
|
|
1342
|
-
const
|
|
1957
|
+
const commit = () => {
|
|
1343
1958
|
if (closed) return;
|
|
1344
1959
|
closed = true;
|
|
1345
1960
|
const next = value();
|
|
1346
1961
|
if (JSON.stringify(next) !== before) opts.save(next);
|
|
1347
|
-
dialog.close();
|
|
1348
|
-
dialog.remove();
|
|
1349
1962
|
};
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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();
|
|
1353
1971
|
areas.get(shown[0] ?? EDITOR_ONLY)?.focus();
|
|
1354
1972
|
}
|
|
1355
1973
|
|
|
@@ -1421,7 +2039,7 @@ function openComments(opts) {
|
|
|
1421
2039
|
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1422
2040
|
);
|
|
1423
2041
|
if (opts.deleteMessage && message.deleted !== true) {
|
|
1424
|
-
const bin = el("button", { className: "shell-cmt-del",
|
|
2042
|
+
const bin = el("button", { className: "shell-cmt-del" }, iconNode("close", 12));
|
|
1425
2043
|
bin.dataset.tip = "Delete this comment";
|
|
1426
2044
|
bin.setAttribute("aria-label", "Delete this comment");
|
|
1427
2045
|
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
@@ -1486,37 +2104,65 @@ var clock = (ms) => {
|
|
|
1486
2104
|
var estimable = (done, total, elapsedMs) => done > 0 && total > 0 && elapsedMs > 1e3 && done / total > 0.1;
|
|
1487
2105
|
function mountJobProgress(host, opts) {
|
|
1488
2106
|
const units = opts.units ?? "runs";
|
|
2107
|
+
const pinned = opts.indeterminate === true;
|
|
2108
|
+
const cancellableByDefault = opts.cancellable ?? opts.onCancel !== void 0;
|
|
1489
2109
|
const fill = el("i", { className: "job-fill" });
|
|
1490
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");
|
|
1491
2114
|
const what = el("span", { className: "job-label" });
|
|
1492
2115
|
const count = el("span", { className: "job-count" });
|
|
1493
2116
|
const time = el("span", { className: "job-time" });
|
|
1494
2117
|
const cancel = el("button", { className: "job-cancel", text: "Cancel" });
|
|
2118
|
+
cancel.type = "button";
|
|
1495
2119
|
const strip = el("div", { className: "job-strip" }, what, bar, count, time, cancel);
|
|
1496
2120
|
strip.hidden = true;
|
|
1497
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
|
+
};
|
|
1498
2133
|
const view = {
|
|
1499
2134
|
element: strip,
|
|
1500
2135
|
get visible() {
|
|
1501
2136
|
return !strip.hidden;
|
|
1502
2137
|
},
|
|
1503
|
-
|
|
2138
|
+
get indeterminate() {
|
|
2139
|
+
return indeterminate;
|
|
2140
|
+
},
|
|
2141
|
+
begin(label2, run) {
|
|
2142
|
+
runPinned = run?.indeterminate ?? pinned;
|
|
2143
|
+
const cancellable = run?.cancellable ?? cancellableByDefault;
|
|
1504
2144
|
what.textContent = label2 ?? "Running\u2026";
|
|
1505
2145
|
count.textContent = "";
|
|
1506
2146
|
time.textContent = "";
|
|
1507
|
-
|
|
2147
|
+
cancel.hidden = !cancellable;
|
|
1508
2148
|
cancel.disabled = false;
|
|
1509
2149
|
cancel.textContent = "Cancel";
|
|
2150
|
+
setMode(true);
|
|
1510
2151
|
strip.hidden = false;
|
|
1511
2152
|
},
|
|
1512
2153
|
update(done, total, elapsedMs) {
|
|
1513
2154
|
if (strip.hidden) strip.hidden = false;
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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)) {
|
|
1518
2164
|
const remaining = elapsedMs / done * (total - done);
|
|
1519
|
-
time.textContent = `${clock(elapsedMs)} elapsed
|
|
2165
|
+
time.textContent = `${clock(elapsedMs)} elapsed, about ${clock(remaining)} left`;
|
|
1520
2166
|
} else {
|
|
1521
2167
|
time.textContent = clock(elapsedMs);
|
|
1522
2168
|
}
|
|
@@ -1528,8 +2174,10 @@ function mountJobProgress(host, opts) {
|
|
|
1528
2174
|
cancel.addEventListener("click", () => {
|
|
1529
2175
|
cancel.disabled = true;
|
|
1530
2176
|
cancel.textContent = "Stopping\u2026";
|
|
1531
|
-
opts.onCancel();
|
|
2177
|
+
opts.onCancel?.();
|
|
1532
2178
|
});
|
|
2179
|
+
cancel.hidden = !cancellableByDefault;
|
|
2180
|
+
setMode(true);
|
|
1533
2181
|
return view;
|
|
1534
2182
|
}
|
|
1535
2183
|
|
|
@@ -1655,11 +2303,14 @@ function staleBar(opts) {
|
|
|
1655
2303
|
className: "stale-bar-msg",
|
|
1656
2304
|
text: `${opts.subject} changed in the editor. ${opts.advice ?? "Restart to play the new version."}`
|
|
1657
2305
|
}),
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
+
)
|
|
1663
2314
|
);
|
|
1664
2315
|
}
|
|
1665
2316
|
|
|
@@ -1701,14 +2352,14 @@ function dismissToast() {
|
|
|
1701
2352
|
}
|
|
1702
2353
|
|
|
1703
2354
|
// src/stepper.ts
|
|
2355
|
+
var STEP_ICON = 12;
|
|
1704
2356
|
function renderStepperBar(host, opts) {
|
|
1705
2357
|
const { items } = opts;
|
|
1706
2358
|
const close = opts.onClose === void 0 ? null : el("button", {
|
|
1707
2359
|
className: "stepbar-nav stepbar-close",
|
|
1708
|
-
text: icon.close,
|
|
1709
2360
|
tip: opts.closeTip ?? "Close",
|
|
1710
2361
|
onClick: opts.onClose
|
|
1711
|
-
});
|
|
2362
|
+
}, iconNode("close", STEP_ICON));
|
|
1712
2363
|
const actions = (opts.actions ?? []).filter((a) => a !== null && a !== void 0);
|
|
1713
2364
|
if (items.length === 0) {
|
|
1714
2365
|
if (opts.empty === void 0) {
|
|
@@ -1732,17 +2383,15 @@ function renderStepperBar(host, opts) {
|
|
|
1732
2383
|
const nav = items.length > 1 ? [
|
|
1733
2384
|
el("button", {
|
|
1734
2385
|
className: "stepbar-nav",
|
|
1735
|
-
text: icon.back,
|
|
1736
2386
|
tip: opts.tips?.prev ?? "Previous",
|
|
1737
2387
|
onClick: () => step(-1)
|
|
1738
|
-
}),
|
|
2388
|
+
}, iconNode("back", STEP_ICON)),
|
|
1739
2389
|
el("span", { className: "stepbar-of", text: `${index + 1}/${items.length}` }),
|
|
1740
2390
|
el("button", {
|
|
1741
2391
|
className: "stepbar-nav",
|
|
1742
|
-
text: icon.forward,
|
|
1743
2392
|
tip: opts.tips?.next ?? "Next",
|
|
1744
2393
|
onClick: () => step(1)
|
|
1745
|
-
})
|
|
2394
|
+
}, iconNode("forward", STEP_ICON))
|
|
1746
2395
|
] : [];
|
|
1747
2396
|
host.replaceChildren(
|
|
1748
2397
|
el("span", { className: `stepbar-count ${opts.tone ?? "muted"}`, text: String(items.length) }),
|
|
@@ -1759,19 +2408,383 @@ function renderStepperBar(host, opts) {
|
|
|
1759
2408
|
);
|
|
1760
2409
|
}
|
|
1761
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
|
+
|
|
1762
2775
|
// src/nav-history.ts
|
|
1763
2776
|
function createNavHistory(opts) {
|
|
1764
2777
|
const cap = opts.cap ?? 100;
|
|
1765
2778
|
const usable = opts.usable ?? (() => true);
|
|
1766
2779
|
let past = [];
|
|
1767
2780
|
let ahead = [];
|
|
1768
|
-
const step = (from, to,
|
|
2781
|
+
const step = (from, to, current3) => {
|
|
1769
2782
|
for (; ; ) {
|
|
1770
2783
|
const p = from.pop();
|
|
1771
2784
|
if (p === void 0) return void 0;
|
|
1772
2785
|
if (!usable(p)) continue;
|
|
1773
|
-
if (opts.same(p,
|
|
1774
|
-
to.push(
|
|
2786
|
+
if (opts.same(p, current3)) continue;
|
|
2787
|
+
to.push(current3);
|
|
1775
2788
|
return p;
|
|
1776
2789
|
}
|
|
1777
2790
|
};
|
|
@@ -1783,11 +2796,11 @@ function createNavHistory(opts) {
|
|
|
1783
2796
|
else past.push(place2);
|
|
1784
2797
|
if (past.length > cap) past = past.slice(past.length - cap);
|
|
1785
2798
|
},
|
|
1786
|
-
back(
|
|
1787
|
-
return step(past, ahead,
|
|
2799
|
+
back(current3) {
|
|
2800
|
+
return step(past, ahead, current3);
|
|
1788
2801
|
},
|
|
1789
|
-
forward(
|
|
1790
|
-
return step(ahead, past,
|
|
2802
|
+
forward(current3) {
|
|
2803
|
+
return step(ahead, past, current3);
|
|
1791
2804
|
},
|
|
1792
2805
|
canBack() {
|
|
1793
2806
|
return past.length > 0;
|
|
@@ -1798,18 +2811,18 @@ function createNavHistory(opts) {
|
|
|
1798
2811
|
};
|
|
1799
2812
|
}
|
|
1800
2813
|
function historyNav(onBack, onForward) {
|
|
1801
|
-
const btn = (
|
|
2814
|
+
const btn = (name, tip2, onClick) => {
|
|
1802
2815
|
const b = document.createElement("button");
|
|
1803
2816
|
b.type = "button";
|
|
1804
2817
|
b.className = "shell-histnav-btn";
|
|
1805
|
-
b.
|
|
2818
|
+
b.append(iconNode(name));
|
|
1806
2819
|
b.dataset["tip"] = tip2;
|
|
1807
2820
|
b.setAttribute("aria-label", tip2);
|
|
1808
2821
|
b.addEventListener("click", onClick);
|
|
1809
2822
|
return b;
|
|
1810
2823
|
};
|
|
1811
|
-
const back = btn("
|
|
1812
|
-
const forward = btn("
|
|
2824
|
+
const back = btn("arrowLeft", "Back", onBack);
|
|
2825
|
+
const forward = btn("arrowRight", "Forward", onForward);
|
|
1813
2826
|
const el2 = document.createElement("span");
|
|
1814
2827
|
el2.className = "shell-histnav";
|
|
1815
2828
|
el2.append(back, forward);
|
|
@@ -1823,13 +2836,21 @@ function historyNav(onBack, onForward) {
|
|
|
1823
2836
|
}
|
|
1824
2837
|
|
|
1825
2838
|
exports.EDIT_MENU = EDIT_MENU;
|
|
2839
|
+
exports.FILE_MENU = FILE_MENU;
|
|
1826
2840
|
exports.GO_MENU = GO_MENU;
|
|
2841
|
+
exports.ICON_NAMES = ICON_NAMES;
|
|
2842
|
+
exports.ICON_SIZE = ICON_SIZE;
|
|
1827
2843
|
exports.JOB_PROGRESS = JOB_PROGRESS;
|
|
1828
2844
|
exports.PALETTE = PALETTE;
|
|
1829
2845
|
exports.PALETTE_SIZE = PALETTE_SIZE;
|
|
1830
2846
|
exports.PANE_MENU = PANE_MENU;
|
|
2847
|
+
exports.PLAY_MENU = PLAY_MENU;
|
|
1831
2848
|
exports.PROPERTY_NAME_HINT = PROPERTY_NAME_HINT;
|
|
2849
|
+
exports.PROPERTY_TYPES = PROPERTY_TYPES;
|
|
2850
|
+
exports.PUBLISH_MENU = PUBLISH_MENU;
|
|
1832
2851
|
exports.RESERVED_PROPERTY_NAMES = RESERVED_PROPERTY_NAMES;
|
|
2852
|
+
exports.REVIEW_MENU = REVIEW_MENU;
|
|
2853
|
+
exports.VIEW_MENU = VIEW_MENU;
|
|
1833
2854
|
exports.askIdentity = askIdentity;
|
|
1834
2855
|
exports.bindPropertyName = bindPropertyName;
|
|
1835
2856
|
exports.bindPropertyRef = bindPropertyRef;
|
|
@@ -1838,52 +2859,80 @@ exports.closeWithExit = closeWithExit;
|
|
|
1838
2859
|
exports.colourFor = colourFor;
|
|
1839
2860
|
exports.colourIndex = colourIndex;
|
|
1840
2861
|
exports.confirmDialog = confirmDialog;
|
|
2862
|
+
exports.copyWithFeedback = copyWithFeedback;
|
|
2863
|
+
exports.createFloating = createFloating;
|
|
1841
2864
|
exports.createNavHistory = createNavHistory;
|
|
1842
2865
|
exports.createSaveController = createSaveController;
|
|
2866
|
+
exports.debounce = debounce;
|
|
2867
|
+
exports.defaultControl = defaultControl;
|
|
2868
|
+
exports.dialogFrame = dialogFrame;
|
|
1843
2869
|
exports.dismissToast = dismissToast;
|
|
1844
2870
|
exports.dupGuard = dupGuard;
|
|
1845
2871
|
exports.el = el;
|
|
1846
2872
|
exports.expandableRow = expandableRow;
|
|
2873
|
+
exports.feedUpdaterDownloadProgress = feedUpdaterDownloadProgress;
|
|
1847
2874
|
exports.firstIllegalPropertyName = firstIllegalPropertyName;
|
|
1848
2875
|
exports.focusNewRow = focusNewRow;
|
|
1849
2876
|
exports.foldVc = foldVc;
|
|
2877
|
+
exports.followButton = followButton;
|
|
2878
|
+
exports.formatCount = formatCount;
|
|
1850
2879
|
exports.gameIdify = gameIdify;
|
|
1851
2880
|
exports.hideTip = hideTip;
|
|
1852
2881
|
exports.historyNav = historyNav;
|
|
1853
2882
|
exports.icon = icon;
|
|
1854
2883
|
exports.iconBtn = iconBtn;
|
|
2884
|
+
exports.iconHtml = iconHtml;
|
|
2885
|
+
exports.iconNameOfGlyph = iconNameOfGlyph;
|
|
2886
|
+
exports.iconNode = iconNode;
|
|
1855
2887
|
exports.iconSvg = iconSvg;
|
|
1856
2888
|
exports.initTooltips = initTooltips;
|
|
1857
2889
|
exports.isCaseOnlyPropertyName = isCaseOnlyPropertyName;
|
|
2890
|
+
exports.isEditableTarget = isEditableTarget;
|
|
2891
|
+
exports.isIconName = isIconName;
|
|
1858
2892
|
exports.isValidGameId = isValidGameId;
|
|
1859
2893
|
exports.isValidPropertyName = isValidPropertyName;
|
|
1860
2894
|
exports.labelled = labelled;
|
|
2895
|
+
exports.linkAddress = linkAddress;
|
|
2896
|
+
exports.linkStatusClass = linkStatusClass;
|
|
2897
|
+
exports.linkStatusTip = linkStatusTip;
|
|
1861
2898
|
exports.lockControls = lockControls;
|
|
1862
2899
|
exports.lockNotice = lockNotice;
|
|
1863
2900
|
exports.mountJobProgress = mountJobProgress;
|
|
2901
|
+
exports.mountLinkStatus = mountLinkStatus;
|
|
1864
2902
|
exports.mountPaneShell = mountPaneShell;
|
|
2903
|
+
exports.mountPropertyList = mountPropertyList;
|
|
1865
2904
|
exports.mountSettingsDialog = mountSettingsDialog;
|
|
2905
|
+
exports.mountWelcome = mountWelcome;
|
|
1866
2906
|
exports.moveItem = moveItem;
|
|
1867
2907
|
exports.openAnchoredPanel = openAnchoredPanel;
|
|
1868
2908
|
exports.openComments = openComments;
|
|
2909
|
+
exports.openContextMenu = openContextMenu;
|
|
1869
2910
|
exports.openGameIdEditor = openGameIdEditor;
|
|
1870
2911
|
exports.openNotesEditor = openNotesEditor;
|
|
2912
|
+
exports.openPopover = openPopover;
|
|
1871
2913
|
exports.paintVcBadges = paintVcBadges;
|
|
1872
2914
|
exports.pinButton = pinButton;
|
|
1873
2915
|
exports.placeAnchored = placeAnchored;
|
|
2916
|
+
exports.plural = plural;
|
|
1874
2917
|
exports.propertyNameProblem = propertyNameProblem;
|
|
1875
2918
|
exports.propertyNameify = propertyNameify;
|
|
1876
2919
|
exports.propertyRefProblem = propertyRefProblem;
|
|
2920
|
+
exports.relativeTime = relativeTime;
|
|
1877
2921
|
exports.renderStepperBar = renderStepperBar;
|
|
1878
2922
|
exports.revalidatePropertyRefs = revalidatePropertyRefs;
|
|
1879
2923
|
exports.revealRow = revealRow;
|
|
2924
|
+
exports.revealRowWhenReady = revealRowWhenReady;
|
|
1880
2925
|
exports.saveIndicator = saveIndicator;
|
|
1881
2926
|
exports.showAbout = showAbout;
|
|
2927
|
+
exports.showUpdaterDialog = showUpdaterDialog;
|
|
2928
|
+
exports.stageChips = stageChips;
|
|
1882
2929
|
exports.staleBar = staleBar;
|
|
1883
2930
|
exports.tagChips = tagChips;
|
|
1884
2931
|
exports.tipAt = tipAt;
|
|
1885
2932
|
exports.tipBold = tipBold;
|
|
1886
2933
|
exports.toast = toast;
|
|
2934
|
+
exports.toolWindowHead = toolWindowHead;
|
|
1887
2935
|
exports.vcBadgeFor = vcBadgeFor;
|
|
2936
|
+
exports.wireReorder = wireReorder;
|
|
1888
2937
|
//# sourceMappingURL=index.cjs.map
|
|
1889
2938
|
//# sourceMappingURL=index.cjs.map
|