Web: Reihen ordnen und ausblenden
Phase 3 des Abgleichs. Dieselbe Rechnung wie Arrangement in der App, samt ihrer wichtigsten Eigenschaft: unbekannte Reihen 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. Ausgeblendete stehen in den Einstellungen weiter unten in der Liste, durchgestrichen. Ohne das könnte man sie nie wieder einblenden. Knöpfe statt Ziehen: auf dem Zeigegerät ist Ziehen schöner, auf einem Trackpad fummelig und mit der Tastatur gar nicht zu machen. Zwei Pfeile können alle drei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2e15cdfddb
commit
14e1fdbe3f
+122
-4
@@ -306,6 +306,18 @@ img { display: block; }
|
|||||||
}
|
}
|
||||||
.hexrow input:focus { outline: none; border-color: var(--accent); }
|
.hexrow input:focus { outline: none; border-color: var(--accent); }
|
||||||
.hint { font-size: 12px; color: var(--text-3); margin: var(--s-snug) 0 0; min-height: 16px; }
|
.hint { font-size: 12px; color: var(--text-3); margin: var(--s-snug) 0 0; min-height: 16px; }
|
||||||
|
.rowline {
|
||||||
|
display: flex; align-items: center; gap: var(--s-snug);
|
||||||
|
padding: var(--s-snug) 0; border-bottom: 1px solid var(--separator);
|
||||||
|
}
|
||||||
|
.rowline:last-child { border-bottom: none; }
|
||||||
|
.rowline.off span { color: var(--text-3); text-decoration: line-through; }
|
||||||
|
.tiny {
|
||||||
|
width: 30px; height: 30px; border-radius: var(--r-control);
|
||||||
|
background: var(--elevated); color: var(--text-2); font-size: 14px; padding: 0;
|
||||||
|
}
|
||||||
|
.tiny:hover:not(:disabled) { color: var(--text); }
|
||||||
|
.tiny:disabled { opacity: .3; cursor: default; }
|
||||||
.setting {
|
.setting {
|
||||||
background: var(--surface); border: 1px solid var(--separator);
|
background: var(--surface); border: 1px solid var(--separator);
|
||||||
border-radius: var(--r-sheet); padding: var(--s-loose); margin-bottom: var(--s-regular);
|
border-radius: var(--r-sheet); padding: var(--s-loose); margin-bottom: var(--s-regular);
|
||||||
@@ -517,6 +529,10 @@ const P = {
|
|||||||
heroHeight: +(localStorage.getItem('flixy.heroHeight') || 82),
|
heroHeight: +(localStorage.getItem('flixy.heroHeight') || 82),
|
||||||
combineContinue: localStorage.getItem('flixy.combineContinue') !== 'false',
|
combineContinue: localStorage.getItem('flixy.combineContinue') !== 'false',
|
||||||
hideWatched: localStorage.getItem('flixy.hideWatched') === 'true',
|
hideWatched: localStorage.getItem('flixy.hideWatched') === 'true',
|
||||||
|
// Welche Reihen ausgeblendet sind und in welcher Reihenfolge der Rest
|
||||||
|
// steht — dieselbe Aufteilung wie `Arrangement` in der App.
|
||||||
|
hiddenRows: JSON.parse(localStorage.getItem('flixy.hiddenRows') || '[]'),
|
||||||
|
rowOrder: JSON.parse(localStorage.getItem('flixy.rowOrder') || '[]'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function applyPrefs() {
|
function applyPrefs() {
|
||||||
@@ -531,10 +547,31 @@ function applyPrefs() {
|
|||||||
|
|
||||||
function setPref(key, value) {
|
function setPref(key, value) {
|
||||||
P[key] = value;
|
P[key] = value;
|
||||||
localStorage.setItem('flixy.' + key, value);
|
// 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.' + key, Array.isArray(value) ? JSON.stringify(value) : value);
|
||||||
applyPrefs();
|
applyPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wendet Auswahl und Reihenfolge auf die Reihen an.
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
Dieselbe Regel wie `Arrangement` in der App, 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. */
|
||||||
|
function applyArrangement(rows) {
|
||||||
|
const visible = rows.filter((r) => !P.hiddenRows.includes(r.id));
|
||||||
|
if (!P.rowOrder.length) return visible;
|
||||||
|
|
||||||
|
const at = new Map(P.rowOrder.map((id, i) => [id, i]));
|
||||||
|
return visible
|
||||||
|
.map((row, i) => ({ row, known: at.has(row.id) ? at.get(row.id) : Infinity, i }))
|
||||||
|
// Bei gleichem Rang gilt die ursprüngliche Folge — sonst springen zwei
|
||||||
|
// unbekannte Reihen bei jedem Laden gegeneinander.
|
||||||
|
.sort((a, b) => (a.known - b.known) || (a.i - b.i))
|
||||||
|
.map((x) => x.row);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Navigation ---------- */
|
/* ---------- Navigation ---------- */
|
||||||
let views = [], current = null;
|
let views = [], current = null;
|
||||||
|
|
||||||
@@ -816,6 +853,13 @@ function renderSettings() {
|
|||||||
Gesehenes aus den Neuzugängen ausblenden</label>
|
Gesehenes aus den Neuzugängen ausblenden</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting">
|
||||||
|
<h4>Reihen der Startseite</h4>
|
||||||
|
<p>Welche Reihen erscheinen und in welcher Folge. Was der Server neu
|
||||||
|
anlegt, kommt ans Ende, statt zu fehlen.</p>
|
||||||
|
<div id="set-rows"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="group">Server</h3>
|
<h3 class="group">Server</h3>
|
||||||
|
|
||||||
<div class="setting">
|
<div class="setting">
|
||||||
@@ -860,6 +904,8 @@ function renderSettings() {
|
|||||||
check('set-combine', 'combineContinue');
|
check('set-combine', 'combineContinue');
|
||||||
check('set-hidewatched', 'hideWatched');
|
check('set-hidewatched', 'hideWatched');
|
||||||
|
|
||||||
|
renderRowArrangement();
|
||||||
|
|
||||||
const z = el('set-zoom'), zo = el('set-zoom-out');
|
const z = el('set-zoom'), zo = el('set-zoom-out');
|
||||||
z.value = P.cardW; zo.textContent = P.cardW + ' px';
|
z.value = P.cardW; zo.textContent = P.cardW + ' px';
|
||||||
z.oninput = () => { zo.textContent = z.value + ' px'; setPref('cardW', +z.value); };
|
z.oninput = () => { zo.textContent = z.value + ' px'; setPref('cardW', +z.value); };
|
||||||
@@ -888,6 +934,11 @@ function renderSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Startseite ---------- */
|
/* ---------- Startseite ---------- */
|
||||||
|
|
||||||
|
/// Welche Reihen es gibt — für die Einstellungen. Erst nach dem ersten Laden
|
||||||
|
/// gefüllt: vorher weiß niemand, welche Mediatheken dieser Server hat.
|
||||||
|
let knownRows = [];
|
||||||
|
|
||||||
async function renderHome() {
|
async function renderHome() {
|
||||||
const c = el('content');
|
const c = el('content');
|
||||||
c.innerHTML = '<div class="spinner"></div>';
|
c.innerHTML = '<div class="spinner"></div>';
|
||||||
@@ -911,15 +962,18 @@ async function renderHome() {
|
|||||||
if (P.combineContinue) {
|
if (P.combineContinue) {
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const merged = [...resume, ...next].filter((i) => !seen.has(i.Id) && seen.add(i.Id));
|
const merged = [...resume, ...next].filter((i) => !seen.has(i.Id) && seen.add(i.Id));
|
||||||
rows = [{ title: 'Weiterschauen', items: merged, wide: true }];
|
// Die zusammengelegte Reihe behält die Kennung der ersten — sonst wäre
|
||||||
|
// sie für die gespeicherte Reihenfolge eine neue und stünde wieder unten.
|
||||||
|
rows = [{ id: 'resume', title: 'Weiterschauen', items: merged, wide: true }];
|
||||||
} else {
|
} else {
|
||||||
rows = [
|
rows = [
|
||||||
{ title: 'Weiterschauen', items: resume, wide: true },
|
{ id: 'resume', title: 'Weiterschauen', items: resume, wide: true },
|
||||||
{ title: 'Als Nächstes', items: next, wide: true },
|
{ id: 'nextup', title: 'Als Nächstes', items: next, wide: true },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(...views.map((v, i) => ({
|
rows.push(...views.map((v, i) => ({
|
||||||
|
id: 'latest.' + v.Id,
|
||||||
title: `Neu in ${v.Name}`,
|
title: `Neu in ${v.Name}`,
|
||||||
// Gesehenes ausblenden gilt nur für Neuzugänge: in „Weiterschauen" wäre
|
// Gesehenes ausblenden gilt nur für Neuzugänge: in „Weiterschauen" wäre
|
||||||
// es sinnlos, dort ist alles angefangen.
|
// es sinnlos, dort ist alles angefangen.
|
||||||
@@ -931,6 +985,12 @@ async function renderHome() {
|
|||||||
})));
|
})));
|
||||||
rows = rows.filter((r) => r.items.length);
|
rows = rows.filter((r) => r.items.length);
|
||||||
|
|
||||||
|
// Was es bei diesem Server überhaupt gibt, merken — **vor** dem Ausblenden.
|
||||||
|
// Die Einstellungen müssen auch die Reihen anbieten, die gerade versteckt
|
||||||
|
// sind; sonst könnte man sie nie wieder einblenden.
|
||||||
|
knownRows = rows.map((r) => ({ id: r.id, title: r.title }));
|
||||||
|
rows = applyArrangement(rows);
|
||||||
|
|
||||||
const heroPool = P.heroSource === 'latestOnly'
|
const heroPool = P.heroSource === 'latestOnly'
|
||||||
? latest.flat()
|
? latest.flat()
|
||||||
: [...resume, ...next, ...latest.flat()];
|
: [...resume, ...next, ...latest.flat()];
|
||||||
@@ -1101,6 +1161,64 @@ const cardSub = (it) =>
|
|||||||
? `S${it.ParentIndexNumber}:E${it.IndexNumber} · ${it.Name}`
|
? `S${it.ParentIndexNumber}:E${it.IndexNumber} · ${it.Name}`
|
||||||
: (it.ProductionYear || '');
|
: (it.ProductionYear || '');
|
||||||
|
|
||||||
|
/* Die Liste der Reihen — sichtbar machen, ausblenden, verschieben.
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
Knöpfe statt Ziehen: auf dem Zeigegerät ist Ziehen schöner, auf einem
|
||||||
|
Laptop-Trackpad mit einer Liste dieser Länge aber fummelig, und mit der
|
||||||
|
Tastatur gar nicht zu machen. Zwei Pfeile können alle drei. */
|
||||||
|
function renderRowArrangement() {
|
||||||
|
const host = el('set-rows');
|
||||||
|
if (!host) return;
|
||||||
|
|
||||||
|
if (!knownRows.length) {
|
||||||
|
host.innerHTML = '<p class="hint">Erst sichtbar, wenn die Startseite einmal geladen war.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In der Folge, die gerade gilt — ausgeblendete hinten dran, damit man sie
|
||||||
|
// wiederfindet.
|
||||||
|
const ordered = applyArrangement(knownRows)
|
||||||
|
.concat(knownRows.filter((r) => P.hiddenRows.includes(r.id)));
|
||||||
|
|
||||||
|
host.innerHTML = '';
|
||||||
|
ordered.forEach((row, index) => {
|
||||||
|
const hidden = P.hiddenRows.includes(row.id);
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'rowline' + (hidden ? ' off' : '');
|
||||||
|
line.innerHTML = `
|
||||||
|
<label class="check" style="flex:1;padding:0">
|
||||||
|
<input type="checkbox" ${hidden ? '' : 'checked'}>
|
||||||
|
<span>${esc(row.title)}</span>
|
||||||
|
</label>
|
||||||
|
<button class="tiny" aria-label="Nach oben" ${index === 0 ? 'disabled' : ''}>↑</button>
|
||||||
|
<button class="tiny" aria-label="Nach unten" ${index === ordered.length - 1 ? 'disabled' : ''}>↓</button>`;
|
||||||
|
|
||||||
|
line.querySelector('input').onchange = (e) => {
|
||||||
|
const next = e.target.checked
|
||||||
|
? P.hiddenRows.filter((id) => id !== row.id)
|
||||||
|
: [...P.hiddenRows, row.id];
|
||||||
|
setPref('hiddenRows', next);
|
||||||
|
renderRowArrangement();
|
||||||
|
};
|
||||||
|
|
||||||
|
const move = (by) => {
|
||||||
|
// Die aktuelle Folge festschreiben, bevor darin verschoben wird: ohne
|
||||||
|
// gespeicherte Folge gäbe es nichts, worin man tauschen könnte.
|
||||||
|
const ids = ordered.map((r) => r.id);
|
||||||
|
const to = index + by;
|
||||||
|
if (to < 0 || to >= ids.length) return;
|
||||||
|
[ids[index], ids[to]] = [ids[to], ids[index]];
|
||||||
|
setPref('rowOrder', ids);
|
||||||
|
renderRowArrangement();
|
||||||
|
};
|
||||||
|
const [up, down] = line.querySelectorAll('button');
|
||||||
|
up.onclick = () => move(-1);
|
||||||
|
down.onclick = () => move(1);
|
||||||
|
|
||||||
|
host.appendChild(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Mediathek ---------- */
|
/* ---------- Mediathek ---------- */
|
||||||
async function renderLibrary(id) {
|
async function renderLibrary(id) {
|
||||||
const c = el('content');
|
const c = el('content');
|
||||||
|
|||||||
Reference in New Issue
Block a user