/* Die Einstellungen. ---------------------------------------------------------------------------- **Pro Gerät, nicht über den Server.** Am Fernseher will man größere Kacheln als am Laptop, und das eine soll das andere nicht umstellen. Dieselbe Entscheidung wie in der App, dort nachzulesen in der Spec zum Abgleich. Namen und Wertebereiche decken sich mit denen der App — ein Regler von 70 bis 180 Prozent heißt auf beiden Seiten dasselbe. Nur der Speicherort unterscheidet sich, hier localStorage statt UserDefaults. */ export type Navigation = 'top' | 'side'; export type Aufmacherquelle = 'continueFirst' | 'latestOnly'; interface Einstellungen { accent: string; nav: Navigation; cardW: number; heroSource: Aufmacherquelle; heroCount: number; heroHeight: number; combineContinue: boolean; hideWatched: boolean; hiddenRows: string[]; rowOrder: string[]; rate: number; skipForward: number; skipBackward: number; quality: number; audioLanguage: string; subtitleLanguage: string; } const vorgabe: Einstellungen = { accent: '#C04AC9', nav: 'top', cardW: 150, heroSource: 'continueFirst', heroCount: 5, heroHeight: 82, combineContinue: true, hideWatched: false, hiddenRows: [], rowOrder: [], rate: 1, skipForward: 30, skipBackward: 10, quality: 0, audioLanguage: '', subtitleLanguage: '', }; function lesen(schluessel: K): Einstellungen[K] { const roh = localStorage.getItem('flixy.' + schluessel); if (roh == null) return vorgabe[schluessel]; const art = typeof vorgabe[schluessel]; if (Array.isArray(vorgabe[schluessel])) { try { return JSON.parse(roh) as Einstellungen[K]; } catch { return vorgabe[schluessel]; } } if (art === 'number') return Number(roh) as Einstellungen[K]; if (art === 'boolean') return (roh === 'true') as Einstellungen[K]; return roh as Einstellungen[K]; } /* Ausgeschrieben statt über `Object.fromEntries` zusammengesetzt: dort geht der Typ jedes einzelnen Feldes verloren, und es bliebe nur ein doppelter Cast, der die Prüfung stilllegt statt sie zu bestehen. */ export const P: Einstellungen = { accent: lesen('accent'), nav: lesen('nav'), cardW: lesen('cardW'), heroSource: lesen('heroSource'), heroCount: lesen('heroCount'), heroHeight: lesen('heroHeight'), combineContinue: lesen('combineContinue'), hideWatched: lesen('hideWatched'), hiddenRows: lesen('hiddenRows'), rowOrder: lesen('rowOrder'), rate: lesen('rate'), skipForward: lesen('skipForward'), skipBackward: lesen('skipBackward'), quality: lesen('quality'), audioLanguage: lesen('audioLanguage'), subtitleLanguage: lesen('subtitleLanguage'), }; export function setzen(schluessel: K, wert: Einstellungen[K]): void { P[schluessel] = wert; // Listen müssen als JSON abgelegt werden — ein Array landete sonst als // "a,b,c" im Speicher und käme als Zeichenkette zurück. localStorage.setItem( 'flixy.' + schluessel, Array.isArray(wert) ? JSON.stringify(wert) : String(wert), ); anwenden(); } export function anwenden(): void { const r = document.documentElement.style; r.setProperty('--accent', P.accent); r.setProperty('--card-w', P.cardW + 'px'); r.setProperty('--hero-h', P.heroHeight + 'vh'); document.getElementById('app')?.classList.toggle('side', P.nav === 'side'); } /** Die Palette der App, damit die Farben sich decken. */ export const PALETTE: readonly [string, string][] = [ ['Ember', '#E5484D'], ['Tangerine', '#F76B15'], ['Amber', '#FFB224'], ['Lime', '#99D52A'], ['Mint', '#3DD68C'], ['Teal', '#12A594'], ['Sky', '#52A9FF'], ['Indigo', '#7C77E8'], ['Orchid', '#C04AC9'], ['Rose', '#F76190'], ]; export const RATES = [0.75, 1.0, 1.25, 1.5, 1.75, 2.0] as const; export const SKIPS = [5, 10, 15, 30, 45] as const; export const QUALITIES: readonly [number, string][] = [ [0, 'Original'], [12000, 'Hoch — 12 Mbit/s'], [8000, 'Mittel — 8 Mbit/s'], [4000, 'Niedrig — 4 Mbit/s'], [2000, 'Sparsam — 2 Mbit/s'], ]; export const LANGUAGES: readonly [string, string][] = [ ['', 'Serverstandard'], ['ger', 'Deutsch'], ['eng', 'Englisch'], ['fre', 'Französisch'], ['spa', 'Spanisch'], ['ita', 'Italienisch'], ['jpn', 'Japanisch'], ['kor', 'Koreanisch'], ]; /* Anordnung der Reihen — dieselbe Rechnung wie `Arrangement` in MediaKit, samt ihrer wichtigsten Eigenschaft: **unbekannte Einträge landen am Ende statt zu verschwinden.** Sie zu verwerfen wäre die einfachere Regel — und der Grund dafür, dass eine neu angelegte Mediathek nie aufgetaucht wäre. */ export function anordnen(reihen: T[]): T[] { const sichtbar = reihen.filter((r) => !P.hiddenRows.includes(r.id)); if (!P.rowOrder.length) return sichtbar; const platz = new Map(P.rowOrder.map((id, i) => [id, i])); return sichtbar .map((reihe, i) => ({ reihe, rang: platz.get(reihe.id) ?? Infinity, i })) // Bei gleichem Rang gilt die ursprüngliche Folge — sonst springen zwei // unbekannte Reihen bei jedem Laden gegeneinander. .sort((a, b) => a.rang - b.rang || a.i - b.i) .map((x) => x.reihe); }