Files
hms-mediaengine/apps/web/src/utils.ts
T
HMS MediaEngine Agent c6a3cf7751 Phase 5: Web-Bedienoberfläche nach §17 (React+Vite, ADR-0006)
- ADR-0006: React 18/19 mit Vite im statischen SPA-Modus
- Workspace §17.2: Statusleiste 32px, einklappbare Werkzeugleiste,
  Layer-Tabelle, Inspector ~30% mit Divider-Resize, umschaltbare
  rechte Seitenleiste, Fußleiste mit Setup/Live-Lock
- Layer-Tabelle §17.3: alle 10 Pflichtspalten, Zeilen 30px,
  7 unterscheidbare Zustände (Glyph+Farbe+Text, nie nur Farbe),
  Pfeiltasten-Navigation, Opacity-Fader mit echtem parameter.set
- Inspector §17.4: 7 Tabs, Zahlenfelder mit Tippen/Ziehen/Pfeiltasten,
  Release-Befehl, Live-Lock sperrt Struktur
- Design-Tokens §17.6: alle als CSS-Variablen (Flächen, Text,
  Akzent-Blau, Zustandsfarben, 4px-Raster, 2-4px Radien,
  Monospace tabular-nums, 100-180ms)
- WebSocket: Snapshot bei Connect, Deltas danach, Reconnect
  1s/2s/4s max 30s; Browser-Neustart stoppt Output nicht (§3.2)
- REST: health/identity/cluster-nodes/parameters/diagnostics,
  Commands mit expected_revision + Revert bei 409
- Kein GO-Button, keine Standby-Anzeige, keine Cue-Carts (§17.1)
- Build: tsc strict + vite, 27 Module, 262KB JS gzip 80KB
- Python-Suite 549 grün unberührt
2026-09-11 03:02:38 +02:00

109 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Formatierungs- und Mapping-Helfer der Weboberfläche.
* Zeitkritische Werte werden tabellarisch monospaced dargestellt (§17.6).
*/
import type { LayerStatus, LayerType } from './types';
/** Klassennamen kompakt zusammenführen; leere/false-Teile entfallen. */
export function cx(...parts: ReadonlyArray<string | false | null | undefined>): string {
return parts.filter((p): p is string => typeof p === 'string' && p.length > 0).join(' ');
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
/** Sekunden → „M:SS.t“ (tabellarisch, Zehntelsekunden). */
export function formatTimecode(totalSec: number): string {
const safe = Number.isFinite(totalSec) && totalSec >= 0 ? totalSec : 0;
const minutes = Math.floor(safe / 60);
const seconds = Math.floor(safe % 60);
const tenth = Math.floor((safe % 1) * 10);
return `${minutes}:${seconds.toString().padStart(2, '0')}.${tenth}`;
}
/** Uhrzeit HH:MM:SS (lokal). */
export function formatClock(date: Date): string {
const h = date.getHours().toString().padStart(2, '0');
const m = date.getMinutes().toString().padStart(2, '0');
const s = date.getSeconds().toString().padStart(2, '0');
return `${h}:${m}:${s}`;
}
export function formatTimeMs(ms: number): string {
return formatClock(new Date(ms));
}
/** FPS mit zwei Dezimalstellen (§17.2); null = keine Messung. */
export function formatFps(v: number | null): string {
return v === null ? '—' : v.toFixed(2);
}
/** Deutsche Zahleneingabe: „12,5“ → 12.5; null wenn nicht parsebar. */
export function parseNumberInput(raw: string): number | null {
const trimmed = raw.trim().replace(',', '.');
if (trimmed === '') return null;
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? parsed : null;
}
export function errorMessage(err: unknown): string {
if (err instanceof Error) return err.message;
return 'unbekannter Fehler';
}
/** Opacity: Engine speichert 0..1, die UI zeigt 0..100. */
export function engineToOpacity(v: number): number {
return clamp(Math.round(v * 100), 0, 100);
}
export function opacityToEngine(v: number): number {
return clamp(v, 0, 100) / 100;
}
/** Text-Label je Zustand Status nie allein über Farbe (§17.6). */
export const STATUS_LABEL: Record<LayerStatus, string> = {
active: 'Aktiv',
paused: 'Pausiert',
preloaded: 'Vorgeladen',
warning: 'Warnung',
error: 'Fehler',
offline: 'Offline',
out_of_sync: 'Nicht synchron',
};
/** Farbtöne je Zustand (ergänzt durch Glyphe/Text, nie allein). */
export const STATUS_TONE: Record<LayerStatus, string> = {
active: 'st-active',
paused: 'st-paused',
preloaded: 'st-preloaded',
warning: 'st-warning',
error: 'st-error',
offline: 'st-offline',
out_of_sync: 'st-out-of-sync',
};
export const TYPE_LABEL: Record<LayerType, string> = {
media: 'Media',
generator: 'Generator',
adjustment: 'Adjustment',
group: 'Group',
};
export const BLEND_MODES: readonly string[] = [
'Normal',
'Add',
'Screen',
'Multiply',
'Dissolve',
'Lighten',
];
export const LOOP_MODES: readonly string[] = ['Loop', 'Once', 'Ping-Pong', 'Hold'];
/** Parameterpfad §10.2 (packages/parameter_engine/hms_parameter/paths.py). */
export function layerOpacityPath(compositionId: string, layerId: string): string {
return `composition/${compositionId}/layer/${layerId}/opacity`;
}