@kitlangton/knobs 0.4.0-next.5 → 0.4.0-next.7
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 +11 -9
- package/dist/dev/{chunk-qc6szek7.js → chunk-a9pwf5c0.js} +30 -20
- package/dist/dev/{chunk-y2axkmz3.js → chunk-tttj4xpd.js} +1 -1
- package/dist/dev/index.js +3 -3
- package/dist/panel.js +30 -20
- package/dist/types/ui/compare-css.d.ts +1 -1
- package/dist/types/ui/compare-gpu.d.ts +2 -0
- package/dist/types/ui/compare-model.d.ts +25 -6
- package/dist/types/ui/compare.d.ts +1 -1
- package/dist/types/ui/index.d.ts +2 -1
- package/dist/types/ui/player-css.d.ts +1 -1
- package/dist/types/ui/player.d.ts +0 -9
- package/dist/types/ui/timeline.d.ts +2 -4
- package/dist/types/ui/walkthrough-annotations.d.ts +42 -0
- package/dist/types/ui/walkthrough-blocks.d.ts +20 -6
- package/dist/types/ui/walkthrough-media.d.ts +3 -1
- package/dist/types/ui/walkthrough-model.d.ts +46 -9
- package/dist/ui-react.js +1687 -394
- package/dist/ui-solid.js +1699 -406
- package/dist/ui.js +5788 -4499
- package/package.json +1 -1
package/dist/ui-solid.js
CHANGED
|
@@ -575,13 +575,14 @@ var compareCss = `
|
|
|
575
575
|
picked: the pill's bright copy is. */
|
|
576
576
|
.knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
|
|
577
577
|
.knobs-cmp-modes[hidden]{display:none}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
578
|
+
/* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
|
|
579
|
+
.knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
|
|
580
|
+
transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
581
|
+
.knobs-ui .knobs-cmp-mode:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
581
582
|
/* Toggles: icon buttons, the accent's wash when on. */
|
|
582
|
-
.knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-
|
|
583
|
-
transition:
|
|
584
|
-
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){
|
|
583
|
+
.knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;
|
|
584
|
+
transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
585
|
+
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
585
586
|
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
586
587
|
.knobs-cmp-toggle svg{display:block}
|
|
587
588
|
.knobs-cmp-fill{flex:1}
|
|
@@ -595,11 +596,10 @@ var compareCss = `
|
|
|
595
596
|
.knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
|
|
596
597
|
.knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
|
|
597
598
|
.knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
|
|
598
|
-
/* Videos: a click on the stage plays and pauses. */
|
|
599
|
-
.knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
|
|
600
599
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
601
|
-
.
|
|
602
|
-
.knobs-compare[data-
|
|
600
|
+
/* Zoomed, it pans: a move cursor, never a hand. */
|
|
601
|
+
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
|
|
602
|
+
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
|
|
603
603
|
/* Wiping, the grip stands in for the cursor. */
|
|
604
604
|
.knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
|
|
605
605
|
.knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
|
|
@@ -646,8 +646,8 @@ var compareCss = `
|
|
|
646
646
|
opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
|
|
647
647
|
.knobs-cmp-zoom[data-on]{opacity:1;scale:1;pointer-events:auto;transition:opacity .2s cubic-bezier(.2,.8,.2,1),scale .2s cubic-bezier(.2,.8,.2,1)}
|
|
648
648
|
:where(.knobs-ui) .knobs-cmp-zoom-reset{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
|
|
649
|
-
display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:
|
|
650
|
-
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){
|
|
649
|
+
display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
650
|
+
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
651
651
|
.knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);font-size:var(--_k-text-meta);pointer-events:none}
|
|
652
652
|
.knobs-cmp-note:empty{display:none}
|
|
653
653
|
.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
|
|
@@ -665,19 +665,29 @@ var playerCss = `
|
|
|
665
665
|
.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
|
|
666
666
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
667
667
|
:where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
|
|
668
|
-
.knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage)
|
|
668
|
+
.knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage)}
|
|
669
669
|
.knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
|
|
670
670
|
.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
|
|
671
671
|
opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
|
|
672
672
|
.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
|
|
673
|
-
.knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
|
|
674
|
-
opacity:0;filter:blur(4px);scale:.96;
|
|
675
|
-
background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
|
|
676
|
-
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
|
|
677
|
-
transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
678
|
-
.knobs-player-glyph svg{width:16px;height:16px}
|
|
679
|
-
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
680
673
|
.knobs-player-line{flex:none;min-height:0}
|
|
674
|
+
/* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
|
|
675
|
+
about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
|
|
676
|
+
resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
|
|
677
|
+
.knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
|
|
678
|
+
.knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
|
|
679
|
+
.knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
|
|
680
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
681
|
+
/* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
|
|
682
|
+
the line, centred on it. */
|
|
683
|
+
.knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
|
|
684
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
|
|
685
|
+
.knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play{width:28px;height:28px;min-width:28px;min-height:28px;border-radius:var(--_k-radius);background:transparent;color:var(--_k-fg-2);
|
|
686
|
+
transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
|
|
687
|
+
.knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
|
|
688
|
+
.knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
689
|
+
.knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
690
|
+
.knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
681
691
|
`;
|
|
682
692
|
|
|
683
693
|
// src/ui/overlay-css.ts
|
|
@@ -3229,16 +3239,17 @@ var init_diff_css = __esm(() => {
|
|
|
3229
3239
|
.knobs-diff-nav>span{min-width:3.6em;text-align:center}
|
|
3230
3240
|
:where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-choice,.knobs-diff-viewed,.knobs-diff-filter>input,.knobs-diff-filter>button){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;
|
|
3231
3241
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
3232
|
-
.knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-
|
|
3233
|
-
transition:background
|
|
3234
|
-
|
|
3242
|
+
.knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-2);cursor:default;
|
|
3243
|
+
transition:background calc(var(--_k-hover-out) * 1ms),opacity .15s}
|
|
3244
|
+
/* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
|
|
3245
|
+
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3235
3246
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3236
3247
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
3237
3248
|
.knobs-diff-btn svg{width:16px;height:16px}
|
|
3238
3249
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
3239
3250
|
.knobs-ui .knobs-diff-viewed{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--_k-control-h);padding:0 8px 0 6px;border-radius:var(--kd-ctl);color:var(--_k-fg-2);
|
|
3240
|
-
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:
|
|
3241
|
-
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--
|
|
3251
|
+
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),color .15s}
|
|
3252
|
+
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3242
3253
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3243
3254
|
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
3244
3255
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
@@ -3257,8 +3268,8 @@ var init_diff_css = __esm(() => {
|
|
|
3257
3268
|
choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
|
|
3258
3269
|
.knobs-diff-seg{position:relative;flex:none;display:grid;grid-template-columns:1fr 1fr;height:var(--_k-control-h);padding:2px;border-radius:var(--kd-ctl);background:var(--_k-fill)}
|
|
3259
3270
|
.knobs-ui .knobs-diff-choice{padding:0 9px;border-radius:max(0px,calc(var(--kd-ctl) - 2px));color:var(--_k-fg-2);font-size:var(--_k-text-meta);
|
|
3260
|
-
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:
|
|
3261
|
-
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){
|
|
3271
|
+
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
3272
|
+
.knobs-ui .knobs-diff-choice:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3262
3273
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
3263
3274
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
3264
3275
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -3279,8 +3290,8 @@ var init_diff_css = __esm(() => {
|
|
|
3279
3290
|
.knobs-diff-hbar{position:absolute;height:5px;pointer-events:none}
|
|
3280
3291
|
.knobs-diff-hbar>i{position:absolute;left:0;top:0;height:5px;border-radius:3px;background:color-mix(in srgb,var(--_k-fg) 30%,transparent);opacity:0;pointer-events:none;
|
|
3281
3292
|
transition:opacity .3s,background .15s}
|
|
3282
|
-
.knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration
|
|
3283
|
-
.knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent)}
|
|
3293
|
+
.knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3294
|
+
.knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3284
3295
|
/* stack: every file down one scroller, each header sticking until the next pushes it off. It's
|
|
3285
3296
|
flush (square, no card around its file), opaque in the code's own colour, with a hairline
|
|
3286
3297
|
under it only while it's stuck; a gap, not a line, parts one file from the next. */
|
|
@@ -3354,8 +3365,8 @@ var init_diff_css = __esm(() => {
|
|
|
3354
3365
|
.knobs-diff-filter svg{width:12px;height:12px;flex:none}
|
|
3355
3366
|
.knobs-ui .knobs-diff-filter>input{flex:1;min-width:0;height:100%;font:var(--_k-weight) var(--_k-font-size)/1 var(--_k-font);color:var(--_k-fg);caret-color:var(--_k-accent);background:transparent}
|
|
3356
3367
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
3357
|
-
.knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:
|
|
3358
|
-
.knobs-ui .knobs-diff-filter>button:hover{background:var(--
|
|
3368
|
+
.knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),opacity .12s}
|
|
3369
|
+
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3359
3370
|
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
3360
3371
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
3361
3372
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
@@ -9769,7 +9780,8 @@ function fuseFrame(L, before, after, k, wipe) {
|
|
|
9769
9780
|
const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
|
|
9770
9781
|
const overlap = before.x + before.w - after.x;
|
|
9771
9782
|
const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
|
|
9772
|
-
|
|
9783
|
+
const blur = t > 0.005 && t < 0.995 ? FUSE.blur * Math.sin(Math.PI * t) : 0;
|
|
9784
|
+
return { cut, feather, seam, blur };
|
|
9773
9785
|
}
|
|
9774
9786
|
function clampZoom(r) {
|
|
9775
9787
|
const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
|
|
@@ -9856,7 +9868,10 @@ function stripPeaks(diffs, o = {}) {
|
|
|
9856
9868
|
});
|
|
9857
9869
|
return out;
|
|
9858
9870
|
}
|
|
9859
|
-
var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE,
|
|
9871
|
+
var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE, fuseCorners = (r, k) => {
|
|
9872
|
+
const inner = r * Math.min(1, Math.max(0, k));
|
|
9873
|
+
return { before: { left: r, right: inner }, after: { left: inner, right: r } };
|
|
9874
|
+
}, labelFold = (fold, mark, blur = 3) => {
|
|
9860
9875
|
const f = Math.min(1, Math.max(0, fold));
|
|
9861
9876
|
const open = 1 - f;
|
|
9862
9877
|
return {
|
|
@@ -9887,7 +9902,7 @@ var init_compare_model = __esm(() => {
|
|
|
9887
9902
|
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
9888
9903
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
9889
9904
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
9890
|
-
FUSE = { feather:
|
|
9905
|
+
FUSE = { feather: 32, blur: 8, seamFade: 48 };
|
|
9891
9906
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
9892
9907
|
});
|
|
9893
9908
|
|
|
@@ -10620,8 +10635,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10620
10635
|
onLost();
|
|
10621
10636
|
};
|
|
10622
10637
|
lostListeners.add(lost);
|
|
10623
|
-
const uniforms = device.createBuffer({ size:
|
|
10624
|
-
const data = new Float32Array(
|
|
10638
|
+
const uniforms = device.createBuffer({ size: 192, usage: 64 | 8 });
|
|
10639
|
+
const data = new Float32Array(48);
|
|
10625
10640
|
const make = (w, h) => device.createTexture({
|
|
10626
10641
|
size: [Math.max(1, w), Math.max(1, h)],
|
|
10627
10642
|
format: "rgba8unorm",
|
|
@@ -10694,6 +10709,7 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10694
10709
|
data.set([f.preview[0] ? 1 : 0, f.preview[1] ? 1 : 0, 0, 0], 32);
|
|
10695
10710
|
r(36, f.beforeClip);
|
|
10696
10711
|
r(40, f.afterClip);
|
|
10712
|
+
data.set(f.radii, 44);
|
|
10697
10713
|
device.queue.writeBuffer(uniforms, 0, data);
|
|
10698
10714
|
const encoder = device.createCommandEncoder();
|
|
10699
10715
|
const pass = encoder.beginRenderPass({
|
|
@@ -10735,6 +10751,7 @@ struct U {
|
|
|
10735
10751
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
10736
10752
|
src: vec4f, // Before, After from their previews
|
|
10737
10753
|
ca: vec4f, cb: vec4f, // where each side may draw
|
|
10754
|
+
radii: vec4f, // the clips' corners: Before left, right; After left, right
|
|
10738
10755
|
}
|
|
10739
10756
|
@group(0) @binding(0) var<uniform> u: U;
|
|
10740
10757
|
@group(0) @binding(1) var A: texture_2d<f32>;
|
|
@@ -10756,8 +10773,18 @@ fn cover(p: vec2f, r: vec4f) -> f32 {
|
|
|
10756
10773
|
return clamp(d + 0.5, 0.0, 1.0);
|
|
10757
10774
|
}
|
|
10758
10775
|
|
|
10759
|
-
//
|
|
10760
|
-
fn
|
|
10776
|
+
// Coverage of rect r at p with its left corners rounded rl and its right ones rr.
|
|
10777
|
+
fn coverRound(p: vec2f, r: vec4f, rl: f32, rr: f32) -> f32 {
|
|
10778
|
+
let h = r.zw * 0.5;
|
|
10779
|
+
let q0 = p - (r.xy + h);
|
|
10780
|
+
let rad = min(select(rl, rr, q0.x > 0.0), min(h.x, h.y));
|
|
10781
|
+
let q = abs(q0) - h + vec2f(rad);
|
|
10782
|
+
let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rad;
|
|
10783
|
+
return clamp(0.5 - d, 0.0, 1.0);
|
|
10784
|
+
}
|
|
10785
|
+
|
|
10786
|
+
// A side's colour where rect r maps it to p, and its coverage inside clip c (corners rc) there.
|
|
10787
|
+
fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, rc: vec2f, near: bool, ready: f32) -> vec4f {
|
|
10761
10788
|
let uv = clamp((p - r.xy) / max(r.zw, vec2f(1e-3)), vec2f(0.0), vec2f(1.0));
|
|
10762
10789
|
var c: vec3f;
|
|
10763
10790
|
if (near) {
|
|
@@ -10766,16 +10793,16 @@ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f3
|
|
|
10766
10793
|
} else {
|
|
10767
10794
|
c = textureSampleLevel(t, smp, uv, 0.0).rgb;
|
|
10768
10795
|
}
|
|
10769
|
-
return vec4f(c, min(cover(p, r),
|
|
10796
|
+
return vec4f(c, min(cover(p, r), coverRound(p, c0, rc.x, rc.y)) * ready);
|
|
10770
10797
|
}
|
|
10771
10798
|
|
|
10772
10799
|
fn takeA(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10773
|
-
if (u.src.x > 0.5) { return take(PA, p, r, c0, near, 1.0); }
|
|
10774
|
-
return take(A, p, r, c0, near, u.diff.z);
|
|
10800
|
+
if (u.src.x > 0.5) { return take(PA, p, r, c0, u.radii.xy, near, 1.0); }
|
|
10801
|
+
return take(A, p, r, c0, u.radii.xy, near, u.diff.z);
|
|
10775
10802
|
}
|
|
10776
10803
|
fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10777
|
-
if (u.src.y > 0.5) { return take(PB, p, r, c0, near, 1.0); }
|
|
10778
|
-
return take(B, p, r, c0, near, u.diff.w);
|
|
10804
|
+
if (u.src.y > 0.5) { return take(PB, p, r, c0, u.radii.zw, near, 1.0); }
|
|
10805
|
+
return take(B, p, r, c0, u.radii.zw, near, u.diff.w);
|
|
10779
10806
|
}
|
|
10780
10807
|
|
|
10781
10808
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
@@ -11195,29 +11222,6 @@ var init_compare_strip = __esm(() => {
|
|
|
11195
11222
|
DIFF = { w: 32, h: 18 };
|
|
11196
11223
|
});
|
|
11197
11224
|
|
|
11198
|
-
// src/ui/player.ts
|
|
11199
|
-
function playGlyph(doc) {
|
|
11200
|
-
const element = doc.createElement("span");
|
|
11201
|
-
element.className = "knobs-player-glyph";
|
|
11202
|
-
element.setAttribute("aria-hidden", "true");
|
|
11203
|
-
let timer = 0;
|
|
11204
|
-
const win = doc.defaultView;
|
|
11205
|
-
return {
|
|
11206
|
-
element,
|
|
11207
|
-
flash(playing) {
|
|
11208
|
-
element.innerHTML = playing ? PLAY2 : PAUSE;
|
|
11209
|
-
element.setAttribute("data-flash", "");
|
|
11210
|
-
win.clearTimeout(timer);
|
|
11211
|
-
timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
|
|
11212
|
-
},
|
|
11213
|
-
dispose: () => win.clearTimeout(timer)
|
|
11214
|
-
};
|
|
11215
|
-
}
|
|
11216
|
-
var FLASH_MS = 450;
|
|
11217
|
-
var init_player = __esm(() => {
|
|
11218
|
-
init_timeline();
|
|
11219
|
-
});
|
|
11220
|
-
|
|
11221
11225
|
// src/ui/compare.ts
|
|
11222
11226
|
function compare(container, options) {
|
|
11223
11227
|
const doc = container.ownerDocument;
|
|
@@ -11336,8 +11340,7 @@ function compare(container, options) {
|
|
|
11336
11340
|
grip.setAttribute("aria-hidden", "true");
|
|
11337
11341
|
handle.append(grip);
|
|
11338
11342
|
const note = el("div", "knobs-cmp-note");
|
|
11339
|
-
|
|
11340
|
-
stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
|
|
11343
|
+
stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note);
|
|
11341
11344
|
root.append(bar, stage);
|
|
11342
11345
|
const wantsStrip = options.strip === true;
|
|
11343
11346
|
let tl = bound;
|
|
@@ -11379,7 +11382,6 @@ function compare(container, options) {
|
|
|
11379
11382
|
let drag = null;
|
|
11380
11383
|
const touches = new Map;
|
|
11381
11384
|
let pinch = null;
|
|
11382
|
-
let press = null;
|
|
11383
11385
|
const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11384
11386
|
const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11385
11387
|
const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
@@ -11391,6 +11393,7 @@ function compare(container, options) {
|
|
|
11391
11393
|
let stageRect = null;
|
|
11392
11394
|
let accent = [0.49, 0.81, 0.61];
|
|
11393
11395
|
let labelW = [0, 0];
|
|
11396
|
+
let frameRadius = 0;
|
|
11394
11397
|
let dirty = true;
|
|
11395
11398
|
let disposed = false;
|
|
11396
11399
|
let near = options.lazy === false;
|
|
@@ -11911,6 +11914,7 @@ function compare(container, options) {
|
|
|
11911
11914
|
const wr = wipeRect(r.L.overlay);
|
|
11912
11915
|
const k = Math.min(1, Math.max(0, lay.x));
|
|
11913
11916
|
const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
|
|
11917
|
+
const corners = fuseCorners(frameRadius, k);
|
|
11914
11918
|
const dw = differenceWeight();
|
|
11915
11919
|
const fusing = 1 - dw;
|
|
11916
11920
|
folded = k * fusing;
|
|
@@ -11967,6 +11971,7 @@ function compare(container, options) {
|
|
|
11967
11971
|
if (renderer === "dom") {
|
|
11968
11972
|
const cut = mode === "difference" ? null : fu;
|
|
11969
11973
|
const place = (n, rect, clip, before) => {
|
|
11974
|
+
const round = before ? corners.before : corners.after;
|
|
11970
11975
|
const st = n.style;
|
|
11971
11976
|
st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
|
|
11972
11977
|
st.width = `${rect.w.toFixed(2)}px`;
|
|
@@ -11976,7 +11981,9 @@ function compare(container, options) {
|
|
|
11976
11981
|
const l = Math.max(0, clip.x - rect.x);
|
|
11977
11982
|
const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
|
|
11978
11983
|
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
|
|
11979
|
-
|
|
11984
|
+
const rr = edge < clip.x + clip.w - 0.5 ? 0 : round.right;
|
|
11985
|
+
const rl = round.left;
|
|
11986
|
+
st.clipPath = t + l + b + right + rl + rr > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px round ${rl.toFixed(2)}px ${rr.toFixed(2)}px ${rr.toFixed(2)}px ${rl.toFixed(2)}px)` : "";
|
|
11980
11987
|
const mask = before && cut && cut.feather > 0.5 ? `linear-gradient(to right,#000 ${(cut.cut - cut.feather - rect.x).toFixed(2)}px,transparent ${(edge - rect.x).toFixed(2)}px)` : "";
|
|
11981
11988
|
st.maskImage = mask;
|
|
11982
11989
|
st.setProperty("-webkit-mask-image", mask);
|
|
@@ -12014,7 +12021,13 @@ function compare(container, options) {
|
|
|
12014
12021
|
gain: 1,
|
|
12015
12022
|
accent,
|
|
12016
12023
|
loupe: loupeShown() && pointer ? { x: pointer.x * dpr, y: pointer.y * dpr, r: LOUPE.r * dpr, zoom: LOUPE.zoom } : null,
|
|
12017
|
-
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)]
|
|
12024
|
+
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)],
|
|
12025
|
+
radii: [
|
|
12026
|
+
corners.before.left * dpr,
|
|
12027
|
+
corners.before.right * dpr,
|
|
12028
|
+
corners.after.left * dpr,
|
|
12029
|
+
corners.after.right * dpr
|
|
12030
|
+
]
|
|
12018
12031
|
});
|
|
12019
12032
|
};
|
|
12020
12033
|
const drawNow = () => {
|
|
@@ -12106,6 +12119,7 @@ function compare(container, options) {
|
|
|
12106
12119
|
accent = [c[0], c[1], c[2]];
|
|
12107
12120
|
const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
|
|
12108
12121
|
labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
|
|
12122
|
+
frameRadius = Number.parseFloat(win.getComputedStyle(stage).borderTopLeftRadius) || 0;
|
|
12109
12123
|
requestDraw();
|
|
12110
12124
|
};
|
|
12111
12125
|
const measure = () => {
|
|
@@ -12196,10 +12210,8 @@ function compare(container, options) {
|
|
|
12196
12210
|
root.focus({ preventScroll: true });
|
|
12197
12211
|
touches.set(e.pointerId, p);
|
|
12198
12212
|
stage.setPointerCapture(e.pointerId);
|
|
12199
|
-
press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
|
|
12200
12213
|
if (touches.size >= 2) {
|
|
12201
12214
|
e.preventDefault();
|
|
12202
|
-
press = null;
|
|
12203
12215
|
return startPinch();
|
|
12204
12216
|
}
|
|
12205
12217
|
if (mode === "wipe") {
|
|
@@ -12227,8 +12239,6 @@ function compare(container, options) {
|
|
|
12227
12239
|
const onMove = (e) => {
|
|
12228
12240
|
const p = local(e);
|
|
12229
12241
|
pointer = p;
|
|
12230
|
-
if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
|
|
12231
|
-
press = null;
|
|
12232
12242
|
if (touches.has(e.pointerId))
|
|
12233
12243
|
touches.set(e.pointerId, p);
|
|
12234
12244
|
if (pinch && touches.size >= 2) {
|
|
@@ -12258,11 +12268,6 @@ function compare(container, options) {
|
|
|
12258
12268
|
drawNow();
|
|
12259
12269
|
};
|
|
12260
12270
|
const endDrag = (e) => {
|
|
12261
|
-
if (press && e.type === "pointerup") {
|
|
12262
|
-
api.toggle();
|
|
12263
|
-
glyph.flash(isPlaying());
|
|
12264
|
-
}
|
|
12265
|
-
press = null;
|
|
12266
12271
|
touches.delete(e.pointerId);
|
|
12267
12272
|
if (touches.size < 2)
|
|
12268
12273
|
pinch = null;
|
|
@@ -12424,7 +12429,6 @@ function compare(container, options) {
|
|
|
12424
12429
|
old.el.replaceWith(s.el);
|
|
12425
12430
|
old.preview.replaceWith(s.preview);
|
|
12426
12431
|
sides[index] = s;
|
|
12427
|
-
root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
|
|
12428
12432
|
note.textContent = "";
|
|
12429
12433
|
setLabel(index, labelOf(index));
|
|
12430
12434
|
if (near)
|
|
@@ -12439,7 +12443,6 @@ function compare(container, options) {
|
|
|
12439
12443
|
stage.insertBefore(sides[0].preview, labels[0]);
|
|
12440
12444
|
setLabel(0, labelOf(0));
|
|
12441
12445
|
setLabel(1, labelOf(1));
|
|
12442
|
-
root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
|
|
12443
12446
|
loupeBtn.setAttribute("aria-pressed", String(loupeOn));
|
|
12444
12447
|
loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
|
|
12445
12448
|
const modePill = modeList.length > 1 ? selectionPill(modes, {
|
|
@@ -12640,7 +12643,6 @@ function compare(container, options) {
|
|
|
12640
12643
|
disposed = true;
|
|
12641
12644
|
loop.stop();
|
|
12642
12645
|
modePill?.dispose();
|
|
12643
|
-
glyph.dispose();
|
|
12644
12646
|
win.clearTimeout(releaseTimer);
|
|
12645
12647
|
ro.disconnect();
|
|
12646
12648
|
nearIo?.disconnect();
|
|
@@ -12681,7 +12683,6 @@ var init_compare = __esm(() => {
|
|
|
12681
12683
|
init_icons();
|
|
12682
12684
|
init_look();
|
|
12683
12685
|
init_rgba();
|
|
12684
|
-
init_player();
|
|
12685
12686
|
init_selection_pill();
|
|
12686
12687
|
init_timeline();
|
|
12687
12688
|
init_timeline_model();
|
|
@@ -17198,11 +17199,629 @@ function toast(message, options = {}) {
|
|
|
17198
17199
|
};
|
|
17199
17200
|
}
|
|
17200
17201
|
|
|
17202
|
+
// src/ui/walkthrough-annotations.ts
|
|
17203
|
+
var EYE = "M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z";
|
|
17204
|
+
var EYE_OFF = "M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z";
|
|
17205
|
+
var glyph = (d) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
|
17206
|
+
var ARROW = '<svg class="knobs-wt-anno-arrow" viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><path d="M3 3 L24 24 M24 24 L24 13 M24 24 L13 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
17207
|
+
var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
|
|
17208
|
+
var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
|
|
17209
|
+
function placeMark(a, rect, view = ALL) {
|
|
17210
|
+
const w = a.w ?? 0;
|
|
17211
|
+
const h = a.h ?? 0;
|
|
17212
|
+
const x = (a.x - view.u) / view.w;
|
|
17213
|
+
const y = (a.y - view.v) / view.h;
|
|
17214
|
+
const ww = w / view.w;
|
|
17215
|
+
const hh = h / view.h;
|
|
17216
|
+
if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
|
|
17217
|
+
return null;
|
|
17218
|
+
return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
|
|
17219
|
+
}
|
|
17220
|
+
var ALL = { u: 0, v: 0, w: 1, h: 1 };
|
|
17221
|
+
var scopeOf = (el) => el.closest(".knobs-walkthrough") ?? el.closest(".knobs-wt-slot") ?? el;
|
|
17222
|
+
var annotationsOn = (scope) => scope.dataset.annotations !== "off";
|
|
17223
|
+
function setAnnotations(scope, on) {
|
|
17224
|
+
if (on)
|
|
17225
|
+
delete scope.dataset.annotations;
|
|
17226
|
+
else
|
|
17227
|
+
scope.dataset.annotations = "off";
|
|
17228
|
+
for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
|
|
17229
|
+
syncToggle(b, on);
|
|
17230
|
+
}
|
|
17231
|
+
function syncToggle(b, on) {
|
|
17232
|
+
b.setAttribute("aria-pressed", String(on));
|
|
17233
|
+
b.title = on ? "Hide annotations (a)" : "Show annotations (a)";
|
|
17234
|
+
b.innerHTML = glyph(on ? EYE : EYE_OFF);
|
|
17235
|
+
}
|
|
17236
|
+
function annotationLayer(host, list, place, corner = host) {
|
|
17237
|
+
const doc = host.ownerDocument;
|
|
17238
|
+
const layer = doc.createElement("div");
|
|
17239
|
+
layer.className = "knobs-wt-anno";
|
|
17240
|
+
layer.setAttribute("aria-hidden", "true");
|
|
17241
|
+
host.append(layer);
|
|
17242
|
+
const toggle = doc.createElement("button");
|
|
17243
|
+
toggle.type = "button";
|
|
17244
|
+
toggle.className = "knobs-wt-anno-toggle";
|
|
17245
|
+
toggle.setAttribute("aria-label", "Annotations");
|
|
17246
|
+
syncToggle(toggle, annotationsOn(scopeOf(host)));
|
|
17247
|
+
toggle.addEventListener("click", (e) => {
|
|
17248
|
+
e.stopPropagation();
|
|
17249
|
+
const scope = scopeOf(host);
|
|
17250
|
+
setAnnotations(scope, !annotationsOn(scope));
|
|
17251
|
+
});
|
|
17252
|
+
corner.append(toggle);
|
|
17253
|
+
let marks = [];
|
|
17254
|
+
const make = (a) => {
|
|
17255
|
+
const m = doc.createElement("div");
|
|
17256
|
+
const shape = shapeOf(a);
|
|
17257
|
+
m.className = "knobs-wt-mark";
|
|
17258
|
+
m.dataset.shape = shape;
|
|
17259
|
+
m.dataset.hidden = "";
|
|
17260
|
+
if (shape === "arrow")
|
|
17261
|
+
m.innerHTML = ARROW;
|
|
17262
|
+
if (a.label) {
|
|
17263
|
+
const l = doc.createElement("span");
|
|
17264
|
+
l.className = "knobs-wt-mark-label";
|
|
17265
|
+
l.textContent = a.label;
|
|
17266
|
+
m.append(l);
|
|
17267
|
+
}
|
|
17268
|
+
layer.append(m);
|
|
17269
|
+
return m;
|
|
17270
|
+
};
|
|
17271
|
+
let settled = false;
|
|
17272
|
+
const update = () => {
|
|
17273
|
+
const p = place();
|
|
17274
|
+
if (marks.length !== p.rects.length) {
|
|
17275
|
+
for (const row of marks)
|
|
17276
|
+
for (const m of row)
|
|
17277
|
+
m.remove();
|
|
17278
|
+
marks = p.rects.map(() => list.map(make));
|
|
17279
|
+
}
|
|
17280
|
+
p.rects.forEach((rect, i) => {
|
|
17281
|
+
list.forEach((a, j) => {
|
|
17282
|
+
const m = marks[i][j];
|
|
17283
|
+
const box = placeMark(a, rect, p.view);
|
|
17284
|
+
const show = box !== null && showsAt(a, p.time);
|
|
17285
|
+
if (box) {
|
|
17286
|
+
m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
|
|
17287
|
+
m.style.width = `${box.w.toFixed(2)}px`;
|
|
17288
|
+
m.style.height = `${box.h.toFixed(2)}px`;
|
|
17289
|
+
}
|
|
17290
|
+
m.toggleAttribute("data-hidden", !show);
|
|
17291
|
+
});
|
|
17292
|
+
});
|
|
17293
|
+
if (!settled) {
|
|
17294
|
+
settled = true;
|
|
17295
|
+
requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
|
|
17296
|
+
}
|
|
17297
|
+
};
|
|
17298
|
+
const ro = new ResizeObserver(() => update());
|
|
17299
|
+
ro.observe(host);
|
|
17300
|
+
update();
|
|
17301
|
+
return {
|
|
17302
|
+
element: layer,
|
|
17303
|
+
update,
|
|
17304
|
+
dispose() {
|
|
17305
|
+
ro.disconnect();
|
|
17306
|
+
layer.remove();
|
|
17307
|
+
toggle.remove();
|
|
17308
|
+
}
|
|
17309
|
+
};
|
|
17310
|
+
}
|
|
17311
|
+
|
|
17201
17312
|
// src/ui/walkthrough-blocks.ts
|
|
17202
17313
|
init_motion();
|
|
17203
|
-
|
|
17314
|
+
|
|
17315
|
+
// src/ui/history.ts
|
|
17316
|
+
init_motion();
|
|
17317
|
+
init_glide();
|
|
17318
|
+
|
|
17319
|
+
// src/ui/history-css.ts
|
|
17320
|
+
var historyCss = `
|
|
17321
|
+
:where(.knobs-history,.knobs-history *){box-sizing:border-box}
|
|
17322
|
+
.knobs-ui.knobs-history{--hy-gutter:128px;--hy-lane:24px;--hy-bar:16px;--hy-axis:20px;--hy-bandrow:0px;--hy-c:var(--_k-fg-3);
|
|
17323
|
+
--hy-fade:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);
|
|
17324
|
+
position:relative;display:flex;flex-direction:column;width:100%;min-width:0;outline:none;color:var(--_k-fg);
|
|
17325
|
+
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
|
|
17326
|
+
text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;user-select:none;-webkit-user-select:none}
|
|
17327
|
+
.knobs-history[data-bands]{--hy-bandrow:var(--_k-leading-small)}
|
|
17328
|
+
.knobs-history[data-narrow]{--hy-gutter:0px}
|
|
17329
|
+
.knobs-history [hidden]{display:none!important}
|
|
17330
|
+
:where(.knobs-ui) :is(.knobs-hy-ev,.knobs-hy-span,.knobs-hy-jump,.knobs-hy-more){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
|
|
17331
|
+
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
17332
|
+
.knobs-history [data-tone="good"]{--hy-c:var(--_k-ok)}
|
|
17333
|
+
.knobs-history [data-tone="warn"]{--hy-c:var(--_k-warn)}
|
|
17334
|
+
.knobs-history [data-tone="bad"]{--hy-c:var(--_k-danger)}
|
|
17335
|
+
.knobs-history [data-tone="info"]{--hy-c:var(--_k-info)}
|
|
17336
|
+
.knobs-history [data-tone="dim"]{--hy-c:var(--_k-fg-3)}
|
|
17337
|
+
|
|
17338
|
+
/* ——— the chart: lane labels beside the plot (above each lane where it's narrow) ——— */
|
|
17339
|
+
.knobs-hy-chart{position:relative;display:grid;grid-template-columns:var(--hy-gutter) minmax(0,1fr)}
|
|
17340
|
+
.knobs-hy-axis{position:relative;grid-column:2;grid-row:1;height:calc(var(--hy-bandrow) + var(--hy-axis))}
|
|
17341
|
+
:is(.knobs-hy-tick,.knobs-hy-now,.knobs-hy-blabel){position:absolute;white-space:nowrap;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17342
|
+
.knobs-hy-tick{bottom:2px;padding-left:4px}
|
|
17343
|
+
.knobs-hy-now{bottom:2px;color:var(--_k-accent-fg);font-weight:var(--_k-weight-label)}
|
|
17344
|
+
.knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
|
|
17345
|
+
.knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
|
|
17346
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17347
|
+
.knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
|
|
17348
|
+
:is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
|
|
17349
|
+
.knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
|
|
17350
|
+
.knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
|
|
17351
|
+
|
|
17352
|
+
/* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
|
|
17353
|
+
.knobs-hy-band{position:absolute;top:0;bottom:0;background:color-mix(in oklch,var(--_k-fg) 3.5%,transparent);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
|
|
17354
|
+
:is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
|
|
17355
|
+
.knobs-hy-grid{background:var(--_k-border-subtle)}
|
|
17356
|
+
.knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
|
|
17357
|
+
.knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17358
|
+
.knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17359
|
+
.knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
|
|
17360
|
+
.knobs-hy-sel[data-on]{opacity:.6}
|
|
17361
|
+
|
|
17362
|
+
/* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
|
|
17363
|
+
.knobs-hy-span{position:absolute;top:calc((var(--hy-lane) - var(--hy-bar)) / 2);height:var(--hy-bar);border-radius:var(--_k-radius-sm);cursor:pointer;
|
|
17364
|
+
background:color-mix(in oklch,var(--hy-c) 24%,transparent);transition:background calc(var(--_k-hover-out) * 1ms),box-shadow calc(var(--_k-hover-out) * 1ms)}
|
|
17365
|
+
.knobs-hy-span[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 34%,transparent) 0 2px,color-mix(in oklch,var(--hy-c) 9%,transparent) 2px 6px)}
|
|
17366
|
+
.knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17367
|
+
.knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
|
|
17368
|
+
.knobs-hy-span[data-clip-start]{border-top-left-radius:0;border-bottom-left-radius:0;-webkit-mask-image:linear-gradient(to right,transparent,#000 16px);mask-image:linear-gradient(to right,transparent,#000 16px)}
|
|
17369
|
+
.knobs-hy-span[data-clip-end]{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-mask-image:linear-gradient(to left,transparent,#000 16px);mask-image:linear-gradient(to left,transparent,#000 16px)}
|
|
17370
|
+
.knobs-hy-tag{position:absolute;top:0;right:6px;font-size:var(--_k-type-small);line-height:var(--hy-bar);white-space:nowrap;pointer-events:none;
|
|
17371
|
+
color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
|
|
17372
|
+
.knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
|
|
17373
|
+
.knobs-hy-tag[data-place="none"]{visibility:hidden}
|
|
17374
|
+
|
|
17375
|
+
/* Event marks (their kind named in the tooltip): a dot for a commit, a diamond for a release, a cross for a deletion, a square for a
|
|
17376
|
+
channel's switch, a triangle for a finding; inferred ones hollow. */
|
|
17377
|
+
.knobs-hy-ev{position:absolute;top:0;width:16px;height:var(--hy-lane);margin-left:-8px;cursor:pointer;--hy-c:var(--_k-fg-2);--hy-s:9px}
|
|
17378
|
+
.knobs-hy-ev::before{content:"";position:absolute;left:50%;top:50%;width:var(--hy-s);height:var(--hy-s);margin:calc(var(--hy-s) / -2) 0 0 calc(var(--hy-s) / -2);
|
|
17379
|
+
border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17380
|
+
.knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
|
|
17381
|
+
.knobs-hy-ev[data-range]::before{left:calc(8px - var(--hy-s) / 2);right:calc(8px - var(--hy-s) / 2);width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17382
|
+
.knobs-hy-ev[data-type="release"]{--hy-s:8px}
|
|
17383
|
+
.knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
|
|
17384
|
+
.knobs-hy-ev[data-type="channel"]{--hy-s:8px}
|
|
17385
|
+
.knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
|
|
17386
|
+
.knobs-hy-ev[data-type="audit"]{--hy-s:10px}
|
|
17387
|
+
.knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
|
|
17388
|
+
.knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
|
|
17389
|
+
background:linear-gradient(var(--hy-c),var(--hy-c)) center/100% 1.75px no-repeat,linear-gradient(var(--hy-c),var(--hy-c)) center/1.75px 100% no-repeat}
|
|
17390
|
+
.knobs-hy-ev[data-inferred]:not([data-type="audit"],[data-type="deletion"])::before{background:transparent;box-shadow:inset 0 0 0 1.5px var(--hy-c)}
|
|
17391
|
+
.knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
|
|
17392
|
+
/* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
|
|
17393
|
+
"Show commits" or while selected. */
|
|
17394
|
+
.knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17395
|
+
.knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
17396
|
+
:is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
|
|
17397
|
+
.knobs-hy-ev[data-hover]::before{scale:1.25}
|
|
17398
|
+
.knobs-hy-ev[data-selected]::before{scale:1.3}
|
|
17399
|
+
.knobs-hy-ev[data-selected]::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--hy-s) + 9px);height:calc(var(--hy-s) + 9px);
|
|
17400
|
+
margin:calc((var(--hy-s) + 9px) / -2) 0 0 calc((var(--hy-s) + 9px) / -2);border-radius:50%;box-shadow:0 0 0 1.5px var(--hy-c)}
|
|
17401
|
+
.knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17402
|
+
.knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
17403
|
+
.knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
17404
|
+
|
|
17405
|
+
/* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
|
|
17406
|
+
.knobs-hy-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--_k-space-inside) var(--_k-space-title);margin-top:var(--_k-space-sibling);padding-left:var(--hy-gutter);
|
|
17407
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17408
|
+
.knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
|
|
17409
|
+
.knobs-ui .knobs-hy-more{height:20px;margin-left:-6px;padding:0 6px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-3);cursor:pointer;
|
|
17410
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17411
|
+
.knobs-ui .knobs-hy-more:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17412
|
+
.knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
17413
|
+
.knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
|
|
17414
|
+
.knobs-hy-swatch[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 60%,transparent) 0 1.5px,transparent 1.5px 4px)}
|
|
17415
|
+
|
|
17416
|
+
/* ——— the detail: one area under the chart, its height on a spring ——— */
|
|
17417
|
+
.knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
17418
|
+
.knobs-hy-detail[data-moving]{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent)}
|
|
17419
|
+
.knobs-hy-dc{padding:var(--_k-space-title) 0 0 var(--hy-gutter);transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17420
|
+
.knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
|
|
17421
|
+
.knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
|
|
17422
|
+
.knobs-hy-dmeta{display:flex;flex-wrap:wrap;gap:0 var(--_k-space-sibling);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17423
|
+
.knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
|
|
17424
|
+
.knobs-hy-dtitle{margin-top:var(--_k-space-inside);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight-title);color:var(--_k-fg);text-wrap:balance}
|
|
17425
|
+
.knobs-hy-dtext{margin:var(--_k-space-inside) 0 0;max-width:68ch;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);color:var(--_k-fg-2);text-wrap:pretty}
|
|
17426
|
+
.knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
|
|
17427
|
+
.knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
|
|
17428
|
+
.knobs-hy-dfoot:empty{display:none}
|
|
17429
|
+
.knobs-hy-evidence{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--_k-radius-sm);background:var(--_k-fill);color:var(--_k-fg-2);
|
|
17430
|
+
font:var(--_k-type-small)/1 var(--_k-mono);text-decoration:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17431
|
+
a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17432
|
+
.knobs-ui .knobs-hy-jump{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px 0 8px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-2);cursor:pointer;
|
|
17433
|
+
font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17434
|
+
.knobs-ui .knobs-hy-jump:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17435
|
+
.knobs-hy-jump svg{width:12px;height:12px}
|
|
17436
|
+
|
|
17437
|
+
/* ——— the pointer's tooltip: title, date, one line ——— */
|
|
17438
|
+
.knobs-hy-tip{position:absolute;left:0;top:0;max-width:280px;padding:6px 8px;border-radius:var(--_k-radius-md);background:var(--_k-bg-overlay);
|
|
17439
|
+
box-shadow:var(--_k-shadow-overlay),0 0 0 1px var(--_k-border);pointer-events:none;opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17440
|
+
.knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17441
|
+
.knobs-hy-tt{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight-label);color:var(--_k-fg);text-wrap:balance}
|
|
17442
|
+
:is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17443
|
+
.knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
|
|
17444
|
+
.knobs-hy-tl:empty{display:none}
|
|
17445
|
+
|
|
17446
|
+
@media(prefers-reduced-motion:reduce){
|
|
17447
|
+
.knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
|
|
17448
|
+
.knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
|
|
17449
|
+
}
|
|
17450
|
+
`;
|
|
17451
|
+
|
|
17452
|
+
// src/ui/history-model.ts
|
|
17453
|
+
var MS_DAY = 86400000;
|
|
17454
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
17455
|
+
var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
|
|
17456
|
+
var parts = (day) => {
|
|
17457
|
+
const t = new Date(day * MS_DAY);
|
|
17458
|
+
return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
|
|
17459
|
+
};
|
|
17460
|
+
var todayDay = (now = Date.now()) => {
|
|
17461
|
+
const t = new Date(now);
|
|
17462
|
+
return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
|
|
17463
|
+
};
|
|
17464
|
+
var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
|
|
17465
|
+
function parseDay(text, year) {
|
|
17466
|
+
const s = text.trim();
|
|
17467
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
17468
|
+
if (iso) {
|
|
17469
|
+
const y = Number(iso[1]);
|
|
17470
|
+
const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
|
|
17471
|
+
return { from: day, to: day, year: y };
|
|
17472
|
+
}
|
|
17473
|
+
const m = /^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:\s*[–—-]\s*(?:([A-Za-z]{3,9})\.?\s+)?(\d{1,2}))?(?:,?\s+(\d{4}))?$/.exec(s);
|
|
17474
|
+
if (!m)
|
|
17475
|
+
return null;
|
|
17476
|
+
const m1 = monthIndex(m[1]);
|
|
17477
|
+
const m2 = m[3] ? monthIndex(m[3]) : m1;
|
|
17478
|
+
if (m1 < 0 || m2 < 0)
|
|
17479
|
+
return null;
|
|
17480
|
+
const written = m[5] ? Number(m[5]) : undefined;
|
|
17481
|
+
const y = written ?? year;
|
|
17482
|
+
if (y === undefined)
|
|
17483
|
+
return null;
|
|
17484
|
+
const from = dayOf(y, m1, Number(m[2]));
|
|
17485
|
+
let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
|
|
17486
|
+
if (to < from)
|
|
17487
|
+
to = dayOf(y + 1, m2, Number(m[4]));
|
|
17488
|
+
return { from, to, ...written !== undefined ? { year: written } : {} };
|
|
17489
|
+
}
|
|
17490
|
+
function formatDay(day) {
|
|
17491
|
+
const { y, m, d } = parts(day);
|
|
17492
|
+
return `${MONTHS[m]} ${d}, ${y}`;
|
|
17493
|
+
}
|
|
17494
|
+
function formatRange(from, to, open = false) {
|
|
17495
|
+
if (open)
|
|
17496
|
+
return `${formatDay(from)} – today`;
|
|
17497
|
+
if (to <= from)
|
|
17498
|
+
return formatDay(from);
|
|
17499
|
+
const a = parts(from);
|
|
17500
|
+
const b = parts(to);
|
|
17501
|
+
if (a.y === b.y && a.m === b.m)
|
|
17502
|
+
return `${MONTHS[a.m]} ${a.d}–${b.d}, ${a.y}`;
|
|
17503
|
+
if (a.y === b.y)
|
|
17504
|
+
return `${MONTHS[a.m]} ${a.d} – ${MONTHS[b.m]} ${b.d}, ${a.y}`;
|
|
17505
|
+
return `${formatDay(from)} – ${formatDay(to)}`;
|
|
17506
|
+
}
|
|
17507
|
+
var formatDays = (n) => `${n} ${n === 1 ? "day" : "days"}`;
|
|
17508
|
+
function guessTone(state) {
|
|
17509
|
+
const s = state.toLowerCase();
|
|
17510
|
+
if (/broken|bad|fail|missing|deleted|down/.test(s))
|
|
17511
|
+
return "bad";
|
|
17512
|
+
if (/warn|redirect|partial|beta|next|pre/.test(s))
|
|
17513
|
+
return "warn";
|
|
17514
|
+
if (/work|ok|good|fixed|live|green/.test(s))
|
|
17515
|
+
return "good";
|
|
17516
|
+
return "dim";
|
|
17517
|
+
}
|
|
17518
|
+
var bandLabel = (heading) => heading.replace(/^\s*\d+\s*[·.:)\-–]\s*/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
17519
|
+
function fromGroups(groups, today) {
|
|
17520
|
+
const events = [];
|
|
17521
|
+
const bands = [];
|
|
17522
|
+
const all = groups.flatMap((g) => g.events ?? g.rows ?? []);
|
|
17523
|
+
let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
|
|
17524
|
+
for (const g of groups) {
|
|
17525
|
+
let lo = Infinity;
|
|
17526
|
+
let hi = -Infinity;
|
|
17527
|
+
for (const e of g.events ?? g.rows ?? []) {
|
|
17528
|
+
const d = parseDay(e.date, year);
|
|
17529
|
+
if (!d)
|
|
17530
|
+
continue;
|
|
17531
|
+
if (d.year !== undefined)
|
|
17532
|
+
year = d.year;
|
|
17533
|
+
lo = Math.min(lo, d.from);
|
|
17534
|
+
hi = Math.max(hi, d.to);
|
|
17535
|
+
events.push({
|
|
17536
|
+
...e,
|
|
17537
|
+
date: iso(d.from),
|
|
17538
|
+
...d.to > d.from ? { to: iso(d.to) } : {},
|
|
17539
|
+
lane: e.lane ?? "code"
|
|
17540
|
+
});
|
|
17541
|
+
}
|
|
17542
|
+
const label = g.heading ? bandLabel(g.heading) : "";
|
|
17543
|
+
if (label && lo <= hi)
|
|
17544
|
+
bands.push({ from: iso(lo), to: iso(hi), label });
|
|
17545
|
+
}
|
|
17546
|
+
return { events, bands };
|
|
17547
|
+
}
|
|
17548
|
+
function iso(day) {
|
|
17549
|
+
const { y, m, d } = parts(day);
|
|
17550
|
+
return `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
17551
|
+
}
|
|
17552
|
+
var startOfMonth = (day) => {
|
|
17553
|
+
const { y, m } = parts(day);
|
|
17554
|
+
return dayOf(y, m, 1);
|
|
17555
|
+
};
|
|
17556
|
+
var TYPE_TONE = { deletion: "bad" };
|
|
17557
|
+
function normalizeHistory(data, now = todayDay()) {
|
|
17558
|
+
const today = data.axis?.today && parseDay(data.axis.today, parts(now).y)?.from || now;
|
|
17559
|
+
const converted = data.groups?.length ? fromGroups(data.groups, today) : { events: [], bands: [] };
|
|
17560
|
+
const rawEvents = [...data.events ?? [], ...converted.events];
|
|
17561
|
+
const rawBands = [...data.bands ?? [], ...converted.bands];
|
|
17562
|
+
const year = parts(today).y;
|
|
17563
|
+
const day = (s) => s ? parseDay(s, year) ?? null : null;
|
|
17564
|
+
const states = new Map;
|
|
17565
|
+
const stateOf = (key) => {
|
|
17566
|
+
const known = states.get(key);
|
|
17567
|
+
if (known)
|
|
17568
|
+
return known;
|
|
17569
|
+
const def = data.states?.[key];
|
|
17570
|
+
const s = { key, label: def?.label ?? key, tone: def?.tone ?? guessTone(key) };
|
|
17571
|
+
states.set(key, s);
|
|
17572
|
+
return s;
|
|
17573
|
+
};
|
|
17574
|
+
const items = new Map;
|
|
17575
|
+
const laneOrder = [];
|
|
17576
|
+
const lanes = new Map;
|
|
17577
|
+
const lane = (id, label = id === "code" ? "Code" : id) => {
|
|
17578
|
+
let l = lanes.get(id);
|
|
17579
|
+
if (!l) {
|
|
17580
|
+
l = { id, label, spans: [], events: [] };
|
|
17581
|
+
lanes.set(id, l);
|
|
17582
|
+
laneOrder.push(id);
|
|
17583
|
+
}
|
|
17584
|
+
return l;
|
|
17585
|
+
};
|
|
17586
|
+
if (rawEvents.some((e) => !e.lane || e.lane === "code") && !data.lanes?.some((l) => l.id === "code"))
|
|
17587
|
+
lane("code");
|
|
17588
|
+
let inferred = false;
|
|
17589
|
+
for (const l of data.lanes ?? []) {
|
|
17590
|
+
const into = lane(l.id, l.label);
|
|
17591
|
+
(l.spans ?? []).forEach((s, i) => {
|
|
17592
|
+
const a = day(s.from);
|
|
17593
|
+
if (!a)
|
|
17594
|
+
return;
|
|
17595
|
+
const open = !s.to;
|
|
17596
|
+
const b = open ? null : day(s.to);
|
|
17597
|
+
const to = open ? today : b?.to ?? a.to;
|
|
17598
|
+
const st = stateOf(s.state);
|
|
17599
|
+
const days = Math.max(0, to - a.from);
|
|
17600
|
+
const own = s.label ?? data.states?.[s.state]?.tag ?? "{d}d";
|
|
17601
|
+
const tags = own === "" ? [] : [...new Set([own, "{d}d"])].map((t) => t.replace(/\{d\}/g, String(days)));
|
|
17602
|
+
const id = s.id ?? `${l.id}/${i}`;
|
|
17603
|
+
const item = {
|
|
17604
|
+
id,
|
|
17605
|
+
kind: "span",
|
|
17606
|
+
lane: l.id,
|
|
17607
|
+
from: a.from,
|
|
17608
|
+
to,
|
|
17609
|
+
open,
|
|
17610
|
+
title: s.title ?? `${l.label} · ${st.label}`,
|
|
17611
|
+
...s.detail ? { detail: s.detail } : {},
|
|
17612
|
+
tone: st.tone,
|
|
17613
|
+
inferred: s.provenance === "inferred",
|
|
17614
|
+
...s.provenance ? { provenance: s.provenance } : {},
|
|
17615
|
+
...s.source ? { source: s.source } : {},
|
|
17616
|
+
evidence: s.evidence ?? [],
|
|
17617
|
+
...s.step ? { step: s.step } : {},
|
|
17618
|
+
state: s.state,
|
|
17619
|
+
tags
|
|
17620
|
+
};
|
|
17621
|
+
if (item.inferred)
|
|
17622
|
+
inferred = true;
|
|
17623
|
+
into.spans.push(item);
|
|
17624
|
+
items.set(id, item);
|
|
17625
|
+
});
|
|
17626
|
+
}
|
|
17627
|
+
const types = [];
|
|
17628
|
+
const events = [];
|
|
17629
|
+
rawEvents.forEach((e, index) => {
|
|
17630
|
+
const a = day(e.date);
|
|
17631
|
+
if (!a)
|
|
17632
|
+
return;
|
|
17633
|
+
const to = Math.max(a.to, day(e.to)?.to ?? a.to);
|
|
17634
|
+
const into = lane(e.lane ?? "code");
|
|
17635
|
+
const id = e.id ?? `e${index}`;
|
|
17636
|
+
const item = {
|
|
17637
|
+
id,
|
|
17638
|
+
kind: "event",
|
|
17639
|
+
lane: into.id,
|
|
17640
|
+
from: a.from,
|
|
17641
|
+
to,
|
|
17642
|
+
open: false,
|
|
17643
|
+
title: e.title,
|
|
17644
|
+
...e.detail ? { detail: e.detail } : {},
|
|
17645
|
+
tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
|
|
17646
|
+
...e.type ? { type: e.type } : {},
|
|
17647
|
+
inferred: e.provenance === "inferred",
|
|
17648
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
17649
|
+
...e.source ? { source: e.source } : {},
|
|
17650
|
+
evidence: e.evidence ?? [],
|
|
17651
|
+
...e.step ? { step: e.step } : {},
|
|
17652
|
+
tags: []
|
|
17653
|
+
};
|
|
17654
|
+
if (item.inferred)
|
|
17655
|
+
inferred = true;
|
|
17656
|
+
if (e.type && !types.includes(e.type))
|
|
17657
|
+
types.push(e.type);
|
|
17658
|
+
into.events.push(item);
|
|
17659
|
+
items.set(id, item);
|
|
17660
|
+
events.push({ item, index });
|
|
17661
|
+
});
|
|
17662
|
+
const bands = [];
|
|
17663
|
+
for (const b of rawBands) {
|
|
17664
|
+
const a = day(b.from);
|
|
17665
|
+
if (!a)
|
|
17666
|
+
continue;
|
|
17667
|
+
const z = b.to ? day(b.to) : null;
|
|
17668
|
+
bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
|
|
17669
|
+
}
|
|
17670
|
+
bands.sort((x, y) => x.from - y.from);
|
|
17671
|
+
const all = [...items.values()].flatMap((i) => [i.from, i.to]);
|
|
17672
|
+
for (const b of bands)
|
|
17673
|
+
all.push(b.from, b.to);
|
|
17674
|
+
const hasOpen = [...items.values()].some((i) => i.open);
|
|
17675
|
+
const lo = all.length ? Math.min(...all) : today - 90;
|
|
17676
|
+
let hi = all.length ? Math.max(...all) : today;
|
|
17677
|
+
if (hasOpen || data.axis?.today)
|
|
17678
|
+
hi = Math.max(hi, today);
|
|
17679
|
+
const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
|
|
17680
|
+
const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
|
|
17681
|
+
const laneIndex = (id) => laneOrder.indexOf(id);
|
|
17682
|
+
const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
|
|
17683
|
+
for (const l of lanes.values())
|
|
17684
|
+
l.events.sort((a, b) => a.from - b.from);
|
|
17685
|
+
return {
|
|
17686
|
+
from,
|
|
17687
|
+
to: Math.max(to, from + 1),
|
|
17688
|
+
today,
|
|
17689
|
+
lanes: laneOrder.map((id) => lanes.get(id)),
|
|
17690
|
+
events: order,
|
|
17691
|
+
bands,
|
|
17692
|
+
states: [...states.values()],
|
|
17693
|
+
types,
|
|
17694
|
+
inferred,
|
|
17695
|
+
verified: [...items.values()].some((i) => i.provenance === "verified"),
|
|
17696
|
+
items
|
|
17697
|
+
};
|
|
17698
|
+
}
|
|
17699
|
+
var DEFAULT_MARKS = ["release", "deletion"];
|
|
17700
|
+
function quietEvents(events, marks) {
|
|
17701
|
+
if (marks === "all")
|
|
17702
|
+
return new Set;
|
|
17703
|
+
const quiet = events.filter((e) => e.type && !marks.includes(e.type));
|
|
17704
|
+
return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
|
|
17705
|
+
}
|
|
17706
|
+
var PLURALS = {
|
|
17707
|
+
commit: "commits",
|
|
17708
|
+
release: "releases",
|
|
17709
|
+
deletion: "deletions",
|
|
17710
|
+
channel: "channel switches",
|
|
17711
|
+
audit: "findings"
|
|
17712
|
+
};
|
|
17713
|
+
function quietLabel(events, quiet) {
|
|
17714
|
+
const types = [];
|
|
17715
|
+
for (const e of events)
|
|
17716
|
+
if (quiet.has(e.id) && e.type && !types.includes(e.type))
|
|
17717
|
+
types.push(e.type);
|
|
17718
|
+
const names = types.map((t) => PLURALS[t] ?? `${t}s`);
|
|
17719
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
|
|
17720
|
+
}
|
|
17721
|
+
function eventOrder(events) {
|
|
17722
|
+
return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
|
|
17723
|
+
}
|
|
17724
|
+
function stepEvent(order, current, dir, at) {
|
|
17725
|
+
if (!order.length)
|
|
17726
|
+
return null;
|
|
17727
|
+
const i = current === null ? -1 : order.findIndex((e) => e.id === current);
|
|
17728
|
+
if (i < 0 && at !== undefined) {
|
|
17729
|
+
const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
|
|
17730
|
+
return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
|
|
17731
|
+
}
|
|
17732
|
+
if (i < 0)
|
|
17733
|
+
return dir > 0 ? order[0] : order[order.length - 1];
|
|
17734
|
+
return order[Math.max(0, Math.min(order.length - 1, i + dir))];
|
|
17735
|
+
}
|
|
17736
|
+
function dayScale(from, to, width) {
|
|
17737
|
+
const span = Math.max(1, to - from);
|
|
17738
|
+
return {
|
|
17739
|
+
x: (day) => (day - from) / span * width,
|
|
17740
|
+
day: (x) => from + x / Math.max(1, width) * span,
|
|
17741
|
+
f: (day) => (day - from) / span
|
|
17742
|
+
};
|
|
17743
|
+
}
|
|
17744
|
+
var STEPS = [1, 2, 3, 6, 12];
|
|
17745
|
+
function monthTicks(from, to, width, minGap = 44) {
|
|
17746
|
+
const perMonth = width / Math.max(1, to - from) * 30.44;
|
|
17747
|
+
const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
|
|
17748
|
+
const ticks = [];
|
|
17749
|
+
const { y } = parts(from);
|
|
17750
|
+
let { m } = parts(from);
|
|
17751
|
+
let d = dayOf(y, m, 1);
|
|
17752
|
+
if (d < from) {
|
|
17753
|
+
m++;
|
|
17754
|
+
d = dayOf(y, m, 1);
|
|
17755
|
+
}
|
|
17756
|
+
while (d <= to) {
|
|
17757
|
+
const p = parts(d);
|
|
17758
|
+
if (p.m % step === 0)
|
|
17759
|
+
ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
|
|
17760
|
+
m++;
|
|
17761
|
+
d = dayOf(y, m, 1);
|
|
17762
|
+
}
|
|
17763
|
+
return ticks;
|
|
17764
|
+
}
|
|
17765
|
+
function fitLabels(labels, width, reserved = [], gap = 8) {
|
|
17766
|
+
let end = -Infinity;
|
|
17767
|
+
return labels.map(({ x, w }) => {
|
|
17768
|
+
const x1 = x + w;
|
|
17769
|
+
if (x < end + gap || x1 > width)
|
|
17770
|
+
return false;
|
|
17771
|
+
if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
|
|
17772
|
+
return false;
|
|
17773
|
+
end = x1;
|
|
17774
|
+
return true;
|
|
17775
|
+
});
|
|
17776
|
+
}
|
|
17777
|
+
function clipSpan(from, to, axisFrom, axisTo) {
|
|
17778
|
+
if (to < axisFrom || from > axisTo)
|
|
17779
|
+
return null;
|
|
17780
|
+
return {
|
|
17781
|
+
from: Math.max(from, axisFrom),
|
|
17782
|
+
to: Math.min(to, axisTo),
|
|
17783
|
+
clipStart: from < axisFrom,
|
|
17784
|
+
clipEnd: to > axisTo
|
|
17785
|
+
};
|
|
17786
|
+
}
|
|
17787
|
+
function fitTag(widths, spanWidth, roomAfter, pad = 6) {
|
|
17788
|
+
for (let i = 0;i < widths.length; i++)
|
|
17789
|
+
if (widths[i] + pad * 2 <= spanWidth)
|
|
17790
|
+
return { index: i, place: "inside" };
|
|
17791
|
+
for (let i = 0;i < widths.length; i++)
|
|
17792
|
+
if (widths[i] + pad <= roomAfter)
|
|
17793
|
+
return { index: i, place: "after" };
|
|
17794
|
+
return null;
|
|
17795
|
+
}
|
|
17796
|
+
function bandRoom(bands, x) {
|
|
17797
|
+
return bands.map((b, i) => {
|
|
17798
|
+
const next = bands.slice(i + 1).find((n) => n.from > b.from);
|
|
17799
|
+
const end = Math.min(b.to, next ? next.from : Infinity);
|
|
17800
|
+
return Math.max(0, x(end) - x(b.from));
|
|
17801
|
+
});
|
|
17802
|
+
}
|
|
17803
|
+
function nearestEvent(events, day, tolerance) {
|
|
17804
|
+
let best = null;
|
|
17805
|
+
let gap = tolerance;
|
|
17806
|
+
for (const e of events) {
|
|
17807
|
+
const d = day < e.from ? e.from - day : day > e.to ? day - e.to : 0;
|
|
17808
|
+
if (d <= gap) {
|
|
17809
|
+
best = e;
|
|
17810
|
+
gap = d;
|
|
17811
|
+
}
|
|
17812
|
+
}
|
|
17813
|
+
return best;
|
|
17814
|
+
}
|
|
17815
|
+
function firstSentence(text) {
|
|
17816
|
+
if (!text)
|
|
17817
|
+
return "";
|
|
17818
|
+
const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
|
|
17819
|
+
const m = /^.*?[.!?](?=\s|$)/.exec(plain);
|
|
17820
|
+
return (m ? m[0] : plain).trim();
|
|
17821
|
+
}
|
|
17822
|
+
|
|
17823
|
+
// src/ui/history.ts
|
|
17204
17824
|
init_look();
|
|
17205
|
-
init_selection_pill();
|
|
17206
17825
|
|
|
17207
17826
|
// src/ui/walkthrough-model.ts
|
|
17208
17827
|
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
@@ -17268,7 +17887,14 @@ function fillMedia(m, base) {
|
|
|
17268
17887
|
case "image":
|
|
17269
17888
|
return { ...m, src: resolveSrc(m.src, base) };
|
|
17270
17889
|
case "history":
|
|
17271
|
-
|
|
17890
|
+
if ("data" in m)
|
|
17891
|
+
return m;
|
|
17892
|
+
return {
|
|
17893
|
+
kind: "history",
|
|
17894
|
+
data: historyData(m),
|
|
17895
|
+
...m.label ? { label: m.label } : {},
|
|
17896
|
+
...m.marks ? { marks: m.marks } : {}
|
|
17897
|
+
};
|
|
17272
17898
|
case "stack":
|
|
17273
17899
|
return {
|
|
17274
17900
|
kind: "stack",
|
|
@@ -17281,17 +17907,26 @@ function fillMedia(m, base) {
|
|
|
17281
17907
|
return m;
|
|
17282
17908
|
}
|
|
17283
17909
|
}
|
|
17284
|
-
function
|
|
17910
|
+
function historyData(m) {
|
|
17911
|
+
const events = m.events ?? [];
|
|
17912
|
+
if (m.lanes || m.bands || m.states || m.axis)
|
|
17913
|
+
return {
|
|
17914
|
+
...m.axis ? { axis: m.axis } : {},
|
|
17915
|
+
...m.lanes ? { lanes: m.lanes } : {},
|
|
17916
|
+
...events.length ? { events } : {},
|
|
17917
|
+
...m.bands ? { bands: m.bands } : {},
|
|
17918
|
+
...m.states ? { states: m.states } : {}
|
|
17919
|
+
};
|
|
17285
17920
|
const groups = [];
|
|
17286
|
-
if (
|
|
17287
|
-
groups.push({ events
|
|
17921
|
+
if (events.length)
|
|
17922
|
+
groups.push({ events });
|
|
17288
17923
|
for (const g of m.groups ?? [])
|
|
17289
17924
|
groups.push({
|
|
17290
17925
|
...g.heading ? { heading: g.heading } : {},
|
|
17291
17926
|
...g.summary ? { summary: g.summary } : {},
|
|
17292
17927
|
events: g.events ?? g.rows ?? []
|
|
17293
17928
|
});
|
|
17294
|
-
return groups;
|
|
17929
|
+
return { groups };
|
|
17295
17930
|
}
|
|
17296
17931
|
function parseBranch(branch) {
|
|
17297
17932
|
if (!branch?.trim())
|
|
@@ -17578,9 +18213,10 @@ function knownPRs(doc, sections) {
|
|
|
17578
18213
|
}
|
|
17579
18214
|
function findPR(prs, n, repo) {
|
|
17580
18215
|
const known = prs[n];
|
|
18216
|
+
const href = repo ? `https://github.com/${repo}/pull/${n}` : undefined;
|
|
17581
18217
|
if (known)
|
|
17582
|
-
return known;
|
|
17583
|
-
return
|
|
18218
|
+
return known.href || !href ? known : { ...known, href };
|
|
18219
|
+
return href ? { href } : null;
|
|
17584
18220
|
}
|
|
17585
18221
|
var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
|
|
17586
18222
|
function withKickers(sections, source, kickers = "pr") {
|
|
@@ -17910,11 +18546,18 @@ function inlineMarkdown(text) {
|
|
|
17910
18546
|
return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
|
|
17911
18547
|
}
|
|
17912
18548
|
|
|
17913
|
-
// src/ui/
|
|
17914
|
-
var
|
|
17915
|
-
|
|
17916
|
-
|
|
18549
|
+
// src/ui/history.ts
|
|
18550
|
+
var HEIGHT2 = springMotion(SPRINGS.default);
|
|
18551
|
+
var GO = svg('<path d="M5 5h6v6M11 5l-6 6"/>');
|
|
18552
|
+
var TYPE_NAMES = {
|
|
18553
|
+
commit: "Commit",
|
|
18554
|
+
release: "Release",
|
|
18555
|
+
deletion: "Deletion",
|
|
18556
|
+
channel: "Channel switch",
|
|
18557
|
+
audit: "Finding"
|
|
17917
18558
|
};
|
|
18559
|
+
var typeName = (t) => TYPE_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1);
|
|
18560
|
+
var REACH = 8;
|
|
17918
18561
|
var el = (doc, tag, cls) => {
|
|
17919
18562
|
const n = doc.createElement(tag);
|
|
17920
18563
|
if (cls)
|
|
@@ -17926,112 +18569,625 @@ var text = (doc, tag, cls, value) => {
|
|
|
17926
18569
|
n.textContent = value;
|
|
17927
18570
|
return n;
|
|
17928
18571
|
};
|
|
18572
|
+
function injectHistoryStyles(doc) {
|
|
18573
|
+
if (doc.querySelector("style[data-knobs-history]"))
|
|
18574
|
+
return;
|
|
18575
|
+
const style = doc.createElement("style");
|
|
18576
|
+
style.dataset.knobsHistory = "";
|
|
18577
|
+
style.textContent = aliased(historyCss);
|
|
18578
|
+
doc.head.append(style);
|
|
18579
|
+
}
|
|
18580
|
+
var when = (it) => it.kind === "span" ? `${formatRange(it.from, it.to, it.open)} · ${formatDays(it.to - it.from)}` : formatRange(it.from, it.to);
|
|
18581
|
+
function history(container, options) {
|
|
18582
|
+
const doc = container.ownerDocument;
|
|
18583
|
+
const win = doc.defaultView;
|
|
18584
|
+
const root = el(doc, "div", "knobs-history");
|
|
18585
|
+
applyLook2(root, options);
|
|
18586
|
+
injectHistoryStyles(doc);
|
|
18587
|
+
root.tabIndex = 0;
|
|
18588
|
+
root.setAttribute("role", "group");
|
|
18589
|
+
root.setAttribute("aria-label", options.label ?? "History");
|
|
18590
|
+
root.setAttribute("aria-roledescription", "timeline");
|
|
18591
|
+
const chart = el(doc, "div", "knobs-hy-chart");
|
|
18592
|
+
const foot = el(doc, "div", "knobs-hy-foot");
|
|
18593
|
+
const detail = el(doc, "div", "knobs-hy-detail");
|
|
18594
|
+
detail.setAttribute("aria-live", "polite");
|
|
18595
|
+
const tip = el(doc, "div", "knobs-hy-tip");
|
|
18596
|
+
tip.setAttribute("aria-hidden", "true");
|
|
18597
|
+
const tipTitle = el(doc, "div", "knobs-hy-tt");
|
|
18598
|
+
const tipDate = el(doc, "div", "knobs-hy-td");
|
|
18599
|
+
const tipLine = el(doc, "div", "knobs-hy-tl");
|
|
18600
|
+
tip.append(tipTitle, tipDate, tipLine);
|
|
18601
|
+
root.append(chart, foot, detail, tip);
|
|
18602
|
+
container.append(root);
|
|
18603
|
+
let model = normalizeHistory(options.data);
|
|
18604
|
+
let selected = options.selected && model.items.has(options.selected) ? options.selected : null;
|
|
18605
|
+
let disposed = false;
|
|
18606
|
+
let quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
18607
|
+
let showAll = false;
|
|
18608
|
+
const walked = () => showAll ? model.events : model.events.filter((e) => !quiet.has(e.id));
|
|
18609
|
+
let nodes = new Map;
|
|
18610
|
+
let tracks = new Map;
|
|
18611
|
+
let over = null;
|
|
18612
|
+
let hoverLine = null;
|
|
18613
|
+
let selLine = null;
|
|
18614
|
+
let scale = dayScale(model.from, model.to, 1);
|
|
18615
|
+
let drawnWidth = -1;
|
|
18616
|
+
let hovered = null;
|
|
18617
|
+
let tipKey = "";
|
|
18618
|
+
const fontOf = (n) => {
|
|
18619
|
+
const c = win.getComputedStyle(n);
|
|
18620
|
+
return `${c.fontStyle} ${c.fontWeight} ${c.fontSize} ${c.fontFamily}`;
|
|
18621
|
+
};
|
|
18622
|
+
let measureCtx;
|
|
18623
|
+
const measure = (s, font) => {
|
|
18624
|
+
if (measureCtx === undefined)
|
|
18625
|
+
measureCtx = doc.createElement("canvas").getContext("2d");
|
|
18626
|
+
if (!measureCtx)
|
|
18627
|
+
return s.length * 6.4;
|
|
18628
|
+
measureCtx.font = font;
|
|
18629
|
+
return measureCtx.measureText(s).width;
|
|
18630
|
+
};
|
|
18631
|
+
function drawFoot() {
|
|
18632
|
+
foot.textContent = "";
|
|
18633
|
+
root.toggleAttribute("data-all", showAll);
|
|
18634
|
+
if (quiet.size) {
|
|
18635
|
+
const t = el(doc, "button", "knobs-hy-more");
|
|
18636
|
+
t.type = "button";
|
|
18637
|
+
t.setAttribute("aria-pressed", String(showAll));
|
|
18638
|
+
t.textContent = `${showAll ? "Hide" : "Show"} ${quietLabel(model.events, quiet)}`;
|
|
18639
|
+
t.addEventListener("click", () => {
|
|
18640
|
+
showAll = !showAll;
|
|
18641
|
+
if (!showAll && selected && quiet.has(selected))
|
|
18642
|
+
select(null, true, true);
|
|
18643
|
+
drawFoot();
|
|
18644
|
+
});
|
|
18645
|
+
foot.append(t);
|
|
18646
|
+
}
|
|
18647
|
+
if (model.inferred && model.verified) {
|
|
18648
|
+
const legend = el(doc, "span", "knobs-hy-legend");
|
|
18649
|
+
for (const [label, inferred] of [
|
|
18650
|
+
["Verified", false],
|
|
18651
|
+
["Inferred", true]
|
|
18652
|
+
]) {
|
|
18653
|
+
const k = el(doc, "span", "knobs-hy-key");
|
|
18654
|
+
const sw = el(doc, "span", "knobs-hy-swatch");
|
|
18655
|
+
sw.dataset.tone = "dim";
|
|
18656
|
+
if (inferred)
|
|
18657
|
+
sw.dataset.inferred = "";
|
|
18658
|
+
k.append(sw, text(doc, "span", "", label));
|
|
18659
|
+
legend.append(k);
|
|
18660
|
+
}
|
|
18661
|
+
foot.append(legend);
|
|
18662
|
+
}
|
|
18663
|
+
foot.hidden = !foot.childElementCount;
|
|
18664
|
+
}
|
|
18665
|
+
function draw() {
|
|
18666
|
+
const width = root.clientWidth;
|
|
18667
|
+
drawnWidth = width;
|
|
18668
|
+
const narrow = width > 0 && width < (options.narrow ?? 560);
|
|
18669
|
+
root.toggleAttribute("data-narrow", narrow);
|
|
18670
|
+
root.toggleAttribute("data-bands", model.bands.length > 0);
|
|
18671
|
+
hideHover();
|
|
18672
|
+
chart.textContent = "";
|
|
18673
|
+
nodes = new Map;
|
|
18674
|
+
tracks = new Map;
|
|
18675
|
+
let gutter = 0;
|
|
18676
|
+
if (!narrow) {
|
|
18677
|
+
const probe = el(doc, "div", "knobs-hy-label");
|
|
18678
|
+
chart.append(probe);
|
|
18679
|
+
const font = fontOf(probe);
|
|
18680
|
+
const gap = parseFloat(win.getComputedStyle(probe).marginRight) || 0;
|
|
18681
|
+
probe.remove();
|
|
18682
|
+
const longest = Math.max(0, ...model.lanes.map((l) => measure(l.label, font)));
|
|
18683
|
+
gutter = Math.round(Math.min(Math.max(longest + gap + 2, 64), Math.max(96, width * 0.3), 200));
|
|
18684
|
+
}
|
|
18685
|
+
root.style.setProperty("--hy-gutter", `${gutter}px`);
|
|
18686
|
+
const plotW = Math.max(1, width - gutter);
|
|
18687
|
+
scale = dayScale(model.from, model.to, plotW);
|
|
18688
|
+
const pct = (d) => `${(scale.f(d) * 100).toFixed(4)}%`;
|
|
18689
|
+
const rows = ["auto"];
|
|
18690
|
+
for (let i = 0;i < model.lanes.length; i++)
|
|
18691
|
+
rows.push(...narrow ? ["auto", "var(--hy-lane)"] : ["var(--hy-lane)"]);
|
|
18692
|
+
chart.style.gridTemplateRows = rows.join(" ");
|
|
18693
|
+
const behind = el(doc, "div", "knobs-hy-layer");
|
|
18694
|
+
behind.setAttribute("aria-hidden", "true");
|
|
18695
|
+
for (const b of model.bands) {
|
|
18696
|
+
const c = clipSpan(b.from, b.to, model.from, model.to);
|
|
18697
|
+
if (!c)
|
|
18698
|
+
continue;
|
|
18699
|
+
const band = el(doc, "div", "knobs-hy-band");
|
|
18700
|
+
band.style.left = pct(c.from);
|
|
18701
|
+
band.style.width = pct(model.from + (c.to - c.from));
|
|
18702
|
+
behind.append(band);
|
|
18703
|
+
}
|
|
18704
|
+
const ticks = monthTicks(model.from, model.to, plotW);
|
|
18705
|
+
for (const t of ticks) {
|
|
18706
|
+
const line = el(doc, "div", "knobs-hy-grid");
|
|
18707
|
+
line.style.left = pct(t.day);
|
|
18708
|
+
behind.append(line);
|
|
18709
|
+
}
|
|
18710
|
+
chart.append(behind);
|
|
18711
|
+
const axis = el(doc, "div", "knobs-hy-axis");
|
|
18712
|
+
axis.setAttribute("aria-hidden", "true");
|
|
18713
|
+
const tickEls = ticks.map((t) => {
|
|
18714
|
+
const n = text(doc, "span", "knobs-hy-tick", t.label);
|
|
18715
|
+
n.style.left = pct(t.day);
|
|
18716
|
+
axis.append(n);
|
|
18717
|
+
return n;
|
|
18718
|
+
});
|
|
18719
|
+
const showToday = model.today >= model.from && model.today <= model.to;
|
|
18720
|
+
const now = showToday ? text(doc, "span", "knobs-hy-now", "Today") : null;
|
|
18721
|
+
if (now)
|
|
18722
|
+
axis.append(now);
|
|
18723
|
+
const room = bandRoom(model.bands, scale.x);
|
|
18724
|
+
const bandEls = model.bands.map((b, i) => {
|
|
18725
|
+
const n = text(doc, "span", "knobs-hy-blabel", b.label);
|
|
18726
|
+
n.style.left = pct(Math.max(b.from, model.from));
|
|
18727
|
+
n.style.maxWidth = `${Math.max(0, room[i])}px`;
|
|
18728
|
+
axis.append(n);
|
|
18729
|
+
return n;
|
|
18730
|
+
});
|
|
18731
|
+
chart.append(axis);
|
|
18732
|
+
model.lanes.forEach((lane, i) => {
|
|
18733
|
+
const row = narrow ? 2 + i * 2 : 2 + i;
|
|
18734
|
+
const label = text(doc, "div", "knobs-hy-label", lane.label);
|
|
18735
|
+
label.style.gridRow = String(row);
|
|
18736
|
+
label.title = lane.label;
|
|
18737
|
+
const track = el(doc, "div", "knobs-hy-track");
|
|
18738
|
+
track.style.gridRow = String(narrow ? row + 1 : row);
|
|
18739
|
+
track.dataset.lane = lane.id;
|
|
18740
|
+
tracks.set(lane.id, track);
|
|
18741
|
+
for (const s of lane.spans) {
|
|
18742
|
+
const c = clipSpan(s.from, s.to, model.from, model.to);
|
|
18743
|
+
if (!c)
|
|
18744
|
+
continue;
|
|
18745
|
+
const b = el(doc, "button", "knobs-hy-span");
|
|
18746
|
+
b.type = "button";
|
|
18747
|
+
b.tabIndex = -1;
|
|
18748
|
+
b.dataset.id = s.id;
|
|
18749
|
+
b.dataset.tone = s.tone;
|
|
18750
|
+
if (s.inferred)
|
|
18751
|
+
b.dataset.inferred = "";
|
|
18752
|
+
if (c.clipStart)
|
|
18753
|
+
b.dataset.clipStart = "";
|
|
18754
|
+
if (c.clipEnd)
|
|
18755
|
+
b.dataset.clipEnd = "";
|
|
18756
|
+
b.style.left = pct(c.from);
|
|
18757
|
+
b.style.width = pct(model.from + (c.to - c.from));
|
|
18758
|
+
b.setAttribute("aria-label", `${s.title}, ${when(s)}`);
|
|
18759
|
+
if (s.tags.length)
|
|
18760
|
+
b.append(text(doc, "span", "knobs-hy-tag", s.tags[0]));
|
|
18761
|
+
track.append(b);
|
|
18762
|
+
nodes.set(s.id, b);
|
|
18763
|
+
}
|
|
18764
|
+
for (const e of lane.events) {
|
|
18765
|
+
if (e.to < model.from || e.from > model.to)
|
|
18766
|
+
continue;
|
|
18767
|
+
const b = el(doc, "button", "knobs-hy-ev");
|
|
18768
|
+
b.type = "button";
|
|
18769
|
+
b.tabIndex = -1;
|
|
18770
|
+
b.dataset.id = e.id;
|
|
18771
|
+
b.dataset.tone = e.tone;
|
|
18772
|
+
if (e.type)
|
|
18773
|
+
b.dataset.type = e.type;
|
|
18774
|
+
if (e.inferred)
|
|
18775
|
+
b.dataset.inferred = "";
|
|
18776
|
+
if (quiet.has(e.id))
|
|
18777
|
+
b.dataset.quiet = "";
|
|
18778
|
+
b.style.left = pct(Math.max(e.from, model.from));
|
|
18779
|
+
if (e.to > e.from) {
|
|
18780
|
+
b.dataset.range = "";
|
|
18781
|
+
b.style.setProperty("--hy-w", `${scale.x(model.from + (e.to - e.from)).toFixed(2)}px`);
|
|
18782
|
+
}
|
|
18783
|
+
b.setAttribute("aria-label", `${when(e)}: ${e.title}`);
|
|
18784
|
+
track.append(b);
|
|
18785
|
+
nodes.set(e.id, b);
|
|
18786
|
+
}
|
|
18787
|
+
chart.append(label, track);
|
|
18788
|
+
});
|
|
18789
|
+
over = el(doc, "div", "knobs-hy-layer");
|
|
18790
|
+
over.setAttribute("aria-hidden", "true");
|
|
18791
|
+
if (showToday) {
|
|
18792
|
+
const today = el(doc, "div", "knobs-hy-today");
|
|
18793
|
+
today.style.left = pct(model.today);
|
|
18794
|
+
over.append(today);
|
|
18795
|
+
}
|
|
18796
|
+
selLine = el(doc, "div", "knobs-hy-sel");
|
|
18797
|
+
hoverLine = el(doc, "div", "knobs-hy-hover");
|
|
18798
|
+
over.append(selLine, hoverLine);
|
|
18799
|
+
chart.append(over);
|
|
18800
|
+
const reserved = [];
|
|
18801
|
+
if (now) {
|
|
18802
|
+
const w = now.offsetWidth;
|
|
18803
|
+
const x0 = Math.max(0, Math.min(plotW - w, scale.x(model.today) - w / 2));
|
|
18804
|
+
now.style.left = `${x0}px`;
|
|
18805
|
+
reserved.push({ x0, x1: x0 + w });
|
|
18806
|
+
}
|
|
18807
|
+
const fits = fitLabels(ticks.map((t, i) => ({ x: scale.x(t.day), w: tickEls[i].offsetWidth })), plotW, reserved);
|
|
18808
|
+
tickEls.forEach((n, i) => n.hidden = !fits[i]);
|
|
18809
|
+
for (const n of bandEls)
|
|
18810
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18811
|
+
for (const n of chart.querySelectorAll(".knobs-hy-label"))
|
|
18812
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18813
|
+
for (const lane of model.lanes) {
|
|
18814
|
+
const spans = lane.spans.map((s) => ({ s, c: clipSpan(s.from, s.to, model.from, model.to) })).filter((x) => x.c).sort((a, b) => a.c.from - b.c.from);
|
|
18815
|
+
spans.forEach(({ s, c }, i) => {
|
|
18816
|
+
const b = nodes.get(s.id);
|
|
18817
|
+
const tag = b?.querySelector(".knobs-hy-tag");
|
|
18818
|
+
if (!b || !tag)
|
|
18819
|
+
return;
|
|
18820
|
+
const font = fontOf(tag);
|
|
18821
|
+
const widths = s.tags.map((t) => measure(t, font));
|
|
18822
|
+
const next = spans[i + 1]?.c;
|
|
18823
|
+
const spanW = scale.x(c.to) - scale.x(c.from);
|
|
18824
|
+
const after = (next ? scale.x(next.from) : plotW) - scale.x(c.to);
|
|
18825
|
+
const fit = fitTag(widths, spanW, after);
|
|
18826
|
+
tag.textContent = fit ? s.tags[fit.index] : s.tags[0] ?? "";
|
|
18827
|
+
tag.dataset.place = fit ? fit.place : "none";
|
|
18828
|
+
});
|
|
18829
|
+
}
|
|
18830
|
+
paintSelection();
|
|
18831
|
+
}
|
|
18832
|
+
const h = new Spring(0, HEIGHT2.omega, HEIGHT2.zeta, 0.25);
|
|
18833
|
+
let goal = 0;
|
|
18834
|
+
let content = null;
|
|
18835
|
+
const loop = frameLoop(win, (dt) => {
|
|
18836
|
+
h.step(dt);
|
|
18837
|
+
const moving = !h.resting;
|
|
18838
|
+
detail.style.height = moving ? `${Math.max(0, h.x).toFixed(2)}px` : goal > 0 ? "" : "0px";
|
|
18839
|
+
detail.toggleAttribute("data-moving", moving);
|
|
18840
|
+
return moving;
|
|
18841
|
+
});
|
|
18842
|
+
function buildDetail(it) {
|
|
18843
|
+
const c = el(doc, "div", "knobs-hy-dc");
|
|
18844
|
+
const meta = el(doc, "div", "knobs-hy-dmeta");
|
|
18845
|
+
meta.append(text(doc, "time", "", when(it)));
|
|
18846
|
+
const lane = model.lanes.find((l) => l.id === it.lane);
|
|
18847
|
+
if (lane && (model.lanes.length > 1 || lane.id !== "code"))
|
|
18848
|
+
meta.append(text(doc, "span", "", lane.label));
|
|
18849
|
+
if (it.type)
|
|
18850
|
+
meta.append(text(doc, "span", "", typeName(it.type)));
|
|
18851
|
+
if (it.provenance || it.source) {
|
|
18852
|
+
const p = text(doc, "span", "", [it.provenance, it.source].filter(Boolean).join(" · "));
|
|
18853
|
+
if (it.inferred) {
|
|
18854
|
+
p.dataset.tone = "warn";
|
|
18855
|
+
p.title = "Worked out from behaviour, not seen in the record";
|
|
18856
|
+
}
|
|
18857
|
+
meta.append(p);
|
|
18858
|
+
}
|
|
18859
|
+
c.append(meta, text(doc, "div", "knobs-hy-dtitle", it.title));
|
|
18860
|
+
if (it.detail) {
|
|
18861
|
+
const p = el(doc, "p", "knobs-hy-dtext");
|
|
18862
|
+
p.innerHTML = inlineMarkdown(it.detail);
|
|
18863
|
+
c.append(p);
|
|
18864
|
+
}
|
|
18865
|
+
const foot = el(doc, "div", "knobs-hy-dfoot");
|
|
18866
|
+
for (const e of it.evidence) {
|
|
18867
|
+
const tag = text(doc, e.href ? "a" : "span", "knobs-hy-evidence", e.label);
|
|
18868
|
+
if (tag instanceof HTMLAnchorElement && e.href) {
|
|
18869
|
+
tag.href = e.href;
|
|
18870
|
+
tag.target = "_blank";
|
|
18871
|
+
tag.rel = "noreferrer";
|
|
18872
|
+
}
|
|
18873
|
+
foot.append(tag);
|
|
18874
|
+
}
|
|
18875
|
+
if (it.step && options.onJump) {
|
|
18876
|
+
const go = el(doc, "button", "knobs-hy-jump");
|
|
18877
|
+
go.type = "button";
|
|
18878
|
+
go.append(text(doc, "span", "", "Go to step"));
|
|
18879
|
+
go.insertAdjacentHTML("beforeend", GO);
|
|
18880
|
+
const step = it.step;
|
|
18881
|
+
go.addEventListener("click", () => options.onJump?.(step));
|
|
18882
|
+
foot.append(go);
|
|
18883
|
+
}
|
|
18884
|
+
c.append(foot);
|
|
18885
|
+
return c;
|
|
18886
|
+
}
|
|
18887
|
+
function showDetail(it, animate) {
|
|
18888
|
+
const from = detail.offsetHeight;
|
|
18889
|
+
const old = content;
|
|
18890
|
+
const next = it ? buildDetail(it) : null;
|
|
18891
|
+
if (old) {
|
|
18892
|
+
if (animate) {
|
|
18893
|
+
old.dataset.leave = "";
|
|
18894
|
+
const drop = () => old.remove();
|
|
18895
|
+
old.addEventListener("transitionend", drop, { once: true });
|
|
18896
|
+
win.setTimeout(drop, 400);
|
|
18897
|
+
} else
|
|
18898
|
+
old.remove();
|
|
18899
|
+
}
|
|
18900
|
+
content = next;
|
|
18901
|
+
if (next) {
|
|
18902
|
+
detail.append(next);
|
|
18903
|
+
if (animate) {
|
|
18904
|
+
next.dataset.enter = "";
|
|
18905
|
+
next.offsetHeight;
|
|
18906
|
+
delete next.dataset.enter;
|
|
18907
|
+
}
|
|
18908
|
+
}
|
|
18909
|
+
goal = next ? next.offsetHeight : 0;
|
|
18910
|
+
if (!animate || reducedMotion(win)) {
|
|
18911
|
+
loop.stop();
|
|
18912
|
+
h.snap(goal);
|
|
18913
|
+
detail.style.height = goal > 0 ? "" : "0px";
|
|
18914
|
+
detail.removeAttribute("data-moving");
|
|
18915
|
+
return;
|
|
18916
|
+
}
|
|
18917
|
+
if (h.resting)
|
|
18918
|
+
h.snap(from);
|
|
18919
|
+
h.set(goal);
|
|
18920
|
+
loop.wake();
|
|
18921
|
+
}
|
|
18922
|
+
function paintSelection() {
|
|
18923
|
+
for (const [id, n] of nodes)
|
|
18924
|
+
n.toggleAttribute("data-selected", id === selected);
|
|
18925
|
+
const it = selected ? model.items.get(selected) : undefined;
|
|
18926
|
+
if (selLine) {
|
|
18927
|
+
const on = it?.kind === "event";
|
|
18928
|
+
selLine.toggleAttribute("data-on", on);
|
|
18929
|
+
if (on)
|
|
18930
|
+
selLine.style.left = `${(scale.f(it.from) * 100).toFixed(4)}%`;
|
|
18931
|
+
}
|
|
18932
|
+
}
|
|
18933
|
+
function select(id, animate, notify) {
|
|
18934
|
+
const next = id && model.items.has(id) ? id : null;
|
|
18935
|
+
if (next === selected)
|
|
18936
|
+
return;
|
|
18937
|
+
selected = next;
|
|
18938
|
+
paintSelection();
|
|
18939
|
+
showDetail(next ? model.items.get(next) : null, animate);
|
|
18940
|
+
if (notify)
|
|
18941
|
+
options.onSelect?.(next);
|
|
18942
|
+
}
|
|
18943
|
+
function itemAt(e) {
|
|
18944
|
+
if (!over)
|
|
18945
|
+
return null;
|
|
18946
|
+
const r = over.getBoundingClientRect();
|
|
18947
|
+
if (!r.width)
|
|
18948
|
+
return null;
|
|
18949
|
+
const x = Math.max(0, Math.min(r.width, e.clientX - r.left));
|
|
18950
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
18951
|
+
const hit = target?.closest("[data-id]");
|
|
18952
|
+
if (hit && chart.contains(hit))
|
|
18953
|
+
return { it: model.items.get(hit.dataset.id) ?? null, x };
|
|
18954
|
+
const laneId = target?.closest(".knobs-hy-track")?.dataset.lane;
|
|
18955
|
+
const lane = laneId ? model.lanes.find((l) => l.id === laneId) : undefined;
|
|
18956
|
+
if (lane) {
|
|
18957
|
+
const reach = REACH / r.width * (model.to - model.from);
|
|
18958
|
+
const near = nearestEvent(lane.events, scale.day(x), reach);
|
|
18959
|
+
if (near)
|
|
18960
|
+
return { it: near, x };
|
|
18961
|
+
}
|
|
18962
|
+
return { it: null, x };
|
|
18963
|
+
}
|
|
18964
|
+
function hideHover() {
|
|
18965
|
+
hoverLine?.removeAttribute("data-on");
|
|
18966
|
+
tip.removeAttribute("data-on");
|
|
18967
|
+
hovered?.removeAttribute("data-hover");
|
|
18968
|
+
hovered = null;
|
|
18969
|
+
tipKey = "";
|
|
18970
|
+
}
|
|
18971
|
+
function hover(e) {
|
|
18972
|
+
const at = itemAt(e);
|
|
18973
|
+
if (!at || !over || !hoverLine)
|
|
18974
|
+
return hideHover();
|
|
18975
|
+
const { it, x } = at;
|
|
18976
|
+
const lineX = it?.kind === "event" ? scale.x(it.from) : x;
|
|
18977
|
+
hoverLine.style.left = `${lineX.toFixed(2)}px`;
|
|
18978
|
+
hoverLine.setAttribute("data-on", "");
|
|
18979
|
+
const node = it ? nodes.get(it.id) ?? null : null;
|
|
18980
|
+
if (node !== hovered) {
|
|
18981
|
+
hovered?.removeAttribute("data-hover");
|
|
18982
|
+
node?.setAttribute("data-hover", "");
|
|
18983
|
+
hovered = node;
|
|
18984
|
+
}
|
|
18985
|
+
const key = it ? it.id : `d${Math.round(scale.day(x))}`;
|
|
18986
|
+
if (key !== tipKey) {
|
|
18987
|
+
tipKey = key;
|
|
18988
|
+
tipTitle.textContent = it ? it.title : formatDay(Math.round(scale.day(x)));
|
|
18989
|
+
tipDate.textContent = it ? [when(it), it.type ? typeName(it.type) : ""].filter(Boolean).join(" · ") : "";
|
|
18990
|
+
tipDate.hidden = !it;
|
|
18991
|
+
tipLine.textContent = it ? firstSentence(it.detail) : "";
|
|
18992
|
+
}
|
|
18993
|
+
const rr = root.getBoundingClientRect();
|
|
18994
|
+
const or = over.getBoundingClientRect();
|
|
18995
|
+
const laneId = it?.lane ?? (e.target instanceof Element ? e.target.closest(".knobs-hy-track")?.dataset.lane : undefined);
|
|
18996
|
+
const lr = laneId && tracks.get(laneId)?.getBoundingClientRect() || or;
|
|
18997
|
+
const tw = tip.offsetWidth;
|
|
18998
|
+
const th = tip.offsetHeight;
|
|
18999
|
+
const left = Math.max(0, Math.min(rr.width - tw, or.left - rr.left + lineX - tw / 2));
|
|
19000
|
+
let top = lr.top - rr.top - th - 6;
|
|
19001
|
+
if (top + rr.top < 0)
|
|
19002
|
+
top = lr.bottom - rr.top + 6;
|
|
19003
|
+
tip.style.transform = `translate3d(${Math.round(left)}px,${Math.round(top)}px,0)`;
|
|
19004
|
+
tip.setAttribute("data-on", "");
|
|
19005
|
+
}
|
|
19006
|
+
chart.addEventListener("pointermove", (e) => {
|
|
19007
|
+
if (e.pointerType === "touch")
|
|
19008
|
+
return;
|
|
19009
|
+
hover(e);
|
|
19010
|
+
});
|
|
19011
|
+
chart.addEventListener("pointerleave", hideHover);
|
|
19012
|
+
chart.addEventListener("mousedown", (e) => {
|
|
19013
|
+
if (!(e.target instanceof Element) || !e.target.closest("button"))
|
|
19014
|
+
return;
|
|
19015
|
+
e.preventDefault();
|
|
19016
|
+
root.focus({ preventScroll: true });
|
|
19017
|
+
});
|
|
19018
|
+
chart.addEventListener("click", (e) => {
|
|
19019
|
+
const at = itemAt(e);
|
|
19020
|
+
const id = at?.it?.id ?? null;
|
|
19021
|
+
select(id !== null && id === selected ? null : id, true, true);
|
|
19022
|
+
});
|
|
19023
|
+
root.addEventListener("keydown", (e) => {
|
|
19024
|
+
const onChart = e.target instanceof Node && chart.contains(e.target);
|
|
19025
|
+
if (e.target !== root && !onChart || e.metaKey || e.ctrlKey || e.altKey)
|
|
19026
|
+
return;
|
|
19027
|
+
let next;
|
|
19028
|
+
const order = walked();
|
|
19029
|
+
const at = selected ? model.items.get(selected)?.from : undefined;
|
|
19030
|
+
if (e.key === "ArrowRight")
|
|
19031
|
+
next = stepEvent(order, selected, 1, at);
|
|
19032
|
+
else if (e.key === "ArrowLeft")
|
|
19033
|
+
next = stepEvent(order, selected, -1, at);
|
|
19034
|
+
else if (e.key === "Home")
|
|
19035
|
+
next = order[0] ?? null;
|
|
19036
|
+
else if (e.key === "End")
|
|
19037
|
+
next = order[order.length - 1] ?? null;
|
|
19038
|
+
else if (e.key === "Escape" && selected)
|
|
19039
|
+
next = null;
|
|
19040
|
+
else if (e.key === "Enter" && selected) {
|
|
19041
|
+
const step = model.items.get(selected)?.step;
|
|
19042
|
+
if (!step || !options.onJump)
|
|
19043
|
+
return;
|
|
19044
|
+
options.onJump(step);
|
|
19045
|
+
e.preventDefault();
|
|
19046
|
+
e.stopPropagation();
|
|
19047
|
+
return;
|
|
19048
|
+
} else
|
|
19049
|
+
return;
|
|
19050
|
+
e.preventDefault();
|
|
19051
|
+
e.stopPropagation();
|
|
19052
|
+
hideHover();
|
|
19053
|
+
select(next?.id ?? null, true, true);
|
|
19054
|
+
});
|
|
19055
|
+
let raf = 0;
|
|
19056
|
+
const ro = new ResizeObserver(() => {
|
|
19057
|
+
if (raf || disposed)
|
|
19058
|
+
return;
|
|
19059
|
+
raf = win.requestAnimationFrame(() => {
|
|
19060
|
+
raf = 0;
|
|
19061
|
+
if (!disposed && root.clientWidth !== drawnWidth)
|
|
19062
|
+
draw();
|
|
19063
|
+
});
|
|
19064
|
+
});
|
|
19065
|
+
ro.observe(root);
|
|
19066
|
+
const restyle = () => draw();
|
|
19067
|
+
root.addEventListener("knobs-restyle", restyle);
|
|
19068
|
+
drawFoot();
|
|
19069
|
+
draw();
|
|
19070
|
+
detail.style.height = "0px";
|
|
19071
|
+
if (selected)
|
|
19072
|
+
showDetail(model.items.get(selected), false);
|
|
19073
|
+
const dispose = () => {
|
|
19074
|
+
if (disposed)
|
|
19075
|
+
return;
|
|
19076
|
+
disposed = true;
|
|
19077
|
+
ro.disconnect();
|
|
19078
|
+
if (raf)
|
|
19079
|
+
win.cancelAnimationFrame(raf);
|
|
19080
|
+
loop.stop();
|
|
19081
|
+
root.removeEventListener("knobs-restyle", restyle);
|
|
19082
|
+
root.remove();
|
|
19083
|
+
};
|
|
19084
|
+
return {
|
|
19085
|
+
element: root,
|
|
19086
|
+
get model() {
|
|
19087
|
+
return model;
|
|
19088
|
+
},
|
|
19089
|
+
get selected() {
|
|
19090
|
+
return selected;
|
|
19091
|
+
},
|
|
19092
|
+
select(id) {
|
|
19093
|
+
select(id, true, false);
|
|
19094
|
+
},
|
|
19095
|
+
set(data) {
|
|
19096
|
+
model = normalizeHistory(data);
|
|
19097
|
+
const keep = selected && model.items.has(selected) ? selected : null;
|
|
19098
|
+
selected = keep;
|
|
19099
|
+
quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
19100
|
+
drawFoot();
|
|
19101
|
+
draw();
|
|
19102
|
+
showDetail(keep ? model.items.get(keep) : null, false);
|
|
19103
|
+
},
|
|
19104
|
+
focus() {
|
|
19105
|
+
root.focus();
|
|
19106
|
+
},
|
|
19107
|
+
dispose,
|
|
19108
|
+
destroy: dispose
|
|
19109
|
+
};
|
|
19110
|
+
}
|
|
19111
|
+
|
|
19112
|
+
// src/ui/walkthrough-blocks.ts
|
|
19113
|
+
init_icons();
|
|
19114
|
+
init_look();
|
|
19115
|
+
init_selection_pill();
|
|
19116
|
+
var GLYPH = {
|
|
19117
|
+
caret: icon("caretRight", { small: true })
|
|
19118
|
+
};
|
|
19119
|
+
var el2 = (doc, tag, cls) => {
|
|
19120
|
+
const n = doc.createElement(tag);
|
|
19121
|
+
if (cls)
|
|
19122
|
+
n.className = cls;
|
|
19123
|
+
return n;
|
|
19124
|
+
};
|
|
19125
|
+
var text2 = (doc, tag, cls, value) => {
|
|
19126
|
+
const n = el2(doc, tag, cls);
|
|
19127
|
+
n.textContent = value;
|
|
19128
|
+
return n;
|
|
19129
|
+
};
|
|
17929
19130
|
var prose = (doc, tag, cls, value) => {
|
|
17930
|
-
const n =
|
|
19131
|
+
const n = el2(doc, tag, cls);
|
|
17931
19132
|
n.innerHTML = inlineMarkdown(value);
|
|
17932
19133
|
return n;
|
|
17933
19134
|
};
|
|
17934
19135
|
var link = (doc, cls, label, href) => {
|
|
17935
19136
|
if (!href)
|
|
17936
|
-
return
|
|
17937
|
-
const a =
|
|
19137
|
+
return text2(doc, "span", cls, label);
|
|
19138
|
+
const a = text2(doc, "a", cls, label);
|
|
17938
19139
|
a.href = href;
|
|
17939
19140
|
a.target = "_blank";
|
|
17940
19141
|
a.rel = "noreferrer";
|
|
17941
19142
|
return a;
|
|
17942
19143
|
};
|
|
17943
|
-
function
|
|
19144
|
+
function rowLink(row, ref, steps) {
|
|
17944
19145
|
const s = ref && steps?.find(ref);
|
|
17945
19146
|
if (!s || !steps)
|
|
17946
|
-
return
|
|
17947
|
-
const
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
19147
|
+
return;
|
|
19148
|
+
const doc = row.ownerDocument;
|
|
19149
|
+
row.dataset.go = "";
|
|
19150
|
+
row.tabIndex = 0;
|
|
19151
|
+
row.title = `Go to “${plainMarkdown(s.title)}”`;
|
|
19152
|
+
row.addEventListener("click", (e) => {
|
|
19153
|
+
if (e.target?.closest("a,button"))
|
|
19154
|
+
return;
|
|
19155
|
+
if (doc.getSelection()?.toString())
|
|
19156
|
+
return;
|
|
19157
|
+
steps.go(s.key);
|
|
19158
|
+
});
|
|
19159
|
+
row.addEventListener("keydown", (e) => {
|
|
19160
|
+
if (e.target !== row || e.key !== "Enter" && e.key !== " ")
|
|
19161
|
+
return;
|
|
19162
|
+
e.preventDefault();
|
|
17954
19163
|
steps.go(s.key);
|
|
17955
19164
|
});
|
|
17956
|
-
return b;
|
|
17957
19165
|
}
|
|
17958
|
-
var tags = (doc, list) => {
|
|
17959
|
-
if (!list?.length)
|
|
17960
|
-
return null;
|
|
17961
|
-
const row = el(doc, "div", "knobs-wt-tags");
|
|
17962
|
-
for (const e of list)
|
|
17963
|
-
row.append(link(doc, "knobs-wt-tag", e.label, e.href));
|
|
17964
|
-
return row;
|
|
17965
|
-
};
|
|
17966
19166
|
function mountHistory(host, m, steps) {
|
|
17967
|
-
const
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
root.append(group);
|
|
17986
|
-
}
|
|
17987
|
-
host.append(root);
|
|
17988
|
-
return { dispose: () => root.remove(), context: () => ({ kind: "history" }) };
|
|
17989
|
-
}
|
|
17990
|
-
function historyEvent(doc, e, steps) {
|
|
17991
|
-
const li = el(doc, "li", "knobs-wt-hev");
|
|
17992
|
-
if (e.tone)
|
|
17993
|
-
li.dataset.tone = e.tone;
|
|
17994
|
-
if (e.type)
|
|
17995
|
-
li.dataset.type = e.type;
|
|
17996
|
-
if (e.provenance)
|
|
17997
|
-
li.dataset.provenance = e.provenance;
|
|
17998
|
-
const when = el(doc, "div", "knobs-wt-hwhen");
|
|
17999
|
-
when.append(text(doc, "time", "knobs-wt-hdate", e.date));
|
|
18000
|
-
if (e.age)
|
|
18001
|
-
when.append(text(doc, "span", "knobs-wt-hage", e.age));
|
|
18002
|
-
const spine = el(doc, "span", "knobs-wt-hspine");
|
|
18003
|
-
spine.setAttribute("aria-hidden", "true");
|
|
18004
|
-
spine.append(el(doc, "span", "knobs-wt-hdot"));
|
|
18005
|
-
const main = el(doc, "div", "knobs-wt-hmain");
|
|
18006
|
-
const title = el(doc, "div", "knobs-wt-htitle");
|
|
18007
|
-
title.append(text(doc, "span", "knobs-wt-htext", e.title));
|
|
18008
|
-
if (e.provenance || e.source) {
|
|
18009
|
-
const p = text(doc, "span", "knobs-wt-prov", [e.provenance, e.source].filter(Boolean).join(" · "));
|
|
18010
|
-
if (e.provenance === "inferred")
|
|
18011
|
-
p.title = "Inferred from behaviour, not seen in the record";
|
|
18012
|
-
else if (e.provenance === "verified")
|
|
18013
|
-
p.title = "Seen in the record";
|
|
18014
|
-
title.append(p);
|
|
18015
|
-
}
|
|
18016
|
-
const go = stepButton(doc, e.step, steps);
|
|
18017
|
-
if (go)
|
|
18018
|
-
title.append(go);
|
|
18019
|
-
main.append(title);
|
|
18020
|
-
if (e.detail)
|
|
18021
|
-
main.append(prose(doc, "p", "knobs-wt-hdetail", e.detail));
|
|
18022
|
-
const t = tags(doc, e.evidence);
|
|
18023
|
-
if (t)
|
|
18024
|
-
main.append(t);
|
|
18025
|
-
li.append(when, spine, main);
|
|
18026
|
-
return li;
|
|
18027
|
-
}
|
|
18028
|
-
function mountStack(host, m, steps) {
|
|
19167
|
+
const h = history(host, {
|
|
19168
|
+
data: m.data,
|
|
19169
|
+
...m.label ? { label: m.label } : {},
|
|
19170
|
+
...m.marks ? { marks: m.marks } : {},
|
|
19171
|
+
...steps ? {
|
|
19172
|
+
onJump: (ref) => {
|
|
19173
|
+
const s = steps.find(ref);
|
|
19174
|
+
if (s)
|
|
19175
|
+
steps.go(s.key);
|
|
19176
|
+
}
|
|
19177
|
+
} : {}
|
|
19178
|
+
});
|
|
19179
|
+
return {
|
|
19180
|
+
dispose: () => h.dispose(),
|
|
19181
|
+
context: () => ({ kind: "history" })
|
|
19182
|
+
};
|
|
19183
|
+
}
|
|
19184
|
+
function mountStack(host, m, steps, findPR) {
|
|
18029
19185
|
const doc = host.ownerDocument;
|
|
18030
19186
|
const layout = stackLayout(m);
|
|
18031
|
-
const root =
|
|
19187
|
+
const root = el2(doc, "div", "knobs-wt-stack");
|
|
18032
19188
|
root.toggleAttribute("data-flat", layout.flat);
|
|
18033
19189
|
const lane = (kind) => {
|
|
18034
|
-
const s =
|
|
19190
|
+
const s = el2(doc, "span", "knobs-wt-lane");
|
|
18035
19191
|
s.dataset.rail = kind;
|
|
18036
19192
|
return s;
|
|
18037
19193
|
};
|
|
@@ -18042,19 +19198,19 @@ function mountStack(host, m, steps) {
|
|
|
18042
19198
|
};
|
|
18043
19199
|
const from = layout.trunk ? 0 : 1;
|
|
18044
19200
|
if (layout.trunk && layout.rows.length) {
|
|
18045
|
-
const row =
|
|
19201
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
18046
19202
|
row.dataset.trunk = "";
|
|
18047
|
-
const lanes =
|
|
19203
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
18048
19204
|
lanes.setAttribute("aria-hidden", "true");
|
|
18049
19205
|
lanes.append(node(true));
|
|
18050
|
-
row.append(lanes,
|
|
19206
|
+
row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
|
|
18051
19207
|
root.append(row);
|
|
18052
19208
|
}
|
|
18053
19209
|
for (const r of layout.rows) {
|
|
18054
19210
|
const it = r.item;
|
|
18055
|
-
const row =
|
|
19211
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
18056
19212
|
if (!layout.flat) {
|
|
18057
|
-
const lanes =
|
|
19213
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
18058
19214
|
lanes.setAttribute("aria-hidden", "true");
|
|
18059
19215
|
for (let k = from;k < r.depth; k++)
|
|
18060
19216
|
lanes.append(lane(r.rails[k] ? "pass" : "none"));
|
|
@@ -18063,24 +19219,24 @@ function mountStack(host, m, steps) {
|
|
|
18063
19219
|
lanes.append(node(r.children > 0));
|
|
18064
19220
|
row.append(lanes);
|
|
18065
19221
|
}
|
|
18066
|
-
const main =
|
|
18067
|
-
const line =
|
|
19222
|
+
const main = el2(doc, "div", "knobs-wt-smain");
|
|
19223
|
+
const line = el2(doc, "div", "knobs-wt-sline");
|
|
18068
19224
|
if (it.order)
|
|
18069
|
-
line.append(
|
|
18070
|
-
|
|
19225
|
+
line.append(text2(doc, "span", "knobs-wt-sord", it.order));
|
|
19226
|
+
const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
|
|
19227
|
+
const pr = link(doc, "knobs-wt-spr", it.pr, it.href ?? known?.href);
|
|
19228
|
+
pr.addEventListener("click", (e) => e.stopPropagation());
|
|
19229
|
+
line.append(pr);
|
|
18071
19230
|
const target = it.step ? steps?.find(it.step) : null;
|
|
18072
19231
|
if (target && steps) {
|
|
18073
|
-
const title =
|
|
19232
|
+
const title = text2(doc, "button", "knobs-wt-stitle", it.title);
|
|
18074
19233
|
title.type = "button";
|
|
18075
|
-
title.title = `Go to “${target.title}”`;
|
|
19234
|
+
title.title = `Go to “${plainMarkdown(target.title)}”`;
|
|
18076
19235
|
title.addEventListener("click", (e) => {
|
|
18077
19236
|
e.stopPropagation();
|
|
18078
19237
|
steps.go(target.key);
|
|
18079
19238
|
});
|
|
18080
19239
|
line.append(title);
|
|
18081
|
-
const go = stepButton(doc, it.step, steps);
|
|
18082
|
-
if (go)
|
|
18083
|
-
line.append(go);
|
|
18084
19240
|
row.dataset.go = "";
|
|
18085
19241
|
row.addEventListener("click", (e) => {
|
|
18086
19242
|
const t = e.target;
|
|
@@ -18091,22 +19247,26 @@ function mountStack(host, m, steps) {
|
|
|
18091
19247
|
steps.go(target.key);
|
|
18092
19248
|
});
|
|
18093
19249
|
} else
|
|
18094
|
-
line.append(
|
|
19250
|
+
line.append(text2(doc, "span", "knobs-wt-stitle", it.title));
|
|
18095
19251
|
main.append(line);
|
|
18096
19252
|
if (it.summary)
|
|
18097
19253
|
main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
|
|
18098
|
-
const meta =
|
|
19254
|
+
const meta = el2(doc, "div", "knobs-wt-smeta");
|
|
18099
19255
|
if (it.head) {
|
|
18100
19256
|
const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
|
|
18101
|
-
meta.append(
|
|
19257
|
+
meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
|
|
18102
19258
|
}
|
|
18103
19259
|
if (it.delta !== undefined)
|
|
18104
19260
|
meta.append(delta(doc, it.delta));
|
|
18105
19261
|
if (meta.childNodes.length)
|
|
18106
19262
|
main.append(meta);
|
|
18107
19263
|
row.append(main);
|
|
18108
|
-
|
|
18109
|
-
|
|
19264
|
+
const said = prStatus(it.status);
|
|
19265
|
+
const state = known?.state ?? said.state;
|
|
19266
|
+
if (state)
|
|
19267
|
+
row.append(prStateLabel(doc, state, said.checks, it.status));
|
|
19268
|
+
else if (it.status) {
|
|
19269
|
+
const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
|
|
18110
19270
|
chip.dataset.tone = it.tone ?? "dim";
|
|
18111
19271
|
row.append(chip);
|
|
18112
19272
|
}
|
|
@@ -18116,40 +19276,38 @@ function mountStack(host, m, steps) {
|
|
|
18116
19276
|
return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
|
|
18117
19277
|
}
|
|
18118
19278
|
function delta(doc, d) {
|
|
18119
|
-
const s =
|
|
19279
|
+
const s = el2(doc, "span", "knobs-wt-delta");
|
|
18120
19280
|
if (typeof d === "string") {
|
|
18121
19281
|
s.textContent = d;
|
|
18122
19282
|
return s;
|
|
18123
19283
|
}
|
|
18124
19284
|
const n = (v) => v.toLocaleString("en-US");
|
|
18125
|
-
s.append(
|
|
18126
|
-
s.append(
|
|
19285
|
+
s.append(text2(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
|
|
19286
|
+
s.append(text2(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
|
|
18127
19287
|
s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
|
|
18128
19288
|
return s;
|
|
18129
19289
|
}
|
|
18130
|
-
function cellContent(doc, into, cell
|
|
19290
|
+
function cellContent(doc, into, cell) {
|
|
18131
19291
|
let content;
|
|
18132
19292
|
if (cell.badge) {
|
|
18133
19293
|
content = link(doc, "knobs-wt-chip", cell.text, cell.href);
|
|
18134
19294
|
content.dataset.tone = cell.badge;
|
|
18135
19295
|
} else {
|
|
18136
|
-
const inner = cell.code ?
|
|
19296
|
+
const inner = cell.code ? text2(doc, "code", "", cell.text) : null;
|
|
18137
19297
|
if (cell.href) {
|
|
18138
19298
|
content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
|
|
18139
19299
|
if (inner)
|
|
18140
19300
|
content.append(inner);
|
|
18141
19301
|
} else
|
|
18142
|
-
content = inner ??
|
|
19302
|
+
content = inner ?? text2(doc, "span", "", cell.text);
|
|
18143
19303
|
if (cell.tone) {
|
|
18144
19304
|
content.classList.add("knobs-wt-ctone");
|
|
18145
19305
|
content.dataset.tone = cell.tone;
|
|
18146
19306
|
}
|
|
18147
19307
|
}
|
|
18148
19308
|
into.append(content);
|
|
18149
|
-
const go = stepButton(doc, cell.step, steps);
|
|
18150
|
-
if (go)
|
|
18151
|
-
into.append(go);
|
|
18152
19309
|
}
|
|
19310
|
+
var rowStep = (r, columns) => columns.map((c) => r[c.key]?.step).find((step) => step !== undefined);
|
|
18153
19311
|
var cellAttrs = (td, c) => {
|
|
18154
19312
|
td.dataset.align = c.align;
|
|
18155
19313
|
if (c.mono)
|
|
@@ -18158,83 +19316,85 @@ var cellAttrs = (td, c) => {
|
|
|
18158
19316
|
function mountTable(host, m, steps) {
|
|
18159
19317
|
const doc = host.ownerDocument;
|
|
18160
19318
|
const win = doc.defaultView;
|
|
18161
|
-
const box =
|
|
18162
|
-
const scroller =
|
|
18163
|
-
const table =
|
|
19319
|
+
const box = el2(doc, "div", "knobs-wt-tablebox");
|
|
19320
|
+
const scroller = el2(doc, "div", "knobs-wt-table");
|
|
19321
|
+
const table = el2(doc, "table");
|
|
18164
19322
|
if (m.caption)
|
|
18165
|
-
table.append(
|
|
18166
|
-
const head =
|
|
18167
|
-
const hr =
|
|
19323
|
+
table.append(text2(doc, "caption", "", m.caption));
|
|
19324
|
+
const head = el2(doc, "thead");
|
|
19325
|
+
const hr = el2(doc, "tr");
|
|
18168
19326
|
for (const c of m.columns) {
|
|
18169
|
-
const th =
|
|
19327
|
+
const th = text2(doc, "th", "", c.label);
|
|
18170
19328
|
th.scope = "col";
|
|
18171
19329
|
th.dataset.align = c.align;
|
|
18172
19330
|
hr.append(th);
|
|
18173
19331
|
}
|
|
18174
19332
|
head.append(hr);
|
|
18175
|
-
const body =
|
|
19333
|
+
const body = el2(doc, "tbody");
|
|
18176
19334
|
for (const r of m.rows) {
|
|
18177
|
-
const tr =
|
|
19335
|
+
const tr = el2(doc, "tr");
|
|
18178
19336
|
for (const c of m.columns) {
|
|
18179
|
-
const td =
|
|
19337
|
+
const td = el2(doc, "td");
|
|
18180
19338
|
cellAttrs(td, c);
|
|
18181
19339
|
const cell = r[c.key];
|
|
18182
19340
|
if (cell) {
|
|
18183
19341
|
if (cell.text.length > 40 && !c.mono && !cell.code)
|
|
18184
19342
|
td.dataset.long = "";
|
|
18185
|
-
cellContent(doc, td, cell
|
|
19343
|
+
cellContent(doc, td, cell);
|
|
18186
19344
|
}
|
|
18187
19345
|
tr.append(td);
|
|
18188
19346
|
}
|
|
19347
|
+
rowLink(tr, rowStep(r, m.columns), steps);
|
|
18189
19348
|
body.append(tr);
|
|
18190
19349
|
}
|
|
18191
19350
|
table.append(head, body);
|
|
18192
19351
|
scroller.append(table);
|
|
18193
|
-
const stack =
|
|
19352
|
+
const stack = el2(doc, "div", "knobs-wt-tstack");
|
|
18194
19353
|
stack.setAttribute("role", "list");
|
|
18195
19354
|
if (m.caption)
|
|
18196
19355
|
stack.setAttribute("aria-label", m.caption);
|
|
18197
19356
|
const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
|
|
18198
19357
|
for (const r of m.rows) {
|
|
18199
|
-
const item =
|
|
19358
|
+
const item = el2(doc, "div", "knobs-wt-trow");
|
|
18200
19359
|
item.setAttribute("role", "listitem");
|
|
18201
|
-
const title =
|
|
19360
|
+
const title = el2(doc, "div", "knobs-wt-ttitle");
|
|
18202
19361
|
if (primary) {
|
|
18203
19362
|
cellAttrs(title, primary);
|
|
18204
19363
|
const cell = r[primary.key];
|
|
18205
19364
|
if (cell)
|
|
18206
|
-
cellContent(doc, title, cell
|
|
19365
|
+
cellContent(doc, title, cell);
|
|
18207
19366
|
}
|
|
18208
|
-
const line =
|
|
19367
|
+
const line = el2(doc, "div", "knobs-wt-tline");
|
|
18209
19368
|
line.append(title);
|
|
18210
|
-
const badges =
|
|
19369
|
+
const badges = el2(doc, "div", "knobs-wt-tbadges");
|
|
18211
19370
|
for (const c of m.columns) {
|
|
18212
19371
|
const cell = r[c.key];
|
|
18213
19372
|
if (c !== primary && cell?.badge)
|
|
18214
|
-
cellContent(doc, badges, cell
|
|
19373
|
+
cellContent(doc, badges, cell);
|
|
18215
19374
|
}
|
|
18216
19375
|
if (badges.childNodes.length)
|
|
18217
19376
|
line.append(badges);
|
|
18218
19377
|
item.append(line);
|
|
18219
|
-
const terms =
|
|
19378
|
+
const terms = el2(doc, "dl", "knobs-wt-tterms");
|
|
18220
19379
|
for (const c of m.columns) {
|
|
18221
19380
|
if (c === primary)
|
|
18222
19381
|
continue;
|
|
18223
19382
|
const cell = r[c.key];
|
|
18224
19383
|
if (!cell || cell.text === "" || cell.badge)
|
|
18225
19384
|
continue;
|
|
18226
|
-
const dd =
|
|
19385
|
+
const dd = el2(doc, "dd");
|
|
18227
19386
|
cellAttrs(dd, c);
|
|
18228
19387
|
dd.removeAttribute("data-align");
|
|
18229
|
-
cellContent(doc, dd, cell
|
|
18230
|
-
terms.append(
|
|
19388
|
+
cellContent(doc, dd, cell);
|
|
19389
|
+
terms.append(text2(doc, "dt", "", c.label), dd);
|
|
18231
19390
|
}
|
|
18232
19391
|
if (terms.childNodes.length)
|
|
18233
19392
|
item.append(terms);
|
|
19393
|
+
rowLink(item, rowStep(r, m.columns), steps);
|
|
18234
19394
|
stack.append(item);
|
|
18235
19395
|
}
|
|
18236
19396
|
if (m.caption)
|
|
18237
|
-
stack.append(
|
|
19397
|
+
stack.append(text2(doc, "p", "knobs-wt-tcaption", m.caption));
|
|
18238
19398
|
box.append(scroller, stack);
|
|
18239
19399
|
host.append(box);
|
|
18240
19400
|
let natural = 0;
|
|
@@ -18288,16 +19448,16 @@ function mountTable(host, m, steps) {
|
|
|
18288
19448
|
}
|
|
18289
19449
|
function chapterPills(host, chapters, io) {
|
|
18290
19450
|
const doc = host.ownerDocument;
|
|
18291
|
-
const list =
|
|
19451
|
+
const list = el2(doc, "div", "knobs-wt-chapters");
|
|
18292
19452
|
list.setAttribute("role", "group");
|
|
18293
19453
|
list.setAttribute("aria-label", "Chapters");
|
|
18294
19454
|
let at = -2;
|
|
18295
19455
|
const rows = chapters.map((c, i) => {
|
|
18296
|
-
const b =
|
|
19456
|
+
const b = el2(doc, "button", "knobs-wt-chapter");
|
|
18297
19457
|
b.type = "button";
|
|
18298
|
-
b.append(
|
|
18299
|
-
b.append(
|
|
18300
|
-
b.append(
|
|
19458
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
|
|
19459
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
|
|
19460
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
|
|
18301
19461
|
b.addEventListener("click", () => {
|
|
18302
19462
|
io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
|
|
18303
19463
|
io.seek(c.start);
|
|
@@ -18335,15 +19495,13 @@ function disclosure(host, options) {
|
|
|
18335
19495
|
const doc = host.ownerDocument;
|
|
18336
19496
|
const win = doc.defaultView;
|
|
18337
19497
|
let open = options.open === true;
|
|
18338
|
-
const root =
|
|
18339
|
-
const toggle =
|
|
19498
|
+
const root = el2(doc, "div", "knobs-wt-disc");
|
|
19499
|
+
const toggle = el2(doc, "button", "knobs-wt-disc-toggle");
|
|
18340
19500
|
toggle.type = "button";
|
|
18341
19501
|
toggle.innerHTML = GLYPH.caret;
|
|
18342
|
-
toggle.append(
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
const viewport = el(doc, "div", "knobs-wt-disc-body");
|
|
18346
|
-
const content = el(doc, "div", "knobs-wt-disc-content");
|
|
19502
|
+
toggle.append(text2(doc, "span", "", options.label));
|
|
19503
|
+
const viewport = el2(doc, "div", "knobs-wt-disc-body");
|
|
19504
|
+
const content = el2(doc, "div", "knobs-wt-disc-content");
|
|
18347
19505
|
content.append(options.content);
|
|
18348
19506
|
viewport.append(content);
|
|
18349
19507
|
root.append(toggle, viewport);
|
|
@@ -18355,7 +19513,9 @@ function disclosure(host, options) {
|
|
|
18355
19513
|
const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
|
|
18356
19514
|
viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
|
|
18357
19515
|
viewport.toggleAttribute("data-moving", moving);
|
|
18358
|
-
|
|
19516
|
+
const o = open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5);
|
|
19517
|
+
content.style.opacity = o.toFixed(3);
|
|
19518
|
+
content.style.filter = o >= 1 ? "" : `blur(${((1 - o) * 3).toFixed(2)}px)`;
|
|
18359
19519
|
};
|
|
18360
19520
|
const loop = frameLoop(win, (dt) => {
|
|
18361
19521
|
h.step(dt);
|
|
@@ -18391,6 +19551,7 @@ function disclosure(host, options) {
|
|
|
18391
19551
|
h.snap(open ? full : 0);
|
|
18392
19552
|
viewport.style.height = open ? "" : "0px";
|
|
18393
19553
|
content.style.opacity = open ? "1" : "0";
|
|
19554
|
+
content.style.filter = "";
|
|
18394
19555
|
return;
|
|
18395
19556
|
}
|
|
18396
19557
|
if (h.resting)
|
|
@@ -18411,10 +19572,36 @@ var PR_PATHS = {
|
|
|
18411
19572
|
pull: "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192Zm120-31V110.63a23.85,23.85,0,0,0-7-17L163.31,56H192a8,8,0,0,0,0-16H144a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31L189.66,105a8,8,0,0,1,2.34,5.66V161a32,32,0,1,0,16,0Zm-8,47a16,16,0,1,1,16-16A16,16,0,0,1,200,208Z",
|
|
18412
19573
|
merge: "M208,112a32.05,32.05,0,0,0-30.69,23l-42.21-6a8,8,0,0,1-4.95-2.71L94.43,84.55A32,32,0,1,0,72,87v82a32,32,0,1,0,16,0V101.63l30,35a24,24,0,0,0,14.83,8.14l44,6.28A32,32,0,1,0,208,112ZM64,56A16,16,0,1,1,80,72,16,16,0,0,1,64,56ZM96,200a16,16,0,1,1-16-16A16,16,0,0,1,96,200Zm112-40a16,16,0,1,1,16-16A16,16,0,0,1,208,160Z"
|
|
18413
19574
|
};
|
|
18414
|
-
var
|
|
19575
|
+
var DRAFT = "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192ZM232,192a32,32,0,1,1-32-32A32,32,0,0,1,232,192ZM216,192a16,16,0,1,0-16,16A16,16,0,0,0,216,192ZM212,120a12,12,0,1,1-12-12A12,12,0,0,1,212,120ZM212,60a12,12,0,1,1-12-12A12,12,0,0,1,212,60Z";
|
|
19576
|
+
var X = "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z";
|
|
19577
|
+
var CHECK3 = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z";
|
|
19578
|
+
var svg12 = (d, evenodd = false) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"${evenodd ? ' fill-rule="evenodd"' : ""} aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
|
19579
|
+
var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
|
|
18415
19580
|
var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
|
|
19581
|
+
function prStatus(status) {
|
|
19582
|
+
const words = (status ?? "").trim().toLowerCase();
|
|
19583
|
+
const first = /^[a-z]+/.exec(words)?.[0] ?? "";
|
|
19584
|
+
const state = first in PR_STATE ? first : null;
|
|
19585
|
+
return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
|
|
19586
|
+
}
|
|
19587
|
+
function prStateLabel(doc, state, checks, title) {
|
|
19588
|
+
const s = el2(doc, "span", "knobs-wt-prstate");
|
|
19589
|
+
s.dataset.state = state;
|
|
19590
|
+
s.innerHTML = prGlyph(state);
|
|
19591
|
+
s.append(text2(doc, "span", "", PR_STATE[state]));
|
|
19592
|
+
if (checks) {
|
|
19593
|
+
const c = el2(doc, "span", "knobs-wt-prchecks");
|
|
19594
|
+
c.innerHTML = svg12(CHECK3);
|
|
19595
|
+
c.title = "Checks pass";
|
|
19596
|
+
c.setAttribute("aria-label", "checks pass");
|
|
19597
|
+
s.append(c);
|
|
19598
|
+
}
|
|
19599
|
+
if (title)
|
|
19600
|
+
s.title = title;
|
|
19601
|
+
return s;
|
|
19602
|
+
}
|
|
18416
19603
|
function prChip(doc, n, pr) {
|
|
18417
|
-
const chip =
|
|
19604
|
+
const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
|
|
18418
19605
|
if (pr.href) {
|
|
18419
19606
|
chip.setAttribute("href", pr.href);
|
|
18420
19607
|
chip.setAttribute("target", "_blank");
|
|
@@ -18422,8 +19609,8 @@ function prChip(doc, n, pr) {
|
|
|
18422
19609
|
}
|
|
18423
19610
|
if (pr.state)
|
|
18424
19611
|
chip.dataset.state = pr.state;
|
|
18425
|
-
chip.innerHTML = prGlyph(pr.state
|
|
18426
|
-
chip.append(
|
|
19612
|
+
chip.innerHTML = prGlyph(pr.state);
|
|
19613
|
+
chip.append(text2(doc, "span", "", `#${n}`));
|
|
18427
19614
|
const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
|
|
18428
19615
|
chip.title = tip;
|
|
18429
19616
|
chip.setAttribute("aria-label", `Pull request ${tip}`);
|
|
@@ -18479,6 +19666,7 @@ function linkPRs(root, find) {
|
|
|
18479
19666
|
// src/ui/walkthrough-media.ts
|
|
18480
19667
|
init_compare_model();
|
|
18481
19668
|
init_glide();
|
|
19669
|
+
init_motion();
|
|
18482
19670
|
init_icons();
|
|
18483
19671
|
init_look();
|
|
18484
19672
|
|
|
@@ -18491,8 +19679,9 @@ var quick = springCss(FADE);
|
|
|
18491
19679
|
var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent)";
|
|
18492
19680
|
var walkthroughCss = `
|
|
18493
19681
|
:where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
|
|
19682
|
+
.knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
|
|
18494
19683
|
.knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;--kw-state:${state.easing};--kw-state-ms:${state.duration}ms;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms;
|
|
18495
|
-
--kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
|
|
19684
|
+
--kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
|
|
18496
19685
|
--kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-under:var(--_k-bg-page);--kw-ink:var(--k-walkthrough-ink,#ff375f);--kw-strong:var(--_k-weight-label);
|
|
18497
19686
|
container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;text-align:left;
|
|
18498
19687
|
letter-spacing:normal;-webkit-font-smoothing:antialiased}
|
|
@@ -18508,7 +19697,7 @@ var walkthroughCss = `
|
|
|
18508
19697
|
/* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
|
|
18509
19698
|
in the room beside it, up to --kw-width: media take the column, prose stops at the measure.
|
|
18510
19699
|
One column where it's narrow ——— */
|
|
18511
|
-
.knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--
|
|
19700
|
+
.knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--kw-rail-gap);align-items:start}
|
|
18512
19701
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr) var(--kw-rail-w)}
|
|
18513
19702
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
|
|
18514
19703
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
|
|
@@ -18586,7 +19775,6 @@ var walkthroughCss = `
|
|
|
18586
19775
|
.knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
|
|
18587
19776
|
.knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
|
|
18588
19777
|
.knobs-wt-feed-title{font-weight:var(--kw-strong)!important}
|
|
18589
|
-
.knobs-wt-feed-count{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18590
19778
|
.knobs-wt-feed-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
|
|
18591
19779
|
.knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
|
|
18592
19780
|
.knobs-wt-fi+.knobs-wt-fi::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_k-space-group) / -2);height:1px;background:var(--_k-border-subtle)}
|
|
@@ -18597,10 +19785,6 @@ var walkthroughCss = `
|
|
|
18597
19785
|
:where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:pointer;outline:none;
|
|
18598
19786
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
18599
19787
|
.knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18600
|
-
:where(.knobs-ui) .knobs-wt-steplink{all:revert;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin:0 0 0 2px;padding:0;vertical-align:-5px;border:0;
|
|
18601
|
-
border-radius:calc(var(--_k-radius) / 2);background:transparent;color:var(--_k-fg-3);cursor:pointer;outline:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18602
|
-
.knobs-ui .knobs-wt-steplink:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18603
|
-
.knobs-wt-steplink svg{width:12px;height:12px}
|
|
18604
19788
|
|
|
18605
19789
|
/* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
|
|
18606
19790
|
script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
|
|
@@ -18611,7 +19795,6 @@ var walkthroughCss = `
|
|
|
18611
19795
|
.knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
18612
19796
|
.knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
|
|
18613
19797
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
|
|
18614
|
-
.knobs-wt-disc-count{color:var(--_k-fg-3)}
|
|
18615
19798
|
.knobs-wt-disc-body{overflow:hidden}
|
|
18616
19799
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
|
|
18617
19800
|
.knobs-wt-disc-body[data-moving]{-webkit-mask-image:linear-gradient(#000 calc(100% - 16px),transparent);mask-image:linear-gradient(#000 calc(100% - 16px),transparent)}
|
|
@@ -18791,6 +19974,10 @@ var walkthroughCss = `
|
|
|
18791
19974
|
/* A slot's label: sentence case as written, small and faint, never a heading. */
|
|
18792
19975
|
.knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:normal;text-transform:none;color:var(--_k-fg-3);${fade2}}
|
|
18793
19976
|
.knobs-wt-body{min-width:0}
|
|
19977
|
+
/* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
|
|
19978
|
+
a small blur on the quick spring, from the top; at rest, nothing. */
|
|
19979
|
+
.knobs-wt-body,.knobs-wt-frame>img{transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
19980
|
+
.knobs-wt-body[data-entering],.knobs-wt-frame[data-loading]>img{opacity:0;filter:blur(3px);transition:none}
|
|
18794
19981
|
/* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
|
|
18795
19982
|
slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
|
|
18796
19983
|
.knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
|
|
@@ -18805,9 +19992,12 @@ var walkthroughCss = `
|
|
|
18805
19992
|
.knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
|
|
18806
19993
|
.knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
|
|
18807
19994
|
.knobs-wt-frame[data-natural]>img{position:static;height:auto}
|
|
18808
|
-
|
|
19995
|
+
/* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
|
|
19996
|
+
control radius and room inside, so a diff's buttons and the code under them can breathe. */
|
|
19997
|
+
.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-space-sibling) var(--_k-pad-card) var(--_k-pad-card);border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
|
|
19998
|
+
.knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
|
|
18809
19999
|
font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
|
|
18810
|
-
.knobs-surface .knobs-wt-code{padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
|
|
20000
|
+
.knobs-surface :is(.knobs-wt-code,.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body){padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
|
|
18811
20001
|
.knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
18812
20002
|
.knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
|
|
18813
20003
|
.knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
|
|
@@ -18834,48 +20024,37 @@ var walkthroughCss = `
|
|
|
18834
20024
|
.knobs-wt-hold{border-radius:var(--kw-frame-r);background:var(--_k-fill)}
|
|
18835
20025
|
.knobs-wt-failed{display:flex;align-items:center;justify-content:center;min-height:80px;color:var(--_k-fg-3);font-size:var(--_k-type-small)}
|
|
18836
20026
|
|
|
20027
|
+
/* Annotations: a calm ring round a box, a dot or an arrow at a point, a small label beside it;
|
|
20028
|
+
each fades in and out with a small blur on the quick spring as its time comes and goes, and a
|
|
20029
|
+
page's switch (the eye, or a) hides them all. Placed in px by script, 1:1 with the picture;
|
|
20030
|
+
last in their box, so they sit over the picture (and under a compare's grip and labels). */
|
|
20031
|
+
.knobs-wt-anno{position:absolute;inset:0;pointer-events:none;overflow:hidden;transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
20032
|
+
.knobs-wt-anno:not([data-settled]),.knobs-wt-anno:not([data-settled]) *{transition:none!important}
|
|
20033
|
+
[data-annotations="off"] .knobs-wt-anno,.knobs-wt-anno[data-moving]{opacity:0;filter:blur(3px)}
|
|
20034
|
+
.knobs-wt-mark{--kw-mark:var(--_k-accent);position:absolute;left:0;top:0;color:var(--kw-mark);transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
20035
|
+
.knobs-wt-mark[data-hidden]{opacity:0;filter:blur(3px)}
|
|
20036
|
+
.knobs-wt-mark[data-shape="box"]{border-radius:6px;box-shadow:0 0 0 1.5px var(--kw-mark),0 0 0 4px color-mix(in srgb,var(--_k-bg) 45%,transparent)}
|
|
20037
|
+
.knobs-wt-mark[data-shape="dot"]::before{content:"";position:absolute;left:-6px;top:-6px;width:12px;height:12px;border-radius:50%;background:var(--kw-mark);box-shadow:0 0 0 3px color-mix(in srgb,var(--kw-mark) 30%,transparent)}
|
|
20038
|
+
.knobs-wt-anno-arrow{position:absolute;right:0;bottom:0;filter:drop-shadow(0 0 2px var(--_k-bg))}
|
|
20039
|
+
.knobs-wt-mark-label{position:absolute;left:0;bottom:calc(100% + 6px);max-width:240px;padding:2px 6px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-bg-overlay);color:var(--_k-fg);
|
|
20040
|
+
font:var(--_k-weight-label) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap;overflow:hidden;box-shadow:var(--_k-shadow-overlay);${fade2}}
|
|
20041
|
+
.knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
|
|
20042
|
+
.knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
|
|
20043
|
+
.knobs-ui .knobs-wt-anno-toggle{all:revert;box-sizing:border-box;position:absolute;top:8px;right:8px;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;margin:0;
|
|
20044
|
+
border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:pointer;opacity:.6;outline:none;
|
|
20045
|
+
transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
20046
|
+
.knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20047
|
+
.knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
20048
|
+
.knobs-cmp-stage>.knobs-wt-anno-toggle{top:auto;bottom:8px}
|
|
20049
|
+
|
|
18837
20050
|
/* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
|
|
18838
20051
|
hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
|
|
18839
|
-
:where(.knobs-wt-
|
|
20052
|
+
:where(.knobs-wt-stack,.knobs-wt-tablebox,.knobs-wt-chapters){--kw-line:var(--_k-leading-body);--kw-rail:var(--_k-border-strong)}
|
|
18840
20053
|
:where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
18841
20054
|
:where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
|
|
18842
20055
|
:where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
18843
20056
|
:where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
|
|
18844
20057
|
:where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
|
|
18845
|
-
.knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
|
18846
|
-
.knobs-ui .knobs-wt-tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-2);font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);
|
|
18847
|
-
text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18848
|
-
.knobs-ui a.knobs-wt-tag:is(:hover,:focus-visible){background:var(--_k-fill-selected);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18849
|
-
|
|
18850
|
-
/* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
|
|
18851
|
-
dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
|
|
18852
|
-
.knobs-wt-history{--kw-hy:calc(5px + var(--kw-line) / 2);display:flex;flex-direction:column;gap:18px;font-size:var(--_k-type-body);line-height:var(--kw-line);color:var(--_k-fg)}
|
|
18853
|
-
.knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
18854
|
-
.knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
|
|
18855
|
-
.knobs-ui .knobs-wt-hheading{margin:0;font:var(--_k-weight-label) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:.04em;text-transform:uppercase;color:var(--_k-fg-2)}
|
|
18856
|
-
.knobs-wt-hsum{color:var(--_k-fg-3);font-size:var(--_k-type-small);font-variant-numeric:tabular-nums}
|
|
18857
|
-
.knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
|
|
18858
|
-
.knobs-wt-hev{display:grid;grid-template-columns:var(--kw-hw,11ch) 14px minmax(0,1fr);grid-template-areas:"when spine main";column-gap:10px;align-items:baseline;padding:5px 0}
|
|
18859
|
-
.knobs-wt-hwhen{grid-area:when;display:flex;flex-direction:column;min-width:0;font:var(--_k-weight) var(--_k-type-label)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
|
|
18860
|
-
.knobs-wt-hage{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
|
|
18861
|
-
.knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
|
|
18862
|
-
.knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
|
|
18863
|
-
.knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
|
|
18864
|
-
.knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
|
|
18865
|
-
.knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
|
|
18866
|
-
.knobs-wt-hdot{position:absolute;left:3.5px;top:calc(var(--kw-hy) - 3.5px);width:7px;height:7px;border-radius:50%;background:var(--kw-tone,var(--_k-fg-3))}
|
|
18867
|
-
.knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
|
|
18868
|
-
.knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
|
|
18869
|
-
.knobs-wt-hev[data-provenance="inferred"] .knobs-wt-hdot{background:transparent;box-shadow:inset 0 0 0 1.5px var(--kw-tone,var(--_k-fg-3))}
|
|
18870
|
-
.knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
18871
|
-
.knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
|
|
18872
|
-
.knobs-wt-prov{padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-3);font:var(--_k-weight) var(--_k-type-small)/18px var(--_k-mono);white-space:nowrap}
|
|
18873
|
-
.knobs-wt-hev[data-provenance="inferred"] .knobs-wt-prov{background:transparent;color:var(--_k-warn);outline:1px dashed color-mix(in oklch,var(--_k-warn) 50%,transparent);outline-offset:-1px}
|
|
18874
|
-
.knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
|
|
18875
|
-
@container knobs-wt-slot (max-width:520px){
|
|
18876
|
-
.knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
|
|
18877
|
-
.knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
|
|
18878
|
-
}
|
|
18879
20058
|
|
|
18880
20059
|
/* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
|
|
18881
20060
|
git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
|
|
@@ -18907,12 +20086,21 @@ var walkthroughCss = `
|
|
|
18907
20086
|
.knobs-wt-add{color:var(--_k-diff-add)}
|
|
18908
20087
|
.knobs-wt-del{color:var(--_k-diff-del)}
|
|
18909
20088
|
.knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
|
|
20089
|
+
/* A PR's state the GitHub way: its glyph and word, open and merged quietly tinted (as PR chips),
|
|
20090
|
+
draft and closed faint; no fill, no dot. A check after it where the checks pass, faint too. One
|
|
20091
|
+
width for every row, so the glyphs and words line up. */
|
|
20092
|
+
.knobs-ui .knobs-wt-prstate{--kw-pr:var(--_k-fg-3);display:inline-flex;align-items:center;gap:4px;min-width:calc(7ch + 32px);height:var(--kw-line);color:var(--kw-pr);
|
|
20093
|
+
font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap}
|
|
20094
|
+
.knobs-ui .knobs-wt-prstate[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
|
|
20095
|
+
.knobs-ui .knobs-wt-prstate[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
|
|
20096
|
+
.knobs-wt-prstate svg{flex:none}
|
|
20097
|
+
.knobs-ui .knobs-wt-prchecks{display:inline-flex;margin-left:2px;color:var(--_k-fg-3)}
|
|
18910
20098
|
.knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
|
|
18911
20099
|
.knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
|
|
18912
20100
|
@container knobs-wt-slot (max-width:520px){
|
|
18913
20101
|
.knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
|
|
18914
20102
|
.knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
|
|
18915
|
-
.knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
|
|
20103
|
+
:is(.knobs-wt-sstatus,.knobs-wt-prstate){grid-column:-2;justify-self:start;margin-top:4px}
|
|
18916
20104
|
}
|
|
18917
20105
|
|
|
18918
20106
|
/* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
|
|
@@ -18920,6 +20108,12 @@ var walkthroughCss = `
|
|
|
18920
20108
|
/* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
|
|
18921
20109
|
and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
|
|
18922
20110
|
.knobs-wt-tablebox{min-width:0}
|
|
20111
|
+
/* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
|
|
20112
|
+
without one line up. */
|
|
20113
|
+
.knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
|
|
20114
|
+
.knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
20115
|
+
.knobs-wt-tablebox tr[data-go]:is(:hover,:focus-visible)>td,.knobs-wt-trow[data-go]:is(:hover,:focus-visible){background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20116
|
+
.knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
18923
20117
|
.knobs-wt-tablebox[data-measure] table{width:min-content}
|
|
18924
20118
|
.knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
|
|
18925
20119
|
.knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
|
|
@@ -19091,6 +20285,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19091
20285
|
fit.style.setProperty("--kw-aspect", String(aspect));
|
|
19092
20286
|
let lastZoom = "all";
|
|
19093
20287
|
let lastLoop = "off";
|
|
20288
|
+
let marks = null;
|
|
20289
|
+
let marking = null;
|
|
19094
20290
|
const c = compare(fit, {
|
|
19095
20291
|
...look,
|
|
19096
20292
|
before: side(before, "Before"),
|
|
@@ -19103,6 +20299,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19103
20299
|
onMode: (next) => {
|
|
19104
20300
|
fit.toggleAttribute("data-overlay", next !== "side");
|
|
19105
20301
|
onEvent?.({ what: "mode", detail: next });
|
|
20302
|
+
const layer = marks?.element;
|
|
20303
|
+
if (layer) {
|
|
20304
|
+
layer.dataset.moving = "";
|
|
20305
|
+
setTimeout(() => {
|
|
20306
|
+
delete layer.dataset.moving;
|
|
20307
|
+
marks?.update();
|
|
20308
|
+
}, MARK_GLIDE);
|
|
20309
|
+
}
|
|
19106
20310
|
},
|
|
19107
20311
|
onView: (view) => {
|
|
19108
20312
|
const z = view.zoom;
|
|
@@ -19113,8 +20317,23 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19113
20317
|
const loopKey = l ? `${formatTime(l.start)}–${formatTime(l.end)}` : "off";
|
|
19114
20318
|
if (loopKey !== lastLoop)
|
|
19115
20319
|
onEvent?.({ what: "loop", detail: lastLoop = loopKey });
|
|
20320
|
+
marking?.kick(MARK_GLIDE);
|
|
19116
20321
|
}
|
|
19117
20322
|
});
|
|
20323
|
+
const stage = fit.querySelector(".knobs-cmp-stage");
|
|
20324
|
+
if (m.annotations?.length && stage) {
|
|
20325
|
+
const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
|
|
20326
|
+
marks = annotationLayer(stage, m.annotations, () => {
|
|
20327
|
+
const l = compareLayout({ x: 0, y: 0, w: stage.clientWidth, h: stage.clientHeight }, shape, shape);
|
|
20328
|
+
return {
|
|
20329
|
+
rects: c.mode === "side" ? [l.side.before, l.side.after] : [l.overlay.after],
|
|
20330
|
+
view: c.zoom,
|
|
20331
|
+
time: c.time
|
|
20332
|
+
};
|
|
20333
|
+
});
|
|
20334
|
+
const layer = marks;
|
|
20335
|
+
marking = follow(doc.defaultView, () => c.playing, () => layer.update());
|
|
20336
|
+
}
|
|
19118
20337
|
const chapters = m.chapters?.length ? m.chapters : null;
|
|
19119
20338
|
if (chapters)
|
|
19120
20339
|
c.timeline?.update({
|
|
@@ -19131,10 +20350,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19131
20350
|
const unlisten = c.timeline?.listen({
|
|
19132
20351
|
onSeek: (time, source) => {
|
|
19133
20352
|
pills?.sync();
|
|
20353
|
+
marking?.kick();
|
|
19134
20354
|
if (source !== "play")
|
|
19135
20355
|
onEvent?.({ what: "seek", time: round2(time) });
|
|
19136
20356
|
},
|
|
19137
|
-
onPlay: (on) =>
|
|
20357
|
+
onPlay: (on) => {
|
|
20358
|
+
marking?.kick();
|
|
20359
|
+
onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) });
|
|
20360
|
+
}
|
|
19138
20361
|
});
|
|
19139
20362
|
requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
|
|
19140
20363
|
return {
|
|
@@ -19161,6 +20384,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19161
20384
|
};
|
|
19162
20385
|
},
|
|
19163
20386
|
dispose: () => {
|
|
20387
|
+
marking?.stop();
|
|
20388
|
+
marks?.dispose();
|
|
19164
20389
|
unlisten?.();
|
|
19165
20390
|
pills?.dispose();
|
|
19166
20391
|
c.dispose();
|
|
@@ -19169,6 +20394,7 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19169
20394
|
};
|
|
19170
20395
|
}
|
|
19171
20396
|
var round2 = (v) => Math.round(v * 100) / 100;
|
|
20397
|
+
var MARK_GLIDE = 450;
|
|
19172
20398
|
var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
|
|
19173
20399
|
function mountVideo(host, m, onEvent) {
|
|
19174
20400
|
const doc = host.ownerDocument;
|
|
@@ -19216,6 +20442,16 @@ function mountVideo(host, m, onEvent) {
|
|
|
19216
20442
|
if (pills)
|
|
19217
20443
|
for (const type of ["timeupdate", "seeking", "seeked"])
|
|
19218
20444
|
v.addEventListener(type, sync);
|
|
20445
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20446
|
+
rects: [
|
|
20447
|
+
contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
|
|
20448
|
+
],
|
|
20449
|
+
time: v.currentTime
|
|
20450
|
+
})) : null;
|
|
20451
|
+
const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
|
|
20452
|
+
if (marking)
|
|
20453
|
+
for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
|
|
20454
|
+
v.addEventListener(type, () => marking.kick());
|
|
19219
20455
|
return {
|
|
19220
20456
|
pause: () => v.pause(),
|
|
19221
20457
|
seekBy: (d) => {
|
|
@@ -19229,6 +20465,8 @@ function mountVideo(host, m, onEvent) {
|
|
|
19229
20465
|
...pills?.current ? { chapter: pills.current.label } : {}
|
|
19230
20466
|
}),
|
|
19231
20467
|
dispose: () => {
|
|
20468
|
+
marking?.stop();
|
|
20469
|
+
marks?.dispose();
|
|
19232
20470
|
pills?.dispose();
|
|
19233
20471
|
v.pause();
|
|
19234
20472
|
v.removeAttribute("src");
|
|
@@ -19246,9 +20484,52 @@ function mountImage(host, m) {
|
|
|
19246
20484
|
img.decoding = "async";
|
|
19247
20485
|
img.alt = m.alt ?? m.label ?? "";
|
|
19248
20486
|
img.src = m.src;
|
|
20487
|
+
if (!img.complete) {
|
|
20488
|
+
frame.dataset.loading = "";
|
|
20489
|
+
img.addEventListener("load", () => delete frame.dataset.loading, { once: true });
|
|
20490
|
+
img.addEventListener("error", () => delete frame.dataset.loading, { once: true });
|
|
20491
|
+
}
|
|
19249
20492
|
frame.append(img);
|
|
19250
20493
|
host.append(frame);
|
|
19251
|
-
|
|
20494
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20495
|
+
rects: [{ x: 0, y: 0, w: frame.clientWidth, h: frame.clientHeight }]
|
|
20496
|
+
})) : null;
|
|
20497
|
+
if (marks)
|
|
20498
|
+
img.addEventListener("load", () => marks.update(), { once: true });
|
|
20499
|
+
return {
|
|
20500
|
+
dispose: () => {
|
|
20501
|
+
marks?.dispose();
|
|
20502
|
+
frame.remove();
|
|
20503
|
+
}
|
|
20504
|
+
};
|
|
20505
|
+
}
|
|
20506
|
+
function contain(box, natural) {
|
|
20507
|
+
if (!(natural.w > 0 && natural.h > 0))
|
|
20508
|
+
return { x: 0, y: 0, w: box.w, h: box.h };
|
|
20509
|
+
const k = Math.min(box.w / natural.w, box.h / natural.h);
|
|
20510
|
+
const w = natural.w * k;
|
|
20511
|
+
const h = natural.h * k;
|
|
20512
|
+
return { x: (box.w - w) / 2, y: (box.h - h) / 2, w, h };
|
|
20513
|
+
}
|
|
20514
|
+
function follow(win, on, fn) {
|
|
20515
|
+
let raf = 0;
|
|
20516
|
+
let until = 0;
|
|
20517
|
+
const tick = () => {
|
|
20518
|
+
fn();
|
|
20519
|
+
raf = on() || performance.now() < until ? win.requestAnimationFrame(tick) : 0;
|
|
20520
|
+
};
|
|
20521
|
+
return {
|
|
20522
|
+
kick(ms = 0) {
|
|
20523
|
+
until = Math.max(until, performance.now() + ms);
|
|
20524
|
+
if (!raf)
|
|
20525
|
+
raf = win.requestAnimationFrame(tick);
|
|
20526
|
+
},
|
|
20527
|
+
stop() {
|
|
20528
|
+
if (raf)
|
|
20529
|
+
win.cancelAnimationFrame(raf);
|
|
20530
|
+
raf = 0;
|
|
20531
|
+
}
|
|
20532
|
+
};
|
|
19252
20533
|
}
|
|
19253
20534
|
async function mountDiff(host, m, look, alive, onEvent) {
|
|
19254
20535
|
const lib = await Promise.resolve().then(() => (init_diff(), {}));
|
|
@@ -19402,6 +20683,14 @@ function mediaSlot(container, options) {
|
|
|
19402
20683
|
let pending = false;
|
|
19403
20684
|
let disposed = false;
|
|
19404
20685
|
let generation = 0;
|
|
20686
|
+
const born = performance.now();
|
|
20687
|
+
const enter = () => {
|
|
20688
|
+
const win = doc.defaultView;
|
|
20689
|
+
if (!win || performance.now() - born < 120 || reducedMotion(win))
|
|
20690
|
+
return;
|
|
20691
|
+
host.dataset.entering = "";
|
|
20692
|
+
win.requestAnimationFrame(() => win.requestAnimationFrame(() => delete host.dataset.entering));
|
|
20693
|
+
};
|
|
19405
20694
|
const hold = (px) => {
|
|
19406
20695
|
host.style.minHeight = px === null ? "" : `${px}px`;
|
|
19407
20696
|
host.classList.toggle("knobs-wt-hold", px !== null);
|
|
@@ -19421,6 +20710,7 @@ function mediaSlot(container, options) {
|
|
|
19421
20710
|
if (!alive())
|
|
19422
20711
|
return r.dispose();
|
|
19423
20712
|
mounted = r;
|
|
20713
|
+
enter();
|
|
19424
20714
|
if (filled)
|
|
19425
20715
|
r.fill?.(true);
|
|
19426
20716
|
if (r.ready)
|
|
@@ -19456,7 +20746,7 @@ function mediaSlot(container, options) {
|
|
|
19456
20746
|
case "history":
|
|
19457
20747
|
return done(mountHistory(host, m, options.steps));
|
|
19458
20748
|
case "stack":
|
|
19459
|
-
return done(mountStack(host, m, options.steps));
|
|
20749
|
+
return done(mountStack(host, m, options.steps, options.findPR));
|
|
19460
20750
|
case "table":
|
|
19461
20751
|
return done(mountTable(host, m, options.steps));
|
|
19462
20752
|
case "mount": {
|
|
@@ -19510,17 +20800,17 @@ function mediaSlot(container, options) {
|
|
|
19510
20800
|
}
|
|
19511
20801
|
};
|
|
19512
20802
|
}
|
|
19513
|
-
var
|
|
20803
|
+
var CHECK4 = icon("check", { small: true });
|
|
19514
20804
|
|
|
19515
20805
|
// src/ui/walkthrough.ts
|
|
19516
|
-
var
|
|
20806
|
+
var el3 = (doc, tag, cls) => {
|
|
19517
20807
|
const n = doc.createElement(tag);
|
|
19518
20808
|
if (cls)
|
|
19519
20809
|
n.className = cls;
|
|
19520
20810
|
return n;
|
|
19521
20811
|
};
|
|
19522
20812
|
var prose2 = (doc, tag, cls, text) => {
|
|
19523
|
-
const n =
|
|
20813
|
+
const n = el3(doc, tag, cls);
|
|
19524
20814
|
n.innerHTML = inlineMarkdown(text);
|
|
19525
20815
|
return n;
|
|
19526
20816
|
};
|
|
@@ -19559,7 +20849,7 @@ function tasteTest(container, options) {
|
|
|
19559
20849
|
});
|
|
19560
20850
|
const pickable = options.pickable !== false;
|
|
19561
20851
|
let picked = pickable ? options.picked ?? null : null;
|
|
19562
|
-
const root =
|
|
20852
|
+
const root = el3(doc, "div", "knobs-wt-options");
|
|
19563
20853
|
if (pickable) {
|
|
19564
20854
|
root.setAttribute("role", "radiogroup");
|
|
19565
20855
|
root.setAttribute("aria-label", "Options");
|
|
@@ -19570,34 +20860,34 @@ function tasteTest(container, options) {
|
|
|
19570
20860
|
applyLook2(root, options);
|
|
19571
20861
|
const slots = [];
|
|
19572
20862
|
const tiles = list.map((o, i) => {
|
|
19573
|
-
const tile =
|
|
20863
|
+
const tile = el3(doc, "div", "knobs-wt-option");
|
|
19574
20864
|
if (!pickable) {
|
|
19575
|
-
const name =
|
|
20865
|
+
const name = el3(doc, "span", "knobs-wt-slot-label");
|
|
19576
20866
|
name.textContent = o.label;
|
|
19577
20867
|
tile.append(name);
|
|
19578
20868
|
}
|
|
19579
|
-
const pick =
|
|
20869
|
+
const pick = el3(doc, "button", "knobs-wt-pick");
|
|
19580
20870
|
pick.type = "button";
|
|
19581
20871
|
pick.setAttribute("role", "radio");
|
|
19582
20872
|
if (i < 9)
|
|
19583
20873
|
pick.setAttribute("aria-keyshortcuts", String(i + 1));
|
|
19584
|
-
const letter =
|
|
20874
|
+
const letter = el3(doc, "span", "knobs-wt-letter");
|
|
19585
20875
|
letter.textContent = o.letter;
|
|
19586
|
-
const name =
|
|
20876
|
+
const name = el3(doc, "span", "knobs-wt-olabel");
|
|
19587
20877
|
name.textContent = o.label;
|
|
19588
20878
|
pick.append(letter, name);
|
|
19589
20879
|
if (o.suggested) {
|
|
19590
|
-
const s =
|
|
20880
|
+
const s = el3(doc, "span", "knobs-wt-suggested");
|
|
19591
20881
|
s.textContent = "Suggested";
|
|
19592
20882
|
pick.append(s);
|
|
19593
20883
|
}
|
|
19594
20884
|
if (i < 9) {
|
|
19595
|
-
const key =
|
|
20885
|
+
const key = el3(doc, "span", "knobs-wt-key");
|
|
19596
20886
|
key.textContent = String(i + 1);
|
|
19597
20887
|
pick.append(key);
|
|
19598
20888
|
}
|
|
19599
|
-
const check =
|
|
19600
|
-
check.innerHTML =
|
|
20889
|
+
const check = el3(doc, "span", "knobs-wt-check");
|
|
20890
|
+
check.innerHTML = CHECK4;
|
|
19601
20891
|
pick.append(check);
|
|
19602
20892
|
pick.addEventListener("click", () => {
|
|
19603
20893
|
const next = picked === o.id ? null : o.id;
|
|
@@ -19662,7 +20952,7 @@ function walkthrough(container, options) {
|
|
|
19662
20952
|
injectWalkthroughStyles(doc);
|
|
19663
20953
|
const look = lookOf2(options);
|
|
19664
20954
|
const storage = options.storage === undefined ? browserStorage() : options.storage;
|
|
19665
|
-
const root =
|
|
20955
|
+
const root = el3(doc, "div", "knobs-walkthrough");
|
|
19666
20956
|
applyLook2(root, options);
|
|
19667
20957
|
container.append(root);
|
|
19668
20958
|
let page = normalizeWalkthrough(options.doc);
|
|
@@ -19875,7 +21165,7 @@ function walkthrough(container, options) {
|
|
|
19875
21165
|
return true;
|
|
19876
21166
|
return false;
|
|
19877
21167
|
};
|
|
19878
|
-
const scrim =
|
|
21168
|
+
const scrim = el3(doc, "div", "knobs-wt-scrim");
|
|
19879
21169
|
scrim.addEventListener("click", () => setFull(false));
|
|
19880
21170
|
const fullRect = () => ({
|
|
19881
21171
|
x: DOCK_INSET,
|
|
@@ -19925,7 +21215,7 @@ function walkthrough(container, options) {
|
|
|
19925
21215
|
}
|
|
19926
21216
|
if (!full) {
|
|
19927
21217
|
const r = v.card.getBoundingClientRect();
|
|
19928
|
-
const hold =
|
|
21218
|
+
const hold = el3(doc, "div", "knobs-wt-hold-card");
|
|
19929
21219
|
hold.style.height = `${r.height}px`;
|
|
19930
21220
|
v.card.before(hold);
|
|
19931
21221
|
v.card.dataset.full = "";
|
|
@@ -20075,7 +21365,7 @@ function walkthrough(container, options) {
|
|
|
20075
21365
|
toast("Couldn't reach the microphone", { tone: "warn", look, doc });
|
|
20076
21366
|
}
|
|
20077
21367
|
};
|
|
20078
|
-
const backPill =
|
|
21368
|
+
const backPill = el3(doc, "button", "knobs-wt-back");
|
|
20079
21369
|
backPill.type = "button";
|
|
20080
21370
|
let backTo = null;
|
|
20081
21371
|
let backTimer = 0;
|
|
@@ -20095,7 +21385,7 @@ function walkthrough(container, options) {
|
|
|
20095
21385
|
return;
|
|
20096
21386
|
backTo = key;
|
|
20097
21387
|
backPill.innerHTML = ICON.back;
|
|
20098
|
-
const words =
|
|
21388
|
+
const words = el3(doc, "span", "knobs-wt-back-label");
|
|
20099
21389
|
words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
|
|
20100
21390
|
backPill.append(words);
|
|
20101
21391
|
backPill.dataset.on = "";
|
|
@@ -20103,16 +21393,16 @@ function walkthrough(container, options) {
|
|
|
20103
21393
|
backTimer = win.setTimeout(hideBack, BACK_MS);
|
|
20104
21394
|
};
|
|
20105
21395
|
const bar = (() => {
|
|
20106
|
-
const dock =
|
|
20107
|
-
const box =
|
|
20108
|
-
const on =
|
|
20109
|
-
const row =
|
|
20110
|
-
const input =
|
|
21396
|
+
const dock = el3(doc, "div", "knobs-wt-dock");
|
|
21397
|
+
const box = el3(doc, "div", "knobs-wt-notebar");
|
|
21398
|
+
const on = el3(doc, "div", "knobs-wt-on");
|
|
21399
|
+
const row = el3(doc, "div", "knobs-wt-row");
|
|
21400
|
+
const input = el3(doc, "textarea", "knobs-wt-input");
|
|
20111
21401
|
input.rows = 1;
|
|
20112
21402
|
input.setAttribute("aria-label", "Note");
|
|
20113
|
-
const attach =
|
|
21403
|
+
const attach = el3(doc, "div", "knobs-wt-attach");
|
|
20114
21404
|
const btn = (icon, label, run) => {
|
|
20115
|
-
const b =
|
|
21405
|
+
const b = el3(doc, "button", "knobs-wt-tool");
|
|
20116
21406
|
b.type = "button";
|
|
20117
21407
|
b.innerHTML = icon;
|
|
20118
21408
|
b.setAttribute("aria-label", label);
|
|
@@ -20123,17 +21413,17 @@ function walkthrough(container, options) {
|
|
|
20123
21413
|
const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
|
|
20124
21414
|
const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
|
|
20125
21415
|
const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
|
|
20126
|
-
const micTime =
|
|
21416
|
+
const micTime = el3(doc, "span", "knobs-wt-rec");
|
|
20127
21417
|
micBtn.append(micTime);
|
|
20128
21418
|
const sendBtn = btn(ICON.send, "Send · Enter", () => send());
|
|
20129
21419
|
sendBtn.dataset.send = "";
|
|
20130
21420
|
row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
|
|
20131
21421
|
box.append(on, row);
|
|
20132
|
-
const launch =
|
|
21422
|
+
const launch = el3(doc, "button", "knobs-wt-launch");
|
|
20133
21423
|
launch.type = "button";
|
|
20134
21424
|
launch.innerHTML = ICON.pen;
|
|
20135
21425
|
launch.append("Note");
|
|
20136
|
-
const kbd =
|
|
21426
|
+
const kbd = el3(doc, "kbd");
|
|
20137
21427
|
kbd.textContent = "n";
|
|
20138
21428
|
launch.append(kbd);
|
|
20139
21429
|
launch.setAttribute("aria-label", "Write a note (n)");
|
|
@@ -20185,9 +21475,9 @@ function walkthrough(container, options) {
|
|
|
20185
21475
|
}
|
|
20186
21476
|
});
|
|
20187
21477
|
const chip = (text, clear) => {
|
|
20188
|
-
const c =
|
|
21478
|
+
const c = el3(doc, "span", "knobs-wt-chip-a");
|
|
20189
21479
|
c.append(text);
|
|
20190
|
-
const x =
|
|
21480
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
20191
21481
|
x.type = "button";
|
|
20192
21482
|
x.innerHTML = ICON.close;
|
|
20193
21483
|
x.setAttribute("aria-label", `Remove ${text}`);
|
|
@@ -20336,9 +21626,9 @@ function walkthrough(container, options) {
|
|
|
20336
21626
|
}
|
|
20337
21627
|
};
|
|
20338
21628
|
const renderNote = (v, n, entering = false) => {
|
|
20339
|
-
const row =
|
|
21629
|
+
const row = el3(doc, "div", "knobs-wt-n");
|
|
20340
21630
|
row.dataset.id = n.id;
|
|
20341
|
-
const meta =
|
|
21631
|
+
const meta = el3(doc, "span", "knobs-wt-n-meta");
|
|
20342
21632
|
const m = n.context.media;
|
|
20343
21633
|
meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
|
|
20344
21634
|
if (m?.time !== undefined) {
|
|
@@ -20354,11 +21644,11 @@ function walkthrough(container, options) {
|
|
|
20354
21644
|
});
|
|
20355
21645
|
}
|
|
20356
21646
|
row.append(meta);
|
|
20357
|
-
const body =
|
|
21647
|
+
const body = el3(doc, "div", "knobs-wt-n-body");
|
|
20358
21648
|
if (n.text)
|
|
20359
21649
|
body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
|
|
20360
21650
|
if (n.voice) {
|
|
20361
|
-
const a =
|
|
21651
|
+
const a = el3(doc, "audio", "knobs-wt-n-voice");
|
|
20362
21652
|
a.controls = true;
|
|
20363
21653
|
a.preload = "none";
|
|
20364
21654
|
a.src = n.voice.url;
|
|
@@ -20378,7 +21668,7 @@ function walkthrough(container, options) {
|
|
|
20378
21668
|
body.append(thumb);
|
|
20379
21669
|
}
|
|
20380
21670
|
row.append(body);
|
|
20381
|
-
const x =
|
|
21671
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
20382
21672
|
x.type = "button";
|
|
20383
21673
|
x.innerHTML = ICON.close;
|
|
20384
21674
|
x.setAttribute("aria-label", "Delete this note");
|
|
@@ -20418,9 +21708,9 @@ function walkthrough(container, options) {
|
|
|
20418
21708
|
row.addEventListener("transitionend", done, { once: true });
|
|
20419
21709
|
win.setTimeout(done, 600);
|
|
20420
21710
|
};
|
|
20421
|
-
const metaLine = (cls) =>
|
|
21711
|
+
const metaLine = (cls) => el3(doc, "div", `knobs-wt-metaline ${cls}`);
|
|
20422
21712
|
const metaText = (line, text, cls) => {
|
|
20423
|
-
const s =
|
|
21713
|
+
const s = el3(doc, "span", cls);
|
|
20424
21714
|
s.textContent = text;
|
|
20425
21715
|
line.append(s);
|
|
20426
21716
|
return s;
|
|
@@ -20433,7 +21723,7 @@ function walkthrough(container, options) {
|
|
|
20433
21723
|
line.append(chip);
|
|
20434
21724
|
return chip;
|
|
20435
21725
|
}
|
|
20436
|
-
const a =
|
|
21726
|
+
const a = el3(doc, "a");
|
|
20437
21727
|
a.href = href;
|
|
20438
21728
|
a.target = "_blank";
|
|
20439
21729
|
a.rel = "noreferrer";
|
|
@@ -20443,58 +21733,58 @@ function walkthrough(container, options) {
|
|
|
20443
21733
|
return a;
|
|
20444
21734
|
};
|
|
20445
21735
|
const feedItem = (f, now) => {
|
|
20446
|
-
const li =
|
|
21736
|
+
const li = el3(doc, "li", "knobs-wt-fi");
|
|
20447
21737
|
li.dataset.kind = f.kind;
|
|
20448
|
-
const q =
|
|
21738
|
+
const q = el3(doc, "div", "knobs-wt-fi-q");
|
|
20449
21739
|
const meta = metaLine("knobs-wt-fi-meta");
|
|
20450
21740
|
if (f.at !== null) {
|
|
20451
21741
|
const when = metaText(meta, ago(f.at, now));
|
|
20452
21742
|
when.title = new Date(f.at).toLocaleString();
|
|
20453
|
-
}
|
|
20454
|
-
metaText(meta, f.kind === "qa" ? "Answer" : "Update");
|
|
21743
|
+
}
|
|
20455
21744
|
const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
|
|
20456
21745
|
if (step) {
|
|
20457
21746
|
const section = page.sections.find((s) => s.id === step.section);
|
|
20458
|
-
const b =
|
|
21747
|
+
const b = el3(doc, "button", "knobs-wt-fi-step");
|
|
20459
21748
|
b.type = "button";
|
|
20460
21749
|
b.textContent = section?.link?.label ?? plainMarkdown(step.title);
|
|
20461
21750
|
b.title = `Go to “${plainMarkdown(step.title)}”`;
|
|
20462
21751
|
b.addEventListener("click", () => go(step.key));
|
|
20463
21752
|
meta.append(b);
|
|
20464
21753
|
}
|
|
20465
|
-
if (f.status) {
|
|
21754
|
+
if (f.status && review) {
|
|
20466
21755
|
const id = f.status.toLowerCase();
|
|
20467
21756
|
const shipped = /^(ship|shipped|merged|done)$/.test(id);
|
|
20468
21757
|
const changes = /^(changes?|changes queued)$/.test(id);
|
|
20469
21758
|
const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
|
|
20470
21759
|
s.dataset.status = shipped ? "on" : changes ? "warn" : "";
|
|
20471
21760
|
}
|
|
20472
|
-
|
|
21761
|
+
if (meta.childNodes.length)
|
|
21762
|
+
q.append(meta);
|
|
20473
21763
|
if (f.q)
|
|
20474
21764
|
q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
|
|
20475
21765
|
else
|
|
20476
21766
|
q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
|
|
20477
21767
|
li.append(q);
|
|
20478
21768
|
if (f.q || f.trail.length) {
|
|
20479
|
-
const a =
|
|
21769
|
+
const a = el3(doc, "div", "knobs-wt-fi-a");
|
|
20480
21770
|
if (f.q)
|
|
20481
21771
|
a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
|
|
20482
21772
|
if (f.trail.length) {
|
|
20483
|
-
const list =
|
|
21773
|
+
const list = el3(doc, "ol", "knobs-wt-trail");
|
|
20484
21774
|
for (const line of f.trail) {
|
|
20485
21775
|
const i = line.indexOf(" · ");
|
|
20486
|
-
const row =
|
|
20487
|
-
const words =
|
|
21776
|
+
const row = el3(doc, "li");
|
|
21777
|
+
const words = el3(doc, "span", "knobs-wt-trail-text");
|
|
20488
21778
|
words.textContent = i < 0 ? line : line.slice(i + 3);
|
|
20489
21779
|
row.append(words);
|
|
20490
21780
|
if (i >= 0) {
|
|
20491
|
-
const at =
|
|
21781
|
+
const at = el3(doc, "span", "knobs-wt-trail-at");
|
|
20492
21782
|
at.textContent = line.slice(0, i);
|
|
20493
21783
|
row.append(at);
|
|
20494
21784
|
}
|
|
20495
21785
|
list.append(row);
|
|
20496
21786
|
}
|
|
20497
|
-
discs.push(disclosure(a, { label: "Context",
|
|
21787
|
+
discs.push(disclosure(a, { label: "Context", content: list }));
|
|
20498
21788
|
}
|
|
20499
21789
|
li.append(a);
|
|
20500
21790
|
}
|
|
@@ -20522,11 +21812,11 @@ function walkthrough(container, options) {
|
|
|
20522
21812
|
root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
|
|
20523
21813
|
root.toggleAttribute("data-cards", options.cards === true);
|
|
20524
21814
|
root.toggleAttribute("data-review", review);
|
|
20525
|
-
const layout =
|
|
20526
|
-
const main =
|
|
20527
|
-
const flow =
|
|
20528
|
-
const head =
|
|
20529
|
-
const h1 =
|
|
21815
|
+
const layout = el3(doc, "div", "knobs-wt-layout");
|
|
21816
|
+
const main = el3(doc, "div", "knobs-wt-main");
|
|
21817
|
+
const flow = el3(doc, "div", "knobs-wt-flow");
|
|
21818
|
+
const head = el3(doc, "header", "knobs-wt-head");
|
|
21819
|
+
const h1 = el3(doc, "h1", "knobs-wt-title");
|
|
20530
21820
|
h1.textContent = page.title;
|
|
20531
21821
|
head.append(h1);
|
|
20532
21822
|
if (page.summary)
|
|
@@ -20534,13 +21824,13 @@ function walkthrough(container, options) {
|
|
|
20534
21824
|
if (page.more)
|
|
20535
21825
|
head.append(more(page.more));
|
|
20536
21826
|
if (page.meta.length) {
|
|
20537
|
-
const meta =
|
|
21827
|
+
const meta = el3(doc, "div", "knobs-wt-meta");
|
|
20538
21828
|
for (const m of page.meta) {
|
|
20539
|
-
const s =
|
|
21829
|
+
const s = el3(doc, "span");
|
|
20540
21830
|
s.append(m.label);
|
|
20541
|
-
const b =
|
|
21831
|
+
const b = el3(doc, "b");
|
|
20542
21832
|
if (m.href) {
|
|
20543
|
-
const a =
|
|
21833
|
+
const a = el3(doc, "a");
|
|
20544
21834
|
a.href = m.href;
|
|
20545
21835
|
a.target = "_blank";
|
|
20546
21836
|
a.rel = "noreferrer";
|
|
@@ -20556,16 +21846,14 @@ function walkthrough(container, options) {
|
|
|
20556
21846
|
flow.append(head);
|
|
20557
21847
|
let feedEl = null;
|
|
20558
21848
|
if (page.feed.length) {
|
|
20559
|
-
feedEl =
|
|
21849
|
+
feedEl = el3(doc, "section", "knobs-wt-feed");
|
|
20560
21850
|
feedEl.id = "wt-latest";
|
|
20561
21851
|
feedEl.setAttribute("aria-label", "Updates and answers");
|
|
20562
|
-
const fh =
|
|
20563
|
-
const h =
|
|
21852
|
+
const fh = el3(doc, "header", "knobs-wt-feed-head");
|
|
21853
|
+
const h = el3(doc, "h2", "knobs-wt-feed-title");
|
|
20564
21854
|
h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
|
|
20565
|
-
|
|
20566
|
-
|
|
20567
|
-
fh.append(h, n);
|
|
20568
|
-
const list = el2(doc, "ol", "knobs-wt-feed-list");
|
|
21855
|
+
fh.append(h);
|
|
21856
|
+
const list = el3(doc, "ol", "knobs-wt-feed-list");
|
|
20569
21857
|
const now = Date.now();
|
|
20570
21858
|
for (const f of page.feed)
|
|
20571
21859
|
list.append(feedItem(f, now));
|
|
@@ -20575,21 +21863,21 @@ function walkthrough(container, options) {
|
|
|
20575
21863
|
let rail = null;
|
|
20576
21864
|
count = fill = tocEl = null;
|
|
20577
21865
|
if (options.rail !== false) {
|
|
20578
|
-
rail =
|
|
21866
|
+
rail = el3(doc, "aside", "knobs-wt-rail");
|
|
20579
21867
|
rail.setAttribute("aria-label", "Contents");
|
|
20580
|
-
tocEl =
|
|
21868
|
+
tocEl = el3(doc, "nav", "knobs-wt-toc");
|
|
20581
21869
|
tocEl.setAttribute("aria-label", "Contents");
|
|
20582
21870
|
if (feedEl) {
|
|
20583
|
-
const group =
|
|
21871
|
+
const group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20584
21872
|
const target = feedEl;
|
|
20585
21873
|
group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
|
|
20586
21874
|
tocEl.append(group);
|
|
20587
21875
|
}
|
|
20588
21876
|
if (review) {
|
|
20589
|
-
const progress =
|
|
20590
|
-
count =
|
|
20591
|
-
const meter =
|
|
20592
|
-
fill =
|
|
21877
|
+
const progress = el3(doc, "div", "knobs-wt-progress");
|
|
21878
|
+
count = el3(doc, "div", "knobs-wt-count");
|
|
21879
|
+
const meter = el3(doc, "div", "knobs-wt-bar");
|
|
21880
|
+
fill = el3(doc, "div", "knobs-wt-fill");
|
|
20593
21881
|
meter.append(fill);
|
|
20594
21882
|
progress.append(count, meter);
|
|
20595
21883
|
rail.append(progress);
|
|
@@ -20598,15 +21886,15 @@ function walkthrough(container, options) {
|
|
|
20598
21886
|
if (review)
|
|
20599
21887
|
rail.append(actions(true));
|
|
20600
21888
|
if (review && options.keys !== false) {
|
|
20601
|
-
const keys =
|
|
21889
|
+
const keys = el3(doc, "div", "knobs-wt-keys");
|
|
20602
21890
|
keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
|
|
20603
21891
|
rail.append(keys);
|
|
20604
21892
|
}
|
|
20605
21893
|
}
|
|
20606
21894
|
for (const section of page.sections) {
|
|
20607
|
-
const sec =
|
|
21895
|
+
const sec = el3(doc, "section", "knobs-wt-section");
|
|
20608
21896
|
sec.id = `wt-${section.id}`;
|
|
20609
|
-
const sh =
|
|
21897
|
+
const sh = el3(doc, "header", "knobs-wt-shead");
|
|
20610
21898
|
const status = review ? section.status : undefined;
|
|
20611
21899
|
if (section.kicker || section.link || status) {
|
|
20612
21900
|
const meta = metaLine("knobs-wt-kicker");
|
|
@@ -20620,10 +21908,10 @@ function walkthrough(container, options) {
|
|
|
20620
21908
|
}
|
|
20621
21909
|
sh.append(meta);
|
|
20622
21910
|
}
|
|
20623
|
-
const h2 =
|
|
21911
|
+
const h2 = el3(doc, "h2", "knobs-wt-heading");
|
|
20624
21912
|
h2.innerHTML = inlineMarkdown(section.title);
|
|
20625
21913
|
sh.append(h2);
|
|
20626
|
-
const intro =
|
|
21914
|
+
const intro = el3(doc, "div", "knobs-wt-sintro");
|
|
20627
21915
|
intro.append(sh);
|
|
20628
21916
|
if (section.summary)
|
|
20629
21917
|
intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
|
|
@@ -20632,7 +21920,7 @@ function walkthrough(container, options) {
|
|
|
20632
21920
|
sec.append(intro);
|
|
20633
21921
|
let group = null;
|
|
20634
21922
|
if (tocEl) {
|
|
20635
|
-
group =
|
|
21923
|
+
group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20636
21924
|
const first = section.steps[0]?.key;
|
|
20637
21925
|
group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
|
|
20638
21926
|
tocEl.append(group);
|
|
@@ -20674,8 +21962,8 @@ function walkthrough(container, options) {
|
|
|
20674
21962
|
}));
|
|
20675
21963
|
};
|
|
20676
21964
|
const more = (text) => {
|
|
20677
|
-
const box =
|
|
20678
|
-
const body =
|
|
21965
|
+
const box = el3(doc, "div", "knobs-wt-more");
|
|
21966
|
+
const body = el3(doc, "div", "knobs-wt-more-text");
|
|
20679
21967
|
for (const para of text.split(/\n\s*\n/))
|
|
20680
21968
|
if (para.trim())
|
|
20681
21969
|
body.append(prose2(doc, "p", "", para.trim()));
|
|
@@ -20683,24 +21971,25 @@ function walkthrough(container, options) {
|
|
|
20683
21971
|
return box;
|
|
20684
21972
|
};
|
|
20685
21973
|
const tocRow = (label, sub, run, section) => {
|
|
20686
|
-
const b =
|
|
21974
|
+
const b = el3(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
|
|
20687
21975
|
b.type = "button";
|
|
20688
21976
|
if (section)
|
|
20689
21977
|
b.dataset.section = section;
|
|
20690
|
-
const t =
|
|
21978
|
+
const t = el3(doc, "span", "knobs-wt-toc-label");
|
|
20691
21979
|
t.textContent = label;
|
|
20692
|
-
const e = el2(doc, "span", "knobs-wt-toc-end");
|
|
20693
21980
|
b.title = label;
|
|
20694
|
-
b.append(t
|
|
21981
|
+
b.append(t);
|
|
21982
|
+
if (review)
|
|
21983
|
+
b.append(el3(doc, "span", "knobs-wt-toc-end"));
|
|
20695
21984
|
b.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
20696
21985
|
b.addEventListener("click", run);
|
|
20697
21986
|
tocRows.push(b);
|
|
20698
21987
|
return b;
|
|
20699
21988
|
};
|
|
20700
21989
|
const actions = (rail) => {
|
|
20701
|
-
const row =
|
|
21990
|
+
const row = el3(doc, "div", "knobs-wt-actions");
|
|
20702
21991
|
const btn = (text, run, primary = false) => {
|
|
20703
|
-
const b =
|
|
21992
|
+
const b = el3(doc, "button", "knobs-wt-btn");
|
|
20704
21993
|
b.type = "button";
|
|
20705
21994
|
b.textContent = text;
|
|
20706
21995
|
if (primary)
|
|
@@ -20738,13 +22027,13 @@ function walkthrough(container, options) {
|
|
|
20738
22027
|
}
|
|
20739
22028
|
};
|
|
20740
22029
|
const stepView = (step, group) => {
|
|
20741
|
-
const card =
|
|
22030
|
+
const card = el3(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
|
|
20742
22031
|
card.id = `wt-${step.key.replace("/", "--")}`;
|
|
20743
22032
|
card.tabIndex = -1;
|
|
20744
22033
|
card.dataset.key = step.key;
|
|
20745
22034
|
card.setAttribute("aria-label", plainMarkdown(step.title));
|
|
20746
|
-
const head =
|
|
20747
|
-
const h3 =
|
|
22035
|
+
const head = el3(doc, "header", "knobs-wt-step-head");
|
|
22036
|
+
const h3 = el3(doc, "h3", "knobs-wt-stepTitle");
|
|
20748
22037
|
h3.innerHTML = inlineMarkdown(step.title);
|
|
20749
22038
|
head.append(h3);
|
|
20750
22039
|
if (step.caption)
|
|
@@ -20756,10 +22045,10 @@ function walkthrough(container, options) {
|
|
|
20756
22045
|
const onEvent = (e) => record({ ...e, step: step.key });
|
|
20757
22046
|
let media = null;
|
|
20758
22047
|
if (step.media.length) {
|
|
20759
|
-
media =
|
|
22048
|
+
media = el3(doc, "div", "knobs-wt-media");
|
|
20760
22049
|
media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
|
|
20761
22050
|
for (const m of step.media)
|
|
20762
|
-
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
|
|
22051
|
+
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
|
|
20763
22052
|
card.append(media);
|
|
20764
22053
|
}
|
|
20765
22054
|
let taste = null;
|
|
@@ -20776,7 +22065,7 @@ function walkthrough(container, options) {
|
|
|
20776
22065
|
});
|
|
20777
22066
|
media ??= taste.element;
|
|
20778
22067
|
}
|
|
20779
|
-
const notes =
|
|
22068
|
+
const notes = el3(doc, "div", "knobs-wt-notes");
|
|
20780
22069
|
if (review)
|
|
20781
22070
|
card.append(notes);
|
|
20782
22071
|
card.addEventListener("focusin", () => {
|
|
@@ -20790,7 +22079,7 @@ function walkthrough(container, options) {
|
|
|
20790
22079
|
if (group) {
|
|
20791
22080
|
toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
|
|
20792
22081
|
if (review) {
|
|
20793
|
-
glyph =
|
|
22082
|
+
glyph = el3(doc, "span", "knobs-wt-toc-glyph");
|
|
20794
22083
|
toc.querySelector(".knobs-wt-toc-end").append(glyph);
|
|
20795
22084
|
}
|
|
20796
22085
|
group.append(toc);
|
|
@@ -20864,6 +22153,10 @@ function walkthrough(container, options) {
|
|
|
20864
22153
|
claim();
|
|
20865
22154
|
return;
|
|
20866
22155
|
}
|
|
22156
|
+
if (key === "a" && !e.shiftKey && root.querySelector(".knobs-wt-anno")) {
|
|
22157
|
+
claim();
|
|
22158
|
+
return setAnnotations(root, !annotationsOn(root));
|
|
22159
|
+
}
|
|
20867
22160
|
if (key === "n" && noteMode !== "hidden") {
|
|
20868
22161
|
claim();
|
|
20869
22162
|
bar.setOpen(true, true);
|
|
@@ -20962,7 +22255,7 @@ function walkthrough(container, options) {
|
|
|
20962
22255
|
}
|
|
20963
22256
|
|
|
20964
22257
|
// src/ui/solid.ts
|
|
20965
|
-
var
|
|
22258
|
+
var follow2 = (read, apply) => {
|
|
20966
22259
|
const changes = propChanges(untrack(read));
|
|
20967
22260
|
createEffect(() => {
|
|
20968
22261
|
const changed = changes(read());
|
|
@@ -21010,7 +22303,7 @@ function Tree(props) {
|
|
|
21010
22303
|
}
|
|
21011
22304
|
}));
|
|
21012
22305
|
untrack(() => props.onReady?.(t));
|
|
21013
|
-
|
|
22306
|
+
follow2(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
|
|
21014
22307
|
if ("items" in c)
|
|
21015
22308
|
t.update(c.items);
|
|
21016
22309
|
if ("filter" in c)
|
|
@@ -21046,7 +22339,7 @@ function CodeDiff(props) {
|
|
|
21046
22339
|
onViewed: props.onViewed && ((viewed, file) => props.onViewed?.(viewed, file))
|
|
21047
22340
|
}));
|
|
21048
22341
|
untrack(() => props.onReady?.(d));
|
|
21049
|
-
|
|
22342
|
+
follow2(diffProps(props), (c) => {
|
|
21050
22343
|
if (c.height)
|
|
21051
22344
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
21052
22345
|
d.update(c);
|
|
@@ -21069,7 +22362,7 @@ function DiffReview(props) {
|
|
|
21069
22362
|
onViewed: (viewed, file) => props.onViewed?.(viewed, file)
|
|
21070
22363
|
}));
|
|
21071
22364
|
untrack(() => props.onReady?.(r));
|
|
21072
|
-
|
|
22365
|
+
follow2(diffProps(props), (c) => {
|
|
21073
22366
|
if (c.height)
|
|
21074
22367
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
21075
22368
|
r.update(c);
|
|
@@ -21091,7 +22384,7 @@ function Timeline(props) {
|
|
|
21091
22384
|
onCross: (m) => props.onCross?.(m)
|
|
21092
22385
|
}));
|
|
21093
22386
|
untrack(() => props.onReady?.(t));
|
|
21094
|
-
|
|
22387
|
+
follow2(() => ({
|
|
21095
22388
|
duration: props.duration,
|
|
21096
22389
|
fps: props.fps,
|
|
21097
22390
|
units: props.units,
|
|
@@ -21120,7 +22413,7 @@ function Compare(props) {
|
|
|
21120
22413
|
onView: (view) => props.onView?.(view)
|
|
21121
22414
|
}));
|
|
21122
22415
|
untrack(() => props.onReady?.(c));
|
|
21123
|
-
|
|
22416
|
+
follow2(() => ({
|
|
21124
22417
|
before: props.before,
|
|
21125
22418
|
after: props.after,
|
|
21126
22419
|
mode: props.mode,
|
|
@@ -21143,7 +22436,7 @@ function CompareStrip(props) {
|
|
|
21143
22436
|
host.style.height = "100%";
|
|
21144
22437
|
const s = untrack(() => compareStrip(host, { ...props, onPick: (time) => props.onPick?.(time) }));
|
|
21145
22438
|
untrack(() => props.onReady?.(s));
|
|
21146
|
-
|
|
22439
|
+
follow2(() => ({ before: props.before.src, after: props.after.src }), (changed) => s.update({
|
|
21147
22440
|
..."before" in changed ? { before: props.before } : {},
|
|
21148
22441
|
..."after" in changed ? { after: props.after } : {}
|
|
21149
22442
|
}));
|
|
@@ -21163,7 +22456,7 @@ function Bento(props) {
|
|
|
21163
22456
|
onActive: (id) => props.onActive?.(id)
|
|
21164
22457
|
}));
|
|
21165
22458
|
untrack(() => props.onReady?.(b));
|
|
21166
|
-
|
|
22459
|
+
follow2(() => ({ items: props.items, picks: props.picks }), (c) => {
|
|
21167
22460
|
if ("items" in c)
|
|
21168
22461
|
b.update(c.items);
|
|
21169
22462
|
if (c.picks)
|
|
@@ -21185,7 +22478,7 @@ function Activity(props) {
|
|
|
21185
22478
|
onPlay: props.onPlay && ((id, event) => props.onPlay?.(id, event))
|
|
21186
22479
|
}));
|
|
21187
22480
|
untrack(() => props.onReady?.(a));
|
|
21188
|
-
|
|
22481
|
+
follow2(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
|
|
21189
22482
|
if (c.events || c.newest)
|
|
21190
22483
|
a.update({ events: c.events, newest: c.newest });
|
|
21191
22484
|
if (c.selected !== undefined && c.selected !== a.selected)
|
|
@@ -21214,7 +22507,7 @@ function OptionPicker(props) {
|
|
|
21214
22507
|
}
|
|
21215
22508
|
}));
|
|
21216
22509
|
untrack(() => props.onReady?.(p));
|
|
21217
|
-
|
|
22510
|
+
follow2(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
|
|
21218
22511
|
if ("options" in c)
|
|
21219
22512
|
p.set(c.options);
|
|
21220
22513
|
if (c.selected != null)
|
|
@@ -21231,7 +22524,7 @@ function Walkthrough(props) {
|
|
|
21231
22524
|
const host = document.createElement("div");
|
|
21232
22525
|
const w = untrack(() => walkthrough(host, { ...props, onFeedback: (feedback) => props.onFeedback?.(feedback) }));
|
|
21233
22526
|
untrack(() => props.onReady?.(w));
|
|
21234
|
-
|
|
22527
|
+
follow2(() => ({ doc: props.doc }), (c) => c.doc && w.set(c.doc));
|
|
21235
22528
|
onCleanup(() => w.dispose());
|
|
21236
22529
|
return host;
|
|
21237
22530
|
}
|
|
@@ -21241,7 +22534,7 @@ function TasteTest(props) {
|
|
|
21241
22534
|
const host = document.createElement("div");
|
|
21242
22535
|
const t = untrack(() => tasteTest(host, { ...props, onPick: (id) => props.onPick?.(id) }));
|
|
21243
22536
|
untrack(() => props.onReady?.(t));
|
|
21244
|
-
|
|
22537
|
+
follow2(() => ({ picked: props.picked }), (c) => ("picked" in c) && t.pick(c.picked ?? null));
|
|
21245
22538
|
onCleanup(() => t.dispose());
|
|
21246
22539
|
return host;
|
|
21247
22540
|
}
|