diff --git a/server/src/ledgrab/static/css/cards.css b/server/src/ledgrab/static/css/cards.css index 36d8bd6..daaeae4 100644 --- a/server/src/ledgrab/static/css/cards.css +++ b/server/src/ledgrab/static/css/cards.css @@ -1253,39 +1253,8 @@ ul.section-tip li { align-items: center; } -/* Collapsible target pipeline metrics */ -.target-metrics-collapse { - margin-top: 4px; -} -.target-metrics-toggle { - cursor: pointer; - font-size: 0.75rem; - color: var(--text-secondary); - padding: 4px 0; - user-select: none; - background: none; - border: none; - font-family: inherit; -} -.target-metrics-toggle::before { - content: '▸ '; -} -.target-metrics-collapse.open .target-metrics-toggle::before { - content: '▾ '; -} -.target-metrics-animate { - display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 0.25s ease; -} -.target-metrics-collapse.open .target-metrics-animate { - grid-template-rows: 1fr; -} -.target-metrics-animate > .target-metrics-expanded { - overflow: hidden; -} - -/* Timing breakdown bar */ +/* Timing breakdown bar — used by graph-editor and the api-input test + modal; the targets card uses its own .target-pipeline wrapper. */ .timing-breakdown { margin-top: 8px; padding: 6px 8px; @@ -2188,6 +2157,63 @@ ul.section-tip li { background: color-mix(in srgb, var(--ch-coral, var(--danger-color)) 10%, transparent); } +/* Inline chip variant — used by the pipeline strip on running target + cards. A bigger numeric (display-font-ish) sits beside a small mono + caps unit, so a chip can carry "127K · frames" without looking like + a label-shaped pill. The data values inside still update via + _patchTargetMetrics. */ +.mod-card .chip--inline { + gap: 4px; + padding: 3px 8px; +} +.mod-card .chip--inline > span { + font-family: var(--font-mono, monospace); + font-size: 0.78rem; + font-weight: 700; + color: var(--lux-ink, var(--text-color)); + font-variant-numeric: tabular-nums; + letter-spacing: 0; +} +.mod-card .chip--inline > small { + font-family: var(--font-mono, monospace); + font-size: 0.55rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--lux-ink-mute, var(--text-secondary)); +} + +/* ── Pipeline strip — replaces the old target-metrics-collapse. A + thin always-visible segmented timing bar plus a tight chip row + below it (timing total / frames / keepalive). The bar is the + diagnostic; the chips are the counters. */ +.mod-card .target-pipeline { + display: flex; + flex-direction: column; + gap: 8px; +} +.mod-card .target-pipeline .timing-bar { + height: 4px; + border-radius: 2px; + background: var(--lux-bg-0, var(--bg-color)); + box-shadow: inset 0 0 0 1px var(--lux-line, var(--border-color)); + margin: 0; + overflow: hidden; + gap: 0; +} +.mod-card .target-pipeline .timing-seg { + transition: flex 0.3s ease; + opacity: 0.85; +} +.mod-card .target-pipeline .timing-seg:hover { + opacity: 1; +} +.mod-card .target-pipeline-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + /* ── Brightness fader (mod-card variant) ────────────────────────── */ .mod-fader { diff --git a/server/src/ledgrab/static/js/features/targets.ts b/server/src/ledgrab/static/js/features/targets.ts index 7b03618..30ce20d 100644 --- a/server/src/ledgrab/static/js/features/targets.ts +++ b/server/src/ledgrab/static/js/features/targets.ts @@ -874,37 +874,27 @@ function _cssSourceName(cssId: any, colorStripSourceMap: any) { // ─── In-place metric patching (avoids full card replacement on polls) ─── -function _buildLedTimingHTML(state: any) { +/** Builds just the inner segments of the timing bar — no wrapper. + * Each segment is flex-sized by its ms cost and tooltipped with its + * stage label. Used by both the initial render and the patcher. + * The wrapper `.timing-bar` lives in the card markup so the bar + * itself doesn't get replaced on every poll (preserves transitions). */ +function _buildLedTimingSegments(state: any): string { const isAudio = state.timing_audio_read_ms != null; - return ` -
-
${t('device.metrics.timing')}
-
${state.timing_total_ms}ms
-
-
- ${isAudio ? ` - - + const send = ``; + if (isAudio) { + return ` + + - ` : ` - ${state.timing_extract_ms != null ? `` : ''} - ${state.timing_map_leds_ms != null ? `` : ''} - ${state.timing_smooth_ms != null ? `` : ''} - `} - -
-
- ${isAudio ? ` - read ${state.timing_audio_read_ms}ms - fft ${state.timing_audio_fft_ms}ms - render ${state.timing_audio_render_ms}ms - ` : ` - ${state.timing_extract_ms != null ? `extract ${state.timing_extract_ms}ms` : ''} - ${state.timing_map_leds_ms != null ? `map ${state.timing_map_leds_ms}ms` : ''} - ${state.timing_smooth_ms != null ? `smooth ${state.timing_smooth_ms}ms` : ''} - `} - send ${state.timing_send_ms}ms -
`; + ${send}`; + } + const segs: string[] = []; + if (state.timing_extract_ms != null) segs.push(``); + if (state.timing_map_leds_ms != null) segs.push(``); + if (state.timing_smooth_ms != null) segs.push(``); + segs.push(send); + return segs.join(''); } function _patchTargetMetrics(target: any) { @@ -942,8 +932,11 @@ function _patchTargetMetrics(target: any) { } } - const timing = card.querySelector('[data-tm="timing"]') as HTMLElement | null; - if (timing && state.timing_total_ms != null) timing.innerHTML = _buildLedTimingHTML(state); + const timingBar = card.querySelector('[data-tm="timing"]') as HTMLElement | null; + if (timingBar && state.timing_total_ms != null) timingBar.innerHTML = _buildLedTimingSegments(state); + + const timingTotal = card.querySelector('[data-tm="timing-total"]') as HTMLElement | null; + if (timingTotal && state.timing_total_ms != null) timingTotal.textContent = String(state.timing_total_ms); const frames = card.querySelector('[data-tm="frames"]') as HTMLElement | null; if (frames) { frames.textContent = formatCompact(metrics.frames_processed || 0); frames.title = String(metrics.frames_processed || 0); } @@ -1102,25 +1095,26 @@ export function createTargetCard(target: LedOutputTarget & { state?: any; metric }); } - // ── Live-update raw HTML: collapsible pipeline detail (frames / - // keepalive / timing breakdown). The FPS sparkline now lives - // inside the FPS metric cell, not in a separate row. ── + // ── Pipeline strip — a single segmented timing bar plus one chip + // row underneath. Replaces the legacy collapsible block: the + // bar is the headline diagnostic, the chips carry the rest. ── + const showKeepalive = state.needs_keepalive !== false; const extraHtml = isProcessing ? ` -
- -
-
-
-
-
${escapeHtml(t('device.metrics.frames'))}
-
---
-
- ${state.needs_keepalive !== false ? ` -
-
${escapeHtml(t('device.metrics.keepalive'))}
-
---
-
` : ''} -
+
+
+
+ + ${ICON_CLOCK}--ms + + + -- + ${escapeHtml(t('device.metrics.frames'))} + + ${showKeepalive ? ` + + -- + ${escapeHtml(t('device.metrics.keepalive'))} + ` : ''}
` : ''; diff --git a/server/src/ledgrab/static/locales/en.json b/server/src/ledgrab/static/locales/en.json index c8d9172..813944f 100644 --- a/server/src/ledgrab/static/locales/en.json +++ b/server/src/ledgrab/static/locales/en.json @@ -702,7 +702,6 @@ "targets.source": "Source:", "targets.source.hint": "Which picture source to capture and process", "targets.source.none": "-- No source assigned --", - "targets.metrics.pipeline": "Pipeline details", "targets.fps": "Target FPS:", "targets.fps.hint": "Target frames per second for capture and LED updates (1-90)", "targets.fps.rec": "Hardware max ≈ {fps} fps ({leds} LEDs)", diff --git a/server/src/ledgrab/static/locales/ru.json b/server/src/ledgrab/static/locales/ru.json index 4f78363..7856abc 100644 --- a/server/src/ledgrab/static/locales/ru.json +++ b/server/src/ledgrab/static/locales/ru.json @@ -686,7 +686,6 @@ "targets.source": "Источник:", "targets.source.hint": "Какой источник изображения захватывать и обрабатывать", "targets.source.none": "-- Источник не назначен --", - "targets.metrics.pipeline": "Детали конвейера", "targets.fps": "Целевой FPS:", "targets.fps.hint": "Целевая частота кадров для захвата и обновления LED (1-90)", "targets.fps.rec": "Макс. аппаратный ≈ {fps} fps ({leds} LED)", diff --git a/server/src/ledgrab/static/locales/zh.json b/server/src/ledgrab/static/locales/zh.json index 6b11dc3..6ce1540 100644 --- a/server/src/ledgrab/static/locales/zh.json +++ b/server/src/ledgrab/static/locales/zh.json @@ -686,7 +686,6 @@ "targets.source": "源:", "targets.source.hint": "要采集和处理的图片源", "targets.source.none": "-- 未分配源 --", - "targets.metrics.pipeline": "管线详情", "targets.fps": "目标 FPS:", "targets.fps.hint": "采集和 LED 更新的目标帧率(1-90)", "targets.fps.rec": "硬件最大 ≈ {fps} fps({leds} 个 LED)",