@hypit/hypit 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/public/studio-adapter.d.ts +31 -2
- package/package.json +4 -1
- package/packages/audio-track-studio/src/index.ts +7 -4
- package/packages/build-result/README.md +58 -0
- package/packages/build-result/package.json +2 -1
- package/packages/build-result/src/decode.ts +10 -8
- package/packages/build-result/src/file-reference.ts +42 -0
- package/packages/build-result/src/index.ts +3 -0
- package/packages/build-result/src/replace-file-windows.ts +58 -0
- package/packages/build-result/src/replace-file.ts +11 -0
- package/packages/build-result/src/store.ts +44 -13
- package/packages/build-result/src/types.ts +38 -8
- package/packages/build-result/src/writer.ts +12 -1
- package/packages/build-result-fs/README.md +23 -1
- package/packages/build-result-s3/README.md +5 -0
- package/packages/build-result-s3/src/repository.ts +19 -5
- package/packages/caption-fine/README.md +17 -2
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +48 -36
- package/packages/caption-fine-studio/package.json +8 -3
- package/packages/caption-fine-studio/src/index.ts +10 -1
- package/packages/cli/README.md +25 -0
- package/packages/cli/src/arguments.ts +16 -11
- package/packages/cli/src/build-planning.ts +6 -1
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +42 -13
- package/packages/cli/src/machine-view.ts +12 -3
- package/packages/cli/src/main.ts +4 -0
- package/packages/cli/src/output.ts +28 -12
- package/packages/cli/src/result-export.ts +22 -13
- package/packages/cli/src/run-file.ts +19 -8
- package/packages/comment-sticker-studio/src/index.ts +2 -1
- package/packages/compiler-node/src/compiler.ts +1 -0
- package/packages/estimate/README.md +56 -3
- package/packages/estimate/src/program.ts +7 -8
- package/packages/fonts-open/README.md +5 -0
- package/packages/fonts-open/package.json +5 -1
- package/packages/fonts-open/src/studio.ts +21 -0
- package/packages/media-track-studio/src/index.ts +7 -2
- package/packages/provider-whisperx-local/README.md +57 -0
- package/packages/ranking/src/render.ts +11 -9
- package/packages/ranking-studio/README.md +11 -0
- package/packages/ranking-studio/package.json +8 -3
- package/packages/ranking-studio/src/index.ts +11 -4
- package/packages/runtime/src/catalog.ts +2 -0
- package/packages/runtime-host-node/src/index.ts +1 -0
- package/packages/runtime-local/README.md +19 -0
- package/packages/runtime-local/src/programs.ts +39 -24
- package/packages/runtime-local/src/runtime.ts +2 -0
- package/packages/script/README.md +22 -0
- package/packages/script/src/edit.ts +123 -73
- package/packages/script/src/lexical.ts +40 -6
- package/packages/script/src/parser.ts +36 -8
- package/packages/script/src/types.ts +4 -0
- package/packages/seedance-kits/README.md +3 -1
- package/packages/seedance-kits/kits/speaker-v1.svs +5 -5
- package/packages/speech-alignment/src/align.ts +45 -21
- package/packages/studio/INSPECTOR.md +104 -0
- package/packages/studio/README.md +65 -4
- package/packages/studio/src/build-library.ts +99 -84
- package/packages/studio/src/library-media.ts +19 -0
- package/packages/studio/src/parameter-values.ts +41 -1
- package/packages/studio/src/parameters.ts +20 -21
- package/packages/studio/src/server.ts +58 -30
- package/packages/studio/src/shared.ts +20 -6
- package/packages/studio/src/studio-registry.ts +22 -0
- package/packages/studio/src/style.css +135 -144
- package/packages/studio/src/temporal-edit.ts +131 -0
- package/packages/studio/src/ui/artifact-name.ts +80 -0
- package/packages/studio/src/ui/artifact-preview.ts +99 -0
- package/packages/studio/src/ui/icons.ts +5 -1
- package/packages/studio/src/ui/library.ts +373 -192
- package/packages/studio/src/ui/main.ts +153 -31
- package/packages/studio/src/ui/material-preview.ts +1 -1
- package/packages/studio/src/ui/overlay.ts +21 -20
- package/packages/studio/src/ui/sidebar-panel.ts +31 -0
- package/packages/studio/src/ui/stage.ts +124 -8
- package/packages/studio/src/ui/timeline.ts +16 -179
- package/packages/studio/start.ts +15 -3
- package/packages/studio-adapter/README.md +21 -0
- package/packages/studio-adapter/src/index.ts +26 -1
- package/packages/temporal-markup/EDITING.md +183 -0
- package/packages/temporal-markup/README.md +4 -0
- package/packages/typography-track-studio/package.json +8 -3
- package/packages/typography-track-studio/src/index.ts +5 -1
- package/packages/video-cli/README.md +12 -3
- package/packages/video-cli/src/creation.ts +11 -5
- package/packages/whisperx/README.md +7 -0
- package/packages/workspace/src/index.ts +2 -0
- package/packages/workspace-fs-node/README.md +4 -1
- package/packages/workspace-fs-node/src/workspace.ts +3 -1
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
--tone-neutral: #6a737c;
|
|
52
52
|
--tone-neutral-rail: #aeb9c4;
|
|
53
53
|
--timeline-label-width: 188px;
|
|
54
|
-
--source-width:
|
|
55
|
-
--workspace-width:
|
|
54
|
+
--source-width: 416px;
|
|
55
|
+
--workspace-width: 320px;
|
|
56
56
|
--timeline-height: 390px;
|
|
57
57
|
--timeline-item-radius: 2px;
|
|
58
58
|
--timeline-selection-stroke: 1px;
|
|
@@ -261,33 +261,10 @@ button:focus-visible, textarea:focus-visible {
|
|
|
261
261
|
box-shadow: 0 -3px 12px color-mix(in srgb, var(--interaction-accent) 28%, transparent);
|
|
262
262
|
}
|
|
263
263
|
.library-tab > span { position: relative; display: grid; place-items: center; width: 16px; height: 16px; flex: none; }
|
|
264
|
+
.library .icon { width: 14px; height: 14px; }
|
|
264
265
|
.library-tab > strong { overflow: hidden; font-size: 12px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
265
266
|
.library-view { flex: 1; min-width: 0; min-height: 0; display: none; flex-direction: column; }
|
|
266
267
|
.library-view.active { display: flex; }
|
|
267
|
-
.library-toolbar {
|
|
268
|
-
min-height: 46px;
|
|
269
|
-
flex: none;
|
|
270
|
-
display: flex;
|
|
271
|
-
align-items: center;
|
|
272
|
-
justify-content: space-between;
|
|
273
|
-
gap: 12px;
|
|
274
|
-
padding: 0 12px;
|
|
275
|
-
border-bottom: 1px solid rgba(255, 255, 255, .045);
|
|
276
|
-
background: #16171a;
|
|
277
|
-
}
|
|
278
|
-
.library-toolbar > div { min-width: 0; }
|
|
279
|
-
.library-toolbar strong, .library-toolbar small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
280
|
-
.library-toolbar strong { color: #d9dbe0; font-size: 11px; font-weight: 600; }
|
|
281
|
-
.library-toolbar small { margin-top: 1px; color: var(--text-3); font-size: 9px; font-weight: 500; }
|
|
282
|
-
.library-context {
|
|
283
|
-
flex: none;
|
|
284
|
-
padding: 3px 6px;
|
|
285
|
-
border: 1px solid #303238;
|
|
286
|
-
border-radius: 4px;
|
|
287
|
-
background: #1d1f23;
|
|
288
|
-
color: #858a93;
|
|
289
|
-
font: 500 9px/1.2 var(--font-mono);
|
|
290
|
-
}
|
|
291
268
|
.library-refresh {
|
|
292
269
|
width: 27px;
|
|
293
270
|
height: 27px;
|
|
@@ -306,7 +283,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
306
283
|
.library.is-refreshing .library-refresh .icon { animation: library-spin 700ms linear infinite; }
|
|
307
284
|
@keyframes library-spin { to { transform: rotate(360deg); } }
|
|
308
285
|
|
|
309
|
-
.
|
|
286
|
+
.sidebar-panel {
|
|
310
287
|
flex: 1;
|
|
311
288
|
min-width: 0;
|
|
312
289
|
min-height: 0;
|
|
@@ -314,20 +291,20 @@ button:focus-visible, textarea:focus-visible {
|
|
|
314
291
|
grid-template-columns: 44px minmax(0, 1fr);
|
|
315
292
|
grid-template-rows: 36px minmax(0, 1fr);
|
|
316
293
|
}
|
|
317
|
-
.
|
|
318
|
-
.
|
|
294
|
+
.sidebar-panel-heading,
|
|
295
|
+
.sidebar-panel-toolbar {
|
|
319
296
|
min-width: 0;
|
|
320
297
|
display: flex;
|
|
321
298
|
align-items: center;
|
|
322
299
|
border-bottom: 1px solid var(--line);
|
|
323
300
|
background: #151619;
|
|
324
301
|
}
|
|
325
|
-
.
|
|
326
|
-
.
|
|
327
|
-
.
|
|
328
|
-
.
|
|
329
|
-
.
|
|
330
|
-
.
|
|
302
|
+
.sidebar-panel-heading { justify-content: center; border-right: 1px solid var(--line); }
|
|
303
|
+
.sidebar-panel-mark { width: 28px; height: 28px; display: grid; place-items: center; color: var(--text-3); }
|
|
304
|
+
.sidebar-panel-mark .icon { width: 14px; height: 14px; }
|
|
305
|
+
.sidebar-panel-toolbar { padding: 0 7px 0 10px; }
|
|
306
|
+
.sidebar-panel-toolbar .code-toolbar { width: 100%; }
|
|
307
|
+
.sidebar-panel-nav {
|
|
331
308
|
min-width: 0;
|
|
332
309
|
min-height: 0;
|
|
333
310
|
overflow-y: auto;
|
|
@@ -337,14 +314,12 @@ button:focus-visible, textarea:focus-visible {
|
|
|
337
314
|
background: #121316;
|
|
338
315
|
scrollbar-width: none;
|
|
339
316
|
}
|
|
340
|
-
.
|
|
341
|
-
.
|
|
317
|
+
.sidebar-panel-nav::-webkit-scrollbar { width: 0; }
|
|
318
|
+
.sidebar-panel-item {
|
|
342
319
|
width: 32px;
|
|
343
320
|
height: 32px;
|
|
344
321
|
display: grid;
|
|
345
|
-
|
|
346
|
-
justify-content: center;
|
|
347
|
-
align-items: center;
|
|
322
|
+
place-items: center;
|
|
348
323
|
margin: 0 auto 2px;
|
|
349
324
|
padding: 0;
|
|
350
325
|
border: 0;
|
|
@@ -354,105 +329,89 @@ button:focus-visible, textarea:focus-visible {
|
|
|
354
329
|
text-align: left;
|
|
355
330
|
transition: color 100ms ease;
|
|
356
331
|
}
|
|
357
|
-
.
|
|
358
|
-
.
|
|
332
|
+
.sidebar-panel-item:hover { color: #bfc2c9; }
|
|
333
|
+
.sidebar-panel-item.active {
|
|
359
334
|
background: #181a1f;
|
|
360
335
|
color: #cfd2d8;
|
|
361
336
|
}
|
|
362
|
-
.
|
|
363
|
-
.
|
|
364
|
-
.
|
|
337
|
+
.sidebar-panel-item .icon { width: 14px; height: 14px; }
|
|
338
|
+
.sidebar-panel-content { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
339
|
+
.sidebar-panel-toolbar { justify-content: space-between; gap: 8px; }
|
|
340
|
+
.media-status { padding: 8px 10px; color: #e58b92; font-size: 10px; }
|
|
365
341
|
.source-code { min-width: 0; min-height: 0; overflow: hidden; }
|
|
366
342
|
.source-code > .code { height: 100%; }
|
|
367
343
|
.source-code .code-scroll { background: #141518; }
|
|
368
344
|
|
|
369
|
-
.
|
|
370
|
-
.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
align-items: center;
|
|
374
|
-
gap: 7px;
|
|
375
|
-
padding: 4px 7px;
|
|
376
|
-
border: 1px solid #292c31;
|
|
377
|
-
border-radius: 5px;
|
|
378
|
-
background: #191a1e;
|
|
379
|
-
}
|
|
380
|
-
.runtime-chip::before { content: ""; width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--interaction-accent); box-shadow: 0 0 8px var(--brand-soft); }
|
|
381
|
-
.runtime-chip > span { min-width: 0; overflow: hidden; color: #a9adb5; font: 9px/1.2 var(--font-mono); text-overflow: ellipsis; white-space: nowrap; }
|
|
382
|
-
.runtime-chip > strong { margin-left: auto; color: #666b74; font-size: 9px; font-weight: 500; }
|
|
383
|
-
.task-list { flex: 1; min-height: 0; overflow: auto; padding: 8px 9px 28px; background: #121316; }
|
|
384
|
-
.task-card {
|
|
385
|
-
min-width: 0;
|
|
386
|
-
display: grid;
|
|
387
|
-
grid-template-columns: 18px minmax(0, 1fr);
|
|
388
|
-
gap: 8px;
|
|
389
|
-
margin-bottom: 6px;
|
|
390
|
-
padding: 10px 10px 9px 8px;
|
|
391
|
-
border: 1px solid #292b31;
|
|
392
|
-
border-radius: 7px;
|
|
393
|
-
background: linear-gradient(135deg, #1c1e22, #191a1e);
|
|
394
|
-
box-shadow: 0 1px 0 rgba(255, 255, 255, .018), 0 6px 18px rgba(0, 0, 0, .08);
|
|
395
|
-
transition: border-color 120ms ease, transform 120ms ease, background 120ms ease;
|
|
396
|
-
}
|
|
397
|
-
.task-card:hover { border-color: #3a3d44; background: linear-gradient(135deg, #202227, #1b1d21); transform: translateY(-1px); }
|
|
398
|
-
.task-state { display: grid; place-items: start center; padding-top: 4px; }
|
|
399
|
-
.task-state span { width: 8px; height: 8px; border-radius: 50%; background: #686d76; box-shadow: 0 0 0 3px rgba(104, 109, 118, .1); }
|
|
400
|
-
.task-running .task-state span, .task-queued .task-state span, .task-waiting .task-state span, .task-active .task-state span { background: #49b9d0; box-shadow: 0 0 0 3px rgba(73, 185, 208, .11), 0 0 10px rgba(73, 185, 208, .18); }
|
|
401
|
-
.task-complete .task-state span { background: var(--ok); box-shadow: 0 0 0 3px rgba(100, 201, 154, .1); }
|
|
402
|
-
.task-failed .task-state span, .task-cancelled .task-state span, .task-blocked .task-state span { background: #d8646d; box-shadow: 0 0 0 3px rgba(216, 100, 109, .1); }
|
|
345
|
+
.task-list { flex: 1; min-height: 0; overflow: auto; padding: 12px 10px 28px; background: #121316; }
|
|
346
|
+
.task-card { min-width: 0; display: grid; grid-template-columns: 24px minmax(0, 1fr) 20px; align-items: start; gap: 9px; margin-bottom: 8px; padding: 11px 9px; border: 1px solid #25272c; border-radius: 4px; background: #191b20; }
|
|
347
|
+
.task-state { display: grid; place-items: center; width: 24px; height: 24px; color: #858c98; }
|
|
348
|
+
.task-state .icon { width: 17px; height: 17px; }
|
|
403
349
|
.task-copy { min-width: 0; }
|
|
404
|
-
.task-title { min-width: 0;
|
|
405
|
-
.task-
|
|
406
|
-
.task-
|
|
407
|
-
.task-
|
|
408
|
-
.task-
|
|
409
|
-
.task-
|
|
410
|
-
.task-
|
|
411
|
-
.task-
|
|
412
|
-
.task-
|
|
413
|
-
.task-
|
|
414
|
-
.task-progress { margin-top: 7px;
|
|
350
|
+
.task-title { min-width: 0; }
|
|
351
|
+
.task-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; max-width: 100%; overflow: hidden; padding: 0; border: 0; background: none; color: #dfe1e5; font: 500 11px/17px var(--font-ui); text-align: left; overflow-wrap: anywhere; cursor: pointer; }
|
|
352
|
+
.task-name:hover { color: #fff; }
|
|
353
|
+
.task-name:focus-visible { outline: 1px solid #626875; outline-offset: 2px; border-radius: 2px; }
|
|
354
|
+
.task-facts { min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 3px 10px; margin-top: 5px; line-height: 14px; }
|
|
355
|
+
.task-status { color: #949aa5; font-size: 10px; }
|
|
356
|
+
.task-facts time { color: #727985; font-size: 9px; }
|
|
357
|
+
.task-running .task-state, .task-running .task-status, .task-queued .task-state, .task-queued .task-status, .task-waiting .task-state, .task-waiting .task-status, .task-saving-result .task-state, .task-saving-result .task-status { color: #8daabb; }
|
|
358
|
+
.task-complete .task-state, .task-complete .task-status { color: #87a896; }
|
|
359
|
+
.task-failed .task-state, .task-failed .task-status, .task-attention .task-state, .task-attention .task-status { color: #c99490; }
|
|
360
|
+
.task-progress { margin-top: 7px; overflow: hidden; color: #9ba1ac; font-size: 10px; line-height: 15px; white-space: nowrap; text-overflow: ellipsis; }
|
|
361
|
+
.task-detail { margin-top: 7px; color: #bc9693; font-size: 10px; line-height: 15px; }
|
|
362
|
+
.task-error-message { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; white-space: pre-wrap; user-select: text; }
|
|
363
|
+
.task-card.selected { background: #24272d; }
|
|
364
|
+
.task-card.selected .task-error-message { display: block; }
|
|
365
|
+
.task-copy-error { display: none; margin-top: 6px; padding: 0; border: 0; background: transparent; color: #aeb4bf; font: inherit; cursor: pointer; }
|
|
366
|
+
.task-card.selected .task-copy-error { display: inline-block; }
|
|
367
|
+
.task-card:focus-visible { outline: 1px solid #626875; outline-offset: -1px; }
|
|
368
|
+
.task-context { flex: 1; min-width: 0; display: flex; align-items: center; gap: 3px; color: #b9bfca; }
|
|
369
|
+
.task-context::before { content: "/"; flex: none; color: #5d6572; margin-right: 3px; }
|
|
370
|
+
.sidebar-panel-toolbar > .library-refresh { margin-left: auto; }
|
|
371
|
+
.sidebar-panel-toolbar:has(.task-context:not([hidden])) > .code-location { flex: none; max-width: 90px; }
|
|
372
|
+
.task-context[hidden] { display: none; }
|
|
373
|
+
.task-context-name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; padding: 3px 0; border: 0; background: transparent; color: inherit; font: 500 10px/16px var(--font-ui); text-align: left; }
|
|
374
|
+
.task-context-name .icon { flex: none; }
|
|
375
|
+
.task-context-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
376
|
+
.task-context-clear { flex: none; display: grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 3px; background: transparent; color: #929aa7; }
|
|
377
|
+
.task-context-clear:hover, .task-context-name:hover { color: #f1f2f5; }
|
|
378
|
+
.task-context-clear:hover { background: #30343c; }
|
|
379
|
+
.task-context button:focus-visible { outline: 1px solid #626875; }
|
|
380
|
+
.task-open { display: grid; place-items: center; width: 20px; height: 24px; padding: 0; border: 0; border-radius: 3px; background: transparent; color: #707885; }
|
|
381
|
+
.task-open .icon { width: 14px; height: 14px; }
|
|
382
|
+
.task-open:hover { color: #dfe1e5; background: #25282f; }
|
|
383
|
+
.task-open:focus-visible { outline: 1px solid #626875; }
|
|
415
384
|
|
|
416
|
-
.artifact-
|
|
417
|
-
.artifact-
|
|
418
|
-
.artifact-
|
|
419
|
-
.artifact-
|
|
420
|
-
.artifact-
|
|
421
|
-
.artifact-
|
|
422
|
-
.artifact-
|
|
423
|
-
.artifact-
|
|
424
|
-
.artifact-
|
|
425
|
-
.artifact-
|
|
426
|
-
.artifact-
|
|
427
|
-
.artifact-
|
|
428
|
-
.artifact-
|
|
429
|
-
.artifact-
|
|
430
|
-
.artifact-
|
|
431
|
-
.artifact-
|
|
432
|
-
.artifact-
|
|
433
|
-
.artifact-
|
|
434
|
-
.artifact-
|
|
435
|
-
.artifact-
|
|
436
|
-
.artifact-copy
|
|
437
|
-
.artifact-
|
|
438
|
-
.artifact-
|
|
385
|
+
.artifact-grid { flex: 1; min-height: 0; overflow: auto; display: grid; grid-template-columns: repeat(auto-fill, 104px); align-content: start; justify-content: space-between; gap: 12px; padding: 14px 12px 30px; background: #121316; }
|
|
386
|
+
.artifact-card { --artifact-selection: #24272d; min-width: 0; padding: 0; border: 0; border-radius: 3px; display: flex; flex-direction: column; color: inherit; text-decoration: none; }
|
|
387
|
+
.artifact-card:focus-visible { outline: none; }
|
|
388
|
+
.artifact-card:focus-visible .artifact-preview { outline: 1px solid #656b79; outline-offset: 2px; border-radius: 3px; }
|
|
389
|
+
.artifact-preview { position: relative; flex: none; min-height: 0; width: 100%; aspect-ratio: 1; display: flex; align-items: center; justify-content: center; }
|
|
390
|
+
.artifact-preview img, .artifact-preview video { position: absolute; display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; border-radius: 2px; object-fit: contain; }
|
|
391
|
+
.artifact-video .artifact-preview video { opacity: 0; pointer-events: none; }
|
|
392
|
+
.artifact-video .artifact-preview video.is-ready { opacity: 1; }
|
|
393
|
+
.artifact-glyph { display: none; }
|
|
394
|
+
.artifact-audio .artifact-glyph { display: grid; position: absolute; place-items: center; width: 100%; height: 42px; color: #87a896; background: #202824; border-radius: 2px; }
|
|
395
|
+
.artifact-audio .artifact-waveform { display: block; visibility: hidden; width: 100%; height: 42px; padding: 8px 4px; background: #202824; }
|
|
396
|
+
.artifact-audio .artifact-waveform.is-ready { visibility: visible; }
|
|
397
|
+
.artifact-waveform.is-ready ~ .artifact-glyph { display: none; }
|
|
398
|
+
.artifact-type { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
399
|
+
.artifact-copy { min-width: 0; height: 44px; padding-top: 6px; text-align: center; }
|
|
400
|
+
.artifact-name, .artifact-name-editor { box-sizing: border-box; width: 100%; margin: 0; padding: 0 4px; border: 1px solid transparent; border-radius: 3px; color: #d8dae0; background: transparent; font: 500 11px/18px var(--font-ui); text-align: center; word-break: break-all; overflow-wrap: anywhere; }
|
|
401
|
+
.artifact-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; max-height: 38px; overflow: hidden; white-space: normal; cursor: text; }
|
|
402
|
+
.artifact-name-text { padding: 1px 2px; border-radius: 3px; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
|
|
403
|
+
.artifact-name:focus-visible { outline: none; box-shadow: inset 0 0 0 1px #626875; }
|
|
404
|
+
.artifact-name[hidden] { display: none; }
|
|
405
|
+
.artifact-copy.editing { height: auto; min-height: 44px; }
|
|
406
|
+
.artifact-name-editor { display: block; min-height: 20px; resize: none; overflow: hidden; outline: none; color: #f1f2f5; box-shadow: inset 0 0 0 1px #626875; }
|
|
407
|
+
.artifact-name-editor:focus-visible { outline: none; box-shadow: inset 0 0 0 1px #626875; }
|
|
408
|
+
.artifact-name-editor::selection { color: #fff; background: #4b5263; }
|
|
409
|
+
.artifact-name-error { display: block; color: #bd9c92; font-size: 9px; line-height: 13px; overflow-wrap: anywhere; }
|
|
439
410
|
.library-empty { grid-column: 1 / -1; min-height: 210px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 28px; text-align: center; }
|
|
440
411
|
.library-empty-icon { width: 44px; height: 44px; display: grid; place-items: center; margin-bottom: 12px; border: 1px solid #292c31; border-radius: 12px; background: linear-gradient(145deg, #202227, #18191d); color: #666b74; box-shadow: 0 9px 24px rgba(0, 0, 0, .12); }
|
|
441
412
|
.library-empty-icon .icon { width: 21px; height: 21px; }
|
|
442
413
|
.library-empty strong { color: #aeb2ba; font-size: 11px; font-weight: 600; }
|
|
443
414
|
.library-empty p { max-width: 260px; margin: 6px 0 0; color: #666b74; font-size: 9px; line-height: 1.55; }
|
|
444
|
-
.library-older {
|
|
445
|
-
grid-column: 1 / -1;
|
|
446
|
-
width: 100%;
|
|
447
|
-
min-height: 32px;
|
|
448
|
-
margin-top: 3px;
|
|
449
|
-
border: 1px solid #292c31;
|
|
450
|
-
border-radius: 6px;
|
|
451
|
-
background: #191a1e;
|
|
452
|
-
color: #858a93;
|
|
453
|
-
font-size: 9px;
|
|
454
|
-
}
|
|
455
|
-
.library-older:hover { border-color: #3a3d44; background: #202227; color: #d1d4da; }
|
|
456
415
|
|
|
457
416
|
.code { --code-inline-end: 12px; flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
|
|
458
417
|
.code-toolbar { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
@@ -904,6 +863,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
904
863
|
box-shadow: 0 10px 26px rgba(0, 0, 0, .42);
|
|
905
864
|
}
|
|
906
865
|
.parameter-select.open-up .parameter-select-menu { top: auto; bottom: calc(100% + 4px); }
|
|
866
|
+
.parameter-select.rich-options .parameter-select-menu { width: 240px; max-height: 240px; }
|
|
907
867
|
.parameter-select-menu[hidden] { display: none; }
|
|
908
868
|
.parameter-select-option {
|
|
909
869
|
width: 100%;
|
|
@@ -921,6 +881,19 @@ button:focus-visible, textarea:focus-visible {
|
|
|
921
881
|
.parameter-select-option.active { background: #202226; color: #d5d7dc; }
|
|
922
882
|
.parameter-select-option:focus-visible { outline: 0; background: #292b30; color: var(--text); }
|
|
923
883
|
.parameter-right > small { flex: none; color: var(--text-3); font-size: 10px; }
|
|
884
|
+
.parameter-value[type="number"] { appearance: textfield; }
|
|
885
|
+
.parameter-value[type="number"]::-webkit-inner-spin-button { opacity: .55; }
|
|
886
|
+
.parameter-value:invalid { border-color: #a45c60; }
|
|
887
|
+
.parameter-value[readonly] { color: var(--text-3); }
|
|
888
|
+
.parameter-multiline { min-height: 64px; resize: vertical; text-align: left; line-height: 1.5; }
|
|
889
|
+
.parameter-select-option { padding: 6px 8px; white-space: normal; text-align: left; }
|
|
890
|
+
.parameter-select-option > small { display: block; margin-top: 3px; color: var(--text-3); font: 10px/1.4 var(--font-ui); }
|
|
891
|
+
.parameter-option-swatch { display: inline-block; width: 12px; height: 12px; margin-right: 7px; border-radius: 2px; vertical-align: middle; box-shadow: inset 0 0 0 1px #ffffff22; }
|
|
892
|
+
.parameter-option-font { display: block; margin-top: 4px; font-size: 18px; }
|
|
893
|
+
.control-color .parameter-right { flex-wrap: wrap; }
|
|
894
|
+
.parameter-swatches { display: flex; flex-wrap: wrap; gap: 5px; padding-top: 3px; }
|
|
895
|
+
.parameter-swatches button { width: 18px; height: 18px; border: 1px solid #ffffff22; border-radius: 3px; }
|
|
896
|
+
.parameter-swatches button:focus-visible { outline: 1px solid var(--text); outline-offset: 2px; }
|
|
924
897
|
.status.saving { color: var(--text-2); }
|
|
925
898
|
.status.saved { color: var(--ok); }
|
|
926
899
|
|
|
@@ -945,6 +918,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
945
918
|
font-variant-numeric: tabular-nums;
|
|
946
919
|
letter-spacing: .01em;
|
|
947
920
|
}
|
|
921
|
+
.timeline { --timeline-ruler-height: 22px; }
|
|
948
922
|
.timeline-body {
|
|
949
923
|
flex: 1;
|
|
950
924
|
min-height: 0;
|
|
@@ -969,7 +943,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
969
943
|
position: sticky;
|
|
970
944
|
top: 0;
|
|
971
945
|
z-index: 10;
|
|
972
|
-
height:
|
|
946
|
+
height: var(--timeline-ruler-height);
|
|
973
947
|
display: flex;
|
|
974
948
|
align-items: center;
|
|
975
949
|
justify-content: flex-end;
|
|
@@ -1014,8 +988,6 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1014
988
|
text-align: left;
|
|
1015
989
|
}
|
|
1016
990
|
.track-copy strong { color: #cfd2d8; font-size: 11px; font-weight: 500; line-height: 18px; }
|
|
1017
|
-
.track-label-has-state { grid-template-columns: 18px minmax(0, 1fr) 18px; }
|
|
1018
|
-
.track-state { display: grid; place-items: center; }
|
|
1019
991
|
.track-attachment-mark {
|
|
1020
992
|
position: relative;
|
|
1021
993
|
width: 18px;
|
|
@@ -1056,7 +1028,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1056
1028
|
position: sticky;
|
|
1057
1029
|
top: 0;
|
|
1058
1030
|
z-index: 9;
|
|
1059
|
-
height:
|
|
1031
|
+
height: var(--timeline-ruler-height);
|
|
1060
1032
|
overflow: hidden;
|
|
1061
1033
|
border-bottom: 1px solid var(--line);
|
|
1062
1034
|
background: #111216;
|
|
@@ -1102,8 +1074,8 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1102
1074
|
}
|
|
1103
1075
|
.tick-last { padding-right: 4px; padding-left: 0; border-right: 1px solid #45484e; border-left: 0; transform: translateX(-100%); }
|
|
1104
1076
|
.lanes { min-height: 0; cursor: default; }
|
|
1105
|
-
.lane { position: relative; min-height: 0; overflow: hidden; border-bottom: 1px solid #191b1f; background: var(--lane); }
|
|
1106
|
-
.semantic-lane { min-height: 0; background: var(--lane); }
|
|
1077
|
+
.lane { position: relative; isolation: isolate; min-height: 0; overflow: hidden; border-bottom: 1px solid #191b1f; background: var(--lane); }
|
|
1078
|
+
.semantic-lane { position: sticky; top: var(--timeline-ruler-height); z-index: 8; min-height: 0; border-bottom: 1px solid var(--line); background: var(--lane); }
|
|
1107
1079
|
.semantic-band {
|
|
1108
1080
|
position: absolute;
|
|
1109
1081
|
right: 0;
|
|
@@ -1208,21 +1180,11 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1208
1180
|
background: var(--semantic-intent);
|
|
1209
1181
|
}
|
|
1210
1182
|
.semantic-moment:focus-visible { outline: 0; }
|
|
1183
|
+
.track-label-semantic { position: sticky; top: var(--timeline-ruler-height); z-index: 8; border-bottom: 1px solid var(--line); background: #111216; }
|
|
1211
1184
|
.track-label-semantic::before {
|
|
1212
1185
|
border-radius: 0;
|
|
1213
1186
|
background: #181a1f;
|
|
1214
1187
|
}
|
|
1215
|
-
.track-fold {
|
|
1216
|
-
min-width: 17px;
|
|
1217
|
-
height: 17px;
|
|
1218
|
-
padding: 0 3px;
|
|
1219
|
-
border: 0;
|
|
1220
|
-
border-radius: 3px;
|
|
1221
|
-
background: #24262a;
|
|
1222
|
-
color: #92969e;
|
|
1223
|
-
font-size: 11px;
|
|
1224
|
-
line-height: 17px;
|
|
1225
|
-
}
|
|
1226
1188
|
.track-label-attached {
|
|
1227
1189
|
/* The branch marker occupies the same 18px prefix as a root icon;
|
|
1228
1190
|
attached and root labels therefore share one exact text column. */
|
|
@@ -1231,6 +1193,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1231
1193
|
}
|
|
1232
1194
|
.track-label-attached::before { inset: var(--timeline-item-inset-y) 6px; background: #181a1f; }
|
|
1233
1195
|
.clip {
|
|
1196
|
+
z-index: 0;
|
|
1234
1197
|
--track: var(--visual);
|
|
1235
1198
|
--item-head: color-mix(in srgb, var(--track) 58%, var(--lane));
|
|
1236
1199
|
--item-body: color-mix(in srgb, var(--track) 20%, var(--lane));
|
|
@@ -1274,7 +1237,7 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1274
1237
|
cursor: ew-resize !important;
|
|
1275
1238
|
}
|
|
1276
1239
|
.clip.selected {
|
|
1277
|
-
z-index:
|
|
1240
|
+
z-index: 1;
|
|
1278
1241
|
overflow: visible;
|
|
1279
1242
|
box-shadow: none;
|
|
1280
1243
|
color: #fff;
|
|
@@ -1520,6 +1483,34 @@ button:focus-visible, textarea:focus-visible {
|
|
|
1520
1483
|
@media (max-width: 920px) {
|
|
1521
1484
|
.meta, .topbar-right { display: none; }
|
|
1522
1485
|
.topbar { grid-template-columns: 1fr minmax(120px, 1fr); }
|
|
1523
|
-
.upper-shell { grid-template-columns: minmax(
|
|
1486
|
+
.upper-shell { grid-template-columns: minmax(320px, var(--source-width)) 8px minmax(300px, 1fr); }
|
|
1524
1487
|
.workspace-handle, .workspace-panel { display: none; }
|
|
1525
1488
|
}
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
/* The same transport serves the composition and selected Result media. */
|
|
1492
|
+
.stage-transport { display: flex; align-items: center; gap: 2px; }
|
|
1493
|
+
.stage-bar button[hidden], .stage-transport[hidden], .stage-artifact[hidden], .stage-scaler[hidden] { display: none; }
|
|
1494
|
+
.stage-bar .stage-mute { position: absolute; right: 12px; margin: 0; }
|
|
1495
|
+
.stage-time { position: absolute; left: 12px; color: #969ba5; font: 10px var(--font-mono); }
|
|
1496
|
+
.stage-title { min-width: 0; }
|
|
1497
|
+
.stage-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1498
|
+
.stage-return { flex: none; margin-left: auto; border: 0; background: transparent; color: #aeb3bd; font: 10px var(--font-ui); cursor: pointer; }
|
|
1499
|
+
.stage-return[hidden] { display: none; }
|
|
1500
|
+
.stage-artifact { position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; min-height: 0; }
|
|
1501
|
+
.stage-artifact > img, .stage-artifact > video { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
|
|
1502
|
+
.stage-artifact audio { display: none; }
|
|
1503
|
+
.stage-audio-wave { width: 85%; padding: 24px 12px; border-radius: 2px; color: #b5c2bb; background: #202824; text-align: center; }
|
|
1504
|
+
.stage-audio-wave img { display: block; width: 100%; height: 100px; }
|
|
1505
|
+
.stage-media-error { position: absolute; bottom: 12px; padding: 8px; background: #24262b; color: #cacdd4; font-size: 11px; }
|
|
1506
|
+
.stage-scrubber { flex: none; width: 100%; height: 10px; padding: 0; margin: -5px 0; z-index: 2; appearance: none; background: transparent; cursor: pointer; }
|
|
1507
|
+
.stage-scrubber::-webkit-slider-runnable-track { height: 2px; background: #676c75; }
|
|
1508
|
+
.stage-scrubber::-webkit-slider-thumb { appearance: none; width: 8px; height: 8px; border-radius: 50%; margin-top: -3px; background: #e1e4e9; }
|
|
1509
|
+
.stage-scrubber::-moz-range-track { height: 2px; background: #676c75; }
|
|
1510
|
+
.stage-scrubber::-moz-range-thumb { width: 8px; height: 8px; border: 0; background: #e1e4e9; }
|
|
1511
|
+
|
|
1512
|
+
.preview-unavailable .artifact-type { position: static; width: auto; height: auto; clip-path: none; color: #9298a2; font-size: 10px; }
|
|
1513
|
+
|
|
1514
|
+
.artifact-card.selected .artifact-preview { background: var(--artifact-selection); border-radius: 3px; }
|
|
1515
|
+
.artifact-card.selected .artifact-name-text { color: #f1f2f5; background: var(--artifact-selection); }
|
|
1516
|
+
.library-continuation { grid-column: 1 / -1; height: 28px; display: grid; place-items: center; color: #777e89; font-size: 10px; }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { StudioEditHandle, StudioSemanticAnchor, StudioTemporalInstantProjection } from "@hypit/studio-adapter";
|
|
2
|
+
|
|
3
|
+
export type SemanticTarget =
|
|
4
|
+
| { readonly kind: "selection"; readonly startAnchorId: string; readonly endAnchorId: string }
|
|
5
|
+
| { readonly kind: "moment"; readonly anchorId: string };
|
|
6
|
+
|
|
7
|
+
type Span = { readonly startFrame: number; readonly endFrameExclusive: number };
|
|
8
|
+
|
|
9
|
+
/** Project only the semantic write exposed by the handle. This does not invert expressions. */
|
|
10
|
+
export function semanticGestureSpan(
|
|
11
|
+
anchors: readonly StudioSemanticAnchor[], handle: StudioEditHandle, target: SemanticTarget,
|
|
12
|
+
): Span | undefined {
|
|
13
|
+
const semantic = handle.semantic;
|
|
14
|
+
const temporal = handle.temporal;
|
|
15
|
+
if (!semantic || !temporal || target.kind !== semantic.kind) return undefined;
|
|
16
|
+
const byId = new Map(anchors.map((anchor, order) => [anchor.id, { ...anchor, order }]));
|
|
17
|
+
let span: Span;
|
|
18
|
+
if (semantic.kind === "moment" && target.kind === "moment") {
|
|
19
|
+
const previous = byId.get(semantic.anchorId);
|
|
20
|
+
const next = byId.get(target.anchorId);
|
|
21
|
+
if (!previous || !next) return undefined;
|
|
22
|
+
const delta = next.frame - previous.frame;
|
|
23
|
+
span = temporal.kind === "instant"
|
|
24
|
+
? { startFrame: temporal.frame + delta, endFrameExclusive: temporal.frame + delta + 1 }
|
|
25
|
+
: { startFrame: temporal.startFrame + delta, endFrameExclusive: temporal.endFrameExclusive + delta };
|
|
26
|
+
} else if (semantic.kind === "selection" && target.kind === "selection") {
|
|
27
|
+
const start = byId.get(target.startAnchorId);
|
|
28
|
+
const end = byId.get(target.endAnchorId);
|
|
29
|
+
if (!start || !end || start.order > end.order) return undefined;
|
|
30
|
+
if (temporal.kind === "instant") {
|
|
31
|
+
if (temporal.authority.kind !== "semantic") return undefined;
|
|
32
|
+
if (temporal.authority.boundary === "start" && target.endAnchorId !== semantic.endAnchorId
|
|
33
|
+
|| temporal.authority.boundary === "end" && target.startAnchorId !== semantic.startAnchorId) return undefined;
|
|
34
|
+
const frame = temporal.authority.boundary === "start" ? start.frame : end.frame;
|
|
35
|
+
span = { startFrame: frame, endFrameExclusive: frame + 1 };
|
|
36
|
+
} else {
|
|
37
|
+
if (handle.gesture === "trim-start" && target.endAnchorId !== semantic.endAnchorId
|
|
38
|
+
|| handle.gesture === "trim-end" && target.startAnchorId !== semantic.startAnchorId) return undefined;
|
|
39
|
+
if (handle.gesture === "move") {
|
|
40
|
+
const stops = [...new Set(anchors.map((anchor) => anchor.frame))].sort((a, b) => a - b);
|
|
41
|
+
const previousStart = byId.get(semantic.startAnchorId);
|
|
42
|
+
const previousEnd = byId.get(semantic.endAnchorId);
|
|
43
|
+
if (!previousStart || !previousEnd
|
|
44
|
+
|| stops.indexOf(start.frame) - stops.indexOf(previousStart.frame)
|
|
45
|
+
!== stops.indexOf(end.frame) - stops.indexOf(previousEnd.frame)) return undefined;
|
|
46
|
+
}
|
|
47
|
+
span = { startFrame: start.frame, endFrameExclusive: end.frame };
|
|
48
|
+
}
|
|
49
|
+
} else return undefined;
|
|
50
|
+
if (!Number.isSafeInteger(span.startFrame) || span.startFrame < 0
|
|
51
|
+
|| !Number.isSafeInteger(span.endFrameExclusive) || span.endFrameExclusive <= span.startFrame) return undefined;
|
|
52
|
+
if (temporal.kind === "window"
|
|
53
|
+
&& ((handle.gesture === "trim-start" && span.endFrameExclusive !== temporal.endFrameExclusive)
|
|
54
|
+
|| (handle.gesture === "trim-end" && span.startFrame !== temporal.startFrame))) return undefined;
|
|
55
|
+
return span;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A stop is a frame position; coincident anchors remain distinct choices at that stop. */
|
|
59
|
+
export function chooseSemanticGesture(input: {
|
|
60
|
+
readonly anchors: readonly StudioSemanticAnchor[];
|
|
61
|
+
readonly handle: StudioEditHandle;
|
|
62
|
+
readonly pointerStart: number;
|
|
63
|
+
readonly pointerNow: number;
|
|
64
|
+
readonly frameCount: number;
|
|
65
|
+
}): SemanticTarget | undefined {
|
|
66
|
+
const { anchors, handle } = input;
|
|
67
|
+
const semantic = handle.semantic;
|
|
68
|
+
const temporal = handle.temporal;
|
|
69
|
+
if (!semantic || !temporal) return undefined;
|
|
70
|
+
const order = new Map(anchors.map((anchor, index) => [anchor.id, index]));
|
|
71
|
+
const byId = new Map(anchors.map((anchor) => [anchor.id, anchor]));
|
|
72
|
+
const at = new Map<number, StudioSemanticAnchor[]>();
|
|
73
|
+
for (const anchor of anchors) at.set(anchor.frame, [...at.get(anchor.frame) ?? [], anchor]);
|
|
74
|
+
const stops = [...at.keys()].sort((a, b) => a - b);
|
|
75
|
+
const ranked = (choices: readonly StudioSemanticAnchor[], preferred: StudioSemanticAnchor) => [...choices].sort((a, b) =>
|
|
76
|
+
Number(a.id !== preferred.id) - Number(b.id !== preferred.id)
|
|
77
|
+
|| Number(a.kind !== preferred.kind) - Number(b.kind !== preferred.kind)
|
|
78
|
+
|| order.get(a.id)! - order.get(b.id)!);
|
|
79
|
+
const valid = (target: SemanticTarget) => {
|
|
80
|
+
const span = semanticGestureSpan(anchors, handle, target);
|
|
81
|
+
return span !== undefined && (temporal.kind === "instant" ? span.startFrame : span.endFrameExclusive) <= input.frameCount;
|
|
82
|
+
};
|
|
83
|
+
const delta = input.pointerNow - input.pointerStart;
|
|
84
|
+
if (semantic.kind === "moment") {
|
|
85
|
+
const current = byId.get(semantic.anchorId);
|
|
86
|
+
if (!current) return undefined;
|
|
87
|
+
const candidates = ranked(anchors, current).sort((a, b) =>
|
|
88
|
+
Math.abs(a.frame - current.frame - delta) - Math.abs(b.frame - current.frame - delta));
|
|
89
|
+
return candidates.map((anchor): SemanticTarget => ({ kind: "moment", anchorId: anchor.id })).find(valid);
|
|
90
|
+
}
|
|
91
|
+
const start = byId.get(semantic.startAnchorId);
|
|
92
|
+
const end = byId.get(semantic.endAnchorId);
|
|
93
|
+
if (!start || !end) return undefined;
|
|
94
|
+
const target = (a: StudioSemanticAnchor, b: StudioSemanticAnchor): SemanticTarget => ({
|
|
95
|
+
kind: "selection", startAnchorId: a.id, endAnchorId: b.id,
|
|
96
|
+
});
|
|
97
|
+
if (handle.gesture === "move" && temporal.kind === "window") {
|
|
98
|
+
const first = stops.indexOf(start.frame);
|
|
99
|
+
const last = stops.indexOf(end.frame);
|
|
100
|
+
const shifts = stops.map((_, index) => index - first)
|
|
101
|
+
.filter((shift) => last + shift >= 0 && last + shift < stops.length)
|
|
102
|
+
.sort((a, b) => Math.abs(stops[first + a]! - start.frame - delta)
|
|
103
|
+
- Math.abs(stops[first + b]! - start.frame - delta) || Math.abs(a) - Math.abs(b));
|
|
104
|
+
for (const shift of shifts) {
|
|
105
|
+
for (const a of ranked(at.get(stops[first + shift]!)!, start)) {
|
|
106
|
+
for (const b of ranked(at.get(stops[last + shift]!)!, end)) {
|
|
107
|
+
const candidate = target(a, b);
|
|
108
|
+
if (valid(candidate)) return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const boundary = temporal.kind === "instant" && temporal.authority.kind === "semantic"
|
|
115
|
+
? temporal.authority.boundary : handle.gesture === "trim-start" ? "start" : "end";
|
|
116
|
+
const current = boundary === "start" ? start : end;
|
|
117
|
+
const candidates = ranked(anchors, current).sort((a, b) =>
|
|
118
|
+
Math.abs(a.frame - current.frame - delta) - Math.abs(b.frame - current.frame - delta));
|
|
119
|
+
return candidates.map((anchor) => boundary === "start" ? target(anchor, end) : target(start, anchor)).find(valid);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/** Keep the reference and write its local frame offset; a bare reference starts at zero. */
|
|
124
|
+
export function formatTemporalPointEdit(
|
|
125
|
+
reference: StudioTemporalInstantProjection["reference"], desired: number, base?: number,
|
|
126
|
+
): string {
|
|
127
|
+
if (reference === "absolute") return `${desired}f`;
|
|
128
|
+
if (base === undefined) throw new Error(`The ${reference} projection base is unavailable.`);
|
|
129
|
+
const offset = desired - base;
|
|
130
|
+
return `${reference}${offset >= 0 ? "+" : ""}${offset}f`;
|
|
131
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Two-line label with an expanding editor; the caller owns persistence. */
|
|
2
|
+
export function createArtifactName(options: {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly editable: boolean;
|
|
5
|
+
readonly select: () => void;
|
|
6
|
+
readonly save: (name: string) => Promise<string>;
|
|
7
|
+
}) {
|
|
8
|
+
const element = document.createElement("div");
|
|
9
|
+
element.className = "artifact-copy";
|
|
10
|
+
const label = document.createElement("button");
|
|
11
|
+
label.type = "button";
|
|
12
|
+
label.className = "artifact-name";
|
|
13
|
+
let name = options.name;
|
|
14
|
+
const text = document.createElement("span");
|
|
15
|
+
text.className = "artifact-name-text";
|
|
16
|
+
text.textContent = name;
|
|
17
|
+
label.append(text);
|
|
18
|
+
label.title = options.editable ? `${name}\nDouble-click or press F2 to rename` : `${name}\nNaming is available when this Build finishes`;
|
|
19
|
+
element.append(label);
|
|
20
|
+
label.addEventListener("click", (event) => { event.stopPropagation(); options.select(); });
|
|
21
|
+
let editing = false;
|
|
22
|
+
|
|
23
|
+
function edit(): void {
|
|
24
|
+
if (editing || !options.editable) return;
|
|
25
|
+
options.select();
|
|
26
|
+
editing = true;
|
|
27
|
+
element.classList.add("editing");
|
|
28
|
+
label.hidden = true;
|
|
29
|
+
const field = document.createElement("textarea");
|
|
30
|
+
field.className = "artifact-name-editor";
|
|
31
|
+
field.value = name;
|
|
32
|
+
field.rows = 1;
|
|
33
|
+
field.setAttribute("aria-label", "Display name");
|
|
34
|
+
const error = document.createElement("span");
|
|
35
|
+
error.className = "artifact-name-error";
|
|
36
|
+
error.setAttribute("role", "status");
|
|
37
|
+
element.append(field, error);
|
|
38
|
+
const resize = () => { field.style.height = "0px"; field.style.height = `${field.scrollHeight + field.offsetHeight - field.clientHeight}px`; };
|
|
39
|
+
resize();
|
|
40
|
+
field.focus();
|
|
41
|
+
field.setSelectionRange(name.length, name.length);
|
|
42
|
+
field.addEventListener("input", resize);
|
|
43
|
+
field.addEventListener("click", (event) => event.stopPropagation());
|
|
44
|
+
let saving = false;
|
|
45
|
+
const close = () => {
|
|
46
|
+
editing = false;
|
|
47
|
+
field.remove();
|
|
48
|
+
error.remove();
|
|
49
|
+
label.hidden = false;
|
|
50
|
+
element.classList.remove("editing");
|
|
51
|
+
};
|
|
52
|
+
const save = async () => {
|
|
53
|
+
if (!editing || saving) return;
|
|
54
|
+
const value = field.value.trim();
|
|
55
|
+
if (value === name) { close(); return; }
|
|
56
|
+
if (value.length === 0) { error.textContent = "Enter a name."; return; }
|
|
57
|
+
saving = true;
|
|
58
|
+
field.readOnly = true;
|
|
59
|
+
error.textContent = "Saving…";
|
|
60
|
+
try {
|
|
61
|
+
name = await options.save(value);
|
|
62
|
+
text.textContent = name;
|
|
63
|
+
label.title = `${name}\nDouble-click or press F2 to rename`;
|
|
64
|
+
close();
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
error.textContent = cause instanceof Error ? cause.message : String(cause);
|
|
67
|
+
} finally {
|
|
68
|
+
saving = false;
|
|
69
|
+
field.readOnly = false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
field.addEventListener("blur", () => { void save(); });
|
|
73
|
+
field.addEventListener("keydown", (event) => {
|
|
74
|
+
if (event.key === "Escape" && !saving) { event.preventDefault(); event.stopPropagation(); close(); label.focus(); }
|
|
75
|
+
else if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); void save(); }
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
label.addEventListener("dblclick", (event) => { event.preventDefault(); event.stopPropagation(); edit(); });
|
|
79
|
+
return { element, edit };
|
|
80
|
+
}
|