@rig-c/review 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -12
- package/docs/DESIGN.md +10 -6
- package/package.json +1 -1
- package/src/chest-regions.ts +253 -0
- package/web/chest-regions-tool.ts +51 -0
- package/web/compare.css +1 -0
- package/web/index.html +3 -6
- package/web/main.ts +69 -38
- package/web/rig-overlays.ts +10 -2
- package/web/styles.css +78 -21
- package/web/view-controls.ts +11 -5
- package/web/workspace-layout.ts +2 -1
- package/web/workspace-ui.ts +23 -11
package/web/styles.css
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* Theme tokens: site values copied from rig-c.org styles.css (dark); light and --rv-* are review-local derivations. No literal colour outside these blocks. */
|
|
2
|
-
:root{--font-sans:Inter,system-ui,sans-serif;--font-display:Inter,system-ui,sans-serif;--font-mono:"JetBrains Mono",ui-monospace,monospace;--radius:10px;--bg:#070b18;--fg:#c9d4e8;--fg-strong:#f3f6fc;--fg-soft:#aebbd4;--fg-muted:#7f8ba6;--border:#18213a;--border-strong:#2a3657;--hover-bg:#0f1730;--active-bg:#142042;--link:#7cc8ff;--link-hover:#b3e0ff;--btn-fg:#08101f;--code-bg:#050814;--code-fg:#d6e2f5;--code-border:#18213a;--code-inline-bg:#121a30;--footer-bg:#050813;--pass:#30d158;--warn:#ffcc00;--fail:#ff453a;--rv-bar:#0b1124;--rv-panel:#0d152b;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:var(--active-bg);--rv-field:var(--code-bg);--rv-segmented:var(--code-bg);--rv-pressed-bg:#133733;--rv-pressed-border:#207c44;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link-hover);--rv-backdrop:rgba(7,11,24,.82);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020}
|
|
3
|
-
:root[data-theme=light]{--bg:#f5f7fb;--fg:#26324a;--fg-strong:#0c1324;--fg-soft:#3b4864;--fg-muted:#5d6982;--border:#d5dcea;--border-strong:#b3bed3;--hover-bg:#e8edf6;--active-bg:#dbe3f3;--link:#1a6fc0;--link-hover:#0f528f;--btn-fg:#ffffff;--code-bg:#eef1f7;--code-fg:#1d2940;--code-border:#d5dcea;--code-inline-bg:#e3e8f2;--footer-bg:#eceff6;--pass:#1e8a3c;--warn:#8a6400;--fail:#b3261e;--rv-bar:#edf1f7;--rv-panel:#e9eef6;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:#d5ddec;--rv-field:#ffffff;--rv-segmented:var(--code-inline-bg);--rv-pressed-bg:#c9ded8;--rv-pressed-border:#6fb286;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link);--rv-backdrop:rgba(12,19,36,.45);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020}
|
|
4
|
-
:root{font-family:var(--font-sans);color:var(--fg);background:var(--bg);font-size:13px;color-scheme:dark;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button{color:inherit;background:var(--rv-control);border:1px solid var(--border-strong);border-radius:6px;padding:7px 10px;cursor:pointer}button:hover{background:var(--rv-control-hover)}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}button.primary{background:var(--link);border-color:var(--link);color:var(--btn-fg);font-weight:600}button.outline{background:transparent}button.block{width:100%;margin-top:10px}a{color:var(--link)}input,select,textarea{color:var(--fg);background:var(--rv-field);border:1px solid var(--border-strong);border-radius:6px;padding:8px;max-width:100%}select,input[type=text],textarea{width:100%}textarea{resize:vertical;line-height:1.6}input[type=range]{accent-color:var(--link);padding:0;min-width:0}input[type=checkbox],input[type=radio]{accent-color:var(--link)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--rv-focus);outline-offset:3px}[hidden]{display:none!important}.topbar{min-height:72px;padding:15px 22px;display:flex;align-items:center;gap:25px;border-bottom:1px solid var(--border);flex-wrap:wrap;background:var(--rv-bar)}.brand{font-size:19px;letter-spacing:-.6px;white-space:nowrap}.brand-mark{display:inline-grid;place-items:center;width:24px;height:24px;border:1px solid var(--link);color:var(--link);border-radius:50%;margin-right:9px;font-size:14px}.topbar strong{font-weight:500;font-size:13px}.topbar #revision{margin-left:9px}.top-actions{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}.subtle,.hint{color:var(--fg-muted);font-size:11px}.hint{line-height:1.6}.workspaces{display:flex;gap:10px;padding:0 18px;border-bottom:1px solid var(--border);background:var(--rv-bar)}.workspaces button{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;padding:15px 13px;font-size:12px;letter-spacing:.06em;color:var(--fg-soft)}.workspaces button[aria-pressed=true]{border-bottom-color:var(--link);color:var(--fg-strong);background:transparent}.workspaces button span{font-size:11px;margin-left:8px;letter-spacing:0;color:var(--fg-muted)}.layout{display:grid;grid-template-columns:190px minmax(250px,1fr) 286px;min-height:calc(100vh - 160px)}aside{background:var(--rv-panel);padding:20px 15px}.left-panel{border-right:1px solid var(--border)}.right-panel{border-left:1px solid var(--border)}.eyebrow{font-size:11px;letter-spacing:.08em;color:var(--fg-muted);margin:0 0 11px}.label{display:block;color:var(--fg-muted);font-size:11px;margin:0 0 7px}.space-top{margin-top:15px}.left-panel section{margin-top:26px}.tool{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;gap:5px;border-color:transparent;background:transparent;padding:9px 6px;margin:3px 0}.tool small{font-size:11px;color:var(--fg-muted)}.checkpoint{display:block;width:100%;background:transparent;border:1px solid transparent;text-align:left;margin:4px 0;padding:10px 9px;white-space:normal;line-height:1.5}.checkpoint[aria-current=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.checkpoint small{display:block;font-size:11px;color:var(--fg-muted);margin-top:3px}.source-link{display:block;font-size:11px;overflow-wrap:anywhere;margin:8px 0}main{min-width:0;display:flex;flex-direction:column;background:var(--bg)}.viewbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px;border-bottom:1px solid var(--border)}.
|
|
2
|
+
:root{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--font-sans:Inter,system-ui,sans-serif;--font-display:Inter,system-ui,sans-serif;--font-mono:"JetBrains Mono",ui-monospace,monospace;--radius:10px;--bg:#070b18;--fg:#c9d4e8;--fg-strong:#f3f6fc;--fg-soft:#aebbd4;--fg-muted:#7f8ba6;--border:#18213a;--border-strong:#2a3657;--hover-bg:#0f1730;--active-bg:#142042;--link:#7cc8ff;--link-hover:#b3e0ff;--btn-fg:#08101f;--code-bg:#050814;--code-fg:#d6e2f5;--code-border:#18213a;--code-inline-bg:#121a30;--footer-bg:#050813;--pass:#30d158;--warn:#ffcc00;--fail:#ff453a;--rv-bar:#0b1124;--rv-panel:#0d152b;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:var(--active-bg);--rv-field:var(--code-bg);--rv-segmented:var(--code-bg);--rv-pressed-bg:#133733;--rv-pressed-border:#207c44;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link-hover);--rv-backdrop:rgba(7,11,24,.82);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#2a3657;--rv-scrollbar-thumb-hover:#3d4c74;--rv-scrollbar-track:transparent;--rv-separator:#1a2747;--rv-separator-hover:#3f7fb8}
|
|
3
|
+
:root[data-theme=light]{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--bg:#f5f7fb;--fg:#26324a;--fg-strong:#0c1324;--fg-soft:#3b4864;--fg-muted:#5d6982;--border:#d5dcea;--border-strong:#b3bed3;--hover-bg:#e8edf6;--active-bg:#dbe3f3;--link:#1a6fc0;--link-hover:#0f528f;--btn-fg:#ffffff;--code-bg:#eef1f7;--code-fg:#1d2940;--code-border:#d5dcea;--code-inline-bg:#e3e8f2;--footer-bg:#eceff6;--pass:#1e8a3c;--warn:#8a6400;--fail:#b3261e;--rv-bar:#edf1f7;--rv-panel:#e9eef6;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:#d5ddec;--rv-field:#ffffff;--rv-segmented:var(--code-inline-bg);--rv-pressed-bg:#c9ded8;--rv-pressed-border:#6fb286;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link);--rv-backdrop:rgba(12,19,36,.45);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#b3bed3;--rv-scrollbar-thumb-hover:#8e9bb5;--rv-scrollbar-track:transparent;--rv-separator:#c9d6ec;--rv-separator-hover:#5b93cf}
|
|
4
|
+
:root{font-family:var(--font-sans);color:var(--fg);background:var(--bg);font-size:13px;color-scheme:dark;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button{color:inherit;background:var(--rv-control);border:1px solid var(--border-strong);border-radius:6px;padding:7px 10px;cursor:pointer}button:hover{background:var(--rv-control-hover)}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}button.primary{background:var(--link);border-color:var(--link);color:var(--btn-fg);font-weight:600}button.outline{background:transparent}button.block{width:100%;margin-top:10px}a{color:var(--link)}input,select,textarea{color:var(--fg);background:var(--rv-field);border:1px solid var(--border-strong);border-radius:6px;padding:8px;max-width:100%}select,input[type=text],textarea{width:100%}textarea{resize:vertical;line-height:1.6}input[type=range]{accent-color:var(--link);padding:0;min-width:0}input[type=checkbox],input[type=radio]{accent-color:var(--link)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--rv-focus);outline-offset:3px}[hidden]{display:none!important}.topbar{min-height:72px;padding:15px 22px;display:flex;align-items:center;gap:25px;border-bottom:1px solid var(--border);flex-wrap:wrap;background:var(--rv-bar)}.brand{font-size:19px;letter-spacing:-.6px;white-space:nowrap}.brand-mark{display:inline-grid;place-items:center;width:24px;height:24px;border:1px solid var(--link);color:var(--link);border-radius:50%;margin-right:9px;font-size:14px}.topbar strong{font-weight:500;font-size:13px}.topbar #revision{margin-left:9px}.top-actions{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}.subtle,.hint{color:var(--fg-muted);font-size:11px}.hint{line-height:1.6}.workspaces{display:flex;gap:10px;padding:0 18px;border-bottom:1px solid var(--border);background:var(--rv-bar)}.workspaces button{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;padding:15px 13px;font-size:12px;letter-spacing:.06em;color:var(--fg-soft)}.workspaces button[aria-pressed=true]{border-bottom-color:var(--link);color:var(--fg-strong);background:transparent}.workspaces button span{font-size:11px;margin-left:8px;letter-spacing:0;color:var(--fg-muted)}.layout{display:grid;grid-template-columns:190px minmax(250px,1fr) 286px;min-height:calc(100vh - 160px)}aside{background:var(--rv-panel);padding:20px 15px}.left-panel{border-right:1px solid var(--border)}.right-panel{border-left:1px solid var(--border)}.eyebrow{font-size:11px;letter-spacing:.08em;color:var(--fg-muted);margin:0 0 11px}.label{display:block;color:var(--fg-muted);font-size:11px;margin:0 0 7px}.space-top{margin-top:15px}.left-panel section{margin-top:26px}.tool{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;gap:5px;border-color:transparent;background:transparent;padding:9px 6px;margin:3px 0}.tool small{font-size:11px;color:var(--fg-muted)}.checkpoint{display:block;width:100%;background:transparent;border:1px solid transparent;text-align:left;margin:4px 0;padding:10px 9px;white-space:normal;line-height:1.5}.checkpoint[aria-current=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.checkpoint small{display:block;font-size:11px;color:var(--fg-muted);margin-top:3px}.source-link{display:block;font-size:11px;overflow-wrap:anywhere;margin:8px 0}main{min-width:0;display:flex;flex-direction:column;background:var(--bg)}.viewbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px;border-bottom:1px solid var(--border)}.zoom-controls{display:flex;gap:4px;align-items:center}.zoom-controls button{padding:4px 8px;font-size:12px}.zoom-controls output{font:11px var(--font-mono);min-width:39px;text-align:center;color:var(--fg-muted)}.stages{display:flex;gap:10px;padding:12px 12px 0;flex:1;min-height:460px}.stage-pane{position:relative;min-width:0;flex:1;border:1px solid var(--border-strong);border-radius:8px;overflow:hidden;background:radial-gradient(ellipse at 50% 40%,var(--rv-pane-hi),var(--rv-pane-lo) 74%)}.pane-label{position:absolute;left:10px;top:10px;z-index:2;background:var(--rv-pane-label-bg);border-radius:4px;padding:4px 7px;color:var(--rv-stage-fg);font-size:11px;pointer-events:none}.viewport{position:relative;overflow:hidden;width:100%;height:100%;min-height:450px;touch-action:none;background-image:radial-gradient(var(--rv-grid-dot) .7px,transparent .7px);background-size:22px 22px}.empty{position:absolute;inset:65px 22px 30px;display:grid;place-items:center;text-align:center;color:var(--rv-stage-fg);font-size:12px;line-height:1.8}.reference-content,#rig-root{position:absolute;transform-origin:0 0}.reference-content img,.reference-content video{display:block;max-width:none}.reference-content svg{display:block;max-width:none}.reference-content video{object-fit:contain}.stage-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:11px 16px;color:var(--fg-muted);font-size:11px}.transport{display:flex;align-items:center;gap:11px;padding:13px;border-top:1px solid var(--border);background:var(--rv-panel)}.transport select{width:auto;max-width:150px;font-size:11px}.transport input{flex:1}.transport output{font:11px var(--font-mono);white-space:nowrap;color:var(--fg-muted)}.sound-panel,.angle-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel)}.row{display:flex;align-items:center;gap:11px;flex-wrap:wrap}.row .eyebrow{margin:0}.sound-panel .row{font-size:11px;margin-bottom:9px}.sound-panel input[type=range]{flex:1;max-width:160px}.cue{display:flex;gap:9px;align-items:center;justify-content:space-between;border-top:1px solid var(--border);padding:9px 0;font-size:11px;flex-wrap:wrap}.cue button{padding:4px 7px;font-size:11px}.angle-panel{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:11px}.angle-panel select{width:auto;max-width:150px}.angle-panel .hint{width:100%;margin:0}.angle-panel input{flex:1}.divider{height:1px;background:var(--border-strong);margin:22px 0}h1{font-size:15px;font-weight:500;line-height:1.6;margin:0 0 7px}.outcome{border:0;padding:0;margin:18px 0 0;display:flex;gap:11px;flex-wrap:wrap}.outcome legend{font-size:11px;color:var(--fg-muted);margin-bottom:8px;padding:0}.outcome label{display:flex;align-items:center;gap:3px;font-size:11px}.proposal-item{border-top:1px solid var(--border);padding:11px 0;margin-top:12px;font-size:11px;overflow-wrap:anywhere}.proposal-item strong{display:block;font-weight:500;margin-bottom:5px}.proposal-item button{font-size:11px;padding:3px 6px;margin-top:7px}footer{display:flex;justify-content:space-between;align-items:center;gap:6px 15px;flex-wrap:nowrap;height:40px;box-sizing:border-box;overflow:hidden;padding:0 18px;border-top:1px solid var(--border);background:var(--footer-bg);font-size:11px;color:var(--fg-soft)}.error{color:var(--fail)!important}.dirty{color:var(--warn)!important}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
|
|
5
5
|
@media(min-width:1500px){.layout{grid-template-columns:218px minmax(250px,1fr) 310px}.stages{min-height:600px}}
|
|
6
6
|
@media(max-width:1000px){.layout{grid-template-columns:158px minmax(250px,1fr) 250px}aside{padding:17px 12px}.topbar{gap:14px}.topbar #revision{display:block;margin-left:0}.stages{min-height:400px}.viewport{min-height:400px}.workspaces span{display:none}}
|
|
7
7
|
@media(max-width:780px){.layout{grid-template-columns:150px minmax(0,1fr)}.right-panel{grid-column:1/-1;border-left:0;border-top:1px solid var(--border);display:grid;grid-template-columns:1fr 1fr;gap:12px 24px}.right-panel>.eyebrow,.right-panel>.label,.right-panel>#review-status{grid-column:1/-1}.right-panel>.divider{display:none}#checkpoint-detail,#proposal-panel{min-width:0}.top-actions{margin-left:0}.stages{min-height:420px}.viewport{min-height:420px}.transport{flex-wrap:wrap}.transport input{min-width:130px}.topbar{padding:14px 16px}}
|
|
8
|
-
@media(max-width:540px){.layout{grid-template-columns:1fr}.left-panel{border-right:0;border-bottom:1px solid var(--border);display:grid;grid-template-columns:1fr 1fr;gap:12px}.left-panel>.eyebrow,.left-panel>#checkpoints{grid-column:1/-1}.left-panel section{margin-top:8px}#checkpoints{display:flex;gap:5px;flex-wrap:wrap}.checkpoint{width:auto;flex:1;min-width:120px}.right-panel{display:block}#proposal-panel{margin-top:25px}.stages{gap:6px;padding:8px}.stage-pane{border-radius:5px}.viewport{min-height:365px}.stages{min-height:365px}.pane-label{left:5px;top:6px;font-size:11px;padding:3px}.viewbar{gap:8px}.workspaces{padding:0 8px}.workspaces button{padding:13px 9px}.stage-footer{padding:9px 12px}.source-link{font-size:11px}footer{padding:
|
|
8
|
+
@media(max-width:540px){.layout{grid-template-columns:1fr}.left-panel{border-right:0;border-bottom:1px solid var(--border);display:grid;grid-template-columns:1fr 1fr;gap:12px}.left-panel>.eyebrow,.left-panel>#checkpoints{grid-column:1/-1}.left-panel section{margin-top:8px}#checkpoints{display:flex;gap:5px;flex-wrap:wrap}.checkpoint{width:auto;flex:1;min-width:120px}.right-panel{display:block}#proposal-panel{margin-top:25px}.stages{gap:6px;padding:8px}.stage-pane{border-radius:5px}.viewport{min-height:365px}.stages{min-height:365px}.pane-label{left:5px;top:6px;font-size:11px;padding:3px}.viewbar{gap:8px}.workspaces{padding:0 8px}.workspaces button{padding:13px 9px}.stage-footer{padding:9px 12px}.source-link{font-size:11px}footer{padding:0 12px}}
|
|
9
9
|
@media(pointer:coarse){button{min-height:42px}input,select,textarea{font-size:16px}.outcome label{min-height:40px}.zoom-controls button{min-width:38px}}
|
|
10
10
|
|
|
11
11
|
.frame-range{display:grid;grid-template-columns:1fr 1fr;gap:9px}.frame-range label{font-size:11px;color:var(--fg-muted)}.frame-range input{width:100%;margin-top:6px}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
input::placeholder,textarea::placeholder{color:var(--fg-muted)}
|
|
38
38
|
|
|
39
39
|
:focus-visible{outline-color:var(--rv-focus)}
|
|
40
|
+
.chest-regions{margin-top:22px;padding-top:15px;border-top:1px solid var(--border)}.chest-regions code{font:11px var(--font-mono)}.chest-grid,.chest-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 10px}.chest-row{border:1px solid var(--border);border-radius:6px;padding:9px 10px;margin:10px 0 0;min-width:0}.chest-row legend{font-size:11px;color:var(--fg-muted);padding:0 4px}.chest-grid label,.chest-row label{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--fg-muted);min-width:0}.chest-row input,.chest-row select,.chest-grid input{width:100%;padding:5px 6px;font-size:11px}.chest-row .chest-check{flex-direction:row;align-items:center;gap:6px}.chest-row .chest-check input{width:auto}.chest-more{margin-top:10px;font-size:11px;color:var(--fg-muted)}.chest-more summary{cursor:pointer}#chest-status.error{color:var(--fail)}
|
|
40
41
|
/* Source pixels retain their original color; the neutral stage stays dark in both themes. */
|
|
41
42
|
|
|
42
43
|
/* The application shell never scrolls; each Inspector tab and the source list owns overflow. */
|
|
@@ -48,11 +49,11 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
48
49
|
.inspector{height:clamp(205px,32dvh,340px);min-height:180px;flex:none;display:flex;flex-direction:column;border-top:1px solid var(--border);background:var(--rv-panel);overflow:hidden}
|
|
49
50
|
.inspector-heading{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:7px 12px;border-bottom:1px solid var(--border);flex:none;font-size:11px}
|
|
50
51
|
.inspector-heading strong{font-size:10px;letter-spacing:.08em;font-weight:500}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
54
55
|
.inspector-body{flex:1;min-height:0;overflow:hidden}
|
|
55
|
-
.inspector-body>[
|
|
56
|
+
.inspector-body>[data-inspector-panel]{height:100%;overflow:auto;padding:12px 15px;min-height:0}
|
|
56
57
|
.inspector .inspection-controls{padding:0;border:0;background:transparent}
|
|
57
58
|
.inspector .provenance{margin-top:10px}
|
|
58
59
|
.inspector .transport{padding:0 0 10px;border:0;background:transparent;flex-wrap:wrap}
|
|
@@ -71,7 +72,7 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
71
72
|
@media(max-width:780px){.layout{grid-template-columns:130px minmax(0,1fr)!important}.inspector{height:36dvh}.stage-footer{padding:5px 10px}.inspector-heading>.hint{display:none}}
|
|
72
73
|
@media(max-width:540px){.layout{grid-template-columns:minmax(0,1fr)!important}.left-panel{display:block;padding:8px;font-size:11px}.layout{grid-template-columns:105px minmax(0,1fr)!important}.topbar,header{padding:7px 10px;gap:8px}.topbar .brand,header .brand{font-size:14px}.top-actions,.actions{gap:4px}.top-actions button,.actions button{padding:4px 6px;font-size:10px}.workspaces button{padding:6px}.inspector{height:40dvh;min-height:150px}.inspector-heading{gap:8px;padding:5px 8px}.inspector-heading button{padding:4px 7px}.stage-footer{display:none}}
|
|
73
74
|
|
|
74
|
-
.inspector .feedback-form>label,.inspector .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}.inspector .frame-range input{width:100%}.inspector [
|
|
75
|
+
.inspector .feedback-form>label,.inspector .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}.inspector .frame-range input{width:100%}.inspector [data-inspector-panel]{overscroll-behavior:contain}.left-panel{overscroll-behavior:contain}#stage-layout .pane-label{max-width:calc(100% - 15px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
75
76
|
|
|
76
77
|
.numeric-view{font:11px/1.5 var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;max-width:100%;padding:8px;background:var(--code-bg);color:var(--code-fg);border-radius:5px}.view-link-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}.draft-notice{flex:none;font-size:11px;padding:0 12px;color:var(--warn)}.draft-notice:has(#draft-state:empty){display:none}.draft-notice #draft-recovery{display:inline-flex;gap:8px;margin-left:8px;flex-wrap:wrap}.draft-notice button{font-size:11px;padding:2px 6px}
|
|
77
78
|
|
|
@@ -91,7 +92,7 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
91
92
|
.workspace-stage-stack>#stage-layout{flex:1;min-height:0;height:auto;overflow:hidden}
|
|
92
93
|
.workspace-layout>.left-panel{grid-column:1;grid-row:1;display:flex!important;flex-direction:column;overflow:hidden;min-width:0;padding:0;border-right:1px solid var(--border)}
|
|
93
94
|
.workspace-sources{flex:1;min-height:0;overflow:auto;padding:10px;overscroll-behavior:contain}
|
|
94
|
-
.workspace-settings{
|
|
95
|
+
.workspace-settings{padding:10px;margin:12px -10px 0;border-top:1px solid var(--border)}
|
|
95
96
|
.workspace-settings h2{font-size:12px;margin:0 0 8px}
|
|
96
97
|
.workspace-settings label{display:flex;align-items:center;gap:6px;font-size:11px;margin:7px 0}
|
|
97
98
|
.workspace-settings select{width:auto;min-width:0;flex:1}
|
|
@@ -99,12 +100,14 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
99
100
|
.workspace-settings input[type=checkbox]{width:auto;flex:none}
|
|
100
101
|
.workspace-layout>.parts-explorer{display:block;grid-template-columns:none;grid-column:5;grid-row:1;width:auto;min-width:0;min-height:0;height:auto;overflow:auto}
|
|
101
102
|
.workspace-layout .inspector{grid-row:3;height:auto!important;min-height:0!important;max-height:none;overflow:hidden}
|
|
102
|
-
.workspace-separator{position:relative;z-index:3;background:var(--
|
|
103
|
-
.workspace-separator
|
|
103
|
+
.workspace-separator{position:relative;z-index:3;background:var(--rv-separator);touch-action:none;cursor:col-resize;min-width:0;min-height:0}
|
|
104
|
+
.workspace-separator:hover{background:var(--rv-separator-hover)}
|
|
105
|
+
/* Drawn 4px (workspaceMinimums.separator); the transparent hit area stays 16px, 22px on coarse pointers. */
|
|
106
|
+
.workspace-separator::after{content:'';position:absolute;inset:0 -6px}
|
|
104
107
|
.workspace-separator:focus-visible{outline:2px solid var(--rv-focus);outline-offset:-2px;background:var(--link)}
|
|
105
108
|
.workspace-separator[data-resizing=true]{background:var(--link-hover)}
|
|
106
109
|
.workspace-side-left{grid-column:2;grid-row:1}.workspace-side-right{grid-column:4;grid-row:1}
|
|
107
|
-
.workspace-inspectorHeight{grid-row:2;cursor:row-resize}.workspace-inspectorHeight::after{inset:-
|
|
110
|
+
.workspace-inspectorHeight{grid-row:2;cursor:row-resize}.workspace-inspectorHeight::after{inset:-6px 0}
|
|
108
111
|
.workspace-drawer-triggers{display:flex;gap:6px;flex-wrap:wrap;flex:none;padding:4px 8px;min-height:0}
|
|
109
112
|
.workspace-drawer-triggers:not(:has(button:not([hidden]))){display:none}
|
|
110
113
|
.workspace-drawer-triggers button{padding:4px 7px;font-size:11px;min-height:28px}
|
|
@@ -116,14 +119,14 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
116
119
|
.workspace-drawer>.parts-explorer{display:block;grid-template-columns:none;flex:1;width:auto;min-width:0;min-height:0;height:auto;overflow:auto}
|
|
117
120
|
.workspace-drawer>.inspector{flex:1;height:auto!important;min-height:0!important;max-height:none;overflow:hidden}
|
|
118
121
|
|
|
119
|
-
@media(pointer:coarse){.workspace-separator::after{inset:0 -
|
|
122
|
+
@media(pointer:coarse){.workspace-separator::after{inset:0 -9px}.workspace-inspectorHeight::after{inset:-9px 0}.workspace-drawer-triggers button{min-height:38px}}
|
|
120
123
|
|
|
121
124
|
/* Workspace rework: parts explorer is the left column; checkpoints and reference fold above it. */
|
|
122
125
|
.package-context{border-bottom:1px solid var(--border);padding-bottom:8px;margin-bottom:10px}
|
|
123
126
|
.package-context>summary{cursor:pointer;display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;font-size:11px}
|
|
124
127
|
.package-context>summary .eyebrow{margin:0}
|
|
125
128
|
.package-context #checkpoint-title{color:var(--fg-soft);overflow-wrap:anywhere}
|
|
126
|
-
.package-context #checkpoints{
|
|
129
|
+
.package-context #checkpoints{margin-top:6px}
|
|
127
130
|
.package-context .checkpoint{padding:6px 8px;margin:2px 0}
|
|
128
131
|
.package-context section{margin-top:10px}
|
|
129
132
|
.left-panel .parts-explorer{border:0;padding:0;background:transparent;overflow:visible}
|
|
@@ -138,16 +141,25 @@ main{min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
|
138
141
|
.layer-toggle:has(input:disabled){opacity:.45;cursor:default}
|
|
139
142
|
.layer-toggle:has(input:focus-visible){outline:2px solid var(--rv-focus);outline-offset:2px}
|
|
140
143
|
.layer-toggle kbd{font:10px var(--font-mono);padding:0 4px;border:1px solid var(--border-strong);border-radius:3px;color:var(--fg-muted)}
|
|
141
|
-
|
|
142
|
-
.
|
|
143
|
-
.
|
|
144
|
-
.
|
|
145
|
-
.view-
|
|
144
|
+
/* View bar is one row: layers, camera sync and zoom, then the View menu at the right end. */
|
|
145
|
+
.viewbar{flex-wrap:nowrap;justify-content:flex-start;gap:8px;padding:8px 10px;min-width:0}
|
|
146
|
+
.viewbar>.layer-toggles{flex:0 1 auto;overflow:hidden}
|
|
147
|
+
.viewbar .zoom-controls{flex:none}
|
|
148
|
+
.view-menu{position:relative;margin-left:auto;flex:none;font-size:11px}
|
|
149
|
+
.view-menu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:5px;padding:5px 9px;border:1px solid var(--border-strong);border-radius:6px;background:var(--rv-control);white-space:nowrap}
|
|
150
|
+
.view-menu>summary::-webkit-details-marker{display:none}
|
|
151
|
+
.view-menu>summary::after{content:'\25BE';color:var(--fg-muted)}
|
|
152
|
+
.view-menu>summary:hover{background:var(--rv-control-hover)}
|
|
153
|
+
.view-menu-list{position:absolute;right:0;top:calc(100% + 4px);z-index:12;display:flex;flex-direction:column;gap:3px;min-width:150px;padding:4px;background:var(--rv-panel);border:1px solid var(--border-strong);border-radius:8px}
|
|
154
|
+
.view-menu-list button{text-align:left;font-size:11px;padding:5px 9px;border-color:transparent;background:transparent}
|
|
155
|
+
.view-menu-list button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}
|
|
156
|
+
.transport .idle-repeat{display:inline-flex;align-items:center;gap:5px;font-size:11px;white-space:nowrap;flex:none}
|
|
157
|
+
.transport .idle-repeat input{flex:none;width:auto;margin:0}
|
|
146
158
|
|
|
147
159
|
/* Footer is the one status area: message | readouts | view region and source hash. */
|
|
148
160
|
footer.status-bar{flex-wrap:nowrap}
|
|
149
161
|
.status-message{flex:1 1 180px;min-width:120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
150
|
-
.status-readouts{flex:0 1 auto;max-width:60%;display:flex;align-items:center;justify-content:flex-end;gap:2px 12px;flex-wrap:
|
|
162
|
+
.status-readouts{flex:0 1 auto;max-width:60%;display:flex;align-items:center;justify-content:flex-end;gap:2px 12px;flex-wrap:nowrap;min-width:0;overflow:hidden}.status-readouts>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
|
151
163
|
.status-readouts .draft-notice{padding:0}
|
|
152
164
|
.status-readouts .draft-notice #draft-recovery{margin-left:6px}
|
|
153
165
|
.status-source{display:flex;align-items:center;gap:12px;margin-left:auto;flex:none;white-space:nowrap}
|
|
@@ -156,3 +168,48 @@ footer.status-bar{flex-wrap:nowrap}
|
|
|
156
168
|
.view-region-body{position:absolute;right:0;bottom:calc(100% + 10px);width:min(520px,90vw);max-height:60dvh;overflow:auto;padding:10px;z-index:10;background:var(--rv-panel);border:1px solid var(--border);border-radius:7px}
|
|
157
169
|
.view-region-body .numeric-view{margin:8px 0}
|
|
158
170
|
|
|
171
|
+
|
|
172
|
+
/* Header mode switch: Setup and Animate reflect the Inspector's selected section. */
|
|
173
|
+
.mode-switch{display:flex;gap:3px;padding:3px;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-segmented)}
|
|
174
|
+
.mode-switch button{border:0;background:transparent;font-size:12px;padding:5px 12px}
|
|
175
|
+
.mode-switch button[aria-pressed=true]{background:var(--rv-pressed-bg);color:var(--rv-pressed-fg)}
|
|
176
|
+
|
|
177
|
+
/* Panel headers stay put while the panel body scrolls beneath them. */
|
|
178
|
+
.sticky-header{position:sticky;top:0;z-index:2;background:var(--rv-panel)}
|
|
179
|
+
/* The left column's fixed header row: title and search never scroll and nothing sits above them. */
|
|
180
|
+
.left-header{flex:none;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--rv-panel)}
|
|
181
|
+
.left-header h2{font-size:12px;font-weight:600;margin:0 0 6px}
|
|
182
|
+
.left-header label{display:block;font-size:11px;margin:0 0 4px}
|
|
183
|
+
.left-header input[type=search]{width:100%;min-width:0}
|
|
184
|
+
.workspace-sources{padding-top:0}
|
|
185
|
+
.workspace-sources>.package-context{padding-top:10px}
|
|
186
|
+
.workspace-settings>.sticky-header{margin:-10px -10px 8px;padding:10px 10px 6px}
|
|
187
|
+
.workspace-drawer-header{display:flex;align-items:center;justify-content:space-between;gap:8px;flex:none;font-size:12px;font-weight:600}
|
|
188
|
+
|
|
189
|
+
/* One thin scrollbar for every scrolling region, from the theme tokens: the standard properties for
|
|
190
|
+
Firefox, a 6px ::-webkit-scrollbar for Chromium/WebKit (Chromium ignores it once scrollbar-width is set). */
|
|
191
|
+
@supports not selector(::-webkit-scrollbar){*{scrollbar-width:thin;scrollbar-color:var(--rv-scrollbar-thumb) var(--rv-scrollbar-track)}}
|
|
192
|
+
*::-webkit-scrollbar{width:6px;height:6px}
|
|
193
|
+
*::-webkit-scrollbar-track{background:var(--rv-scrollbar-track)}
|
|
194
|
+
*::-webkit-scrollbar-thumb{background:var(--rv-scrollbar-thumb);border-radius:3px}
|
|
195
|
+
*::-webkit-scrollbar-thumb:hover{background:var(--rv-scrollbar-thumb-hover)}
|
|
196
|
+
*::-webkit-scrollbar-corner{background:transparent}
|
|
197
|
+
|
|
198
|
+
/* Review dialog: today's Feedback section in a native modal opened from the top bar's Write review. */
|
|
199
|
+
.review-dialog{position:fixed;inset:0;width:min(94vw,1100px);max-width:94vw;max-height:90dvh;margin:auto;padding:0;border:1px solid var(--border-strong);border-radius:8px;background:var(--rv-panel);color:inherit;overflow:hidden}
|
|
200
|
+
.review-dialog:not([open]){display:none}
|
|
201
|
+
.review-dialog[open]{display:flex;flex-direction:column}
|
|
202
|
+
.review-dialog::backdrop{background:var(--rv-backdrop)}
|
|
203
|
+
.review-dialog-header{flex:none;display:flex;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--border)}
|
|
204
|
+
.review-dialog-header h2{margin:0;font-size:14px;font-weight:600;color:var(--fg-strong)}
|
|
205
|
+
.review-dialog-header .hint{margin:0;flex:1;min-width:0}
|
|
206
|
+
.review-dialog-body{flex:1;min-height:0;overflow:auto;padding:14px 16px;overscroll-behavior:contain}
|
|
207
|
+
.review-dialog-actions{flex:none;display:flex;align-items:center;gap:12px;padding:10px 14px;border-top:1px solid var(--border)}
|
|
208
|
+
.review-message{flex:1;min-width:0;font-size:11px;color:var(--fg-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
209
|
+
.review-message.error{color:var(--fail)}
|
|
210
|
+
.review-draft-state{font-size:11px;color:var(--warn)}
|
|
211
|
+
.review-draft-state:empty{display:none}
|
|
212
|
+
.review-dialog .provenance{margin-top:10px}
|
|
213
|
+
.review-dialog .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}
|
|
214
|
+
.review-dialog .report-panel{padding:10px 0}
|
|
215
|
+
@media(max-width:540px){.review-dialog-header .hint{display:none}}
|
package/web/view-controls.ts
CHANGED
|
@@ -93,10 +93,16 @@ export function bindPan(viewport:HTMLElement,move:(dx:number,dy:number)=>void,en
|
|
|
93
93
|
return ()=>{blur();window.removeEventListener('blur',blur);document.removeEventListener('keydown',keyDown,true);window.removeEventListener('keyup',keyUp,true);viewport.removeEventListener('pointerdown',start);viewport.removeEventListener('pointermove',drag);viewport.removeEventListener('pointerup',end);viewport.removeEventListener('pointercancel',end);viewport.removeEventListener('lostpointercapture',end);viewport.removeEventListener('dragstart',native);};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** beforeChange runs while the old panel is still visible; changed runs after the new one is shown.
|
|
96
|
+
/** beforeChange runs while the old panel is still visible; changed runs after the new one is shown.
|
|
97
|
+
* The selected section is the one source of the mode. A `[data-inspector]` element is a section selector:
|
|
98
|
+
* a `role=tab` in an Inspector tab row (aria-selected, roving tabindex, arrow keys) or a header `[data-mode]`
|
|
99
|
+
* toggle that is itself the selector (aria-pressed). Mode buttons without `data-inspector` select the matching
|
|
100
|
+
* tab and only reflect it; a tab with no mode button (compare's Feedback) leaves every mode button unpressed. */
|
|
97
101
|
export function bindInspectorTabs(changed:()=>void,beforeChange:()=>void=()=>{}):void {
|
|
98
|
-
const tabs=[...document.querySelectorAll<HTMLButtonElement>('[data-inspector]')];
|
|
99
|
-
const
|
|
100
|
-
|
|
102
|
+
const tabs=[...document.querySelectorAll<HTMLButtonElement>('[data-inspector]')],modes=[...document.querySelectorAll<HTMLButtonElement>('[data-mode]')].filter(mode=>!tabs.includes(mode));
|
|
103
|
+
const toggle=(item:HTMLButtonElement)=>item.dataset.mode!==undefined,state=(item:HTMLButtonElement)=>toggle(item)?'aria-pressed':'aria-selected';
|
|
104
|
+
const reflect=()=>{if(!modes.length)return;const selected=tabs.find(item=>item.getAttribute(state(item))==='true')?.dataset.inspector;for(const mode of modes)mode.setAttribute('aria-pressed',String(mode.dataset.mode===selected));};
|
|
105
|
+
const activate=(tab:HTMLButtonElement)=>{beforeChange();for(const item of tabs){const active=item===tab;item.setAttribute(state(item),String(active));if(!toggle(item))item.tabIndex=active?0:-1;}for(const panel of document.querySelectorAll<HTMLElement>('[data-inspector-panel]'))panel.hidden=panel.dataset.inspectorPanel!==tab.dataset.inspector;reflect();changed();};
|
|
106
|
+
for(const mode of modes)mode.onclick=()=>{const tab=tabs.find(item=>item.dataset.inspector===mode.dataset.mode);if(tab)activate(tab);};reflect();
|
|
107
|
+
for(const [index,tab] of tabs.entries()){tab.onclick=()=>activate(tab);if(toggle(tab))continue;tab.onkeydown=event=>{const next=event.key==='ArrowRight'?(index+1)%tabs.length:event.key==='ArrowLeft'?(index+tabs.length-1)%tabs.length:event.key==='Home'?0:event.key==='End'?tabs.length-1:null;if(next===null)return;event.preventDefault();const target=tabs[next];if(target){activate(target);target.focus();}};}
|
|
101
108
|
}
|
|
102
|
-
|
package/web/workspace-layout.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Effective workspace geometry and local DOM bindings. Never stores source or global preferences. */
|
|
2
2
|
import {parseWorkspacePanels,panelBounds,type WorkspacePanels} from './workspace-preferences.ts';
|
|
3
3
|
export type WorkspacePanel=keyof WorkspacePanels;
|
|
4
|
-
|
|
4
|
+
/** `separator` is the drawn thickness of every resize handle in CSS pixels; its hit area is wider (styles.css `::after`). */
|
|
5
|
+
export const workspaceMinimums=Object.freeze({stageWidth:240,stageHeight:180,separator:4});
|
|
5
6
|
export interface SeparatorRange {min:number;max:number;value:number;enabled:boolean}
|
|
6
7
|
export type WorkspaceColumnKey=Exclude<WorkspacePanel,'inspectorHeight'>;
|
|
7
8
|
/** Which saved width each side column uses; null means the page has no column on that side.
|
package/web/workspace-ui.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Browser workspace presentation. Source selection, player clocks and review decisions stay with the page. */
|
|
2
2
|
import {WorkspacePreferenceStore,panelBounds,type WorkspacePanels,type WorkspacePreferences,type WorkspaceTheme} from './workspace-preferences.ts';
|
|
3
|
-
import {allocateWorkspaceLayout,bindWorkspaceSeparator,bindWorkspaceDrawer,defaultWorkspaceColumns,InspectorScrollMemory,inspectorTabs,type InspectorTab,type WorkspaceColumns,type WorkspacePanel,type SeparatorRange} from './workspace-layout.ts';
|
|
3
|
+
import {allocateWorkspaceLayout,bindWorkspaceSeparator,bindWorkspaceDrawer,defaultWorkspaceColumns,InspectorScrollMemory,inspectorTabs,workspaceMinimums,type InspectorTab,type WorkspaceColumns,type WorkspacePanel,type SeparatorRange} from './workspace-layout.ts';
|
|
4
4
|
|
|
5
5
|
/** Reachable effective bounds, accounting for the other panel's saved desired size. */
|
|
6
6
|
export function workspaceResizeRange(available:{width:number;height:number},desired:WorkspacePanels,panel:WorkspacePanel,columns:WorkspaceColumns=defaultWorkspaceColumns):SeparatorRange {
|
|
@@ -16,11 +16,16 @@ export function workspaceResizeTarget(available:{width:number;height:number},des
|
|
|
16
16
|
for(let i=0;i<48;i++){const mid=(low+high)/2,value=allocateWorkspaceLayout(available,{...desired,[panel]:mid},columns)[panel];if(value<target)low=mid;else high=mid;}
|
|
17
17
|
return (low+high)/2;
|
|
18
18
|
}
|
|
19
|
+
/** The selected Inspector section name, from either a selected tab or a pressed header selector. */
|
|
20
|
+
export function selectedInspector(root:Pick<Document,'querySelector'>=document):string|undefined {
|
|
21
|
+
return root.querySelector<HTMLElement>('[data-inspector][aria-selected=true],[data-inspector][aria-pressed=true]')?.dataset.inspector;
|
|
22
|
+
}
|
|
19
23
|
export interface WorkspaceUI {
|
|
20
24
|
defaults:()=>WorkspacePreferences;beforeTab:()=>void;afterTab:()=>void;scroll:()=>string;restoreScroll:(value:unknown)=>void;
|
|
21
25
|
revealInspector:()=>void;refresh:()=>void;dispose:()=>void;
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
/** `currentTheme`, when given, is the appearance the Settings theme select shows; otherwise it shows the saved default. */
|
|
28
|
+
export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:WorkspaceTheme)=>void;currentTheme?:()=>WorkspaceTheme|undefined}):WorkspaceUI {
|
|
24
29
|
const require=<T extends HTMLElement=HTMLElement>(id:string)=>{const node=document.getElementById(id);if(!node)throw new Error('Missing workspace element: '+id);return node as T;};
|
|
25
30
|
const layout=document.querySelector<HTMLElement>('.layout')!,main=layout.querySelector<HTMLElement>('main')!,stage=require('stage-layout'),inspector=document.querySelector<HTMLElement>('.inspector')!,explorer=require('parts-explorer');
|
|
26
31
|
layout.classList.add('workspace-layout');
|
|
@@ -28,11 +33,16 @@ export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:Wo
|
|
|
28
33
|
// A parts explorer placed inside the left column makes that column the explorer: it keeps the explorer's
|
|
29
34
|
// saved width and the page has no right column. Otherwise sources stay left and the explorer stays right.
|
|
30
35
|
const explorerLeft=left.contains(explorer),columns:WorkspaceColumns=explorerLeft?{left:'rightWidth',right:null}:defaultWorkspaceColumns;
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
// A section is an Inspector panel only while it carries data-inspector-panel; the review page's Feedback
|
|
37
|
+
// section lives in the review dialog instead and keeps its id without being a panel.
|
|
38
|
+
const tabs=inspectorTabs.filter(tab=>document.getElementById('inspector-'+tab)?.dataset.inspectorPanel===tab);if(!tabs.length)throw new Error('Missing workspace element: inspector-'+inspectorTabs.join('|inspector-'));
|
|
39
|
+
// The left column is a fixed header row (a leading `.left-header`, when the page has one) and one scrolling
|
|
40
|
+
// body holding everything else, Settings included: the body is the column's only scroll container.
|
|
41
|
+
const header=[...left.children].find(node=>node.className.split(' ').includes('left-header'))??null;
|
|
42
|
+
const sourceList=document.createElement('div');sourceList.className='workspace-sources';sourceList.append(...[...left.childNodes].filter(node=>node!==header));left.append(sourceList);
|
|
33
43
|
const settings=document.createElement('section');settings.className='workspace-settings';settings.setAttribute('aria-label','Settings');
|
|
34
|
-
settings.innerHTML='<h2>Settings</h2><label>Theme <select id="settings-theme" data-app-preference><option value="system">System</option><option value="dark">Dark</option><option value="light">Light</option></select></label><label><input id="settings-sync" data-app-preference type="checkbox">Sync cameras by default</label><label><input id="settings-idle" data-app-preference type="checkbox">Repeat idle by default</label><button id="settings-reset" type="button">Reset layout</button><p class="hint">Defaults apply to new inspections. Links and source drafts retain their explicit view.</p><p id="settings-state" class="hint" role="status"></p>';
|
|
35
|
-
|
|
44
|
+
settings.innerHTML='<h2 class="sticky-header">Settings</h2><label>Theme <select id="settings-theme" data-app-preference><option value="system">System</option><option value="dark">Dark</option><option value="light">Light</option></select></label><label><input id="settings-sync" data-app-preference type="checkbox">Sync cameras by default</label><label><input id="settings-idle" data-app-preference type="checkbox">Repeat idle by default</label><button id="settings-reset" type="button">Reset layout</button><p class="hint">Defaults apply to new inspections. Links and source drafts retain their explicit view.</p><p id="settings-state" class="hint" role="status"></p>';
|
|
45
|
+
sourceList.append(settings);
|
|
36
46
|
const theme=require<HTMLSelectElement>('settings-theme'),sync=require<HTMLInputElement>('settings-sync'),idle=require<HTMLInputElement>('settings-idle'),status=require('settings-state');
|
|
37
47
|
const store=new WorkspacePreferenceStore({onStatus:value=>{status.textContent=value.detail;}});let desired={...store.get().panels};
|
|
38
48
|
const stack=document.createElement('div');stack.className='workspace-stage-stack';for(const child of [...main.children])if(child!==inspector)stack.append(child);main.prepend(stack);
|
|
@@ -46,10 +56,12 @@ export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:Wo
|
|
|
46
56
|
const handle=document.createElement('div');handle.className='workspace-separator workspace-'+panel+(sides[panel]?' workspace-side-'+sides[panel]:'');handles[panel]=handle;
|
|
47
57
|
if(sides[panel]==='left')node(panel).after(handle);else if(sides[panel]==='right')node(panel).before(handle);else inspector.before(handle);
|
|
48
58
|
const trigger=document.createElement('button');trigger.type='button';trigger.textContent=labels[panel];trigger.hidden=true;triggers.append(trigger);buttons[panel]=trigger;
|
|
49
|
-
const dialog=document.createElement('dialog');dialog.className='workspace-drawer';dialog.id='workspace-drawer-'+panel;dialog.dataset.panel=panel;const close=document.createElement('button');close.type='button';close.textContent='Close '+labels[panel];close.className='workspace-drawer-close';
|
|
59
|
+
const dialog=document.createElement('dialog');dialog.className='workspace-drawer';dialog.id='workspace-drawer-'+panel;dialog.dataset.panel=panel;const close=document.createElement('button');close.type='button';close.textContent='Close '+labels[panel];close.className='workspace-drawer-close';
|
|
60
|
+
// The drawer's header stays above its scrolling panel: title and the close action.
|
|
61
|
+
const header=document.createElement('div');header.className='sticky-header workspace-drawer-header';const title=document.createElement('span');title.textContent=labels[panel];header.append(title,close);dialog.append(header);document.body.append(dialog);dialogs[panel]=dialog;
|
|
50
62
|
disposers.push(bindWorkspaceDrawer({trigger,drawer:dialog,label:labels[panel],close}));
|
|
51
63
|
}
|
|
52
|
-
const memory=new InspectorScrollMemory();const active=():InspectorTab=>{const tab=
|
|
64
|
+
const memory=new InspectorScrollMemory();const active=():InspectorTab=>{const tab=selectedInspector() as InspectorTab|undefined;return tab&&tabs.includes(tab)?tab:tabs[0]!;};
|
|
53
65
|
let disposed=false,frame=0,applying=false,available={width:0,height:0},effective=allocateWorkspaceLayout(available,desired,columns);
|
|
54
66
|
const panelFor=(tab:InspectorTab)=>require('inspector-'+tab);
|
|
55
67
|
const capture=()=>{const tab=active(),panel=panelFor(tab),saved=memory.snapshot()[tab];if(!panel.hidden&&(!saved||saved.x<=Math.max(0,panel.scrollWidth-panel.clientWidth)&&saved.y<=Math.max(0,panel.scrollHeight-panel.clientHeight)))memory.capture(tab,panel);};
|
|
@@ -69,13 +81,13 @@ export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:Wo
|
|
|
69
81
|
available={width:Math.max(0,layout.clientWidth),height:Math.max(0,layout.clientHeight-chrome)};effective=allocateWorkspaceLayout(available,desired,columns);
|
|
70
82
|
const mode=(panel:WorkspacePanel)=>effective[panel==='leftWidth'?'leftMode':panel==='rightWidth'?'rightMode':'inspectorMode'];
|
|
71
83
|
for(const panel of present)dock(panel,mode(panel)==='drawer');
|
|
72
|
-
const side=(key:WorkspaceColumns['left'])=>key&&mode(key)==='panel'?{width:effective[key],separator:
|
|
84
|
+
const gap=workspaceMinimums.separator,side=(key:WorkspaceColumns['left'])=>key&&mode(key)==='panel'?{width:effective[key],separator:gap}:{width:0,separator:0},l=side(columns.left),r=side(columns.right);
|
|
73
85
|
layout.style.setProperty('--workspace-columns',`${l.width}px ${l.separator}px minmax(0,1fr) ${r.separator}px ${r.width}px`);
|
|
74
|
-
main.style.gridTemplateRows=`minmax(0,1fr) ${effective.inspectorMode==='panel'?
|
|
86
|
+
main.style.gridTemplateRows=`minmax(0,1fr) ${effective.inspectorMode==='panel'?gap:0}px ${effective.inspectorHeight}px`;
|
|
75
87
|
layout.dataset.stageMinimum=String(effective.stageMinimumSatisfied);for(const binding of bindings)binding.refresh();options.changed();restore();
|
|
76
88
|
}finally{applying=false;}
|
|
77
89
|
}
|
|
78
|
-
const renderSettings=()=>{const value=store.get();theme.value=value.theme;sync.checked=value.defaultCameraSync;idle.checked=value.defaultIdleRepeat;status.textContent=store.status().detail;};
|
|
90
|
+
const renderSettings=()=>{const value=store.get();theme.value=options.currentTheme?.()??value.theme;sync.checked=value.defaultCameraSync;idle.checked=value.defaultIdleRepeat;status.textContent=store.status().detail;};
|
|
79
91
|
theme.onchange=()=>{store.update({theme:theme.value as WorkspaceTheme});options.defaultTheme(store.get().theme);};sync.onchange=()=>{store.update({defaultCameraSync:sync.checked});};idle.onchange=()=>{store.update({defaultIdleRepeat:idle.checked});};require<HTMLButtonElement>('settings-reset').onclick=()=>store.resetLayout();
|
|
80
92
|
const unsubscribe=store.subscribe(value=>{desired={...value.panels};renderSettings();schedule();});renderSettings();
|
|
81
93
|
for(const tab of tabs){
|