Drei Zähler für dieselbe Sache, und keiner davon offensichtlich falsch

Während eines Scans stand in der Mediathek 20, in der Verwaltung 14 und
in der Seitenleiste 0. Die Verwaltung frischte alle fünf Runden auf, die
Seitenleiste erst am Ende des Laufs. Jetzt laufen beide auf demselben
Takt, alle drei Runden.

Dazu: während ein Lauf läuft, sind Scannen, Metadaten auffrischen,
Zurücksetzen und Entfernen gesperrt — die ersten beiden prallen am
Server ab und sind nur verwirrend, die anderen beiden löschen Zeilen,
während der Lauf welche schreibt. Umbenennen und Prüfung bleiben
bedienbar. Die Sperre richtet sich nach dem Zustand, den der Server
meldet, nicht nach dem Klick: wer die Seite neu lädt, während ein Lauf
läuft, findet sie ebenfalls gesperrt vor.

Die Reihenfolge der Mediatheken wird jetzt in der Seitenleiste
umsortiert, über „Seitenleiste bearbeiten" neben der Überschrift. Sie
wirkt dort, also gehört der Griff dorthin — vorher schob man in den
Einstellungen und musste danach nachsehen, ob es das war, was man
wollte.

Der Zuordnungsdialog zeigt die Herkunft: Ordner, Zahl der Dateien und
ein paar Namen. Liegt eine Zuordnung völlig daneben, sagt der angezeigte
Titel nichts mehr darüber, welche Datei gemeint ist.

Und SQLite läuft jetzt im WAL-Modus. In der Vorgabe sperrt ein Schreiber
die ganze Datei: solange der Scan schrieb, kam keine Leseanfrage durch.
Genau so sah es aus — Bilddialog und Protokoll liefen während eines
Laufs sporadisch ins Leere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-04 16:31:50 +02:00
co-authored by Claude Opus 5
parent e1b3625809
commit a22fa9b58e
7 changed files with 348 additions and 65 deletions
+93 -8
View File
@@ -1,6 +1,7 @@
import './style.css';
import {
systemauskunft, angemeldet, istAdmin, abmelden, sitzung, bibliotheken,
reihenfolgeSetzen,
type Hinweis, type Bibliothek,
} from './api';
import { icons, icon } from './icons';
@@ -133,6 +134,9 @@ function kopfleisteBauen(): void {
/* ---------- Seitenleiste ---------- */
/** Ob die Seitenleiste gerade umsortiert wird. */
let leisteBearbeiten = false;
function leisteBauen(): void {
leiste.innerHTML = '';
@@ -142,31 +146,112 @@ function leisteBauen(): void {
// Zeile „Filme", hinter der zwei getrennt angelegte Bestände liegen, nähme
// die Entscheidung zurück, sie zu trennen.
if (mediatheken.length) {
gruppe('Mediatheken');
mediatheken.forEach(b => eintrag(
b.name, `#/m/${b.id}`, b.kind === 'Shows' ? 'serien' : 'filme', b.itemCount));
gruppe('Mediatheken', istAdmin() && mediatheken.length > 1);
mediatheken.forEach((b, i) => eintrag(
b.name, `#/m/${b.id}`, b.kind === 'Shows' ? 'serien' : 'filme', b.itemCount, i));
}
if (istAdmin()) { gruppe('Server'); eintrag('Verwaltung', '#/verwaltung', 'verwaltung'); }
function gruppe(text: string): void {
/** Eine Gruppenüberschrift — bei den Mediatheken mit dem Stift daneben.
*
* **Der Griff sitzt dort, wo die Reihenfolge wirkt.** Vorher standen die
* Pfeile in den Einstellungen: man schob dort etwas und musste danach in
* die Seitenleiste sehen, ob es das war, was man wollte.
*/
function gruppe(text: string, mitStift = false): void {
const d = document.createElement('div');
d.className = 'nav-gruppe';
d.textContent = text;
const t = document.createElement('span');
t.textContent = text;
d.appendChild(t);
if (mitStift) {
const stift = document.createElement('button');
stift.className = 'nav-bearbeiten' + (leisteBearbeiten ? ' an' : '');
stift.innerHTML = leisteBearbeiten
? '<span class="icon">' + icons.haken + '</span>'
: '<span class="icon">' + icons.stift + '</span>';
stift.title = leisteBearbeiten ? 'Fertig' : 'Seitenleiste bearbeiten';
stift.setAttribute('aria-label', stift.title);
stift.onclick = () => { leisteBearbeiten = !leisteBearbeiten; leisteBauen(); };
d.appendChild(stift);
}
leiste.appendChild(d);
}
function eintrag(
text: string, ziel: string, bild: keyof typeof icons, anzahl?: number | null,
text: string, ziel: string, bild: keyof typeof icons,
anzahl?: number | null, platz?: number,
): void {
const b = document.createElement('button');
b.dataset.ziel = ziel;
b.innerHTML = `<span class="icon">${icons[bild]}</span>`
+ `<span class="name">${esc(text)}</span>`
+ (anzahl != null ? `<span class="zahl">${anzahl}</span>` : '');
b.onclick = () => { location.hash = ziel; };
+ (anzahl != null && !leisteBearbeiten ? `<span class="zahl">${anzahl}</span>` : '');
// Im Bearbeitungsmodus führt die Zeile nirgendwohin — sonst wechselt man
// beim Zielen auf einen Pfeil versehentlich die Ansicht.
if (leisteBearbeiten && platz != null) {
b.classList.add('verschiebbar');
b.appendChild(pfeil('hoch', platz, platz - 1, platz === 0, text));
b.appendChild(pfeil('runter', platz, platz + 1, platz === mediatheken.length - 1, text));
} else {
b.onclick = () => { location.hash = ziel; };
}
leiste.appendChild(b);
}
/** Ein Pfeil zum Schieben.
*
* **Zwei Knöpfe statt Ziehen und Ablegen.** Ziehen ist schöner, aber mit
* der Tastatur unbedienbar und auf dem Telefon ein Glücksspiel.
*/
function pfeil(
richtung: 'hoch' | 'runter', von: number, nach: number, gesperrt: boolean, name: string,
): HTMLElement {
const p = document.createElement('span');
p.className = 'nav-pfeil' + (gesperrt ? ' gesperrt' : '');
p.setAttribute('role', 'button');
if (!gesperrt) p.setAttribute('tabindex', '0');
p.setAttribute('aria-label', `„${name}" nach ${richtung === 'hoch' ? 'oben' : 'unten'}`);
p.innerHTML = icons.hoch;
if (richtung === 'runter') p.style.transform = 'rotate(180deg)';
const los = (e: Event) => {
e.stopPropagation();
if (gesperrt) return;
void schieben(von, nach);
};
p.onclick = los;
p.onkeydown = e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); los(e); } };
return p;
}
}
/** Schiebt eine Mediathek und schickt die ganze neue Reihenfolge.
*
* **Die ganze Liste, nicht „eins hoch".** Zwei Benutzer, die gleichzeitig
* schieben, kämen sonst in einen Zustand, den keiner von beiden wollte.
*/
async function schieben(von: number, nach: number): Promise<void> {
if (nach < 0 || nach >= mediatheken.length) return;
const neu = [...mediatheken];
const [eintrag] = neu.splice(von, 1);
neu.splice(nach, 0, eintrag!);
// Sofort zeichnen, damit der Klick sich sofort anfühlt; der Server bekommt
// dieselbe Liste und widerspricht beim nächsten Laden, falls doch etwas
// schiefging.
mediatheken = neu;
leisteBauen();
try { await reihenfolgeSetzen(neu.map(b => b.id)); }
catch { await zaehlerAuffrischen(); }
}
/* ---------- Router ---------- */