style: stack per-class override rows instead of a wide table (REQ-181 layout)
The width:100% table plus the new MaxBox column let the Class column absorb the slack, stranding the Container checkbox away from its class. One block per class now: name + Container checkbox on the first line, Conf/IoU/MinBox/ MaxBox inputs in a wrapping grid below. Behavior identical - KEYS, buildClassParams, compose() copy line, toggleContainer revert, aria labels.
This commit is contained in:
1 parent
d3a6aa49b6
commit
2971f9192c
4 files changed
+75
-61
No files matched your search
+4
-2
@@ -359,9 +359,11 @@ that class to its full-set detections (blank if no full-set run covered it); cle
|
||||
examples is then a plain full-set view again. Run Preview always refreshes the full-set for
|
||||
every selected class first (with empty exemplars), then re-runs the exemplared classes
|
||||
sequentially; drawing an example never triggers a full-set re-run. The
|
||||
per-class overrides table also carries a **Container** checkbox (REQ-184) that toggles
|
||||
per-class overrides block (REQ-181) stacks one row per class — name plus **Container**
|
||||
checkbox on the first line, the four override inputs in a wrapping grid below — and that
|
||||
checkbox toggles
|
||||
`project_classes.container` through `PATCH /api/projects/{id} { containers: … }`. The same
|
||||
table carries a **Copy** button (REQ-186): one click puts every selected class's effective
|
||||
block carries a **Copy** button (REQ-186): one click puts every selected class's effective
|
||||
settings (per-class override where set, else the global slider; container = the checkbox
|
||||
state) on the clipboard as short lines `class <name> conf … iou … minbox … maxbox … container
|
||||
true|false`, through `clipboard.js`'s `copyText` (Clipboard API with an `execCommand`
|
||||
|
||||
@@ -1389,6 +1389,22 @@ read it.
|
||||
with expected drops; node eval proves `buildClassParams` emits `max_box_frac`;
|
||||
`git diff --stat` = 5 backend + 5 frontend files + docs.
|
||||
|
||||
## Task — stacked per-class override rows (REQ-181 layout) `[DONE]`
|
||||
|
||||
1. The overrides surface drops the wide table for one block per class: name line with the
|
||||
**Container** checkbox (REQ-184) at the right, then a wrapping
|
||||
`repeat(auto-fit, minmax(118px, 1fr))` grid of `Conf`/`IoU`/`MinBox`/`MaxBox` inputs, each
|
||||
labelled in its own colour. Cause of the old cramping: `width: 100%` + the new MaxBox
|
||||
column let the Class column swallow the slack, stranding the checkbox far right.
|
||||
Behavior untouched — `KEYS`, `buildClassParams`, `compose()` copy line, `toggleContainer`
|
||||
revert path, `title`/`aria-label` all identical → verify: **[DONE]** reviewer diff-proved
|
||||
the functional hunk is render-only (SPEC ✅, 4 Minor all fixed: two stale ui-spec
|
||||
"table/columns" lines, `label` margin-bottom, dead class hook; plus long-name ellipsis +
|
||||
`flexShrink: 0` guard); `npm run build` green; grid math checked at 375/768/920/1040 px;
|
||||
contrast ≥6.6:1 on all label colors; focus ring left to the global `input:focus`
|
||||
(accent border + glow) — no CSS added; **browser eyeball still owed by the user**
|
||||
(375 px 2-col wrap, long class names).
|
||||
|
||||
## Known open points
|
||||
|
||||
- *Not closed by any task, by choice:* **any rebuild kills the running job.** Task 14's resume
|
||||
|
||||
+10
-6
@@ -449,9 +449,13 @@ Two columns inside one dialog (920 px wide, max 96 vw / 90 vh, scrollable).
|
||||
- Confidence threshold — 0.05…0.95 step 0.05, default 0.35.
|
||||
- NMS IoU threshold — 0…0.9 step 0.05, default 0.0.
|
||||
- Min box size (fraction of frame) — 0…0.5 step 0.005, default 0, shown as a percentage.
|
||||
- **Per-class overrides** (REQ-181) — a compact table below the sliders, one row per currently
|
||||
selected class: `Conf` / `IoU` / `MinBox` / `MaxBox` number inputs and a **Container** checkbox
|
||||
(REQ-184). Each input's placeholder shows the current global value and an empty input
|
||||
- **Per-class overrides** (REQ-181) — one **stacked block** per currently selected class
|
||||
below the sliders: the class name on the first line with its **Container** checkbox
|
||||
(REQ-184) at the right, then a wrapping grid of `Conf` / `IoU` / `MinBox` / `MaxBox`
|
||||
number inputs, each labelled in its own colour. No column headers, no wide table — a
|
||||
narrow modal wraps the grid instead of squeezing the numbers, and the checkbox never
|
||||
drifts away from its class. Each input's placeholder shows the current global value and
|
||||
an empty input
|
||||
inherits it; only filled cells are sent, as
|
||||
`class_params` on **both** the preview and the job request, so preview and run cannot
|
||||
disagree. Classes without an override behave exactly as before. The checkbox marks the
|
||||
@@ -462,7 +466,7 @@ Two columns inside one dialog (920 px wide, max 96 vw / 90 vh, scrollable).
|
||||
unmarked classes are governed by IoU alone. Toggling is optimistic
|
||||
`PATCH /projects/{id} { containers: { [classId]: bool } }` into `project_classes.container`
|
||||
with revert on rejection — preview and batch job read the same stored flag.
|
||||
- **Copy (REQ-186)** — small right-aligned ghost button above the overrides table; label
|
||||
- **Copy (REQ-186)** — small right-aligned ghost button above the overrides block; label
|
||||
`Copy`, flips to `Copied` for 1.5 s inside an `aria-live` span, and on failure silently
|
||||
stays `Copy`. One line per selected class, e.g.
|
||||
`class sack conf 0.4 iou 0.6 minbox 0.005 maxbox 1 container false` — an empty input copies the
|
||||
@@ -537,8 +541,8 @@ Same anatomy, wider (1040 px), with three differences:
|
||||
must not abort the rest; the backend GPU lock serialises the real work anyway. Progress is
|
||||
reported as `done/total (failed)` and the result message is `Queued N auto-annotation job(s)`.
|
||||
|
||||
The per-class overrides table (REQ-181) appears here too, below the sliders — same columns
|
||||
including the **Container** checkbox (REQ-184) — and its
|
||||
The per-class overrides block (REQ-181) appears here too, below the sliders — the same
|
||||
stacked rows, **Container** checkbox (REQ-184) included — and its
|
||||
`class_params` ride along on every preview and start request — one set of overrides applies
|
||||
to all selected batches.
|
||||
|
||||
|
||||
@@ -92,64 +92,56 @@ export default function ClassParamsTable({ classNames, globals, value, onChange,
|
||||
<span aria-live="polite">{copied ? 'Copied' : 'Copy'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', textAlign: 'left', padding: '2px 4px' }}>Class</th>
|
||||
{KEYS.map(k => (
|
||||
<th key={k.key} className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', color: k.color, padding: '2px 4px' }}>{k.label}</th>
|
||||
))}
|
||||
{project && (
|
||||
<th className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', textAlign: 'left', padding: '2px 4px' }}>Container</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* Stacked rows (REQ-181): the name line carries the container flag, the
|
||||
grid below it the four overrides, so a wide class list never squeezes
|
||||
the numbers or strands the checkbox away from its class. */}
|
||||
<div>
|
||||
{classNames.map(name => {
|
||||
const cls = project?.classes.find(c => c.name === name)
|
||||
return (
|
||||
<tr key={name}>
|
||||
<td className="mono" style={{ fontSize: '0.76rem', padding: '2px 4px' }}>{name}</td>
|
||||
{KEYS.map(({ key, label, step, color }) => (
|
||||
<td key={key} style={{ padding: '2px 4px' }}>
|
||||
<input
|
||||
type="number"
|
||||
value={value[name]?.[key] ?? ''}
|
||||
min={0}
|
||||
max={1}
|
||||
step={step}
|
||||
placeholder={String(globals[key])}
|
||||
title={`Empty = use the global value (${globals[key]})`}
|
||||
aria-label={`${name} ${label} override, empty uses global ${globals[key]}`}
|
||||
onChange={(e) => set(name, key, e.target.value)}
|
||||
style={{
|
||||
width: 64, fontSize: '0.76rem', background: '#09090b', color,
|
||||
border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px',
|
||||
cursor: 'text',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
{project && (
|
||||
<td style={{ padding: '2px 4px', textAlign: 'center' }}>
|
||||
{cls && (
|
||||
<label title={`Keep boxes inside ${name} when auto-annotating (containment is not overlap)`} style={{ cursor: 'pointer', display: 'inline-flex' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={containers.has(name)}
|
||||
onChange={() => toggleContainer(cls)}
|
||||
aria-label={`Mark ${name} as container`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
<div key={name} style={{ padding: '6px 0', borderBottom: '1px solid #27272a' }}>
|
||||
<div className="row" style={{ justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
|
||||
<span className="mono" style={{ fontSize: '0.76rem', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</span>
|
||||
{project && cls && (
|
||||
<label title={`Keep boxes inside ${name} when auto-annotating (containment is not overlap)`} style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0, fontSize: '0.74rem' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={containers.has(name)}
|
||||
onChange={() => toggleContainer(cls)}
|
||||
aria-label={`Mark ${name} as container`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
Container
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(118px, 1fr))', gap: 6, marginTop: 4 }}>
|
||||
{KEYS.map(({ key, label, step, color }) => (
|
||||
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0, marginBottom: 0 }}>
|
||||
<span className="hint" style={{ fontSize: '0.7rem', color, whiteSpace: 'nowrap' }}>{label}</span>
|
||||
<input
|
||||
type="number"
|
||||
value={value[name]?.[key] ?? ''}
|
||||
min={0}
|
||||
max={1}
|
||||
step={step}
|
||||
placeholder={String(globals[key])}
|
||||
title={`Empty = use the global value (${globals[key]})`}
|
||||
aria-label={`${name} ${label} override, empty uses global ${globals[key]}`}
|
||||
onChange={(e) => set(name, key, e.target.value)}
|
||||
style={{
|
||||
width: '100%', minWidth: 0, fontSize: '0.76rem', background: '#09090b', color,
|
||||
border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px',
|
||||
cursor: 'text',
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user