Web frontend: perf panel, cover warmup, incremental browse rendering
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
158
web/src/components/PerfPanel.tsx
Normal file
158
web/src/components/PerfPanel.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
/** Per-knob performance settings, shown under `?pi=1`. Stored in this browser's
|
||||
* localStorage (see lib/perfSettings.ts) and applied by a reload, because the knobs are
|
||||
* read once at module load. Nothing changes until "Speichern". */
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
FULL,
|
||||
PERF,
|
||||
PI,
|
||||
resetPerfSettings,
|
||||
savePerfSettings,
|
||||
type PerfSettings,
|
||||
} from "../lib/perfSettings";
|
||||
|
||||
type BoolKey = { [K in keyof PerfSettings]: PerfSettings[K] extends boolean ? K : never }[keyof PerfSettings];
|
||||
type NumKey = { [K in keyof PerfSettings]: PerfSettings[K] extends number ? K : never }[keyof PerfSettings];
|
||||
|
||||
const TOGGLES: [BoolKey, string, string][] = [
|
||||
["ambience", "Animierter Hintergrund", "Canvas im Abspielbildschirm (größter CPU-Verbraucher)"],
|
||||
["cardBlur", "Weichzeichner auf Karten", "Backdrop-Blur je Album-Karte und Titelzeile"],
|
||||
["viewTransitions", "Übergänge", "Ein-/Ausblenden beim Öffnen von Gruppen und Alben"],
|
||||
["decorativeAnimations", "Deko-Animationen", "Blasen im Zimmer, schwimmender Delfin"],
|
||||
["cardShadows", "Schatten auf Karten", "Schlagschatten unter den Album-Karten"],
|
||||
["tileShadows", "Schatten auf Kacheln", "Schlagschatten unter den Kacheln im Raster"],
|
||||
["petAnimations", "Tippen: Tiere bewegen sich", "Aus: Tiere stehen still"],
|
||||
["tippenBubbles", "Tippen: Blasen", "Aufsteigende Blasen im Hintergrund"],
|
||||
["tippenViewTransitions", "Tippen: Übergänge", "Ein-/Ausblenden der Bildschirme"],
|
||||
["serviceWorker", "Service Worker", "Aus: vorhandener Worker wird entfernt"],
|
||||
["coverWarmup", "Cover vorladen", "Cover im Hintergrund dekodieren"],
|
||||
];
|
||||
|
||||
const NUMBERS: [NumKey, string, string, number, number, number][] = [
|
||||
["playbackClockFps", "Fortschrittsbalken: Updates/s", "0 = jeder Frame", 0, 60, 1],
|
||||
["ambienceScale", "Hintergrund: Auflösung", "Faktor der Canvas-Größe", 0.1, 1, 0.05],
|
||||
["ambienceMaxPx", "Hintergrund: max. Pixel", "Obergrenze je Achse", 256, 4096, 64],
|
||||
["ambienceMaxFps", "Hintergrund: max. FPS", "0 = unbegrenzt", 0, 60, 1],
|
||||
["ambienceMaxBubbles", "Hintergrund: max. Blasen", "0 = unbegrenzt", 0, 500, 5],
|
||||
];
|
||||
|
||||
const pill = (background: string, color: string) =>
|
||||
({
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
background,
|
||||
color,
|
||||
fontSize: 15,
|
||||
fontWeight: 800,
|
||||
padding: "11px 20px",
|
||||
borderRadius: 999,
|
||||
}) as const;
|
||||
|
||||
export function PerfPanel({ onClose }: { onClose: () => void }) {
|
||||
const [draft, setDraft] = useState<PerfSettings>({ ...PERF });
|
||||
|
||||
const save = () => {
|
||||
savePerfSettings(draft);
|
||||
location.reload();
|
||||
};
|
||||
const reset = () => {
|
||||
resetPerfSettings();
|
||||
location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overlay" style={{ zIndex: 7, background: "oklch(15% 0.03 210 / .75)" }}>
|
||||
<div className="sheet" style={{ padding: 28, width: 480, maxHeight: "100%", overflow: "auto" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<div style={{ fontSize: 20, fontWeight: 900, color: "var(--ink)", flex: 1 }}>
|
||||
⚡ Leistung
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Schließen"
|
||||
style={{
|
||||
...pill("oklch(88% 0.02 210)", "var(--ink)"),
|
||||
fontSize: 18,
|
||||
fontWeight: 900,
|
||||
width: 36,
|
||||
height: 36,
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 10, marginTop: 16 }}>
|
||||
<button onClick={() => setDraft({ ...PI })} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||||
Pi-Vorgabe
|
||||
</button>
|
||||
<button onClick={() => setDraft({ ...FULL })} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||||
Alles an
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
|
||||
{TOGGLES.map(([key, label, hint]) => (
|
||||
<label key={key} style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft[key]}
|
||||
onChange={(event) => setDraft({ ...draft, [key]: event.target.checked })}
|
||||
style={{ width: 22, height: 22 }}
|
||||
/>
|
||||
<span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 800, color: "var(--ink)" }}>{label}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 210)" }}>
|
||||
{hint}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
|
||||
{NUMBERS.map(([key, label, hint, min, max, step]) => (
|
||||
<label key={key} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 800, color: "var(--ink)" }}>{label}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 210)" }}>
|
||||
{hint}
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={draft[key]}
|
||||
onChange={(event) => {
|
||||
const value = event.target.valueAsNumber;
|
||||
if (Number.isFinite(value)) setDraft({ ...draft, [key]: value });
|
||||
}}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
borderRadius: 10,
|
||||
border: "2px solid oklch(85% 0.02 210)",
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
|
||||
<div style={{ display: "flex", gap: 10, marginTop: 6 }}>
|
||||
<button onClick={save} style={pill("var(--accent)", "#fff")}>
|
||||
Speichern & neu laden
|
||||
</button>
|
||||
<button onClick={reset} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||||
Zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 210)", lineHeight: 1.5 }}>
|
||||
Wird nur in diesem Browser gespeichert und gilt nur mit <code>?pi=1</code>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user