@kitlangton/knobs 0.4.0-next.5 → 0.4.0-next.6
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 +8 -6
- package/dist/dev/{chunk-y2axkmz3.js → chunk-nhz9en4k.js} +1 -1
- package/dist/dev/{chunk-qc6szek7.js → chunk-ya44t7z5.js} +7 -3
- package/dist/dev/index.js +3 -3
- package/dist/panel.js +7 -3
- 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/index.d.ts +2 -1
- package/dist/types/ui/player-css.d.ts +1 -1
- package/dist/types/ui/walkthrough-annotations.d.ts +42 -0
- package/dist/types/ui/walkthrough-blocks.d.ts +6 -4
- package/dist/types/ui/walkthrough-model.d.ts +46 -9
- package/dist/ui-react.js +1601 -320
- package/dist/ui-solid.js +1613 -332
- package/dist/ui.js +5547 -4264
- package/package.json +1 -1
package/dist/ui-solid.js
CHANGED
|
@@ -577,11 +577,11 @@ var compareCss = `
|
|
|
577
577
|
.knobs-cmp-modes[hidden]{display:none}
|
|
578
578
|
.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:pointer;white-space:nowrap;
|
|
579
579
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
580
|
-
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
580
|
+
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
581
581
|
/* Toggles: icon buttons, the accent's wash when on. */
|
|
582
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-3);cursor:pointer;
|
|
583
583
|
transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
|
|
584
|
-
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
|
|
584
|
+
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
585
585
|
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
586
586
|
.knobs-cmp-toggle svg{display:block}
|
|
587
587
|
.knobs-cmp-fill{flex:1}
|
|
@@ -647,7 +647,7 @@ var compareCss = `
|
|
|
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
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:pointer;transition:color .15s,background .15s}
|
|
650
|
-
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent)}
|
|
650
|
+
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent);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}
|
|
@@ -678,6 +678,10 @@ var playerCss = `
|
|
|
678
678
|
.knobs-player-glyph svg{width:16px;height:16px}
|
|
679
679
|
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
680
680
|
.knobs-player-line{flex:none;min-height:0}
|
|
681
|
+
/* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
|
|
682
|
+
states are set here, after it, so the player's line and every timeline share them. */
|
|
683
|
+
.knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
|
|
684
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
681
685
|
`;
|
|
682
686
|
|
|
683
687
|
// src/ui/overlay-css.ts
|
|
@@ -3231,14 +3235,14 @@ var init_diff_css = __esm(() => {
|
|
|
3231
3235
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
3232
3236
|
.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-3);cursor:pointer;
|
|
3233
3237
|
transition:background .15s,color .15s,opacity .15s}
|
|
3234
|
-
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3238
|
+
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3235
3239
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3236
3240
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
3237
3241
|
.knobs-diff-btn svg{width:16px;height:16px}
|
|
3238
3242
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
3239
3243
|
.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
3244
|
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
3241
|
-
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3245
|
+
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3242
3246
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3243
3247
|
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
3244
3248
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
@@ -3258,7 +3262,7 @@ var init_diff_css = __esm(() => {
|
|
|
3258
3262
|
.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
3263
|
.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
3264
|
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
3261
|
-
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
3265
|
+
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3262
3266
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
3263
3267
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
3264
3268
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -3279,8 +3283,8 @@ var init_diff_css = __esm(() => {
|
|
|
3279
3283
|
.knobs-diff-hbar{position:absolute;height:5px;pointer-events:none}
|
|
3280
3284
|
.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
3285
|
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)}
|
|
3286
|
+
.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)}
|
|
3287
|
+
.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
3288
|
/* stack: every file down one scroller, each header sticking until the next pushes it off. It's
|
|
3285
3289
|
flush (square, no card around its file), opaque in the code's own colour, with a hairline
|
|
3286
3290
|
under it only while it's stuck; a gap, not a line, parts one file from the next. */
|
|
@@ -3355,7 +3359,7 @@ var init_diff_css = __esm(() => {
|
|
|
3355
3359
|
.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
3360
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
3357
3361
|
.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:pointer;transition:background .12s,color .12s,opacity .12s}
|
|
3358
|
-
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3362
|
+
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3359
3363
|
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
3360
3364
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
3361
3365
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
@@ -9769,7 +9773,8 @@ function fuseFrame(L, before, after, k, wipe) {
|
|
|
9769
9773
|
const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
|
|
9770
9774
|
const overlap = before.x + before.w - after.x;
|
|
9771
9775
|
const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
|
|
9772
|
-
|
|
9776
|
+
const blur = t > 0.005 && t < 0.995 ? FUSE.blur * Math.sin(Math.PI * t) : 0;
|
|
9777
|
+
return { cut, feather, seam, blur };
|
|
9773
9778
|
}
|
|
9774
9779
|
function clampZoom(r) {
|
|
9775
9780
|
const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
|
|
@@ -9856,7 +9861,10 @@ function stripPeaks(diffs, o = {}) {
|
|
|
9856
9861
|
});
|
|
9857
9862
|
return out;
|
|
9858
9863
|
}
|
|
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,
|
|
9864
|
+
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) => {
|
|
9865
|
+
const inner = r * Math.min(1, Math.max(0, k));
|
|
9866
|
+
return { before: { left: r, right: inner }, after: { left: inner, right: r } };
|
|
9867
|
+
}, labelFold = (fold, mark, blur = 3) => {
|
|
9860
9868
|
const f = Math.min(1, Math.max(0, fold));
|
|
9861
9869
|
const open = 1 - f;
|
|
9862
9870
|
return {
|
|
@@ -9887,7 +9895,7 @@ var init_compare_model = __esm(() => {
|
|
|
9887
9895
|
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
9888
9896
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
9889
9897
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
9890
|
-
FUSE = { feather:
|
|
9898
|
+
FUSE = { feather: 32, blur: 8, seamFade: 48 };
|
|
9891
9899
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
9892
9900
|
});
|
|
9893
9901
|
|
|
@@ -10620,8 +10628,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10620
10628
|
onLost();
|
|
10621
10629
|
};
|
|
10622
10630
|
lostListeners.add(lost);
|
|
10623
|
-
const uniforms = device.createBuffer({ size:
|
|
10624
|
-
const data = new Float32Array(
|
|
10631
|
+
const uniforms = device.createBuffer({ size: 192, usage: 64 | 8 });
|
|
10632
|
+
const data = new Float32Array(48);
|
|
10625
10633
|
const make = (w, h) => device.createTexture({
|
|
10626
10634
|
size: [Math.max(1, w), Math.max(1, h)],
|
|
10627
10635
|
format: "rgba8unorm",
|
|
@@ -10694,6 +10702,7 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10694
10702
|
data.set([f.preview[0] ? 1 : 0, f.preview[1] ? 1 : 0, 0, 0], 32);
|
|
10695
10703
|
r(36, f.beforeClip);
|
|
10696
10704
|
r(40, f.afterClip);
|
|
10705
|
+
data.set(f.radii, 44);
|
|
10697
10706
|
device.queue.writeBuffer(uniforms, 0, data);
|
|
10698
10707
|
const encoder = device.createCommandEncoder();
|
|
10699
10708
|
const pass = encoder.beginRenderPass({
|
|
@@ -10735,6 +10744,7 @@ struct U {
|
|
|
10735
10744
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
10736
10745
|
src: vec4f, // Before, After from their previews
|
|
10737
10746
|
ca: vec4f, cb: vec4f, // where each side may draw
|
|
10747
|
+
radii: vec4f, // the clips' corners: Before left, right; After left, right
|
|
10738
10748
|
}
|
|
10739
10749
|
@group(0) @binding(0) var<uniform> u: U;
|
|
10740
10750
|
@group(0) @binding(1) var A: texture_2d<f32>;
|
|
@@ -10756,8 +10766,18 @@ fn cover(p: vec2f, r: vec4f) -> f32 {
|
|
|
10756
10766
|
return clamp(d + 0.5, 0.0, 1.0);
|
|
10757
10767
|
}
|
|
10758
10768
|
|
|
10759
|
-
//
|
|
10760
|
-
fn
|
|
10769
|
+
// Coverage of rect r at p with its left corners rounded rl and its right ones rr.
|
|
10770
|
+
fn coverRound(p: vec2f, r: vec4f, rl: f32, rr: f32) -> f32 {
|
|
10771
|
+
let h = r.zw * 0.5;
|
|
10772
|
+
let q0 = p - (r.xy + h);
|
|
10773
|
+
let rad = min(select(rl, rr, q0.x > 0.0), min(h.x, h.y));
|
|
10774
|
+
let q = abs(q0) - h + vec2f(rad);
|
|
10775
|
+
let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rad;
|
|
10776
|
+
return clamp(0.5 - d, 0.0, 1.0);
|
|
10777
|
+
}
|
|
10778
|
+
|
|
10779
|
+
// A side's colour where rect r maps it to p, and its coverage inside clip c (corners rc) there.
|
|
10780
|
+
fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, rc: vec2f, near: bool, ready: f32) -> vec4f {
|
|
10761
10781
|
let uv = clamp((p - r.xy) / max(r.zw, vec2f(1e-3)), vec2f(0.0), vec2f(1.0));
|
|
10762
10782
|
var c: vec3f;
|
|
10763
10783
|
if (near) {
|
|
@@ -10766,16 +10786,16 @@ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f3
|
|
|
10766
10786
|
} else {
|
|
10767
10787
|
c = textureSampleLevel(t, smp, uv, 0.0).rgb;
|
|
10768
10788
|
}
|
|
10769
|
-
return vec4f(c, min(cover(p, r),
|
|
10789
|
+
return vec4f(c, min(cover(p, r), coverRound(p, c0, rc.x, rc.y)) * ready);
|
|
10770
10790
|
}
|
|
10771
10791
|
|
|
10772
10792
|
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);
|
|
10793
|
+
if (u.src.x > 0.5) { return take(PA, p, r, c0, u.radii.xy, near, 1.0); }
|
|
10794
|
+
return take(A, p, r, c0, u.radii.xy, near, u.diff.z);
|
|
10775
10795
|
}
|
|
10776
10796
|
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);
|
|
10797
|
+
if (u.src.y > 0.5) { return take(PB, p, r, c0, u.radii.zw, near, 1.0); }
|
|
10798
|
+
return take(B, p, r, c0, u.radii.zw, near, u.diff.w);
|
|
10779
10799
|
}
|
|
10780
10800
|
|
|
10781
10801
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
@@ -11391,6 +11411,7 @@ function compare(container, options) {
|
|
|
11391
11411
|
let stageRect = null;
|
|
11392
11412
|
let accent = [0.49, 0.81, 0.61];
|
|
11393
11413
|
let labelW = [0, 0];
|
|
11414
|
+
let frameRadius = 0;
|
|
11394
11415
|
let dirty = true;
|
|
11395
11416
|
let disposed = false;
|
|
11396
11417
|
let near = options.lazy === false;
|
|
@@ -11911,6 +11932,7 @@ function compare(container, options) {
|
|
|
11911
11932
|
const wr = wipeRect(r.L.overlay);
|
|
11912
11933
|
const k = Math.min(1, Math.max(0, lay.x));
|
|
11913
11934
|
const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
|
|
11935
|
+
const corners = fuseCorners(frameRadius, k);
|
|
11914
11936
|
const dw = differenceWeight();
|
|
11915
11937
|
const fusing = 1 - dw;
|
|
11916
11938
|
folded = k * fusing;
|
|
@@ -11967,6 +11989,7 @@ function compare(container, options) {
|
|
|
11967
11989
|
if (renderer === "dom") {
|
|
11968
11990
|
const cut = mode === "difference" ? null : fu;
|
|
11969
11991
|
const place = (n, rect, clip, before) => {
|
|
11992
|
+
const round = before ? corners.before : corners.after;
|
|
11970
11993
|
const st = n.style;
|
|
11971
11994
|
st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
|
|
11972
11995
|
st.width = `${rect.w.toFixed(2)}px`;
|
|
@@ -11976,7 +11999,9 @@ function compare(container, options) {
|
|
|
11976
11999
|
const l = Math.max(0, clip.x - rect.x);
|
|
11977
12000
|
const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
|
|
11978
12001
|
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
|
|
11979
|
-
|
|
12002
|
+
const rr = edge < clip.x + clip.w - 0.5 ? 0 : round.right;
|
|
12003
|
+
const rl = round.left;
|
|
12004
|
+
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
12005
|
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
12006
|
st.maskImage = mask;
|
|
11982
12007
|
st.setProperty("-webkit-mask-image", mask);
|
|
@@ -12014,7 +12039,13 @@ function compare(container, options) {
|
|
|
12014
12039
|
gain: 1,
|
|
12015
12040
|
accent,
|
|
12016
12041
|
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)]
|
|
12042
|
+
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)],
|
|
12043
|
+
radii: [
|
|
12044
|
+
corners.before.left * dpr,
|
|
12045
|
+
corners.before.right * dpr,
|
|
12046
|
+
corners.after.left * dpr,
|
|
12047
|
+
corners.after.right * dpr
|
|
12048
|
+
]
|
|
12018
12049
|
});
|
|
12019
12050
|
};
|
|
12020
12051
|
const drawNow = () => {
|
|
@@ -12106,6 +12137,7 @@ function compare(container, options) {
|
|
|
12106
12137
|
accent = [c[0], c[1], c[2]];
|
|
12107
12138
|
const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
|
|
12108
12139
|
labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
|
|
12140
|
+
frameRadius = Number.parseFloat(win.getComputedStyle(stage).borderTopLeftRadius) || 0;
|
|
12109
12141
|
requestDraw();
|
|
12110
12142
|
};
|
|
12111
12143
|
const measure = () => {
|
|
@@ -17198,11 +17230,629 @@ function toast(message, options = {}) {
|
|
|
17198
17230
|
};
|
|
17199
17231
|
}
|
|
17200
17232
|
|
|
17233
|
+
// src/ui/walkthrough-annotations.ts
|
|
17234
|
+
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";
|
|
17235
|
+
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";
|
|
17236
|
+
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>`;
|
|
17237
|
+
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>';
|
|
17238
|
+
var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
|
|
17239
|
+
var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
|
|
17240
|
+
function placeMark(a, rect, view = ALL) {
|
|
17241
|
+
const w = a.w ?? 0;
|
|
17242
|
+
const h = a.h ?? 0;
|
|
17243
|
+
const x = (a.x - view.u) / view.w;
|
|
17244
|
+
const y = (a.y - view.v) / view.h;
|
|
17245
|
+
const ww = w / view.w;
|
|
17246
|
+
const hh = h / view.h;
|
|
17247
|
+
if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
|
|
17248
|
+
return null;
|
|
17249
|
+
return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
|
|
17250
|
+
}
|
|
17251
|
+
var ALL = { u: 0, v: 0, w: 1, h: 1 };
|
|
17252
|
+
var scopeOf = (el) => el.closest(".knobs-walkthrough") ?? el.closest(".knobs-wt-slot") ?? el;
|
|
17253
|
+
var annotationsOn = (scope) => scope.dataset.annotations !== "off";
|
|
17254
|
+
function setAnnotations(scope, on) {
|
|
17255
|
+
if (on)
|
|
17256
|
+
delete scope.dataset.annotations;
|
|
17257
|
+
else
|
|
17258
|
+
scope.dataset.annotations = "off";
|
|
17259
|
+
for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
|
|
17260
|
+
syncToggle(b, on);
|
|
17261
|
+
}
|
|
17262
|
+
function syncToggle(b, on) {
|
|
17263
|
+
b.setAttribute("aria-pressed", String(on));
|
|
17264
|
+
b.title = on ? "Hide annotations (a)" : "Show annotations (a)";
|
|
17265
|
+
b.innerHTML = glyph(on ? EYE : EYE_OFF);
|
|
17266
|
+
}
|
|
17267
|
+
function annotationLayer(host, list, place, corner = host) {
|
|
17268
|
+
const doc = host.ownerDocument;
|
|
17269
|
+
const layer = doc.createElement("div");
|
|
17270
|
+
layer.className = "knobs-wt-anno";
|
|
17271
|
+
layer.setAttribute("aria-hidden", "true");
|
|
17272
|
+
host.append(layer);
|
|
17273
|
+
const toggle = doc.createElement("button");
|
|
17274
|
+
toggle.type = "button";
|
|
17275
|
+
toggle.className = "knobs-wt-anno-toggle";
|
|
17276
|
+
toggle.setAttribute("aria-label", "Annotations");
|
|
17277
|
+
syncToggle(toggle, annotationsOn(scopeOf(host)));
|
|
17278
|
+
toggle.addEventListener("click", (e) => {
|
|
17279
|
+
e.stopPropagation();
|
|
17280
|
+
const scope = scopeOf(host);
|
|
17281
|
+
setAnnotations(scope, !annotationsOn(scope));
|
|
17282
|
+
});
|
|
17283
|
+
corner.append(toggle);
|
|
17284
|
+
let marks = [];
|
|
17285
|
+
const make = (a) => {
|
|
17286
|
+
const m = doc.createElement("div");
|
|
17287
|
+
const shape = shapeOf(a);
|
|
17288
|
+
m.className = "knobs-wt-mark";
|
|
17289
|
+
m.dataset.shape = shape;
|
|
17290
|
+
m.dataset.hidden = "";
|
|
17291
|
+
if (shape === "arrow")
|
|
17292
|
+
m.innerHTML = ARROW;
|
|
17293
|
+
if (a.label) {
|
|
17294
|
+
const l = doc.createElement("span");
|
|
17295
|
+
l.className = "knobs-wt-mark-label";
|
|
17296
|
+
l.textContent = a.label;
|
|
17297
|
+
m.append(l);
|
|
17298
|
+
}
|
|
17299
|
+
layer.append(m);
|
|
17300
|
+
return m;
|
|
17301
|
+
};
|
|
17302
|
+
let settled = false;
|
|
17303
|
+
const update = () => {
|
|
17304
|
+
const p = place();
|
|
17305
|
+
if (marks.length !== p.rects.length) {
|
|
17306
|
+
for (const row of marks)
|
|
17307
|
+
for (const m of row)
|
|
17308
|
+
m.remove();
|
|
17309
|
+
marks = p.rects.map(() => list.map(make));
|
|
17310
|
+
}
|
|
17311
|
+
p.rects.forEach((rect, i) => {
|
|
17312
|
+
list.forEach((a, j) => {
|
|
17313
|
+
const m = marks[i][j];
|
|
17314
|
+
const box = placeMark(a, rect, p.view);
|
|
17315
|
+
const show = box !== null && showsAt(a, p.time);
|
|
17316
|
+
if (box) {
|
|
17317
|
+
m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
|
|
17318
|
+
m.style.width = `${box.w.toFixed(2)}px`;
|
|
17319
|
+
m.style.height = `${box.h.toFixed(2)}px`;
|
|
17320
|
+
}
|
|
17321
|
+
m.toggleAttribute("data-hidden", !show);
|
|
17322
|
+
});
|
|
17323
|
+
});
|
|
17324
|
+
if (!settled) {
|
|
17325
|
+
settled = true;
|
|
17326
|
+
requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
|
|
17327
|
+
}
|
|
17328
|
+
};
|
|
17329
|
+
const ro = new ResizeObserver(() => update());
|
|
17330
|
+
ro.observe(host);
|
|
17331
|
+
update();
|
|
17332
|
+
return {
|
|
17333
|
+
element: layer,
|
|
17334
|
+
update,
|
|
17335
|
+
dispose() {
|
|
17336
|
+
ro.disconnect();
|
|
17337
|
+
layer.remove();
|
|
17338
|
+
toggle.remove();
|
|
17339
|
+
}
|
|
17340
|
+
};
|
|
17341
|
+
}
|
|
17342
|
+
|
|
17201
17343
|
// src/ui/walkthrough-blocks.ts
|
|
17202
17344
|
init_motion();
|
|
17203
|
-
|
|
17345
|
+
|
|
17346
|
+
// src/ui/history.ts
|
|
17347
|
+
init_motion();
|
|
17348
|
+
init_glide();
|
|
17349
|
+
|
|
17350
|
+
// src/ui/history-css.ts
|
|
17351
|
+
var historyCss = `
|
|
17352
|
+
:where(.knobs-history,.knobs-history *){box-sizing:border-box}
|
|
17353
|
+
.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);
|
|
17354
|
+
--hy-fade:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);
|
|
17355
|
+
position:relative;display:flex;flex-direction:column;width:100%;min-width:0;outline:none;color:var(--_k-fg);
|
|
17356
|
+
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
|
|
17357
|
+
text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;user-select:none;-webkit-user-select:none}
|
|
17358
|
+
.knobs-history[data-bands]{--hy-bandrow:var(--_k-leading-small)}
|
|
17359
|
+
.knobs-history[data-narrow]{--hy-gutter:0px}
|
|
17360
|
+
.knobs-history [hidden]{display:none!important}
|
|
17361
|
+
: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;
|
|
17362
|
+
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
17363
|
+
.knobs-history [data-tone="good"]{--hy-c:var(--_k-ok)}
|
|
17364
|
+
.knobs-history [data-tone="warn"]{--hy-c:var(--_k-warn)}
|
|
17365
|
+
.knobs-history [data-tone="bad"]{--hy-c:var(--_k-danger)}
|
|
17366
|
+
.knobs-history [data-tone="info"]{--hy-c:var(--_k-info)}
|
|
17367
|
+
.knobs-history [data-tone="dim"]{--hy-c:var(--_k-fg-3)}
|
|
17368
|
+
|
|
17369
|
+
/* ——— the chart: lane labels beside the plot (above each lane where it's narrow) ——— */
|
|
17370
|
+
.knobs-hy-chart{position:relative;display:grid;grid-template-columns:var(--hy-gutter) minmax(0,1fr)}
|
|
17371
|
+
.knobs-hy-axis{position:relative;grid-column:2;grid-row:1;height:calc(var(--hy-bandrow) + var(--hy-axis))}
|
|
17372
|
+
: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)}
|
|
17373
|
+
.knobs-hy-tick{bottom:2px;padding-left:4px}
|
|
17374
|
+
.knobs-hy-now{bottom:2px;color:var(--_k-accent-fg);font-weight:var(--_k-weight-label)}
|
|
17375
|
+
.knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
|
|
17376
|
+
.knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
|
|
17377
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17378
|
+
.knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
|
|
17379
|
+
:is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
|
|
17380
|
+
.knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
|
|
17381
|
+
.knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
|
|
17382
|
+
|
|
17383
|
+
/* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
|
|
17384
|
+
.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)}
|
|
17385
|
+
: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}
|
|
17386
|
+
.knobs-hy-grid{background:var(--_k-border-subtle)}
|
|
17387
|
+
.knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
|
|
17388
|
+
.knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17389
|
+
.knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17390
|
+
.knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
|
|
17391
|
+
.knobs-hy-sel[data-on]{opacity:.6}
|
|
17392
|
+
|
|
17393
|
+
/* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
|
|
17394
|
+
.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;
|
|
17395
|
+
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)}
|
|
17396
|
+
.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)}
|
|
17397
|
+
.knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17398
|
+
.knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
|
|
17399
|
+
.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)}
|
|
17400
|
+
.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)}
|
|
17401
|
+
.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;
|
|
17402
|
+
color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
|
|
17403
|
+
.knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
|
|
17404
|
+
.knobs-hy-tag[data-place="none"]{visibility:hidden}
|
|
17405
|
+
|
|
17406
|
+
/* 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
|
|
17407
|
+
channel's switch, a triangle for a finding; inferred ones hollow. */
|
|
17408
|
+
.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}
|
|
17409
|
+
.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);
|
|
17410
|
+
border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17411
|
+
.knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
|
|
17412
|
+
.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)}
|
|
17413
|
+
.knobs-hy-ev[data-type="release"]{--hy-s:8px}
|
|
17414
|
+
.knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
|
|
17415
|
+
.knobs-hy-ev[data-type="channel"]{--hy-s:8px}
|
|
17416
|
+
.knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
|
|
17417
|
+
.knobs-hy-ev[data-type="audit"]{--hy-s:10px}
|
|
17418
|
+
.knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
|
|
17419
|
+
.knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
|
|
17420
|
+
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}
|
|
17421
|
+
.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)}
|
|
17422
|
+
.knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
|
|
17423
|
+
/* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
|
|
17424
|
+
"Show commits" or while selected. */
|
|
17425
|
+
.knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17426
|
+
.knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
17427
|
+
:is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
|
|
17428
|
+
.knobs-hy-ev[data-hover]::before{scale:1.25}
|
|
17429
|
+
.knobs-hy-ev[data-selected]::before{scale:1.3}
|
|
17430
|
+
.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);
|
|
17431
|
+
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)}
|
|
17432
|
+
.knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17433
|
+
.knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
17434
|
+
.knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
17435
|
+
|
|
17436
|
+
/* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
|
|
17437
|
+
.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);
|
|
17438
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17439
|
+
.knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
|
|
17440
|
+
.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;
|
|
17441
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17442
|
+
.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)}
|
|
17443
|
+
.knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
17444
|
+
.knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
|
|
17445
|
+
.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)}
|
|
17446
|
+
|
|
17447
|
+
/* ——— the detail: one area under the chart, its height on a spring ——— */
|
|
17448
|
+
.knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
17449
|
+
.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)}
|
|
17450
|
+
.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)}
|
|
17451
|
+
.knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
|
|
17452
|
+
.knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
|
|
17453
|
+
.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)}
|
|
17454
|
+
.knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
|
|
17455
|
+
.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}
|
|
17456
|
+
.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}
|
|
17457
|
+
.knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
|
|
17458
|
+
.knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
|
|
17459
|
+
.knobs-hy-dfoot:empty{display:none}
|
|
17460
|
+
.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);
|
|
17461
|
+
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)}
|
|
17462
|
+
a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17463
|
+
.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;
|
|
17464
|
+
font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17465
|
+
.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)}
|
|
17466
|
+
.knobs-hy-jump svg{width:12px;height:12px}
|
|
17467
|
+
|
|
17468
|
+
/* ——— the pointer's tooltip: title, date, one line ——— */
|
|
17469
|
+
.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);
|
|
17470
|
+
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)}
|
|
17471
|
+
.knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17472
|
+
.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}
|
|
17473
|
+
:is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17474
|
+
.knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
|
|
17475
|
+
.knobs-hy-tl:empty{display:none}
|
|
17476
|
+
|
|
17477
|
+
@media(prefers-reduced-motion:reduce){
|
|
17478
|
+
.knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
|
|
17479
|
+
.knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
|
|
17480
|
+
}
|
|
17481
|
+
`;
|
|
17482
|
+
|
|
17483
|
+
// src/ui/history-model.ts
|
|
17484
|
+
var MS_DAY = 86400000;
|
|
17485
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
17486
|
+
var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
|
|
17487
|
+
var parts = (day) => {
|
|
17488
|
+
const t = new Date(day * MS_DAY);
|
|
17489
|
+
return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
|
|
17490
|
+
};
|
|
17491
|
+
var todayDay = (now = Date.now()) => {
|
|
17492
|
+
const t = new Date(now);
|
|
17493
|
+
return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
|
|
17494
|
+
};
|
|
17495
|
+
var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
|
|
17496
|
+
function parseDay(text, year) {
|
|
17497
|
+
const s = text.trim();
|
|
17498
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
17499
|
+
if (iso) {
|
|
17500
|
+
const y = Number(iso[1]);
|
|
17501
|
+
const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
|
|
17502
|
+
return { from: day, to: day, year: y };
|
|
17503
|
+
}
|
|
17504
|
+
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);
|
|
17505
|
+
if (!m)
|
|
17506
|
+
return null;
|
|
17507
|
+
const m1 = monthIndex(m[1]);
|
|
17508
|
+
const m2 = m[3] ? monthIndex(m[3]) : m1;
|
|
17509
|
+
if (m1 < 0 || m2 < 0)
|
|
17510
|
+
return null;
|
|
17511
|
+
const written = m[5] ? Number(m[5]) : undefined;
|
|
17512
|
+
const y = written ?? year;
|
|
17513
|
+
if (y === undefined)
|
|
17514
|
+
return null;
|
|
17515
|
+
const from = dayOf(y, m1, Number(m[2]));
|
|
17516
|
+
let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
|
|
17517
|
+
if (to < from)
|
|
17518
|
+
to = dayOf(y + 1, m2, Number(m[4]));
|
|
17519
|
+
return { from, to, ...written !== undefined ? { year: written } : {} };
|
|
17520
|
+
}
|
|
17521
|
+
function formatDay(day) {
|
|
17522
|
+
const { y, m, d } = parts(day);
|
|
17523
|
+
return `${MONTHS[m]} ${d}, ${y}`;
|
|
17524
|
+
}
|
|
17525
|
+
function formatRange(from, to, open = false) {
|
|
17526
|
+
if (open)
|
|
17527
|
+
return `${formatDay(from)} – today`;
|
|
17528
|
+
if (to <= from)
|
|
17529
|
+
return formatDay(from);
|
|
17530
|
+
const a = parts(from);
|
|
17531
|
+
const b = parts(to);
|
|
17532
|
+
if (a.y === b.y && a.m === b.m)
|
|
17533
|
+
return `${MONTHS[a.m]} ${a.d}–${b.d}, ${a.y}`;
|
|
17534
|
+
if (a.y === b.y)
|
|
17535
|
+
return `${MONTHS[a.m]} ${a.d} – ${MONTHS[b.m]} ${b.d}, ${a.y}`;
|
|
17536
|
+
return `${formatDay(from)} – ${formatDay(to)}`;
|
|
17537
|
+
}
|
|
17538
|
+
var formatDays = (n) => `${n} ${n === 1 ? "day" : "days"}`;
|
|
17539
|
+
function guessTone(state) {
|
|
17540
|
+
const s = state.toLowerCase();
|
|
17541
|
+
if (/broken|bad|fail|missing|deleted|down/.test(s))
|
|
17542
|
+
return "bad";
|
|
17543
|
+
if (/warn|redirect|partial|beta|next|pre/.test(s))
|
|
17544
|
+
return "warn";
|
|
17545
|
+
if (/work|ok|good|fixed|live|green/.test(s))
|
|
17546
|
+
return "good";
|
|
17547
|
+
return "dim";
|
|
17548
|
+
}
|
|
17549
|
+
var bandLabel = (heading) => heading.replace(/^\s*\d+\s*[·.:)\-–]\s*/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
17550
|
+
function fromGroups(groups, today) {
|
|
17551
|
+
const events = [];
|
|
17552
|
+
const bands = [];
|
|
17553
|
+
const all = groups.flatMap((g) => g.events ?? g.rows ?? []);
|
|
17554
|
+
let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
|
|
17555
|
+
for (const g of groups) {
|
|
17556
|
+
let lo = Infinity;
|
|
17557
|
+
let hi = -Infinity;
|
|
17558
|
+
for (const e of g.events ?? g.rows ?? []) {
|
|
17559
|
+
const d = parseDay(e.date, year);
|
|
17560
|
+
if (!d)
|
|
17561
|
+
continue;
|
|
17562
|
+
if (d.year !== undefined)
|
|
17563
|
+
year = d.year;
|
|
17564
|
+
lo = Math.min(lo, d.from);
|
|
17565
|
+
hi = Math.max(hi, d.to);
|
|
17566
|
+
events.push({
|
|
17567
|
+
...e,
|
|
17568
|
+
date: iso(d.from),
|
|
17569
|
+
...d.to > d.from ? { to: iso(d.to) } : {},
|
|
17570
|
+
lane: e.lane ?? "code"
|
|
17571
|
+
});
|
|
17572
|
+
}
|
|
17573
|
+
const label = g.heading ? bandLabel(g.heading) : "";
|
|
17574
|
+
if (label && lo <= hi)
|
|
17575
|
+
bands.push({ from: iso(lo), to: iso(hi), label });
|
|
17576
|
+
}
|
|
17577
|
+
return { events, bands };
|
|
17578
|
+
}
|
|
17579
|
+
function iso(day) {
|
|
17580
|
+
const { y, m, d } = parts(day);
|
|
17581
|
+
return `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
17582
|
+
}
|
|
17583
|
+
var startOfMonth = (day) => {
|
|
17584
|
+
const { y, m } = parts(day);
|
|
17585
|
+
return dayOf(y, m, 1);
|
|
17586
|
+
};
|
|
17587
|
+
var TYPE_TONE = { deletion: "bad" };
|
|
17588
|
+
function normalizeHistory(data, now = todayDay()) {
|
|
17589
|
+
const today = data.axis?.today && parseDay(data.axis.today, parts(now).y)?.from || now;
|
|
17590
|
+
const converted = data.groups?.length ? fromGroups(data.groups, today) : { events: [], bands: [] };
|
|
17591
|
+
const rawEvents = [...data.events ?? [], ...converted.events];
|
|
17592
|
+
const rawBands = [...data.bands ?? [], ...converted.bands];
|
|
17593
|
+
const year = parts(today).y;
|
|
17594
|
+
const day = (s) => s ? parseDay(s, year) ?? null : null;
|
|
17595
|
+
const states = new Map;
|
|
17596
|
+
const stateOf = (key) => {
|
|
17597
|
+
const known = states.get(key);
|
|
17598
|
+
if (known)
|
|
17599
|
+
return known;
|
|
17600
|
+
const def = data.states?.[key];
|
|
17601
|
+
const s = { key, label: def?.label ?? key, tone: def?.tone ?? guessTone(key) };
|
|
17602
|
+
states.set(key, s);
|
|
17603
|
+
return s;
|
|
17604
|
+
};
|
|
17605
|
+
const items = new Map;
|
|
17606
|
+
const laneOrder = [];
|
|
17607
|
+
const lanes = new Map;
|
|
17608
|
+
const lane = (id, label = id === "code" ? "Code" : id) => {
|
|
17609
|
+
let l = lanes.get(id);
|
|
17610
|
+
if (!l) {
|
|
17611
|
+
l = { id, label, spans: [], events: [] };
|
|
17612
|
+
lanes.set(id, l);
|
|
17613
|
+
laneOrder.push(id);
|
|
17614
|
+
}
|
|
17615
|
+
return l;
|
|
17616
|
+
};
|
|
17617
|
+
if (rawEvents.some((e) => !e.lane || e.lane === "code") && !data.lanes?.some((l) => l.id === "code"))
|
|
17618
|
+
lane("code");
|
|
17619
|
+
let inferred = false;
|
|
17620
|
+
for (const l of data.lanes ?? []) {
|
|
17621
|
+
const into = lane(l.id, l.label);
|
|
17622
|
+
(l.spans ?? []).forEach((s, i) => {
|
|
17623
|
+
const a = day(s.from);
|
|
17624
|
+
if (!a)
|
|
17625
|
+
return;
|
|
17626
|
+
const open = !s.to;
|
|
17627
|
+
const b = open ? null : day(s.to);
|
|
17628
|
+
const to = open ? today : b?.to ?? a.to;
|
|
17629
|
+
const st = stateOf(s.state);
|
|
17630
|
+
const days = Math.max(0, to - a.from);
|
|
17631
|
+
const own = s.label ?? data.states?.[s.state]?.tag ?? "{d}d";
|
|
17632
|
+
const tags = own === "" ? [] : [...new Set([own, "{d}d"])].map((t) => t.replace(/\{d\}/g, String(days)));
|
|
17633
|
+
const id = s.id ?? `${l.id}/${i}`;
|
|
17634
|
+
const item = {
|
|
17635
|
+
id,
|
|
17636
|
+
kind: "span",
|
|
17637
|
+
lane: l.id,
|
|
17638
|
+
from: a.from,
|
|
17639
|
+
to,
|
|
17640
|
+
open,
|
|
17641
|
+
title: s.title ?? `${l.label} · ${st.label}`,
|
|
17642
|
+
...s.detail ? { detail: s.detail } : {},
|
|
17643
|
+
tone: st.tone,
|
|
17644
|
+
inferred: s.provenance === "inferred",
|
|
17645
|
+
...s.provenance ? { provenance: s.provenance } : {},
|
|
17646
|
+
...s.source ? { source: s.source } : {},
|
|
17647
|
+
evidence: s.evidence ?? [],
|
|
17648
|
+
...s.step ? { step: s.step } : {},
|
|
17649
|
+
state: s.state,
|
|
17650
|
+
tags
|
|
17651
|
+
};
|
|
17652
|
+
if (item.inferred)
|
|
17653
|
+
inferred = true;
|
|
17654
|
+
into.spans.push(item);
|
|
17655
|
+
items.set(id, item);
|
|
17656
|
+
});
|
|
17657
|
+
}
|
|
17658
|
+
const types = [];
|
|
17659
|
+
const events = [];
|
|
17660
|
+
rawEvents.forEach((e, index) => {
|
|
17661
|
+
const a = day(e.date);
|
|
17662
|
+
if (!a)
|
|
17663
|
+
return;
|
|
17664
|
+
const to = Math.max(a.to, day(e.to)?.to ?? a.to);
|
|
17665
|
+
const into = lane(e.lane ?? "code");
|
|
17666
|
+
const id = e.id ?? `e${index}`;
|
|
17667
|
+
const item = {
|
|
17668
|
+
id,
|
|
17669
|
+
kind: "event",
|
|
17670
|
+
lane: into.id,
|
|
17671
|
+
from: a.from,
|
|
17672
|
+
to,
|
|
17673
|
+
open: false,
|
|
17674
|
+
title: e.title,
|
|
17675
|
+
...e.detail ? { detail: e.detail } : {},
|
|
17676
|
+
tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
|
|
17677
|
+
...e.type ? { type: e.type } : {},
|
|
17678
|
+
inferred: e.provenance === "inferred",
|
|
17679
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
17680
|
+
...e.source ? { source: e.source } : {},
|
|
17681
|
+
evidence: e.evidence ?? [],
|
|
17682
|
+
...e.step ? { step: e.step } : {},
|
|
17683
|
+
tags: []
|
|
17684
|
+
};
|
|
17685
|
+
if (item.inferred)
|
|
17686
|
+
inferred = true;
|
|
17687
|
+
if (e.type && !types.includes(e.type))
|
|
17688
|
+
types.push(e.type);
|
|
17689
|
+
into.events.push(item);
|
|
17690
|
+
items.set(id, item);
|
|
17691
|
+
events.push({ item, index });
|
|
17692
|
+
});
|
|
17693
|
+
const bands = [];
|
|
17694
|
+
for (const b of rawBands) {
|
|
17695
|
+
const a = day(b.from);
|
|
17696
|
+
if (!a)
|
|
17697
|
+
continue;
|
|
17698
|
+
const z = b.to ? day(b.to) : null;
|
|
17699
|
+
bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
|
|
17700
|
+
}
|
|
17701
|
+
bands.sort((x, y) => x.from - y.from);
|
|
17702
|
+
const all = [...items.values()].flatMap((i) => [i.from, i.to]);
|
|
17703
|
+
for (const b of bands)
|
|
17704
|
+
all.push(b.from, b.to);
|
|
17705
|
+
const hasOpen = [...items.values()].some((i) => i.open);
|
|
17706
|
+
const lo = all.length ? Math.min(...all) : today - 90;
|
|
17707
|
+
let hi = all.length ? Math.max(...all) : today;
|
|
17708
|
+
if (hasOpen || data.axis?.today)
|
|
17709
|
+
hi = Math.max(hi, today);
|
|
17710
|
+
const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
|
|
17711
|
+
const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
|
|
17712
|
+
const laneIndex = (id) => laneOrder.indexOf(id);
|
|
17713
|
+
const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
|
|
17714
|
+
for (const l of lanes.values())
|
|
17715
|
+
l.events.sort((a, b) => a.from - b.from);
|
|
17716
|
+
return {
|
|
17717
|
+
from,
|
|
17718
|
+
to: Math.max(to, from + 1),
|
|
17719
|
+
today,
|
|
17720
|
+
lanes: laneOrder.map((id) => lanes.get(id)),
|
|
17721
|
+
events: order,
|
|
17722
|
+
bands,
|
|
17723
|
+
states: [...states.values()],
|
|
17724
|
+
types,
|
|
17725
|
+
inferred,
|
|
17726
|
+
verified: [...items.values()].some((i) => i.provenance === "verified"),
|
|
17727
|
+
items
|
|
17728
|
+
};
|
|
17729
|
+
}
|
|
17730
|
+
var DEFAULT_MARKS = ["release", "deletion"];
|
|
17731
|
+
function quietEvents(events, marks) {
|
|
17732
|
+
if (marks === "all")
|
|
17733
|
+
return new Set;
|
|
17734
|
+
const quiet = events.filter((e) => e.type && !marks.includes(e.type));
|
|
17735
|
+
return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
|
|
17736
|
+
}
|
|
17737
|
+
var PLURALS = {
|
|
17738
|
+
commit: "commits",
|
|
17739
|
+
release: "releases",
|
|
17740
|
+
deletion: "deletions",
|
|
17741
|
+
channel: "channel switches",
|
|
17742
|
+
audit: "findings"
|
|
17743
|
+
};
|
|
17744
|
+
function quietLabel(events, quiet) {
|
|
17745
|
+
const types = [];
|
|
17746
|
+
for (const e of events)
|
|
17747
|
+
if (quiet.has(e.id) && e.type && !types.includes(e.type))
|
|
17748
|
+
types.push(e.type);
|
|
17749
|
+
const names = types.map((t) => PLURALS[t] ?? `${t}s`);
|
|
17750
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
|
|
17751
|
+
}
|
|
17752
|
+
function eventOrder(events) {
|
|
17753
|
+
return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
|
|
17754
|
+
}
|
|
17755
|
+
function stepEvent(order, current, dir, at) {
|
|
17756
|
+
if (!order.length)
|
|
17757
|
+
return null;
|
|
17758
|
+
const i = current === null ? -1 : order.findIndex((e) => e.id === current);
|
|
17759
|
+
if (i < 0 && at !== undefined) {
|
|
17760
|
+
const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
|
|
17761
|
+
return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
|
|
17762
|
+
}
|
|
17763
|
+
if (i < 0)
|
|
17764
|
+
return dir > 0 ? order[0] : order[order.length - 1];
|
|
17765
|
+
return order[Math.max(0, Math.min(order.length - 1, i + dir))];
|
|
17766
|
+
}
|
|
17767
|
+
function dayScale(from, to, width) {
|
|
17768
|
+
const span = Math.max(1, to - from);
|
|
17769
|
+
return {
|
|
17770
|
+
x: (day) => (day - from) / span * width,
|
|
17771
|
+
day: (x) => from + x / Math.max(1, width) * span,
|
|
17772
|
+
f: (day) => (day - from) / span
|
|
17773
|
+
};
|
|
17774
|
+
}
|
|
17775
|
+
var STEPS = [1, 2, 3, 6, 12];
|
|
17776
|
+
function monthTicks(from, to, width, minGap = 44) {
|
|
17777
|
+
const perMonth = width / Math.max(1, to - from) * 30.44;
|
|
17778
|
+
const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
|
|
17779
|
+
const ticks = [];
|
|
17780
|
+
const { y } = parts(from);
|
|
17781
|
+
let { m } = parts(from);
|
|
17782
|
+
let d = dayOf(y, m, 1);
|
|
17783
|
+
if (d < from) {
|
|
17784
|
+
m++;
|
|
17785
|
+
d = dayOf(y, m, 1);
|
|
17786
|
+
}
|
|
17787
|
+
while (d <= to) {
|
|
17788
|
+
const p = parts(d);
|
|
17789
|
+
if (p.m % step === 0)
|
|
17790
|
+
ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
|
|
17791
|
+
m++;
|
|
17792
|
+
d = dayOf(y, m, 1);
|
|
17793
|
+
}
|
|
17794
|
+
return ticks;
|
|
17795
|
+
}
|
|
17796
|
+
function fitLabels(labels, width, reserved = [], gap = 8) {
|
|
17797
|
+
let end = -Infinity;
|
|
17798
|
+
return labels.map(({ x, w }) => {
|
|
17799
|
+
const x1 = x + w;
|
|
17800
|
+
if (x < end + gap || x1 > width)
|
|
17801
|
+
return false;
|
|
17802
|
+
if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
|
|
17803
|
+
return false;
|
|
17804
|
+
end = x1;
|
|
17805
|
+
return true;
|
|
17806
|
+
});
|
|
17807
|
+
}
|
|
17808
|
+
function clipSpan(from, to, axisFrom, axisTo) {
|
|
17809
|
+
if (to < axisFrom || from > axisTo)
|
|
17810
|
+
return null;
|
|
17811
|
+
return {
|
|
17812
|
+
from: Math.max(from, axisFrom),
|
|
17813
|
+
to: Math.min(to, axisTo),
|
|
17814
|
+
clipStart: from < axisFrom,
|
|
17815
|
+
clipEnd: to > axisTo
|
|
17816
|
+
};
|
|
17817
|
+
}
|
|
17818
|
+
function fitTag(widths, spanWidth, roomAfter, pad = 6) {
|
|
17819
|
+
for (let i = 0;i < widths.length; i++)
|
|
17820
|
+
if (widths[i] + pad * 2 <= spanWidth)
|
|
17821
|
+
return { index: i, place: "inside" };
|
|
17822
|
+
for (let i = 0;i < widths.length; i++)
|
|
17823
|
+
if (widths[i] + pad <= roomAfter)
|
|
17824
|
+
return { index: i, place: "after" };
|
|
17825
|
+
return null;
|
|
17826
|
+
}
|
|
17827
|
+
function bandRoom(bands, x) {
|
|
17828
|
+
return bands.map((b, i) => {
|
|
17829
|
+
const next = bands.slice(i + 1).find((n) => n.from > b.from);
|
|
17830
|
+
const end = Math.min(b.to, next ? next.from : Infinity);
|
|
17831
|
+
return Math.max(0, x(end) - x(b.from));
|
|
17832
|
+
});
|
|
17833
|
+
}
|
|
17834
|
+
function nearestEvent(events, day, tolerance) {
|
|
17835
|
+
let best = null;
|
|
17836
|
+
let gap = tolerance;
|
|
17837
|
+
for (const e of events) {
|
|
17838
|
+
const d = day < e.from ? e.from - day : day > e.to ? day - e.to : 0;
|
|
17839
|
+
if (d <= gap) {
|
|
17840
|
+
best = e;
|
|
17841
|
+
gap = d;
|
|
17842
|
+
}
|
|
17843
|
+
}
|
|
17844
|
+
return best;
|
|
17845
|
+
}
|
|
17846
|
+
function firstSentence(text) {
|
|
17847
|
+
if (!text)
|
|
17848
|
+
return "";
|
|
17849
|
+
const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
|
|
17850
|
+
const m = /^.*?[.!?](?=\s|$)/.exec(plain);
|
|
17851
|
+
return (m ? m[0] : plain).trim();
|
|
17852
|
+
}
|
|
17853
|
+
|
|
17854
|
+
// src/ui/history.ts
|
|
17204
17855
|
init_look();
|
|
17205
|
-
init_selection_pill();
|
|
17206
17856
|
|
|
17207
17857
|
// src/ui/walkthrough-model.ts
|
|
17208
17858
|
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
@@ -17268,7 +17918,14 @@ function fillMedia(m, base) {
|
|
|
17268
17918
|
case "image":
|
|
17269
17919
|
return { ...m, src: resolveSrc(m.src, base) };
|
|
17270
17920
|
case "history":
|
|
17271
|
-
|
|
17921
|
+
if ("data" in m)
|
|
17922
|
+
return m;
|
|
17923
|
+
return {
|
|
17924
|
+
kind: "history",
|
|
17925
|
+
data: historyData(m),
|
|
17926
|
+
...m.label ? { label: m.label } : {},
|
|
17927
|
+
...m.marks ? { marks: m.marks } : {}
|
|
17928
|
+
};
|
|
17272
17929
|
case "stack":
|
|
17273
17930
|
return {
|
|
17274
17931
|
kind: "stack",
|
|
@@ -17281,17 +17938,26 @@ function fillMedia(m, base) {
|
|
|
17281
17938
|
return m;
|
|
17282
17939
|
}
|
|
17283
17940
|
}
|
|
17284
|
-
function
|
|
17941
|
+
function historyData(m) {
|
|
17942
|
+
const events = m.events ?? [];
|
|
17943
|
+
if (m.lanes || m.bands || m.states || m.axis)
|
|
17944
|
+
return {
|
|
17945
|
+
...m.axis ? { axis: m.axis } : {},
|
|
17946
|
+
...m.lanes ? { lanes: m.lanes } : {},
|
|
17947
|
+
...events.length ? { events } : {},
|
|
17948
|
+
...m.bands ? { bands: m.bands } : {},
|
|
17949
|
+
...m.states ? { states: m.states } : {}
|
|
17950
|
+
};
|
|
17285
17951
|
const groups = [];
|
|
17286
|
-
if (
|
|
17287
|
-
groups.push({ events
|
|
17952
|
+
if (events.length)
|
|
17953
|
+
groups.push({ events });
|
|
17288
17954
|
for (const g of m.groups ?? [])
|
|
17289
17955
|
groups.push({
|
|
17290
17956
|
...g.heading ? { heading: g.heading } : {},
|
|
17291
17957
|
...g.summary ? { summary: g.summary } : {},
|
|
17292
17958
|
events: g.events ?? g.rows ?? []
|
|
17293
17959
|
});
|
|
17294
|
-
return groups;
|
|
17960
|
+
return { groups };
|
|
17295
17961
|
}
|
|
17296
17962
|
function parseBranch(branch) {
|
|
17297
17963
|
if (!branch?.trim())
|
|
@@ -17910,11 +18576,18 @@ function inlineMarkdown(text) {
|
|
|
17910
18576
|
return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
|
|
17911
18577
|
}
|
|
17912
18578
|
|
|
17913
|
-
// src/ui/
|
|
17914
|
-
var
|
|
17915
|
-
|
|
17916
|
-
|
|
18579
|
+
// src/ui/history.ts
|
|
18580
|
+
var HEIGHT2 = springMotion(SPRINGS.default);
|
|
18581
|
+
var GO = svg('<path d="M5 5h6v6M11 5l-6 6"/>');
|
|
18582
|
+
var TYPE_NAMES = {
|
|
18583
|
+
commit: "Commit",
|
|
18584
|
+
release: "Release",
|
|
18585
|
+
deletion: "Deletion",
|
|
18586
|
+
channel: "Channel switch",
|
|
18587
|
+
audit: "Finding"
|
|
17917
18588
|
};
|
|
18589
|
+
var typeName = (t) => TYPE_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1);
|
|
18590
|
+
var REACH = 8;
|
|
17918
18591
|
var el = (doc, tag, cls) => {
|
|
17919
18592
|
const n = doc.createElement(tag);
|
|
17920
18593
|
if (cls)
|
|
@@ -17926,112 +18599,625 @@ var text = (doc, tag, cls, value) => {
|
|
|
17926
18599
|
n.textContent = value;
|
|
17927
18600
|
return n;
|
|
17928
18601
|
};
|
|
18602
|
+
function injectHistoryStyles(doc) {
|
|
18603
|
+
if (doc.querySelector("style[data-knobs-history]"))
|
|
18604
|
+
return;
|
|
18605
|
+
const style = doc.createElement("style");
|
|
18606
|
+
style.dataset.knobsHistory = "";
|
|
18607
|
+
style.textContent = aliased(historyCss);
|
|
18608
|
+
doc.head.append(style);
|
|
18609
|
+
}
|
|
18610
|
+
var when = (it) => it.kind === "span" ? `${formatRange(it.from, it.to, it.open)} · ${formatDays(it.to - it.from)}` : formatRange(it.from, it.to);
|
|
18611
|
+
function history(container, options) {
|
|
18612
|
+
const doc = container.ownerDocument;
|
|
18613
|
+
const win = doc.defaultView;
|
|
18614
|
+
const root = el(doc, "div", "knobs-history");
|
|
18615
|
+
applyLook2(root, options);
|
|
18616
|
+
injectHistoryStyles(doc);
|
|
18617
|
+
root.tabIndex = 0;
|
|
18618
|
+
root.setAttribute("role", "group");
|
|
18619
|
+
root.setAttribute("aria-label", options.label ?? "History");
|
|
18620
|
+
root.setAttribute("aria-roledescription", "timeline");
|
|
18621
|
+
const chart = el(doc, "div", "knobs-hy-chart");
|
|
18622
|
+
const foot = el(doc, "div", "knobs-hy-foot");
|
|
18623
|
+
const detail = el(doc, "div", "knobs-hy-detail");
|
|
18624
|
+
detail.setAttribute("aria-live", "polite");
|
|
18625
|
+
const tip = el(doc, "div", "knobs-hy-tip");
|
|
18626
|
+
tip.setAttribute("aria-hidden", "true");
|
|
18627
|
+
const tipTitle = el(doc, "div", "knobs-hy-tt");
|
|
18628
|
+
const tipDate = el(doc, "div", "knobs-hy-td");
|
|
18629
|
+
const tipLine = el(doc, "div", "knobs-hy-tl");
|
|
18630
|
+
tip.append(tipTitle, tipDate, tipLine);
|
|
18631
|
+
root.append(chart, foot, detail, tip);
|
|
18632
|
+
container.append(root);
|
|
18633
|
+
let model = normalizeHistory(options.data);
|
|
18634
|
+
let selected = options.selected && model.items.has(options.selected) ? options.selected : null;
|
|
18635
|
+
let disposed = false;
|
|
18636
|
+
let quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
18637
|
+
let showAll = false;
|
|
18638
|
+
const walked = () => showAll ? model.events : model.events.filter((e) => !quiet.has(e.id));
|
|
18639
|
+
let nodes = new Map;
|
|
18640
|
+
let tracks = new Map;
|
|
18641
|
+
let over = null;
|
|
18642
|
+
let hoverLine = null;
|
|
18643
|
+
let selLine = null;
|
|
18644
|
+
let scale = dayScale(model.from, model.to, 1);
|
|
18645
|
+
let drawnWidth = -1;
|
|
18646
|
+
let hovered = null;
|
|
18647
|
+
let tipKey = "";
|
|
18648
|
+
const fontOf = (n) => {
|
|
18649
|
+
const c = win.getComputedStyle(n);
|
|
18650
|
+
return `${c.fontStyle} ${c.fontWeight} ${c.fontSize} ${c.fontFamily}`;
|
|
18651
|
+
};
|
|
18652
|
+
let measureCtx;
|
|
18653
|
+
const measure = (s, font) => {
|
|
18654
|
+
if (measureCtx === undefined)
|
|
18655
|
+
measureCtx = doc.createElement("canvas").getContext("2d");
|
|
18656
|
+
if (!measureCtx)
|
|
18657
|
+
return s.length * 6.4;
|
|
18658
|
+
measureCtx.font = font;
|
|
18659
|
+
return measureCtx.measureText(s).width;
|
|
18660
|
+
};
|
|
18661
|
+
function drawFoot() {
|
|
18662
|
+
foot.textContent = "";
|
|
18663
|
+
root.toggleAttribute("data-all", showAll);
|
|
18664
|
+
if (quiet.size) {
|
|
18665
|
+
const t = el(doc, "button", "knobs-hy-more");
|
|
18666
|
+
t.type = "button";
|
|
18667
|
+
t.setAttribute("aria-pressed", String(showAll));
|
|
18668
|
+
t.textContent = `${showAll ? "Hide" : "Show"} ${quietLabel(model.events, quiet)}`;
|
|
18669
|
+
t.addEventListener("click", () => {
|
|
18670
|
+
showAll = !showAll;
|
|
18671
|
+
if (!showAll && selected && quiet.has(selected))
|
|
18672
|
+
select(null, true, true);
|
|
18673
|
+
drawFoot();
|
|
18674
|
+
});
|
|
18675
|
+
foot.append(t);
|
|
18676
|
+
}
|
|
18677
|
+
if (model.inferred && model.verified) {
|
|
18678
|
+
const legend = el(doc, "span", "knobs-hy-legend");
|
|
18679
|
+
for (const [label, inferred] of [
|
|
18680
|
+
["Verified", false],
|
|
18681
|
+
["Inferred", true]
|
|
18682
|
+
]) {
|
|
18683
|
+
const k = el(doc, "span", "knobs-hy-key");
|
|
18684
|
+
const sw = el(doc, "span", "knobs-hy-swatch");
|
|
18685
|
+
sw.dataset.tone = "dim";
|
|
18686
|
+
if (inferred)
|
|
18687
|
+
sw.dataset.inferred = "";
|
|
18688
|
+
k.append(sw, text(doc, "span", "", label));
|
|
18689
|
+
legend.append(k);
|
|
18690
|
+
}
|
|
18691
|
+
foot.append(legend);
|
|
18692
|
+
}
|
|
18693
|
+
foot.hidden = !foot.childElementCount;
|
|
18694
|
+
}
|
|
18695
|
+
function draw() {
|
|
18696
|
+
const width = root.clientWidth;
|
|
18697
|
+
drawnWidth = width;
|
|
18698
|
+
const narrow = width > 0 && width < (options.narrow ?? 560);
|
|
18699
|
+
root.toggleAttribute("data-narrow", narrow);
|
|
18700
|
+
root.toggleAttribute("data-bands", model.bands.length > 0);
|
|
18701
|
+
hideHover();
|
|
18702
|
+
chart.textContent = "";
|
|
18703
|
+
nodes = new Map;
|
|
18704
|
+
tracks = new Map;
|
|
18705
|
+
let gutter = 0;
|
|
18706
|
+
if (!narrow) {
|
|
18707
|
+
const probe = el(doc, "div", "knobs-hy-label");
|
|
18708
|
+
chart.append(probe);
|
|
18709
|
+
const font = fontOf(probe);
|
|
18710
|
+
const gap = parseFloat(win.getComputedStyle(probe).marginRight) || 0;
|
|
18711
|
+
probe.remove();
|
|
18712
|
+
const longest = Math.max(0, ...model.lanes.map((l) => measure(l.label, font)));
|
|
18713
|
+
gutter = Math.round(Math.min(Math.max(longest + gap + 2, 64), Math.max(96, width * 0.3), 200));
|
|
18714
|
+
}
|
|
18715
|
+
root.style.setProperty("--hy-gutter", `${gutter}px`);
|
|
18716
|
+
const plotW = Math.max(1, width - gutter);
|
|
18717
|
+
scale = dayScale(model.from, model.to, plotW);
|
|
18718
|
+
const pct = (d) => `${(scale.f(d) * 100).toFixed(4)}%`;
|
|
18719
|
+
const rows = ["auto"];
|
|
18720
|
+
for (let i = 0;i < model.lanes.length; i++)
|
|
18721
|
+
rows.push(...narrow ? ["auto", "var(--hy-lane)"] : ["var(--hy-lane)"]);
|
|
18722
|
+
chart.style.gridTemplateRows = rows.join(" ");
|
|
18723
|
+
const behind = el(doc, "div", "knobs-hy-layer");
|
|
18724
|
+
behind.setAttribute("aria-hidden", "true");
|
|
18725
|
+
for (const b of model.bands) {
|
|
18726
|
+
const c = clipSpan(b.from, b.to, model.from, model.to);
|
|
18727
|
+
if (!c)
|
|
18728
|
+
continue;
|
|
18729
|
+
const band = el(doc, "div", "knobs-hy-band");
|
|
18730
|
+
band.style.left = pct(c.from);
|
|
18731
|
+
band.style.width = pct(model.from + (c.to - c.from));
|
|
18732
|
+
behind.append(band);
|
|
18733
|
+
}
|
|
18734
|
+
const ticks = monthTicks(model.from, model.to, plotW);
|
|
18735
|
+
for (const t of ticks) {
|
|
18736
|
+
const line = el(doc, "div", "knobs-hy-grid");
|
|
18737
|
+
line.style.left = pct(t.day);
|
|
18738
|
+
behind.append(line);
|
|
18739
|
+
}
|
|
18740
|
+
chart.append(behind);
|
|
18741
|
+
const axis = el(doc, "div", "knobs-hy-axis");
|
|
18742
|
+
axis.setAttribute("aria-hidden", "true");
|
|
18743
|
+
const tickEls = ticks.map((t) => {
|
|
18744
|
+
const n = text(doc, "span", "knobs-hy-tick", t.label);
|
|
18745
|
+
n.style.left = pct(t.day);
|
|
18746
|
+
axis.append(n);
|
|
18747
|
+
return n;
|
|
18748
|
+
});
|
|
18749
|
+
const showToday = model.today >= model.from && model.today <= model.to;
|
|
18750
|
+
const now = showToday ? text(doc, "span", "knobs-hy-now", "Today") : null;
|
|
18751
|
+
if (now)
|
|
18752
|
+
axis.append(now);
|
|
18753
|
+
const room = bandRoom(model.bands, scale.x);
|
|
18754
|
+
const bandEls = model.bands.map((b, i) => {
|
|
18755
|
+
const n = text(doc, "span", "knobs-hy-blabel", b.label);
|
|
18756
|
+
n.style.left = pct(Math.max(b.from, model.from));
|
|
18757
|
+
n.style.maxWidth = `${Math.max(0, room[i])}px`;
|
|
18758
|
+
axis.append(n);
|
|
18759
|
+
return n;
|
|
18760
|
+
});
|
|
18761
|
+
chart.append(axis);
|
|
18762
|
+
model.lanes.forEach((lane, i) => {
|
|
18763
|
+
const row = narrow ? 2 + i * 2 : 2 + i;
|
|
18764
|
+
const label = text(doc, "div", "knobs-hy-label", lane.label);
|
|
18765
|
+
label.style.gridRow = String(row);
|
|
18766
|
+
label.title = lane.label;
|
|
18767
|
+
const track = el(doc, "div", "knobs-hy-track");
|
|
18768
|
+
track.style.gridRow = String(narrow ? row + 1 : row);
|
|
18769
|
+
track.dataset.lane = lane.id;
|
|
18770
|
+
tracks.set(lane.id, track);
|
|
18771
|
+
for (const s of lane.spans) {
|
|
18772
|
+
const c = clipSpan(s.from, s.to, model.from, model.to);
|
|
18773
|
+
if (!c)
|
|
18774
|
+
continue;
|
|
18775
|
+
const b = el(doc, "button", "knobs-hy-span");
|
|
18776
|
+
b.type = "button";
|
|
18777
|
+
b.tabIndex = -1;
|
|
18778
|
+
b.dataset.id = s.id;
|
|
18779
|
+
b.dataset.tone = s.tone;
|
|
18780
|
+
if (s.inferred)
|
|
18781
|
+
b.dataset.inferred = "";
|
|
18782
|
+
if (c.clipStart)
|
|
18783
|
+
b.dataset.clipStart = "";
|
|
18784
|
+
if (c.clipEnd)
|
|
18785
|
+
b.dataset.clipEnd = "";
|
|
18786
|
+
b.style.left = pct(c.from);
|
|
18787
|
+
b.style.width = pct(model.from + (c.to - c.from));
|
|
18788
|
+
b.setAttribute("aria-label", `${s.title}, ${when(s)}`);
|
|
18789
|
+
if (s.tags.length)
|
|
18790
|
+
b.append(text(doc, "span", "knobs-hy-tag", s.tags[0]));
|
|
18791
|
+
track.append(b);
|
|
18792
|
+
nodes.set(s.id, b);
|
|
18793
|
+
}
|
|
18794
|
+
for (const e of lane.events) {
|
|
18795
|
+
if (e.to < model.from || e.from > model.to)
|
|
18796
|
+
continue;
|
|
18797
|
+
const b = el(doc, "button", "knobs-hy-ev");
|
|
18798
|
+
b.type = "button";
|
|
18799
|
+
b.tabIndex = -1;
|
|
18800
|
+
b.dataset.id = e.id;
|
|
18801
|
+
b.dataset.tone = e.tone;
|
|
18802
|
+
if (e.type)
|
|
18803
|
+
b.dataset.type = e.type;
|
|
18804
|
+
if (e.inferred)
|
|
18805
|
+
b.dataset.inferred = "";
|
|
18806
|
+
if (quiet.has(e.id))
|
|
18807
|
+
b.dataset.quiet = "";
|
|
18808
|
+
b.style.left = pct(Math.max(e.from, model.from));
|
|
18809
|
+
if (e.to > e.from) {
|
|
18810
|
+
b.dataset.range = "";
|
|
18811
|
+
b.style.setProperty("--hy-w", `${scale.x(model.from + (e.to - e.from)).toFixed(2)}px`);
|
|
18812
|
+
}
|
|
18813
|
+
b.setAttribute("aria-label", `${when(e)}: ${e.title}`);
|
|
18814
|
+
track.append(b);
|
|
18815
|
+
nodes.set(e.id, b);
|
|
18816
|
+
}
|
|
18817
|
+
chart.append(label, track);
|
|
18818
|
+
});
|
|
18819
|
+
over = el(doc, "div", "knobs-hy-layer");
|
|
18820
|
+
over.setAttribute("aria-hidden", "true");
|
|
18821
|
+
if (showToday) {
|
|
18822
|
+
const today = el(doc, "div", "knobs-hy-today");
|
|
18823
|
+
today.style.left = pct(model.today);
|
|
18824
|
+
over.append(today);
|
|
18825
|
+
}
|
|
18826
|
+
selLine = el(doc, "div", "knobs-hy-sel");
|
|
18827
|
+
hoverLine = el(doc, "div", "knobs-hy-hover");
|
|
18828
|
+
over.append(selLine, hoverLine);
|
|
18829
|
+
chart.append(over);
|
|
18830
|
+
const reserved = [];
|
|
18831
|
+
if (now) {
|
|
18832
|
+
const w = now.offsetWidth;
|
|
18833
|
+
const x0 = Math.max(0, Math.min(plotW - w, scale.x(model.today) - w / 2));
|
|
18834
|
+
now.style.left = `${x0}px`;
|
|
18835
|
+
reserved.push({ x0, x1: x0 + w });
|
|
18836
|
+
}
|
|
18837
|
+
const fits = fitLabels(ticks.map((t, i) => ({ x: scale.x(t.day), w: tickEls[i].offsetWidth })), plotW, reserved);
|
|
18838
|
+
tickEls.forEach((n, i) => n.hidden = !fits[i]);
|
|
18839
|
+
for (const n of bandEls)
|
|
18840
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18841
|
+
for (const n of chart.querySelectorAll(".knobs-hy-label"))
|
|
18842
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18843
|
+
for (const lane of model.lanes) {
|
|
18844
|
+
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);
|
|
18845
|
+
spans.forEach(({ s, c }, i) => {
|
|
18846
|
+
const b = nodes.get(s.id);
|
|
18847
|
+
const tag = b?.querySelector(".knobs-hy-tag");
|
|
18848
|
+
if (!b || !tag)
|
|
18849
|
+
return;
|
|
18850
|
+
const font = fontOf(tag);
|
|
18851
|
+
const widths = s.tags.map((t) => measure(t, font));
|
|
18852
|
+
const next = spans[i + 1]?.c;
|
|
18853
|
+
const spanW = scale.x(c.to) - scale.x(c.from);
|
|
18854
|
+
const after = (next ? scale.x(next.from) : plotW) - scale.x(c.to);
|
|
18855
|
+
const fit = fitTag(widths, spanW, after);
|
|
18856
|
+
tag.textContent = fit ? s.tags[fit.index] : s.tags[0] ?? "";
|
|
18857
|
+
tag.dataset.place = fit ? fit.place : "none";
|
|
18858
|
+
});
|
|
18859
|
+
}
|
|
18860
|
+
paintSelection();
|
|
18861
|
+
}
|
|
18862
|
+
const h = new Spring(0, HEIGHT2.omega, HEIGHT2.zeta, 0.25);
|
|
18863
|
+
let goal = 0;
|
|
18864
|
+
let content = null;
|
|
18865
|
+
const loop = frameLoop(win, (dt) => {
|
|
18866
|
+
h.step(dt);
|
|
18867
|
+
const moving = !h.resting;
|
|
18868
|
+
detail.style.height = moving ? `${Math.max(0, h.x).toFixed(2)}px` : goal > 0 ? "" : "0px";
|
|
18869
|
+
detail.toggleAttribute("data-moving", moving);
|
|
18870
|
+
return moving;
|
|
18871
|
+
});
|
|
18872
|
+
function buildDetail(it) {
|
|
18873
|
+
const c = el(doc, "div", "knobs-hy-dc");
|
|
18874
|
+
const meta = el(doc, "div", "knobs-hy-dmeta");
|
|
18875
|
+
meta.append(text(doc, "time", "", when(it)));
|
|
18876
|
+
const lane = model.lanes.find((l) => l.id === it.lane);
|
|
18877
|
+
if (lane && (model.lanes.length > 1 || lane.id !== "code"))
|
|
18878
|
+
meta.append(text(doc, "span", "", lane.label));
|
|
18879
|
+
if (it.type)
|
|
18880
|
+
meta.append(text(doc, "span", "", typeName(it.type)));
|
|
18881
|
+
if (it.provenance || it.source) {
|
|
18882
|
+
const p = text(doc, "span", "", [it.provenance, it.source].filter(Boolean).join(" · "));
|
|
18883
|
+
if (it.inferred) {
|
|
18884
|
+
p.dataset.tone = "warn";
|
|
18885
|
+
p.title = "Worked out from behaviour, not seen in the record";
|
|
18886
|
+
}
|
|
18887
|
+
meta.append(p);
|
|
18888
|
+
}
|
|
18889
|
+
c.append(meta, text(doc, "div", "knobs-hy-dtitle", it.title));
|
|
18890
|
+
if (it.detail) {
|
|
18891
|
+
const p = el(doc, "p", "knobs-hy-dtext");
|
|
18892
|
+
p.innerHTML = inlineMarkdown(it.detail);
|
|
18893
|
+
c.append(p);
|
|
18894
|
+
}
|
|
18895
|
+
const foot = el(doc, "div", "knobs-hy-dfoot");
|
|
18896
|
+
for (const e of it.evidence) {
|
|
18897
|
+
const tag = text(doc, e.href ? "a" : "span", "knobs-hy-evidence", e.label);
|
|
18898
|
+
if (tag instanceof HTMLAnchorElement && e.href) {
|
|
18899
|
+
tag.href = e.href;
|
|
18900
|
+
tag.target = "_blank";
|
|
18901
|
+
tag.rel = "noreferrer";
|
|
18902
|
+
}
|
|
18903
|
+
foot.append(tag);
|
|
18904
|
+
}
|
|
18905
|
+
if (it.step && options.onJump) {
|
|
18906
|
+
const go = el(doc, "button", "knobs-hy-jump");
|
|
18907
|
+
go.type = "button";
|
|
18908
|
+
go.append(text(doc, "span", "", "Go to step"));
|
|
18909
|
+
go.insertAdjacentHTML("beforeend", GO);
|
|
18910
|
+
const step = it.step;
|
|
18911
|
+
go.addEventListener("click", () => options.onJump?.(step));
|
|
18912
|
+
foot.append(go);
|
|
18913
|
+
}
|
|
18914
|
+
c.append(foot);
|
|
18915
|
+
return c;
|
|
18916
|
+
}
|
|
18917
|
+
function showDetail(it, animate) {
|
|
18918
|
+
const from = detail.offsetHeight;
|
|
18919
|
+
const old = content;
|
|
18920
|
+
const next = it ? buildDetail(it) : null;
|
|
18921
|
+
if (old) {
|
|
18922
|
+
if (animate) {
|
|
18923
|
+
old.dataset.leave = "";
|
|
18924
|
+
const drop = () => old.remove();
|
|
18925
|
+
old.addEventListener("transitionend", drop, { once: true });
|
|
18926
|
+
win.setTimeout(drop, 400);
|
|
18927
|
+
} else
|
|
18928
|
+
old.remove();
|
|
18929
|
+
}
|
|
18930
|
+
content = next;
|
|
18931
|
+
if (next) {
|
|
18932
|
+
detail.append(next);
|
|
18933
|
+
if (animate) {
|
|
18934
|
+
next.dataset.enter = "";
|
|
18935
|
+
next.offsetHeight;
|
|
18936
|
+
delete next.dataset.enter;
|
|
18937
|
+
}
|
|
18938
|
+
}
|
|
18939
|
+
goal = next ? next.offsetHeight : 0;
|
|
18940
|
+
if (!animate || reducedMotion(win)) {
|
|
18941
|
+
loop.stop();
|
|
18942
|
+
h.snap(goal);
|
|
18943
|
+
detail.style.height = goal > 0 ? "" : "0px";
|
|
18944
|
+
detail.removeAttribute("data-moving");
|
|
18945
|
+
return;
|
|
18946
|
+
}
|
|
18947
|
+
if (h.resting)
|
|
18948
|
+
h.snap(from);
|
|
18949
|
+
h.set(goal);
|
|
18950
|
+
loop.wake();
|
|
18951
|
+
}
|
|
18952
|
+
function paintSelection() {
|
|
18953
|
+
for (const [id, n] of nodes)
|
|
18954
|
+
n.toggleAttribute("data-selected", id === selected);
|
|
18955
|
+
const it = selected ? model.items.get(selected) : undefined;
|
|
18956
|
+
if (selLine) {
|
|
18957
|
+
const on = it?.kind === "event";
|
|
18958
|
+
selLine.toggleAttribute("data-on", on);
|
|
18959
|
+
if (on)
|
|
18960
|
+
selLine.style.left = `${(scale.f(it.from) * 100).toFixed(4)}%`;
|
|
18961
|
+
}
|
|
18962
|
+
}
|
|
18963
|
+
function select(id, animate, notify) {
|
|
18964
|
+
const next = id && model.items.has(id) ? id : null;
|
|
18965
|
+
if (next === selected)
|
|
18966
|
+
return;
|
|
18967
|
+
selected = next;
|
|
18968
|
+
paintSelection();
|
|
18969
|
+
showDetail(next ? model.items.get(next) : null, animate);
|
|
18970
|
+
if (notify)
|
|
18971
|
+
options.onSelect?.(next);
|
|
18972
|
+
}
|
|
18973
|
+
function itemAt(e) {
|
|
18974
|
+
if (!over)
|
|
18975
|
+
return null;
|
|
18976
|
+
const r = over.getBoundingClientRect();
|
|
18977
|
+
if (!r.width)
|
|
18978
|
+
return null;
|
|
18979
|
+
const x = Math.max(0, Math.min(r.width, e.clientX - r.left));
|
|
18980
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
18981
|
+
const hit = target?.closest("[data-id]");
|
|
18982
|
+
if (hit && chart.contains(hit))
|
|
18983
|
+
return { it: model.items.get(hit.dataset.id) ?? null, x };
|
|
18984
|
+
const laneId = target?.closest(".knobs-hy-track")?.dataset.lane;
|
|
18985
|
+
const lane = laneId ? model.lanes.find((l) => l.id === laneId) : undefined;
|
|
18986
|
+
if (lane) {
|
|
18987
|
+
const reach = REACH / r.width * (model.to - model.from);
|
|
18988
|
+
const near = nearestEvent(lane.events, scale.day(x), reach);
|
|
18989
|
+
if (near)
|
|
18990
|
+
return { it: near, x };
|
|
18991
|
+
}
|
|
18992
|
+
return { it: null, x };
|
|
18993
|
+
}
|
|
18994
|
+
function hideHover() {
|
|
18995
|
+
hoverLine?.removeAttribute("data-on");
|
|
18996
|
+
tip.removeAttribute("data-on");
|
|
18997
|
+
hovered?.removeAttribute("data-hover");
|
|
18998
|
+
hovered = null;
|
|
18999
|
+
tipKey = "";
|
|
19000
|
+
}
|
|
19001
|
+
function hover(e) {
|
|
19002
|
+
const at = itemAt(e);
|
|
19003
|
+
if (!at || !over || !hoverLine)
|
|
19004
|
+
return hideHover();
|
|
19005
|
+
const { it, x } = at;
|
|
19006
|
+
const lineX = it?.kind === "event" ? scale.x(it.from) : x;
|
|
19007
|
+
hoverLine.style.left = `${lineX.toFixed(2)}px`;
|
|
19008
|
+
hoverLine.setAttribute("data-on", "");
|
|
19009
|
+
const node = it ? nodes.get(it.id) ?? null : null;
|
|
19010
|
+
if (node !== hovered) {
|
|
19011
|
+
hovered?.removeAttribute("data-hover");
|
|
19012
|
+
node?.setAttribute("data-hover", "");
|
|
19013
|
+
hovered = node;
|
|
19014
|
+
}
|
|
19015
|
+
const key = it ? it.id : `d${Math.round(scale.day(x))}`;
|
|
19016
|
+
if (key !== tipKey) {
|
|
19017
|
+
tipKey = key;
|
|
19018
|
+
tipTitle.textContent = it ? it.title : formatDay(Math.round(scale.day(x)));
|
|
19019
|
+
tipDate.textContent = it ? [when(it), it.type ? typeName(it.type) : ""].filter(Boolean).join(" · ") : "";
|
|
19020
|
+
tipDate.hidden = !it;
|
|
19021
|
+
tipLine.textContent = it ? firstSentence(it.detail) : "";
|
|
19022
|
+
}
|
|
19023
|
+
const rr = root.getBoundingClientRect();
|
|
19024
|
+
const or = over.getBoundingClientRect();
|
|
19025
|
+
const laneId = it?.lane ?? (e.target instanceof Element ? e.target.closest(".knobs-hy-track")?.dataset.lane : undefined);
|
|
19026
|
+
const lr = laneId && tracks.get(laneId)?.getBoundingClientRect() || or;
|
|
19027
|
+
const tw = tip.offsetWidth;
|
|
19028
|
+
const th = tip.offsetHeight;
|
|
19029
|
+
const left = Math.max(0, Math.min(rr.width - tw, or.left - rr.left + lineX - tw / 2));
|
|
19030
|
+
let top = lr.top - rr.top - th - 6;
|
|
19031
|
+
if (top + rr.top < 0)
|
|
19032
|
+
top = lr.bottom - rr.top + 6;
|
|
19033
|
+
tip.style.transform = `translate3d(${Math.round(left)}px,${Math.round(top)}px,0)`;
|
|
19034
|
+
tip.setAttribute("data-on", "");
|
|
19035
|
+
}
|
|
19036
|
+
chart.addEventListener("pointermove", (e) => {
|
|
19037
|
+
if (e.pointerType === "touch")
|
|
19038
|
+
return;
|
|
19039
|
+
hover(e);
|
|
19040
|
+
});
|
|
19041
|
+
chart.addEventListener("pointerleave", hideHover);
|
|
19042
|
+
chart.addEventListener("mousedown", (e) => {
|
|
19043
|
+
if (!(e.target instanceof Element) || !e.target.closest("button"))
|
|
19044
|
+
return;
|
|
19045
|
+
e.preventDefault();
|
|
19046
|
+
root.focus({ preventScroll: true });
|
|
19047
|
+
});
|
|
19048
|
+
chart.addEventListener("click", (e) => {
|
|
19049
|
+
const at = itemAt(e);
|
|
19050
|
+
const id = at?.it?.id ?? null;
|
|
19051
|
+
select(id !== null && id === selected ? null : id, true, true);
|
|
19052
|
+
});
|
|
19053
|
+
root.addEventListener("keydown", (e) => {
|
|
19054
|
+
const onChart = e.target instanceof Node && chart.contains(e.target);
|
|
19055
|
+
if (e.target !== root && !onChart || e.metaKey || e.ctrlKey || e.altKey)
|
|
19056
|
+
return;
|
|
19057
|
+
let next;
|
|
19058
|
+
const order = walked();
|
|
19059
|
+
const at = selected ? model.items.get(selected)?.from : undefined;
|
|
19060
|
+
if (e.key === "ArrowRight")
|
|
19061
|
+
next = stepEvent(order, selected, 1, at);
|
|
19062
|
+
else if (e.key === "ArrowLeft")
|
|
19063
|
+
next = stepEvent(order, selected, -1, at);
|
|
19064
|
+
else if (e.key === "Home")
|
|
19065
|
+
next = order[0] ?? null;
|
|
19066
|
+
else if (e.key === "End")
|
|
19067
|
+
next = order[order.length - 1] ?? null;
|
|
19068
|
+
else if (e.key === "Escape" && selected)
|
|
19069
|
+
next = null;
|
|
19070
|
+
else if (e.key === "Enter" && selected) {
|
|
19071
|
+
const step = model.items.get(selected)?.step;
|
|
19072
|
+
if (!step || !options.onJump)
|
|
19073
|
+
return;
|
|
19074
|
+
options.onJump(step);
|
|
19075
|
+
e.preventDefault();
|
|
19076
|
+
e.stopPropagation();
|
|
19077
|
+
return;
|
|
19078
|
+
} else
|
|
19079
|
+
return;
|
|
19080
|
+
e.preventDefault();
|
|
19081
|
+
e.stopPropagation();
|
|
19082
|
+
hideHover();
|
|
19083
|
+
select(next?.id ?? null, true, true);
|
|
19084
|
+
});
|
|
19085
|
+
let raf = 0;
|
|
19086
|
+
const ro = new ResizeObserver(() => {
|
|
19087
|
+
if (raf || disposed)
|
|
19088
|
+
return;
|
|
19089
|
+
raf = win.requestAnimationFrame(() => {
|
|
19090
|
+
raf = 0;
|
|
19091
|
+
if (!disposed && root.clientWidth !== drawnWidth)
|
|
19092
|
+
draw();
|
|
19093
|
+
});
|
|
19094
|
+
});
|
|
19095
|
+
ro.observe(root);
|
|
19096
|
+
const restyle = () => draw();
|
|
19097
|
+
root.addEventListener("knobs-restyle", restyle);
|
|
19098
|
+
drawFoot();
|
|
19099
|
+
draw();
|
|
19100
|
+
detail.style.height = "0px";
|
|
19101
|
+
if (selected)
|
|
19102
|
+
showDetail(model.items.get(selected), false);
|
|
19103
|
+
const dispose = () => {
|
|
19104
|
+
if (disposed)
|
|
19105
|
+
return;
|
|
19106
|
+
disposed = true;
|
|
19107
|
+
ro.disconnect();
|
|
19108
|
+
if (raf)
|
|
19109
|
+
win.cancelAnimationFrame(raf);
|
|
19110
|
+
loop.stop();
|
|
19111
|
+
root.removeEventListener("knobs-restyle", restyle);
|
|
19112
|
+
root.remove();
|
|
19113
|
+
};
|
|
19114
|
+
return {
|
|
19115
|
+
element: root,
|
|
19116
|
+
get model() {
|
|
19117
|
+
return model;
|
|
19118
|
+
},
|
|
19119
|
+
get selected() {
|
|
19120
|
+
return selected;
|
|
19121
|
+
},
|
|
19122
|
+
select(id) {
|
|
19123
|
+
select(id, true, false);
|
|
19124
|
+
},
|
|
19125
|
+
set(data) {
|
|
19126
|
+
model = normalizeHistory(data);
|
|
19127
|
+
const keep = selected && model.items.has(selected) ? selected : null;
|
|
19128
|
+
selected = keep;
|
|
19129
|
+
quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
19130
|
+
drawFoot();
|
|
19131
|
+
draw();
|
|
19132
|
+
showDetail(keep ? model.items.get(keep) : null, false);
|
|
19133
|
+
},
|
|
19134
|
+
focus() {
|
|
19135
|
+
root.focus();
|
|
19136
|
+
},
|
|
19137
|
+
dispose,
|
|
19138
|
+
destroy: dispose
|
|
19139
|
+
};
|
|
19140
|
+
}
|
|
19141
|
+
|
|
19142
|
+
// src/ui/walkthrough-blocks.ts
|
|
19143
|
+
init_icons();
|
|
19144
|
+
init_look();
|
|
19145
|
+
init_selection_pill();
|
|
19146
|
+
var GLYPH = {
|
|
19147
|
+
caret: icon("caretRight", { small: true })
|
|
19148
|
+
};
|
|
19149
|
+
var el2 = (doc, tag, cls) => {
|
|
19150
|
+
const n = doc.createElement(tag);
|
|
19151
|
+
if (cls)
|
|
19152
|
+
n.className = cls;
|
|
19153
|
+
return n;
|
|
19154
|
+
};
|
|
19155
|
+
var text2 = (doc, tag, cls, value) => {
|
|
19156
|
+
const n = el2(doc, tag, cls);
|
|
19157
|
+
n.textContent = value;
|
|
19158
|
+
return n;
|
|
19159
|
+
};
|
|
17929
19160
|
var prose = (doc, tag, cls, value) => {
|
|
17930
|
-
const n =
|
|
19161
|
+
const n = el2(doc, tag, cls);
|
|
17931
19162
|
n.innerHTML = inlineMarkdown(value);
|
|
17932
19163
|
return n;
|
|
17933
19164
|
};
|
|
17934
19165
|
var link = (doc, cls, label, href) => {
|
|
17935
19166
|
if (!href)
|
|
17936
|
-
return
|
|
17937
|
-
const a =
|
|
19167
|
+
return text2(doc, "span", cls, label);
|
|
19168
|
+
const a = text2(doc, "a", cls, label);
|
|
17938
19169
|
a.href = href;
|
|
17939
19170
|
a.target = "_blank";
|
|
17940
19171
|
a.rel = "noreferrer";
|
|
17941
19172
|
return a;
|
|
17942
19173
|
};
|
|
17943
|
-
function
|
|
19174
|
+
function rowLink(row, ref, steps) {
|
|
17944
19175
|
const s = ref && steps?.find(ref);
|
|
17945
19176
|
if (!s || !steps)
|
|
17946
|
-
return
|
|
17947
|
-
const
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
19177
|
+
return;
|
|
19178
|
+
const doc = row.ownerDocument;
|
|
19179
|
+
row.dataset.go = "";
|
|
19180
|
+
row.tabIndex = 0;
|
|
19181
|
+
row.title = `Go to “${plainMarkdown(s.title)}”`;
|
|
19182
|
+
row.addEventListener("click", (e) => {
|
|
19183
|
+
if (e.target?.closest("a,button"))
|
|
19184
|
+
return;
|
|
19185
|
+
if (doc.getSelection()?.toString())
|
|
19186
|
+
return;
|
|
19187
|
+
steps.go(s.key);
|
|
19188
|
+
});
|
|
19189
|
+
row.addEventListener("keydown", (e) => {
|
|
19190
|
+
if (e.target !== row || e.key !== "Enter" && e.key !== " ")
|
|
19191
|
+
return;
|
|
19192
|
+
e.preventDefault();
|
|
17954
19193
|
steps.go(s.key);
|
|
17955
19194
|
});
|
|
17956
|
-
return b;
|
|
17957
19195
|
}
|
|
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
19196
|
function mountHistory(host, m, steps) {
|
|
17967
|
-
const
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
list.append(historyEvent(doc, e, steps));
|
|
17984
|
-
group.append(list);
|
|
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;
|
|
19197
|
+
const h = history(host, {
|
|
19198
|
+
data: m.data,
|
|
19199
|
+
...m.label ? { label: m.label } : {},
|
|
19200
|
+
...m.marks ? { marks: m.marks } : {},
|
|
19201
|
+
...steps ? {
|
|
19202
|
+
onJump: (ref) => {
|
|
19203
|
+
const s = steps.find(ref);
|
|
19204
|
+
if (s)
|
|
19205
|
+
steps.go(s.key);
|
|
19206
|
+
}
|
|
19207
|
+
} : {}
|
|
19208
|
+
});
|
|
19209
|
+
return {
|
|
19210
|
+
dispose: () => h.dispose(),
|
|
19211
|
+
context: () => ({ kind: "history" })
|
|
19212
|
+
};
|
|
18027
19213
|
}
|
|
18028
19214
|
function mountStack(host, m, steps) {
|
|
18029
19215
|
const doc = host.ownerDocument;
|
|
18030
19216
|
const layout = stackLayout(m);
|
|
18031
|
-
const root =
|
|
19217
|
+
const root = el2(doc, "div", "knobs-wt-stack");
|
|
18032
19218
|
root.toggleAttribute("data-flat", layout.flat);
|
|
18033
19219
|
const lane = (kind) => {
|
|
18034
|
-
const s =
|
|
19220
|
+
const s = el2(doc, "span", "knobs-wt-lane");
|
|
18035
19221
|
s.dataset.rail = kind;
|
|
18036
19222
|
return s;
|
|
18037
19223
|
};
|
|
@@ -18042,19 +19228,19 @@ function mountStack(host, m, steps) {
|
|
|
18042
19228
|
};
|
|
18043
19229
|
const from = layout.trunk ? 0 : 1;
|
|
18044
19230
|
if (layout.trunk && layout.rows.length) {
|
|
18045
|
-
const row =
|
|
19231
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
18046
19232
|
row.dataset.trunk = "";
|
|
18047
|
-
const lanes =
|
|
19233
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
18048
19234
|
lanes.setAttribute("aria-hidden", "true");
|
|
18049
19235
|
lanes.append(node(true));
|
|
18050
|
-
row.append(lanes,
|
|
19236
|
+
row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
|
|
18051
19237
|
root.append(row);
|
|
18052
19238
|
}
|
|
18053
19239
|
for (const r of layout.rows) {
|
|
18054
19240
|
const it = r.item;
|
|
18055
|
-
const row =
|
|
19241
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
18056
19242
|
if (!layout.flat) {
|
|
18057
|
-
const lanes =
|
|
19243
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
18058
19244
|
lanes.setAttribute("aria-hidden", "true");
|
|
18059
19245
|
for (let k = from;k < r.depth; k++)
|
|
18060
19246
|
lanes.append(lane(r.rails[k] ? "pass" : "none"));
|
|
@@ -18063,24 +19249,21 @@ function mountStack(host, m, steps) {
|
|
|
18063
19249
|
lanes.append(node(r.children > 0));
|
|
18064
19250
|
row.append(lanes);
|
|
18065
19251
|
}
|
|
18066
|
-
const main =
|
|
18067
|
-
const line =
|
|
19252
|
+
const main = el2(doc, "div", "knobs-wt-smain");
|
|
19253
|
+
const line = el2(doc, "div", "knobs-wt-sline");
|
|
18068
19254
|
if (it.order)
|
|
18069
|
-
line.append(
|
|
19255
|
+
line.append(text2(doc, "span", "knobs-wt-sord", it.order));
|
|
18070
19256
|
line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
|
|
18071
19257
|
const target = it.step ? steps?.find(it.step) : null;
|
|
18072
19258
|
if (target && steps) {
|
|
18073
|
-
const title =
|
|
19259
|
+
const title = text2(doc, "button", "knobs-wt-stitle", it.title);
|
|
18074
19260
|
title.type = "button";
|
|
18075
|
-
title.title = `Go to “${target.title}”`;
|
|
19261
|
+
title.title = `Go to “${plainMarkdown(target.title)}”`;
|
|
18076
19262
|
title.addEventListener("click", (e) => {
|
|
18077
19263
|
e.stopPropagation();
|
|
18078
19264
|
steps.go(target.key);
|
|
18079
19265
|
});
|
|
18080
19266
|
line.append(title);
|
|
18081
|
-
const go = stepButton(doc, it.step, steps);
|
|
18082
|
-
if (go)
|
|
18083
|
-
line.append(go);
|
|
18084
19267
|
row.dataset.go = "";
|
|
18085
19268
|
row.addEventListener("click", (e) => {
|
|
18086
19269
|
const t = e.target;
|
|
@@ -18091,14 +19274,14 @@ function mountStack(host, m, steps) {
|
|
|
18091
19274
|
steps.go(target.key);
|
|
18092
19275
|
});
|
|
18093
19276
|
} else
|
|
18094
|
-
line.append(
|
|
19277
|
+
line.append(text2(doc, "span", "knobs-wt-stitle", it.title));
|
|
18095
19278
|
main.append(line);
|
|
18096
19279
|
if (it.summary)
|
|
18097
19280
|
main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
|
|
18098
|
-
const meta =
|
|
19281
|
+
const meta = el2(doc, "div", "knobs-wt-smeta");
|
|
18099
19282
|
if (it.head) {
|
|
18100
19283
|
const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
|
|
18101
|
-
meta.append(
|
|
19284
|
+
meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
|
|
18102
19285
|
}
|
|
18103
19286
|
if (it.delta !== undefined)
|
|
18104
19287
|
meta.append(delta(doc, it.delta));
|
|
@@ -18106,7 +19289,7 @@ function mountStack(host, m, steps) {
|
|
|
18106
19289
|
main.append(meta);
|
|
18107
19290
|
row.append(main);
|
|
18108
19291
|
if (it.status) {
|
|
18109
|
-
const chip =
|
|
19292
|
+
const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
|
|
18110
19293
|
chip.dataset.tone = it.tone ?? "dim";
|
|
18111
19294
|
row.append(chip);
|
|
18112
19295
|
}
|
|
@@ -18116,40 +19299,38 @@ function mountStack(host, m, steps) {
|
|
|
18116
19299
|
return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
|
|
18117
19300
|
}
|
|
18118
19301
|
function delta(doc, d) {
|
|
18119
|
-
const s =
|
|
19302
|
+
const s = el2(doc, "span", "knobs-wt-delta");
|
|
18120
19303
|
if (typeof d === "string") {
|
|
18121
19304
|
s.textContent = d;
|
|
18122
19305
|
return s;
|
|
18123
19306
|
}
|
|
18124
19307
|
const n = (v) => v.toLocaleString("en-US");
|
|
18125
|
-
s.append(
|
|
18126
|
-
s.append(
|
|
19308
|
+
s.append(text2(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
|
|
19309
|
+
s.append(text2(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
|
|
18127
19310
|
s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
|
|
18128
19311
|
return s;
|
|
18129
19312
|
}
|
|
18130
|
-
function cellContent(doc, into, cell
|
|
19313
|
+
function cellContent(doc, into, cell) {
|
|
18131
19314
|
let content;
|
|
18132
19315
|
if (cell.badge) {
|
|
18133
19316
|
content = link(doc, "knobs-wt-chip", cell.text, cell.href);
|
|
18134
19317
|
content.dataset.tone = cell.badge;
|
|
18135
19318
|
} else {
|
|
18136
|
-
const inner = cell.code ?
|
|
19319
|
+
const inner = cell.code ? text2(doc, "code", "", cell.text) : null;
|
|
18137
19320
|
if (cell.href) {
|
|
18138
19321
|
content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
|
|
18139
19322
|
if (inner)
|
|
18140
19323
|
content.append(inner);
|
|
18141
19324
|
} else
|
|
18142
|
-
content = inner ??
|
|
19325
|
+
content = inner ?? text2(doc, "span", "", cell.text);
|
|
18143
19326
|
if (cell.tone) {
|
|
18144
19327
|
content.classList.add("knobs-wt-ctone");
|
|
18145
19328
|
content.dataset.tone = cell.tone;
|
|
18146
19329
|
}
|
|
18147
19330
|
}
|
|
18148
19331
|
into.append(content);
|
|
18149
|
-
const go = stepButton(doc, cell.step, steps);
|
|
18150
|
-
if (go)
|
|
18151
|
-
into.append(go);
|
|
18152
19332
|
}
|
|
19333
|
+
var rowStep = (r, columns) => columns.map((c) => r[c.key]?.step).find((step) => step !== undefined);
|
|
18153
19334
|
var cellAttrs = (td, c) => {
|
|
18154
19335
|
td.dataset.align = c.align;
|
|
18155
19336
|
if (c.mono)
|
|
@@ -18158,83 +19339,85 @@ var cellAttrs = (td, c) => {
|
|
|
18158
19339
|
function mountTable(host, m, steps) {
|
|
18159
19340
|
const doc = host.ownerDocument;
|
|
18160
19341
|
const win = doc.defaultView;
|
|
18161
|
-
const box =
|
|
18162
|
-
const scroller =
|
|
18163
|
-
const table =
|
|
19342
|
+
const box = el2(doc, "div", "knobs-wt-tablebox");
|
|
19343
|
+
const scroller = el2(doc, "div", "knobs-wt-table");
|
|
19344
|
+
const table = el2(doc, "table");
|
|
18164
19345
|
if (m.caption)
|
|
18165
|
-
table.append(
|
|
18166
|
-
const head =
|
|
18167
|
-
const hr =
|
|
19346
|
+
table.append(text2(doc, "caption", "", m.caption));
|
|
19347
|
+
const head = el2(doc, "thead");
|
|
19348
|
+
const hr = el2(doc, "tr");
|
|
18168
19349
|
for (const c of m.columns) {
|
|
18169
|
-
const th =
|
|
19350
|
+
const th = text2(doc, "th", "", c.label);
|
|
18170
19351
|
th.scope = "col";
|
|
18171
19352
|
th.dataset.align = c.align;
|
|
18172
19353
|
hr.append(th);
|
|
18173
19354
|
}
|
|
18174
19355
|
head.append(hr);
|
|
18175
|
-
const body =
|
|
19356
|
+
const body = el2(doc, "tbody");
|
|
18176
19357
|
for (const r of m.rows) {
|
|
18177
|
-
const tr =
|
|
19358
|
+
const tr = el2(doc, "tr");
|
|
18178
19359
|
for (const c of m.columns) {
|
|
18179
|
-
const td =
|
|
19360
|
+
const td = el2(doc, "td");
|
|
18180
19361
|
cellAttrs(td, c);
|
|
18181
19362
|
const cell = r[c.key];
|
|
18182
19363
|
if (cell) {
|
|
18183
19364
|
if (cell.text.length > 40 && !c.mono && !cell.code)
|
|
18184
19365
|
td.dataset.long = "";
|
|
18185
|
-
cellContent(doc, td, cell
|
|
19366
|
+
cellContent(doc, td, cell);
|
|
18186
19367
|
}
|
|
18187
19368
|
tr.append(td);
|
|
18188
19369
|
}
|
|
19370
|
+
rowLink(tr, rowStep(r, m.columns), steps);
|
|
18189
19371
|
body.append(tr);
|
|
18190
19372
|
}
|
|
18191
19373
|
table.append(head, body);
|
|
18192
19374
|
scroller.append(table);
|
|
18193
|
-
const stack =
|
|
19375
|
+
const stack = el2(doc, "div", "knobs-wt-tstack");
|
|
18194
19376
|
stack.setAttribute("role", "list");
|
|
18195
19377
|
if (m.caption)
|
|
18196
19378
|
stack.setAttribute("aria-label", m.caption);
|
|
18197
19379
|
const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
|
|
18198
19380
|
for (const r of m.rows) {
|
|
18199
|
-
const item =
|
|
19381
|
+
const item = el2(doc, "div", "knobs-wt-trow");
|
|
18200
19382
|
item.setAttribute("role", "listitem");
|
|
18201
|
-
const title =
|
|
19383
|
+
const title = el2(doc, "div", "knobs-wt-ttitle");
|
|
18202
19384
|
if (primary) {
|
|
18203
19385
|
cellAttrs(title, primary);
|
|
18204
19386
|
const cell = r[primary.key];
|
|
18205
19387
|
if (cell)
|
|
18206
|
-
cellContent(doc, title, cell
|
|
19388
|
+
cellContent(doc, title, cell);
|
|
18207
19389
|
}
|
|
18208
|
-
const line =
|
|
19390
|
+
const line = el2(doc, "div", "knobs-wt-tline");
|
|
18209
19391
|
line.append(title);
|
|
18210
|
-
const badges =
|
|
19392
|
+
const badges = el2(doc, "div", "knobs-wt-tbadges");
|
|
18211
19393
|
for (const c of m.columns) {
|
|
18212
19394
|
const cell = r[c.key];
|
|
18213
19395
|
if (c !== primary && cell?.badge)
|
|
18214
|
-
cellContent(doc, badges, cell
|
|
19396
|
+
cellContent(doc, badges, cell);
|
|
18215
19397
|
}
|
|
18216
19398
|
if (badges.childNodes.length)
|
|
18217
19399
|
line.append(badges);
|
|
18218
19400
|
item.append(line);
|
|
18219
|
-
const terms =
|
|
19401
|
+
const terms = el2(doc, "dl", "knobs-wt-tterms");
|
|
18220
19402
|
for (const c of m.columns) {
|
|
18221
19403
|
if (c === primary)
|
|
18222
19404
|
continue;
|
|
18223
19405
|
const cell = r[c.key];
|
|
18224
19406
|
if (!cell || cell.text === "" || cell.badge)
|
|
18225
19407
|
continue;
|
|
18226
|
-
const dd =
|
|
19408
|
+
const dd = el2(doc, "dd");
|
|
18227
19409
|
cellAttrs(dd, c);
|
|
18228
19410
|
dd.removeAttribute("data-align");
|
|
18229
|
-
cellContent(doc, dd, cell
|
|
18230
|
-
terms.append(
|
|
19411
|
+
cellContent(doc, dd, cell);
|
|
19412
|
+
terms.append(text2(doc, "dt", "", c.label), dd);
|
|
18231
19413
|
}
|
|
18232
19414
|
if (terms.childNodes.length)
|
|
18233
19415
|
item.append(terms);
|
|
19416
|
+
rowLink(item, rowStep(r, m.columns), steps);
|
|
18234
19417
|
stack.append(item);
|
|
18235
19418
|
}
|
|
18236
19419
|
if (m.caption)
|
|
18237
|
-
stack.append(
|
|
19420
|
+
stack.append(text2(doc, "p", "knobs-wt-tcaption", m.caption));
|
|
18238
19421
|
box.append(scroller, stack);
|
|
18239
19422
|
host.append(box);
|
|
18240
19423
|
let natural = 0;
|
|
@@ -18288,16 +19471,16 @@ function mountTable(host, m, steps) {
|
|
|
18288
19471
|
}
|
|
18289
19472
|
function chapterPills(host, chapters, io) {
|
|
18290
19473
|
const doc = host.ownerDocument;
|
|
18291
|
-
const list =
|
|
19474
|
+
const list = el2(doc, "div", "knobs-wt-chapters");
|
|
18292
19475
|
list.setAttribute("role", "group");
|
|
18293
19476
|
list.setAttribute("aria-label", "Chapters");
|
|
18294
19477
|
let at = -2;
|
|
18295
19478
|
const rows = chapters.map((c, i) => {
|
|
18296
|
-
const b =
|
|
19479
|
+
const b = el2(doc, "button", "knobs-wt-chapter");
|
|
18297
19480
|
b.type = "button";
|
|
18298
|
-
b.append(
|
|
18299
|
-
b.append(
|
|
18300
|
-
b.append(
|
|
19481
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
|
|
19482
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
|
|
19483
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
|
|
18301
19484
|
b.addEventListener("click", () => {
|
|
18302
19485
|
io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
|
|
18303
19486
|
io.seek(c.start);
|
|
@@ -18335,15 +19518,13 @@ function disclosure(host, options) {
|
|
|
18335
19518
|
const doc = host.ownerDocument;
|
|
18336
19519
|
const win = doc.defaultView;
|
|
18337
19520
|
let open = options.open === true;
|
|
18338
|
-
const root =
|
|
18339
|
-
const toggle =
|
|
19521
|
+
const root = el2(doc, "div", "knobs-wt-disc");
|
|
19522
|
+
const toggle = el2(doc, "button", "knobs-wt-disc-toggle");
|
|
18340
19523
|
toggle.type = "button";
|
|
18341
19524
|
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");
|
|
19525
|
+
toggle.append(text2(doc, "span", "", options.label));
|
|
19526
|
+
const viewport = el2(doc, "div", "knobs-wt-disc-body");
|
|
19527
|
+
const content = el2(doc, "div", "knobs-wt-disc-content");
|
|
18347
19528
|
content.append(options.content);
|
|
18348
19529
|
viewport.append(content);
|
|
18349
19530
|
root.append(toggle, viewport);
|
|
@@ -18355,7 +19536,9 @@ function disclosure(host, options) {
|
|
|
18355
19536
|
const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
|
|
18356
19537
|
viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
|
|
18357
19538
|
viewport.toggleAttribute("data-moving", moving);
|
|
18358
|
-
|
|
19539
|
+
const o = open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5);
|
|
19540
|
+
content.style.opacity = o.toFixed(3);
|
|
19541
|
+
content.style.filter = o >= 1 ? "" : `blur(${((1 - o) * 3).toFixed(2)}px)`;
|
|
18359
19542
|
};
|
|
18360
19543
|
const loop = frameLoop(win, (dt) => {
|
|
18361
19544
|
h.step(dt);
|
|
@@ -18391,6 +19574,7 @@ function disclosure(host, options) {
|
|
|
18391
19574
|
h.snap(open ? full : 0);
|
|
18392
19575
|
viewport.style.height = open ? "" : "0px";
|
|
18393
19576
|
content.style.opacity = open ? "1" : "0";
|
|
19577
|
+
content.style.filter = "";
|
|
18394
19578
|
return;
|
|
18395
19579
|
}
|
|
18396
19580
|
if (h.resting)
|
|
@@ -18414,7 +19598,7 @@ var PR_PATHS = {
|
|
|
18414
19598
|
var prGlyph = (merged) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true" focusable="false"><path d="${merged ? PR_PATHS.merge : PR_PATHS.pull}"/></svg>`;
|
|
18415
19599
|
var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
|
|
18416
19600
|
function prChip(doc, n, pr) {
|
|
18417
|
-
const chip =
|
|
19601
|
+
const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
|
|
18418
19602
|
if (pr.href) {
|
|
18419
19603
|
chip.setAttribute("href", pr.href);
|
|
18420
19604
|
chip.setAttribute("target", "_blank");
|
|
@@ -18423,7 +19607,7 @@ function prChip(doc, n, pr) {
|
|
|
18423
19607
|
if (pr.state)
|
|
18424
19608
|
chip.dataset.state = pr.state;
|
|
18425
19609
|
chip.innerHTML = prGlyph(pr.state === "merged");
|
|
18426
|
-
chip.append(
|
|
19610
|
+
chip.append(text2(doc, "span", "", `#${n}`));
|
|
18427
19611
|
const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
|
|
18428
19612
|
chip.title = tip;
|
|
18429
19613
|
chip.setAttribute("aria-label", `Pull request ${tip}`);
|
|
@@ -18479,6 +19663,7 @@ function linkPRs(root, find) {
|
|
|
18479
19663
|
// src/ui/walkthrough-media.ts
|
|
18480
19664
|
init_compare_model();
|
|
18481
19665
|
init_glide();
|
|
19666
|
+
init_motion();
|
|
18482
19667
|
init_icons();
|
|
18483
19668
|
init_look();
|
|
18484
19669
|
|
|
@@ -18491,8 +19676,9 @@ var quick = springCss(FADE);
|
|
|
18491
19676
|
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
19677
|
var walkthroughCss = `
|
|
18493
19678
|
:where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
|
|
19679
|
+
.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
19680
|
.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));
|
|
19681
|
+
--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
19682
|
--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
19683
|
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
19684
|
letter-spacing:normal;-webkit-font-smoothing:antialiased}
|
|
@@ -18508,7 +19694,7 @@ var walkthroughCss = `
|
|
|
18508
19694
|
/* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
|
|
18509
19695
|
in the room beside it, up to --kw-width: media take the column, prose stops at the measure.
|
|
18510
19696
|
One column where it's narrow ——— */
|
|
18511
|
-
.knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--
|
|
19697
|
+
.knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--kw-rail-gap);align-items:start}
|
|
18512
19698
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr) var(--kw-rail-w)}
|
|
18513
19699
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
|
|
18514
19700
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
|
|
@@ -18586,7 +19772,6 @@ var walkthroughCss = `
|
|
|
18586
19772
|
.knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
|
|
18587
19773
|
.knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
|
|
18588
19774
|
.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
19775
|
.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
19776
|
.knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
|
|
18592
19777
|
.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 +19782,6 @@ var walkthroughCss = `
|
|
|
18597
19782
|
: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
19783
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
18599
19784
|
.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
19785
|
|
|
18605
19786
|
/* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
|
|
18606
19787
|
script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
|
|
@@ -18611,7 +19792,6 @@ var walkthroughCss = `
|
|
|
18611
19792
|
.knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
18612
19793
|
.knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
|
|
18613
19794
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
|
|
18614
|
-
.knobs-wt-disc-count{color:var(--_k-fg-3)}
|
|
18615
19795
|
.knobs-wt-disc-body{overflow:hidden}
|
|
18616
19796
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
|
|
18617
19797
|
.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 +19971,10 @@ var walkthroughCss = `
|
|
|
18791
19971
|
/* A slot's label: sentence case as written, small and faint, never a heading. */
|
|
18792
19972
|
.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
19973
|
.knobs-wt-body{min-width:0}
|
|
19974
|
+
/* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
|
|
19975
|
+
a small blur on the quick spring, from the top; at rest, nothing. */
|
|
19976
|
+
.knobs-wt-body,.knobs-wt-frame>img{transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
19977
|
+
.knobs-wt-body[data-entering],.knobs-wt-frame[data-loading]>img{opacity:0;filter:blur(3px);transition:none}
|
|
18794
19978
|
/* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
|
|
18795
19979
|
slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
|
|
18796
19980
|
.knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
|
|
@@ -18805,9 +19989,12 @@ var walkthroughCss = `
|
|
|
18805
19989
|
.knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
|
|
18806
19990
|
.knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
|
|
18807
19991
|
.knobs-wt-frame[data-natural]>img{position:static;height:auto}
|
|
18808
|
-
|
|
19992
|
+
/* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
|
|
19993
|
+
control radius and room inside, so a diff's buttons and the code under them can breathe. */
|
|
19994
|
+
.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)}
|
|
19995
|
+
.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
19996
|
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)}
|
|
19997
|
+
.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
19998
|
.knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
18812
19999
|
.knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
|
|
18813
20000
|
.knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
|
|
@@ -18834,48 +20021,37 @@ var walkthroughCss = `
|
|
|
18834
20021
|
.knobs-wt-hold{border-radius:var(--kw-frame-r);background:var(--_k-fill)}
|
|
18835
20022
|
.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
20023
|
|
|
20024
|
+
/* Annotations: a calm ring round a box, a dot or an arrow at a point, a small label beside it;
|
|
20025
|
+
each fades in and out with a small blur on the quick spring as its time comes and goes, and a
|
|
20026
|
+
page's switch (the eye, or a) hides them all. Placed in px by script, 1:1 with the picture;
|
|
20027
|
+
last in their box, so they sit over the picture (and under a compare's grip and labels). */
|
|
20028
|
+
.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)}
|
|
20029
|
+
.knobs-wt-anno:not([data-settled]),.knobs-wt-anno:not([data-settled]) *{transition:none!important}
|
|
20030
|
+
[data-annotations="off"] .knobs-wt-anno,.knobs-wt-anno[data-moving]{opacity:0;filter:blur(3px)}
|
|
20031
|
+
.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)}
|
|
20032
|
+
.knobs-wt-mark[data-hidden]{opacity:0;filter:blur(3px)}
|
|
20033
|
+
.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)}
|
|
20034
|
+
.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)}
|
|
20035
|
+
.knobs-wt-anno-arrow{position:absolute;right:0;bottom:0;filter:drop-shadow(0 0 2px var(--_k-bg))}
|
|
20036
|
+
.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);
|
|
20037
|
+
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}}
|
|
20038
|
+
.knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
|
|
20039
|
+
.knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
|
|
20040
|
+
.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;
|
|
20041
|
+
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;
|
|
20042
|
+
transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
20043
|
+
.knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20044
|
+
.knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
20045
|
+
.knobs-cmp-stage>.knobs-wt-anno-toggle{top:auto;bottom:8px}
|
|
20046
|
+
|
|
18837
20047
|
/* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
|
|
18838
20048
|
hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
|
|
18839
|
-
:where(.knobs-wt-
|
|
20049
|
+
:where(.knobs-wt-stack,.knobs-wt-tablebox,.knobs-wt-chapters){--kw-line:var(--_k-leading-body);--kw-rail:var(--_k-border-strong)}
|
|
18840
20050
|
:where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
18841
20051
|
:where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
|
|
18842
20052
|
:where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
18843
20053
|
:where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
|
|
18844
20054
|
: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
20055
|
|
|
18880
20056
|
/* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
|
|
18881
20057
|
git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
|
|
@@ -18920,6 +20096,12 @@ var walkthroughCss = `
|
|
|
18920
20096
|
/* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
|
|
18921
20097
|
and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
|
|
18922
20098
|
.knobs-wt-tablebox{min-width:0}
|
|
20099
|
+
/* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
|
|
20100
|
+
without one line up. */
|
|
20101
|
+
.knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
|
|
20102
|
+
.knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
20103
|
+
.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)}
|
|
20104
|
+
.knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
18923
20105
|
.knobs-wt-tablebox[data-measure] table{width:min-content}
|
|
18924
20106
|
.knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
|
|
18925
20107
|
.knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
|
|
@@ -19091,6 +20273,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19091
20273
|
fit.style.setProperty("--kw-aspect", String(aspect));
|
|
19092
20274
|
let lastZoom = "all";
|
|
19093
20275
|
let lastLoop = "off";
|
|
20276
|
+
let marks = null;
|
|
20277
|
+
let marking = null;
|
|
19094
20278
|
const c = compare(fit, {
|
|
19095
20279
|
...look,
|
|
19096
20280
|
before: side(before, "Before"),
|
|
@@ -19103,6 +20287,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19103
20287
|
onMode: (next) => {
|
|
19104
20288
|
fit.toggleAttribute("data-overlay", next !== "side");
|
|
19105
20289
|
onEvent?.({ what: "mode", detail: next });
|
|
20290
|
+
const layer = marks?.element;
|
|
20291
|
+
if (layer) {
|
|
20292
|
+
layer.dataset.moving = "";
|
|
20293
|
+
setTimeout(() => {
|
|
20294
|
+
delete layer.dataset.moving;
|
|
20295
|
+
marks?.update();
|
|
20296
|
+
}, MARK_GLIDE);
|
|
20297
|
+
}
|
|
19106
20298
|
},
|
|
19107
20299
|
onView: (view) => {
|
|
19108
20300
|
const z = view.zoom;
|
|
@@ -19113,8 +20305,23 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19113
20305
|
const loopKey = l ? `${formatTime(l.start)}–${formatTime(l.end)}` : "off";
|
|
19114
20306
|
if (loopKey !== lastLoop)
|
|
19115
20307
|
onEvent?.({ what: "loop", detail: lastLoop = loopKey });
|
|
20308
|
+
marking?.kick(MARK_GLIDE);
|
|
19116
20309
|
}
|
|
19117
20310
|
});
|
|
20311
|
+
const stage = fit.querySelector(".knobs-cmp-stage");
|
|
20312
|
+
if (m.annotations?.length && stage) {
|
|
20313
|
+
const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
|
|
20314
|
+
marks = annotationLayer(stage, m.annotations, () => {
|
|
20315
|
+
const l = compareLayout({ x: 0, y: 0, w: stage.clientWidth, h: stage.clientHeight }, shape, shape);
|
|
20316
|
+
return {
|
|
20317
|
+
rects: c.mode === "side" ? [l.side.before, l.side.after] : [l.overlay.after],
|
|
20318
|
+
view: c.zoom,
|
|
20319
|
+
time: c.time
|
|
20320
|
+
};
|
|
20321
|
+
});
|
|
20322
|
+
const layer = marks;
|
|
20323
|
+
marking = follow(doc.defaultView, () => c.playing, () => layer.update());
|
|
20324
|
+
}
|
|
19118
20325
|
const chapters = m.chapters?.length ? m.chapters : null;
|
|
19119
20326
|
if (chapters)
|
|
19120
20327
|
c.timeline?.update({
|
|
@@ -19131,10 +20338,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19131
20338
|
const unlisten = c.timeline?.listen({
|
|
19132
20339
|
onSeek: (time, source) => {
|
|
19133
20340
|
pills?.sync();
|
|
20341
|
+
marking?.kick();
|
|
19134
20342
|
if (source !== "play")
|
|
19135
20343
|
onEvent?.({ what: "seek", time: round2(time) });
|
|
19136
20344
|
},
|
|
19137
|
-
onPlay: (on) =>
|
|
20345
|
+
onPlay: (on) => {
|
|
20346
|
+
marking?.kick();
|
|
20347
|
+
onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) });
|
|
20348
|
+
}
|
|
19138
20349
|
});
|
|
19139
20350
|
requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
|
|
19140
20351
|
return {
|
|
@@ -19161,6 +20372,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19161
20372
|
};
|
|
19162
20373
|
},
|
|
19163
20374
|
dispose: () => {
|
|
20375
|
+
marking?.stop();
|
|
20376
|
+
marks?.dispose();
|
|
19164
20377
|
unlisten?.();
|
|
19165
20378
|
pills?.dispose();
|
|
19166
20379
|
c.dispose();
|
|
@@ -19169,6 +20382,7 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19169
20382
|
};
|
|
19170
20383
|
}
|
|
19171
20384
|
var round2 = (v) => Math.round(v * 100) / 100;
|
|
20385
|
+
var MARK_GLIDE = 450;
|
|
19172
20386
|
var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
|
|
19173
20387
|
function mountVideo(host, m, onEvent) {
|
|
19174
20388
|
const doc = host.ownerDocument;
|
|
@@ -19216,6 +20430,16 @@ function mountVideo(host, m, onEvent) {
|
|
|
19216
20430
|
if (pills)
|
|
19217
20431
|
for (const type of ["timeupdate", "seeking", "seeked"])
|
|
19218
20432
|
v.addEventListener(type, sync);
|
|
20433
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20434
|
+
rects: [
|
|
20435
|
+
contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
|
|
20436
|
+
],
|
|
20437
|
+
time: v.currentTime
|
|
20438
|
+
})) : null;
|
|
20439
|
+
const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
|
|
20440
|
+
if (marking)
|
|
20441
|
+
for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
|
|
20442
|
+
v.addEventListener(type, () => marking.kick());
|
|
19219
20443
|
return {
|
|
19220
20444
|
pause: () => v.pause(),
|
|
19221
20445
|
seekBy: (d) => {
|
|
@@ -19229,6 +20453,8 @@ function mountVideo(host, m, onEvent) {
|
|
|
19229
20453
|
...pills?.current ? { chapter: pills.current.label } : {}
|
|
19230
20454
|
}),
|
|
19231
20455
|
dispose: () => {
|
|
20456
|
+
marking?.stop();
|
|
20457
|
+
marks?.dispose();
|
|
19232
20458
|
pills?.dispose();
|
|
19233
20459
|
v.pause();
|
|
19234
20460
|
v.removeAttribute("src");
|
|
@@ -19246,9 +20472,52 @@ function mountImage(host, m) {
|
|
|
19246
20472
|
img.decoding = "async";
|
|
19247
20473
|
img.alt = m.alt ?? m.label ?? "";
|
|
19248
20474
|
img.src = m.src;
|
|
20475
|
+
if (!img.complete) {
|
|
20476
|
+
frame.dataset.loading = "";
|
|
20477
|
+
img.addEventListener("load", () => delete frame.dataset.loading, { once: true });
|
|
20478
|
+
img.addEventListener("error", () => delete frame.dataset.loading, { once: true });
|
|
20479
|
+
}
|
|
19249
20480
|
frame.append(img);
|
|
19250
20481
|
host.append(frame);
|
|
19251
|
-
|
|
20482
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20483
|
+
rects: [{ x: 0, y: 0, w: frame.clientWidth, h: frame.clientHeight }]
|
|
20484
|
+
})) : null;
|
|
20485
|
+
if (marks)
|
|
20486
|
+
img.addEventListener("load", () => marks.update(), { once: true });
|
|
20487
|
+
return {
|
|
20488
|
+
dispose: () => {
|
|
20489
|
+
marks?.dispose();
|
|
20490
|
+
frame.remove();
|
|
20491
|
+
}
|
|
20492
|
+
};
|
|
20493
|
+
}
|
|
20494
|
+
function contain(box, natural) {
|
|
20495
|
+
if (!(natural.w > 0 && natural.h > 0))
|
|
20496
|
+
return { x: 0, y: 0, w: box.w, h: box.h };
|
|
20497
|
+
const k = Math.min(box.w / natural.w, box.h / natural.h);
|
|
20498
|
+
const w = natural.w * k;
|
|
20499
|
+
const h = natural.h * k;
|
|
20500
|
+
return { x: (box.w - w) / 2, y: (box.h - h) / 2, w, h };
|
|
20501
|
+
}
|
|
20502
|
+
function follow(win, on, fn) {
|
|
20503
|
+
let raf = 0;
|
|
20504
|
+
let until = 0;
|
|
20505
|
+
const tick = () => {
|
|
20506
|
+
fn();
|
|
20507
|
+
raf = on() || performance.now() < until ? win.requestAnimationFrame(tick) : 0;
|
|
20508
|
+
};
|
|
20509
|
+
return {
|
|
20510
|
+
kick(ms = 0) {
|
|
20511
|
+
until = Math.max(until, performance.now() + ms);
|
|
20512
|
+
if (!raf)
|
|
20513
|
+
raf = win.requestAnimationFrame(tick);
|
|
20514
|
+
},
|
|
20515
|
+
stop() {
|
|
20516
|
+
if (raf)
|
|
20517
|
+
win.cancelAnimationFrame(raf);
|
|
20518
|
+
raf = 0;
|
|
20519
|
+
}
|
|
20520
|
+
};
|
|
19252
20521
|
}
|
|
19253
20522
|
async function mountDiff(host, m, look, alive, onEvent) {
|
|
19254
20523
|
const lib = await Promise.resolve().then(() => (init_diff(), {}));
|
|
@@ -19402,6 +20671,14 @@ function mediaSlot(container, options) {
|
|
|
19402
20671
|
let pending = false;
|
|
19403
20672
|
let disposed = false;
|
|
19404
20673
|
let generation = 0;
|
|
20674
|
+
const born = performance.now();
|
|
20675
|
+
const enter = () => {
|
|
20676
|
+
const win = doc.defaultView;
|
|
20677
|
+
if (!win || performance.now() - born < 120 || reducedMotion(win))
|
|
20678
|
+
return;
|
|
20679
|
+
host.dataset.entering = "";
|
|
20680
|
+
win.requestAnimationFrame(() => win.requestAnimationFrame(() => delete host.dataset.entering));
|
|
20681
|
+
};
|
|
19405
20682
|
const hold = (px) => {
|
|
19406
20683
|
host.style.minHeight = px === null ? "" : `${px}px`;
|
|
19407
20684
|
host.classList.toggle("knobs-wt-hold", px !== null);
|
|
@@ -19421,6 +20698,7 @@ function mediaSlot(container, options) {
|
|
|
19421
20698
|
if (!alive())
|
|
19422
20699
|
return r.dispose();
|
|
19423
20700
|
mounted = r;
|
|
20701
|
+
enter();
|
|
19424
20702
|
if (filled)
|
|
19425
20703
|
r.fill?.(true);
|
|
19426
20704
|
if (r.ready)
|
|
@@ -19513,14 +20791,14 @@ function mediaSlot(container, options) {
|
|
|
19513
20791
|
var CHECK3 = icon("check", { small: true });
|
|
19514
20792
|
|
|
19515
20793
|
// src/ui/walkthrough.ts
|
|
19516
|
-
var
|
|
20794
|
+
var el3 = (doc, tag, cls) => {
|
|
19517
20795
|
const n = doc.createElement(tag);
|
|
19518
20796
|
if (cls)
|
|
19519
20797
|
n.className = cls;
|
|
19520
20798
|
return n;
|
|
19521
20799
|
};
|
|
19522
20800
|
var prose2 = (doc, tag, cls, text) => {
|
|
19523
|
-
const n =
|
|
20801
|
+
const n = el3(doc, tag, cls);
|
|
19524
20802
|
n.innerHTML = inlineMarkdown(text);
|
|
19525
20803
|
return n;
|
|
19526
20804
|
};
|
|
@@ -19559,7 +20837,7 @@ function tasteTest(container, options) {
|
|
|
19559
20837
|
});
|
|
19560
20838
|
const pickable = options.pickable !== false;
|
|
19561
20839
|
let picked = pickable ? options.picked ?? null : null;
|
|
19562
|
-
const root =
|
|
20840
|
+
const root = el3(doc, "div", "knobs-wt-options");
|
|
19563
20841
|
if (pickable) {
|
|
19564
20842
|
root.setAttribute("role", "radiogroup");
|
|
19565
20843
|
root.setAttribute("aria-label", "Options");
|
|
@@ -19570,33 +20848,33 @@ function tasteTest(container, options) {
|
|
|
19570
20848
|
applyLook2(root, options);
|
|
19571
20849
|
const slots = [];
|
|
19572
20850
|
const tiles = list.map((o, i) => {
|
|
19573
|
-
const tile =
|
|
20851
|
+
const tile = el3(doc, "div", "knobs-wt-option");
|
|
19574
20852
|
if (!pickable) {
|
|
19575
|
-
const name =
|
|
20853
|
+
const name = el3(doc, "span", "knobs-wt-slot-label");
|
|
19576
20854
|
name.textContent = o.label;
|
|
19577
20855
|
tile.append(name);
|
|
19578
20856
|
}
|
|
19579
|
-
const pick =
|
|
20857
|
+
const pick = el3(doc, "button", "knobs-wt-pick");
|
|
19580
20858
|
pick.type = "button";
|
|
19581
20859
|
pick.setAttribute("role", "radio");
|
|
19582
20860
|
if (i < 9)
|
|
19583
20861
|
pick.setAttribute("aria-keyshortcuts", String(i + 1));
|
|
19584
|
-
const letter =
|
|
20862
|
+
const letter = el3(doc, "span", "knobs-wt-letter");
|
|
19585
20863
|
letter.textContent = o.letter;
|
|
19586
|
-
const name =
|
|
20864
|
+
const name = el3(doc, "span", "knobs-wt-olabel");
|
|
19587
20865
|
name.textContent = o.label;
|
|
19588
20866
|
pick.append(letter, name);
|
|
19589
20867
|
if (o.suggested) {
|
|
19590
|
-
const s =
|
|
20868
|
+
const s = el3(doc, "span", "knobs-wt-suggested");
|
|
19591
20869
|
s.textContent = "Suggested";
|
|
19592
20870
|
pick.append(s);
|
|
19593
20871
|
}
|
|
19594
20872
|
if (i < 9) {
|
|
19595
|
-
const key =
|
|
20873
|
+
const key = el3(doc, "span", "knobs-wt-key");
|
|
19596
20874
|
key.textContent = String(i + 1);
|
|
19597
20875
|
pick.append(key);
|
|
19598
20876
|
}
|
|
19599
|
-
const check =
|
|
20877
|
+
const check = el3(doc, "span", "knobs-wt-check");
|
|
19600
20878
|
check.innerHTML = CHECK3;
|
|
19601
20879
|
pick.append(check);
|
|
19602
20880
|
pick.addEventListener("click", () => {
|
|
@@ -19662,7 +20940,7 @@ function walkthrough(container, options) {
|
|
|
19662
20940
|
injectWalkthroughStyles(doc);
|
|
19663
20941
|
const look = lookOf2(options);
|
|
19664
20942
|
const storage = options.storage === undefined ? browserStorage() : options.storage;
|
|
19665
|
-
const root =
|
|
20943
|
+
const root = el3(doc, "div", "knobs-walkthrough");
|
|
19666
20944
|
applyLook2(root, options);
|
|
19667
20945
|
container.append(root);
|
|
19668
20946
|
let page = normalizeWalkthrough(options.doc);
|
|
@@ -19875,7 +21153,7 @@ function walkthrough(container, options) {
|
|
|
19875
21153
|
return true;
|
|
19876
21154
|
return false;
|
|
19877
21155
|
};
|
|
19878
|
-
const scrim =
|
|
21156
|
+
const scrim = el3(doc, "div", "knobs-wt-scrim");
|
|
19879
21157
|
scrim.addEventListener("click", () => setFull(false));
|
|
19880
21158
|
const fullRect = () => ({
|
|
19881
21159
|
x: DOCK_INSET,
|
|
@@ -19925,7 +21203,7 @@ function walkthrough(container, options) {
|
|
|
19925
21203
|
}
|
|
19926
21204
|
if (!full) {
|
|
19927
21205
|
const r = v.card.getBoundingClientRect();
|
|
19928
|
-
const hold =
|
|
21206
|
+
const hold = el3(doc, "div", "knobs-wt-hold-card");
|
|
19929
21207
|
hold.style.height = `${r.height}px`;
|
|
19930
21208
|
v.card.before(hold);
|
|
19931
21209
|
v.card.dataset.full = "";
|
|
@@ -20075,7 +21353,7 @@ function walkthrough(container, options) {
|
|
|
20075
21353
|
toast("Couldn't reach the microphone", { tone: "warn", look, doc });
|
|
20076
21354
|
}
|
|
20077
21355
|
};
|
|
20078
|
-
const backPill =
|
|
21356
|
+
const backPill = el3(doc, "button", "knobs-wt-back");
|
|
20079
21357
|
backPill.type = "button";
|
|
20080
21358
|
let backTo = null;
|
|
20081
21359
|
let backTimer = 0;
|
|
@@ -20095,7 +21373,7 @@ function walkthrough(container, options) {
|
|
|
20095
21373
|
return;
|
|
20096
21374
|
backTo = key;
|
|
20097
21375
|
backPill.innerHTML = ICON.back;
|
|
20098
|
-
const words =
|
|
21376
|
+
const words = el3(doc, "span", "knobs-wt-back-label");
|
|
20099
21377
|
words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
|
|
20100
21378
|
backPill.append(words);
|
|
20101
21379
|
backPill.dataset.on = "";
|
|
@@ -20103,16 +21381,16 @@ function walkthrough(container, options) {
|
|
|
20103
21381
|
backTimer = win.setTimeout(hideBack, BACK_MS);
|
|
20104
21382
|
};
|
|
20105
21383
|
const bar = (() => {
|
|
20106
|
-
const dock =
|
|
20107
|
-
const box =
|
|
20108
|
-
const on =
|
|
20109
|
-
const row =
|
|
20110
|
-
const input =
|
|
21384
|
+
const dock = el3(doc, "div", "knobs-wt-dock");
|
|
21385
|
+
const box = el3(doc, "div", "knobs-wt-notebar");
|
|
21386
|
+
const on = el3(doc, "div", "knobs-wt-on");
|
|
21387
|
+
const row = el3(doc, "div", "knobs-wt-row");
|
|
21388
|
+
const input = el3(doc, "textarea", "knobs-wt-input");
|
|
20111
21389
|
input.rows = 1;
|
|
20112
21390
|
input.setAttribute("aria-label", "Note");
|
|
20113
|
-
const attach =
|
|
21391
|
+
const attach = el3(doc, "div", "knobs-wt-attach");
|
|
20114
21392
|
const btn = (icon, label, run) => {
|
|
20115
|
-
const b =
|
|
21393
|
+
const b = el3(doc, "button", "knobs-wt-tool");
|
|
20116
21394
|
b.type = "button";
|
|
20117
21395
|
b.innerHTML = icon;
|
|
20118
21396
|
b.setAttribute("aria-label", label);
|
|
@@ -20123,17 +21401,17 @@ function walkthrough(container, options) {
|
|
|
20123
21401
|
const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
|
|
20124
21402
|
const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
|
|
20125
21403
|
const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
|
|
20126
|
-
const micTime =
|
|
21404
|
+
const micTime = el3(doc, "span", "knobs-wt-rec");
|
|
20127
21405
|
micBtn.append(micTime);
|
|
20128
21406
|
const sendBtn = btn(ICON.send, "Send · Enter", () => send());
|
|
20129
21407
|
sendBtn.dataset.send = "";
|
|
20130
21408
|
row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
|
|
20131
21409
|
box.append(on, row);
|
|
20132
|
-
const launch =
|
|
21410
|
+
const launch = el3(doc, "button", "knobs-wt-launch");
|
|
20133
21411
|
launch.type = "button";
|
|
20134
21412
|
launch.innerHTML = ICON.pen;
|
|
20135
21413
|
launch.append("Note");
|
|
20136
|
-
const kbd =
|
|
21414
|
+
const kbd = el3(doc, "kbd");
|
|
20137
21415
|
kbd.textContent = "n";
|
|
20138
21416
|
launch.append(kbd);
|
|
20139
21417
|
launch.setAttribute("aria-label", "Write a note (n)");
|
|
@@ -20185,9 +21463,9 @@ function walkthrough(container, options) {
|
|
|
20185
21463
|
}
|
|
20186
21464
|
});
|
|
20187
21465
|
const chip = (text, clear) => {
|
|
20188
|
-
const c =
|
|
21466
|
+
const c = el3(doc, "span", "knobs-wt-chip-a");
|
|
20189
21467
|
c.append(text);
|
|
20190
|
-
const x =
|
|
21468
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
20191
21469
|
x.type = "button";
|
|
20192
21470
|
x.innerHTML = ICON.close;
|
|
20193
21471
|
x.setAttribute("aria-label", `Remove ${text}`);
|
|
@@ -20336,9 +21614,9 @@ function walkthrough(container, options) {
|
|
|
20336
21614
|
}
|
|
20337
21615
|
};
|
|
20338
21616
|
const renderNote = (v, n, entering = false) => {
|
|
20339
|
-
const row =
|
|
21617
|
+
const row = el3(doc, "div", "knobs-wt-n");
|
|
20340
21618
|
row.dataset.id = n.id;
|
|
20341
|
-
const meta =
|
|
21619
|
+
const meta = el3(doc, "span", "knobs-wt-n-meta");
|
|
20342
21620
|
const m = n.context.media;
|
|
20343
21621
|
meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
|
|
20344
21622
|
if (m?.time !== undefined) {
|
|
@@ -20354,11 +21632,11 @@ function walkthrough(container, options) {
|
|
|
20354
21632
|
});
|
|
20355
21633
|
}
|
|
20356
21634
|
row.append(meta);
|
|
20357
|
-
const body =
|
|
21635
|
+
const body = el3(doc, "div", "knobs-wt-n-body");
|
|
20358
21636
|
if (n.text)
|
|
20359
21637
|
body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
|
|
20360
21638
|
if (n.voice) {
|
|
20361
|
-
const a =
|
|
21639
|
+
const a = el3(doc, "audio", "knobs-wt-n-voice");
|
|
20362
21640
|
a.controls = true;
|
|
20363
21641
|
a.preload = "none";
|
|
20364
21642
|
a.src = n.voice.url;
|
|
@@ -20378,7 +21656,7 @@ function walkthrough(container, options) {
|
|
|
20378
21656
|
body.append(thumb);
|
|
20379
21657
|
}
|
|
20380
21658
|
row.append(body);
|
|
20381
|
-
const x =
|
|
21659
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
20382
21660
|
x.type = "button";
|
|
20383
21661
|
x.innerHTML = ICON.close;
|
|
20384
21662
|
x.setAttribute("aria-label", "Delete this note");
|
|
@@ -20418,9 +21696,9 @@ function walkthrough(container, options) {
|
|
|
20418
21696
|
row.addEventListener("transitionend", done, { once: true });
|
|
20419
21697
|
win.setTimeout(done, 600);
|
|
20420
21698
|
};
|
|
20421
|
-
const metaLine = (cls) =>
|
|
21699
|
+
const metaLine = (cls) => el3(doc, "div", `knobs-wt-metaline ${cls}`);
|
|
20422
21700
|
const metaText = (line, text, cls) => {
|
|
20423
|
-
const s =
|
|
21701
|
+
const s = el3(doc, "span", cls);
|
|
20424
21702
|
s.textContent = text;
|
|
20425
21703
|
line.append(s);
|
|
20426
21704
|
return s;
|
|
@@ -20433,7 +21711,7 @@ function walkthrough(container, options) {
|
|
|
20433
21711
|
line.append(chip);
|
|
20434
21712
|
return chip;
|
|
20435
21713
|
}
|
|
20436
|
-
const a =
|
|
21714
|
+
const a = el3(doc, "a");
|
|
20437
21715
|
a.href = href;
|
|
20438
21716
|
a.target = "_blank";
|
|
20439
21717
|
a.rel = "noreferrer";
|
|
@@ -20443,58 +21721,58 @@ function walkthrough(container, options) {
|
|
|
20443
21721
|
return a;
|
|
20444
21722
|
};
|
|
20445
21723
|
const feedItem = (f, now) => {
|
|
20446
|
-
const li =
|
|
21724
|
+
const li = el3(doc, "li", "knobs-wt-fi");
|
|
20447
21725
|
li.dataset.kind = f.kind;
|
|
20448
|
-
const q =
|
|
21726
|
+
const q = el3(doc, "div", "knobs-wt-fi-q");
|
|
20449
21727
|
const meta = metaLine("knobs-wt-fi-meta");
|
|
20450
21728
|
if (f.at !== null) {
|
|
20451
21729
|
const when = metaText(meta, ago(f.at, now));
|
|
20452
21730
|
when.title = new Date(f.at).toLocaleString();
|
|
20453
|
-
}
|
|
20454
|
-
metaText(meta, f.kind === "qa" ? "Answer" : "Update");
|
|
21731
|
+
}
|
|
20455
21732
|
const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
|
|
20456
21733
|
if (step) {
|
|
20457
21734
|
const section = page.sections.find((s) => s.id === step.section);
|
|
20458
|
-
const b =
|
|
21735
|
+
const b = el3(doc, "button", "knobs-wt-fi-step");
|
|
20459
21736
|
b.type = "button";
|
|
20460
21737
|
b.textContent = section?.link?.label ?? plainMarkdown(step.title);
|
|
20461
21738
|
b.title = `Go to “${plainMarkdown(step.title)}”`;
|
|
20462
21739
|
b.addEventListener("click", () => go(step.key));
|
|
20463
21740
|
meta.append(b);
|
|
20464
21741
|
}
|
|
20465
|
-
if (f.status) {
|
|
21742
|
+
if (f.status && review) {
|
|
20466
21743
|
const id = f.status.toLowerCase();
|
|
20467
21744
|
const shipped = /^(ship|shipped|merged|done)$/.test(id);
|
|
20468
21745
|
const changes = /^(changes?|changes queued)$/.test(id);
|
|
20469
21746
|
const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
|
|
20470
21747
|
s.dataset.status = shipped ? "on" : changes ? "warn" : "";
|
|
20471
21748
|
}
|
|
20472
|
-
|
|
21749
|
+
if (meta.childNodes.length)
|
|
21750
|
+
q.append(meta);
|
|
20473
21751
|
if (f.q)
|
|
20474
21752
|
q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
|
|
20475
21753
|
else
|
|
20476
21754
|
q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
|
|
20477
21755
|
li.append(q);
|
|
20478
21756
|
if (f.q || f.trail.length) {
|
|
20479
|
-
const a =
|
|
21757
|
+
const a = el3(doc, "div", "knobs-wt-fi-a");
|
|
20480
21758
|
if (f.q)
|
|
20481
21759
|
a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
|
|
20482
21760
|
if (f.trail.length) {
|
|
20483
|
-
const list =
|
|
21761
|
+
const list = el3(doc, "ol", "knobs-wt-trail");
|
|
20484
21762
|
for (const line of f.trail) {
|
|
20485
21763
|
const i = line.indexOf(" · ");
|
|
20486
|
-
const row =
|
|
20487
|
-
const words =
|
|
21764
|
+
const row = el3(doc, "li");
|
|
21765
|
+
const words = el3(doc, "span", "knobs-wt-trail-text");
|
|
20488
21766
|
words.textContent = i < 0 ? line : line.slice(i + 3);
|
|
20489
21767
|
row.append(words);
|
|
20490
21768
|
if (i >= 0) {
|
|
20491
|
-
const at =
|
|
21769
|
+
const at = el3(doc, "span", "knobs-wt-trail-at");
|
|
20492
21770
|
at.textContent = line.slice(0, i);
|
|
20493
21771
|
row.append(at);
|
|
20494
21772
|
}
|
|
20495
21773
|
list.append(row);
|
|
20496
21774
|
}
|
|
20497
|
-
discs.push(disclosure(a, { label: "Context",
|
|
21775
|
+
discs.push(disclosure(a, { label: "Context", content: list }));
|
|
20498
21776
|
}
|
|
20499
21777
|
li.append(a);
|
|
20500
21778
|
}
|
|
@@ -20522,11 +21800,11 @@ function walkthrough(container, options) {
|
|
|
20522
21800
|
root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
|
|
20523
21801
|
root.toggleAttribute("data-cards", options.cards === true);
|
|
20524
21802
|
root.toggleAttribute("data-review", review);
|
|
20525
|
-
const layout =
|
|
20526
|
-
const main =
|
|
20527
|
-
const flow =
|
|
20528
|
-
const head =
|
|
20529
|
-
const h1 =
|
|
21803
|
+
const layout = el3(doc, "div", "knobs-wt-layout");
|
|
21804
|
+
const main = el3(doc, "div", "knobs-wt-main");
|
|
21805
|
+
const flow = el3(doc, "div", "knobs-wt-flow");
|
|
21806
|
+
const head = el3(doc, "header", "knobs-wt-head");
|
|
21807
|
+
const h1 = el3(doc, "h1", "knobs-wt-title");
|
|
20530
21808
|
h1.textContent = page.title;
|
|
20531
21809
|
head.append(h1);
|
|
20532
21810
|
if (page.summary)
|
|
@@ -20534,13 +21812,13 @@ function walkthrough(container, options) {
|
|
|
20534
21812
|
if (page.more)
|
|
20535
21813
|
head.append(more(page.more));
|
|
20536
21814
|
if (page.meta.length) {
|
|
20537
|
-
const meta =
|
|
21815
|
+
const meta = el3(doc, "div", "knobs-wt-meta");
|
|
20538
21816
|
for (const m of page.meta) {
|
|
20539
|
-
const s =
|
|
21817
|
+
const s = el3(doc, "span");
|
|
20540
21818
|
s.append(m.label);
|
|
20541
|
-
const b =
|
|
21819
|
+
const b = el3(doc, "b");
|
|
20542
21820
|
if (m.href) {
|
|
20543
|
-
const a =
|
|
21821
|
+
const a = el3(doc, "a");
|
|
20544
21822
|
a.href = m.href;
|
|
20545
21823
|
a.target = "_blank";
|
|
20546
21824
|
a.rel = "noreferrer";
|
|
@@ -20556,16 +21834,14 @@ function walkthrough(container, options) {
|
|
|
20556
21834
|
flow.append(head);
|
|
20557
21835
|
let feedEl = null;
|
|
20558
21836
|
if (page.feed.length) {
|
|
20559
|
-
feedEl =
|
|
21837
|
+
feedEl = el3(doc, "section", "knobs-wt-feed");
|
|
20560
21838
|
feedEl.id = "wt-latest";
|
|
20561
21839
|
feedEl.setAttribute("aria-label", "Updates and answers");
|
|
20562
|
-
const fh =
|
|
20563
|
-
const h =
|
|
21840
|
+
const fh = el3(doc, "header", "knobs-wt-feed-head");
|
|
21841
|
+
const h = el3(doc, "h2", "knobs-wt-feed-title");
|
|
20564
21842
|
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");
|
|
21843
|
+
fh.append(h);
|
|
21844
|
+
const list = el3(doc, "ol", "knobs-wt-feed-list");
|
|
20569
21845
|
const now = Date.now();
|
|
20570
21846
|
for (const f of page.feed)
|
|
20571
21847
|
list.append(feedItem(f, now));
|
|
@@ -20575,21 +21851,21 @@ function walkthrough(container, options) {
|
|
|
20575
21851
|
let rail = null;
|
|
20576
21852
|
count = fill = tocEl = null;
|
|
20577
21853
|
if (options.rail !== false) {
|
|
20578
|
-
rail =
|
|
21854
|
+
rail = el3(doc, "aside", "knobs-wt-rail");
|
|
20579
21855
|
rail.setAttribute("aria-label", "Contents");
|
|
20580
|
-
tocEl =
|
|
21856
|
+
tocEl = el3(doc, "nav", "knobs-wt-toc");
|
|
20581
21857
|
tocEl.setAttribute("aria-label", "Contents");
|
|
20582
21858
|
if (feedEl) {
|
|
20583
|
-
const group =
|
|
21859
|
+
const group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20584
21860
|
const target = feedEl;
|
|
20585
21861
|
group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
|
|
20586
21862
|
tocEl.append(group);
|
|
20587
21863
|
}
|
|
20588
21864
|
if (review) {
|
|
20589
|
-
const progress =
|
|
20590
|
-
count =
|
|
20591
|
-
const meter =
|
|
20592
|
-
fill =
|
|
21865
|
+
const progress = el3(doc, "div", "knobs-wt-progress");
|
|
21866
|
+
count = el3(doc, "div", "knobs-wt-count");
|
|
21867
|
+
const meter = el3(doc, "div", "knobs-wt-bar");
|
|
21868
|
+
fill = el3(doc, "div", "knobs-wt-fill");
|
|
20593
21869
|
meter.append(fill);
|
|
20594
21870
|
progress.append(count, meter);
|
|
20595
21871
|
rail.append(progress);
|
|
@@ -20598,15 +21874,15 @@ function walkthrough(container, options) {
|
|
|
20598
21874
|
if (review)
|
|
20599
21875
|
rail.append(actions(true));
|
|
20600
21876
|
if (review && options.keys !== false) {
|
|
20601
|
-
const keys =
|
|
21877
|
+
const keys = el3(doc, "div", "knobs-wt-keys");
|
|
20602
21878
|
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
21879
|
rail.append(keys);
|
|
20604
21880
|
}
|
|
20605
21881
|
}
|
|
20606
21882
|
for (const section of page.sections) {
|
|
20607
|
-
const sec =
|
|
21883
|
+
const sec = el3(doc, "section", "knobs-wt-section");
|
|
20608
21884
|
sec.id = `wt-${section.id}`;
|
|
20609
|
-
const sh =
|
|
21885
|
+
const sh = el3(doc, "header", "knobs-wt-shead");
|
|
20610
21886
|
const status = review ? section.status : undefined;
|
|
20611
21887
|
if (section.kicker || section.link || status) {
|
|
20612
21888
|
const meta = metaLine("knobs-wt-kicker");
|
|
@@ -20620,10 +21896,10 @@ function walkthrough(container, options) {
|
|
|
20620
21896
|
}
|
|
20621
21897
|
sh.append(meta);
|
|
20622
21898
|
}
|
|
20623
|
-
const h2 =
|
|
21899
|
+
const h2 = el3(doc, "h2", "knobs-wt-heading");
|
|
20624
21900
|
h2.innerHTML = inlineMarkdown(section.title);
|
|
20625
21901
|
sh.append(h2);
|
|
20626
|
-
const intro =
|
|
21902
|
+
const intro = el3(doc, "div", "knobs-wt-sintro");
|
|
20627
21903
|
intro.append(sh);
|
|
20628
21904
|
if (section.summary)
|
|
20629
21905
|
intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
|
|
@@ -20632,7 +21908,7 @@ function walkthrough(container, options) {
|
|
|
20632
21908
|
sec.append(intro);
|
|
20633
21909
|
let group = null;
|
|
20634
21910
|
if (tocEl) {
|
|
20635
|
-
group =
|
|
21911
|
+
group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20636
21912
|
const first = section.steps[0]?.key;
|
|
20637
21913
|
group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
|
|
20638
21914
|
tocEl.append(group);
|
|
@@ -20674,8 +21950,8 @@ function walkthrough(container, options) {
|
|
|
20674
21950
|
}));
|
|
20675
21951
|
};
|
|
20676
21952
|
const more = (text) => {
|
|
20677
|
-
const box =
|
|
20678
|
-
const body =
|
|
21953
|
+
const box = el3(doc, "div", "knobs-wt-more");
|
|
21954
|
+
const body = el3(doc, "div", "knobs-wt-more-text");
|
|
20679
21955
|
for (const para of text.split(/\n\s*\n/))
|
|
20680
21956
|
if (para.trim())
|
|
20681
21957
|
body.append(prose2(doc, "p", "", para.trim()));
|
|
@@ -20683,24 +21959,25 @@ function walkthrough(container, options) {
|
|
|
20683
21959
|
return box;
|
|
20684
21960
|
};
|
|
20685
21961
|
const tocRow = (label, sub, run, section) => {
|
|
20686
|
-
const b =
|
|
21962
|
+
const b = el3(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
|
|
20687
21963
|
b.type = "button";
|
|
20688
21964
|
if (section)
|
|
20689
21965
|
b.dataset.section = section;
|
|
20690
|
-
const t =
|
|
21966
|
+
const t = el3(doc, "span", "knobs-wt-toc-label");
|
|
20691
21967
|
t.textContent = label;
|
|
20692
|
-
const e = el2(doc, "span", "knobs-wt-toc-end");
|
|
20693
21968
|
b.title = label;
|
|
20694
|
-
b.append(t
|
|
21969
|
+
b.append(t);
|
|
21970
|
+
if (review)
|
|
21971
|
+
b.append(el3(doc, "span", "knobs-wt-toc-end"));
|
|
20695
21972
|
b.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
20696
21973
|
b.addEventListener("click", run);
|
|
20697
21974
|
tocRows.push(b);
|
|
20698
21975
|
return b;
|
|
20699
21976
|
};
|
|
20700
21977
|
const actions = (rail) => {
|
|
20701
|
-
const row =
|
|
21978
|
+
const row = el3(doc, "div", "knobs-wt-actions");
|
|
20702
21979
|
const btn = (text, run, primary = false) => {
|
|
20703
|
-
const b =
|
|
21980
|
+
const b = el3(doc, "button", "knobs-wt-btn");
|
|
20704
21981
|
b.type = "button";
|
|
20705
21982
|
b.textContent = text;
|
|
20706
21983
|
if (primary)
|
|
@@ -20738,13 +22015,13 @@ function walkthrough(container, options) {
|
|
|
20738
22015
|
}
|
|
20739
22016
|
};
|
|
20740
22017
|
const stepView = (step, group) => {
|
|
20741
|
-
const card =
|
|
22018
|
+
const card = el3(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
|
|
20742
22019
|
card.id = `wt-${step.key.replace("/", "--")}`;
|
|
20743
22020
|
card.tabIndex = -1;
|
|
20744
22021
|
card.dataset.key = step.key;
|
|
20745
22022
|
card.setAttribute("aria-label", plainMarkdown(step.title));
|
|
20746
|
-
const head =
|
|
20747
|
-
const h3 =
|
|
22023
|
+
const head = el3(doc, "header", "knobs-wt-step-head");
|
|
22024
|
+
const h3 = el3(doc, "h3", "knobs-wt-stepTitle");
|
|
20748
22025
|
h3.innerHTML = inlineMarkdown(step.title);
|
|
20749
22026
|
head.append(h3);
|
|
20750
22027
|
if (step.caption)
|
|
@@ -20756,7 +22033,7 @@ function walkthrough(container, options) {
|
|
|
20756
22033
|
const onEvent = (e) => record({ ...e, step: step.key });
|
|
20757
22034
|
let media = null;
|
|
20758
22035
|
if (step.media.length) {
|
|
20759
|
-
media =
|
|
22036
|
+
media = el3(doc, "div", "knobs-wt-media");
|
|
20760
22037
|
media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
|
|
20761
22038
|
for (const m of step.media)
|
|
20762
22039
|
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
|
|
@@ -20776,7 +22053,7 @@ function walkthrough(container, options) {
|
|
|
20776
22053
|
});
|
|
20777
22054
|
media ??= taste.element;
|
|
20778
22055
|
}
|
|
20779
|
-
const notes =
|
|
22056
|
+
const notes = el3(doc, "div", "knobs-wt-notes");
|
|
20780
22057
|
if (review)
|
|
20781
22058
|
card.append(notes);
|
|
20782
22059
|
card.addEventListener("focusin", () => {
|
|
@@ -20790,7 +22067,7 @@ function walkthrough(container, options) {
|
|
|
20790
22067
|
if (group) {
|
|
20791
22068
|
toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
|
|
20792
22069
|
if (review) {
|
|
20793
|
-
glyph =
|
|
22070
|
+
glyph = el3(doc, "span", "knobs-wt-toc-glyph");
|
|
20794
22071
|
toc.querySelector(".knobs-wt-toc-end").append(glyph);
|
|
20795
22072
|
}
|
|
20796
22073
|
group.append(toc);
|
|
@@ -20864,6 +22141,10 @@ function walkthrough(container, options) {
|
|
|
20864
22141
|
claim();
|
|
20865
22142
|
return;
|
|
20866
22143
|
}
|
|
22144
|
+
if (key === "a" && !e.shiftKey && root.querySelector(".knobs-wt-anno")) {
|
|
22145
|
+
claim();
|
|
22146
|
+
return setAnnotations(root, !annotationsOn(root));
|
|
22147
|
+
}
|
|
20867
22148
|
if (key === "n" && noteMode !== "hidden") {
|
|
20868
22149
|
claim();
|
|
20869
22150
|
bar.setOpen(true, true);
|
|
@@ -20962,7 +22243,7 @@ function walkthrough(container, options) {
|
|
|
20962
22243
|
}
|
|
20963
22244
|
|
|
20964
22245
|
// src/ui/solid.ts
|
|
20965
|
-
var
|
|
22246
|
+
var follow2 = (read, apply) => {
|
|
20966
22247
|
const changes = propChanges(untrack(read));
|
|
20967
22248
|
createEffect(() => {
|
|
20968
22249
|
const changed = changes(read());
|
|
@@ -21010,7 +22291,7 @@ function Tree(props) {
|
|
|
21010
22291
|
}
|
|
21011
22292
|
}));
|
|
21012
22293
|
untrack(() => props.onReady?.(t));
|
|
21013
|
-
|
|
22294
|
+
follow2(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
|
|
21014
22295
|
if ("items" in c)
|
|
21015
22296
|
t.update(c.items);
|
|
21016
22297
|
if ("filter" in c)
|
|
@@ -21046,7 +22327,7 @@ function CodeDiff(props) {
|
|
|
21046
22327
|
onViewed: props.onViewed && ((viewed, file) => props.onViewed?.(viewed, file))
|
|
21047
22328
|
}));
|
|
21048
22329
|
untrack(() => props.onReady?.(d));
|
|
21049
|
-
|
|
22330
|
+
follow2(diffProps(props), (c) => {
|
|
21050
22331
|
if (c.height)
|
|
21051
22332
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
21052
22333
|
d.update(c);
|
|
@@ -21069,7 +22350,7 @@ function DiffReview(props) {
|
|
|
21069
22350
|
onViewed: (viewed, file) => props.onViewed?.(viewed, file)
|
|
21070
22351
|
}));
|
|
21071
22352
|
untrack(() => props.onReady?.(r));
|
|
21072
|
-
|
|
22353
|
+
follow2(diffProps(props), (c) => {
|
|
21073
22354
|
if (c.height)
|
|
21074
22355
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
21075
22356
|
r.update(c);
|
|
@@ -21091,7 +22372,7 @@ function Timeline(props) {
|
|
|
21091
22372
|
onCross: (m) => props.onCross?.(m)
|
|
21092
22373
|
}));
|
|
21093
22374
|
untrack(() => props.onReady?.(t));
|
|
21094
|
-
|
|
22375
|
+
follow2(() => ({
|
|
21095
22376
|
duration: props.duration,
|
|
21096
22377
|
fps: props.fps,
|
|
21097
22378
|
units: props.units,
|
|
@@ -21120,7 +22401,7 @@ function Compare(props) {
|
|
|
21120
22401
|
onView: (view) => props.onView?.(view)
|
|
21121
22402
|
}));
|
|
21122
22403
|
untrack(() => props.onReady?.(c));
|
|
21123
|
-
|
|
22404
|
+
follow2(() => ({
|
|
21124
22405
|
before: props.before,
|
|
21125
22406
|
after: props.after,
|
|
21126
22407
|
mode: props.mode,
|
|
@@ -21143,7 +22424,7 @@ function CompareStrip(props) {
|
|
|
21143
22424
|
host.style.height = "100%";
|
|
21144
22425
|
const s = untrack(() => compareStrip(host, { ...props, onPick: (time) => props.onPick?.(time) }));
|
|
21145
22426
|
untrack(() => props.onReady?.(s));
|
|
21146
|
-
|
|
22427
|
+
follow2(() => ({ before: props.before.src, after: props.after.src }), (changed) => s.update({
|
|
21147
22428
|
..."before" in changed ? { before: props.before } : {},
|
|
21148
22429
|
..."after" in changed ? { after: props.after } : {}
|
|
21149
22430
|
}));
|
|
@@ -21163,7 +22444,7 @@ function Bento(props) {
|
|
|
21163
22444
|
onActive: (id) => props.onActive?.(id)
|
|
21164
22445
|
}));
|
|
21165
22446
|
untrack(() => props.onReady?.(b));
|
|
21166
|
-
|
|
22447
|
+
follow2(() => ({ items: props.items, picks: props.picks }), (c) => {
|
|
21167
22448
|
if ("items" in c)
|
|
21168
22449
|
b.update(c.items);
|
|
21169
22450
|
if (c.picks)
|
|
@@ -21185,7 +22466,7 @@ function Activity(props) {
|
|
|
21185
22466
|
onPlay: props.onPlay && ((id, event) => props.onPlay?.(id, event))
|
|
21186
22467
|
}));
|
|
21187
22468
|
untrack(() => props.onReady?.(a));
|
|
21188
|
-
|
|
22469
|
+
follow2(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
|
|
21189
22470
|
if (c.events || c.newest)
|
|
21190
22471
|
a.update({ events: c.events, newest: c.newest });
|
|
21191
22472
|
if (c.selected !== undefined && c.selected !== a.selected)
|
|
@@ -21214,7 +22495,7 @@ function OptionPicker(props) {
|
|
|
21214
22495
|
}
|
|
21215
22496
|
}));
|
|
21216
22497
|
untrack(() => props.onReady?.(p));
|
|
21217
|
-
|
|
22498
|
+
follow2(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
|
|
21218
22499
|
if ("options" in c)
|
|
21219
22500
|
p.set(c.options);
|
|
21220
22501
|
if (c.selected != null)
|
|
@@ -21231,7 +22512,7 @@ function Walkthrough(props) {
|
|
|
21231
22512
|
const host = document.createElement("div");
|
|
21232
22513
|
const w = untrack(() => walkthrough(host, { ...props, onFeedback: (feedback) => props.onFeedback?.(feedback) }));
|
|
21233
22514
|
untrack(() => props.onReady?.(w));
|
|
21234
|
-
|
|
22515
|
+
follow2(() => ({ doc: props.doc }), (c) => c.doc && w.set(c.doc));
|
|
21235
22516
|
onCleanup(() => w.dispose());
|
|
21236
22517
|
return host;
|
|
21237
22518
|
}
|
|
@@ -21241,7 +22522,7 @@ function TasteTest(props) {
|
|
|
21241
22522
|
const host = document.createElement("div");
|
|
21242
22523
|
const t = untrack(() => tasteTest(host, { ...props, onPick: (id) => props.onPick?.(id) }));
|
|
21243
22524
|
untrack(() => props.onReady?.(t));
|
|
21244
|
-
|
|
22525
|
+
follow2(() => ({ picked: props.picked }), (c) => ("picked" in c) && t.pick(c.picked ?? null));
|
|
21245
22526
|
onCleanup(() => t.dispose());
|
|
21246
22527
|
return host;
|
|
21247
22528
|
}
|