Elf Abschnitte in einer Spalte waren zehn zu viel
Die Verwaltung war eine Seite mit 1786 Zeilen. Wer das Protokoll sehen wollte, scrollte an Mediatheken, Prüfliste, Benutzern und Speicher vorbei — und jede neue Einstellung hätte sie länger gemacht. Jetzt hat sie eine eigene Leiste mit sieben Bereichen, jeder mit seiner Adresse: `#/verwaltung/protokolle` lässt sich ablegen, und ein Neuladen bleibt dort stehen. Die Leiste sieht aus wie die Hauptleiste daneben, damit sie als Navigation gelesen wird und nicht als Knopfreihe; am Telefon wird aus der Spalte ein Streifen zum Schieben. **Gebaut wird nur, was offen ist.** Das ist nicht nur sparsamer: das Abspielfenster fragt im Fünf-Sekunden-Takt nach, die Mediathekenliste beobachtet laufende Scans. Gemessen: nach dem Wechsel in einen anderen Bereich kommt zwölf Sekunden lang keine Anfrage mehr. `admin.ts` ist in acht Dateien zerlegt — eine je Bereich, dazu `teile.ts` für die gemeinsame Überschrift und `index.ts` für Leiste und Weiche. Zwei Dinge mussten dabei nachziehen: **Die Markierung in der Hauptleiste.** Sie verglich stur zwei Segmente; bei `#/verwaltung/metadaten` traf das auf keinen Eintrag, und „Verwaltung" verlor seinen Balken. Jetzt gilt auch ein Segment. **„5 Titel warten — ansehen".** Der Knopf sprang mit `scrollIntoView` zur Prüfliste. Die steht nicht mehr im selben Dokument — er wechselt jetzt den Bereich. Die Hervorhebung danach entfällt damit, und mit ihr ihr CSS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
94c6604e8b
commit
1e4a231d1b
@@ -0,0 +1,218 @@
|
||||
import {
|
||||
sprachen, spracheSetzen, uebersetzungenHolen, meineSprache, metadatenEinstellung,
|
||||
metadatenSetzen, sprachverteilung, abweichendeAuffrischen,
|
||||
} from '../../api';
|
||||
import { esc, knopf, fehlerzeile, meldung } from '../../util';
|
||||
import { dialog } from '../../dialog';
|
||||
import { icon } from '../../icons';
|
||||
|
||||
import { abschnitt } from './teile';
|
||||
|
||||
/** Sprache und Herkunft der Metadaten. */
|
||||
|
||||
/* ---------- Server ---------- */
|
||||
|
||||
/** Sprache — eigener Abschnitt, ganz oben.
|
||||
*
|
||||
* **Warum nicht in der Serverliste.** Dort stand sie, zwischen „Name",
|
||||
* „Metadaten", „Doppelte Einträge" und „Fassung". Gesucht wurde sie
|
||||
* trotzdem, gefunden nicht — und das ist die Antwort: eine Einstellung, die
|
||||
* man sucht und nicht findet, steht am falschen Platz, egal wie gut sie
|
||||
* beschriftet ist.
|
||||
*
|
||||
* **Oben, weil sie alles andere färbt.** In dieser Sprache werden Metadaten
|
||||
* geholt und gespeichert; wer sie spät umstellt, frischt den ganzen Bestand
|
||||
* noch einmal auf.
|
||||
*/
|
||||
export function sprachbereich(): HTMLElement {
|
||||
const bereich = document.createElement('section');
|
||||
bereich.appendChild(abschnitt('Sprache'));
|
||||
|
||||
const liste = document.createElement('div');
|
||||
liste.className = 'einstellungen';
|
||||
bereich.appendChild(liste);
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const stand = await metadatenEinstellung();
|
||||
const eigeneSprache = meineSprache();
|
||||
liste.innerHTML = '';
|
||||
// **Die Sprache des Servers als eigene Zeile.**
|
||||
//
|
||||
// Sie steckte im Dialog „Metadaten" — zusammen mit dem TMDB-Schlüssel,
|
||||
// also dort, wo niemand nach einer Sprache sucht. Wer die Mediathek auf
|
||||
// Englisch vorfand, sah nur „Meine Sprache" und hielt das für die
|
||||
// einzige Möglichkeit.
|
||||
liste.appendChild(zeile(
|
||||
'Sprache des Servers',
|
||||
sprachen.find(x => x.wert === stand.language)?.text ?? stand.language,
|
||||
'In dieser Sprache werden Titel und Beschreibungen geholt, und sie '
|
||||
+ 'bestimmt die Altersfreigabe. Gilt für alle. Vorhandene Titel behalten '
|
||||
+ 'ihre alte Sprache — dafür gibt es bei jeder Mediathek „Metadaten '
|
||||
+ 'auffrischen".',
|
||||
() => dialog({
|
||||
titel: 'Sprache des Servers',
|
||||
text: 'Die Umstellung wirkt auf neue Funde sofort. Für den vorhandenen '
|
||||
+ 'Bestand drückst du danach bei jeder Mediathek „Metadaten auffrischen" '
|
||||
+ '— sonst behalten die Titel ihre alte Sprache.',
|
||||
felder: [{
|
||||
name: 'sprache', beschriftung: 'Sprache', auswahl: sprachen, wert: stand.language,
|
||||
}],
|
||||
bestaetigen: 'Übernehmen',
|
||||
tun: async wert => {
|
||||
await metadatenSetzen({ language: wert('sprache') });
|
||||
location.reload();
|
||||
},
|
||||
})));
|
||||
|
||||
// **Die eigene Sprache, getrennt von der des Servers.** Die obere
|
||||
// bestimmt, was geholt wird; diese nur, was man selbst zu sehen bekommt.
|
||||
// **Was tatsächlich vorliegt — nicht was eingestellt ist.**
|
||||
//
|
||||
// Der eigentliche Fehler war nicht, dass Titel englisch blieben, sondern
|
||||
// dass sich nicht feststellen liess, ob sie es sind: nirgends stand, in
|
||||
// welcher Sprache ein Titel geholt wurde. Hier steht es, als Zahl.
|
||||
void sprachverteilung().then(v => {
|
||||
const benennen = (k: string | null) => k === null
|
||||
? 'unbekannt (vor dieser Fassung geholt)'
|
||||
: sprachen.find(x => x.wert === k)?.text ?? k;
|
||||
|
||||
const zeileBestand = zeile(
|
||||
'Bestand',
|
||||
v.anteile.length === 0
|
||||
? 'Noch nichts zugeordnet'
|
||||
: v.anteile.map(a => `${a.anzahl} × ${benennen(a.sprache)}`).join(' · '),
|
||||
v.abweichend === 0
|
||||
? 'Alles liegt in der Serversprache vor.'
|
||||
: `${v.abweichend} Titel liegen in einer anderen Sprache vor. Das Auffrischen `
|
||||
+ 'holt genau diese neu — nicht den ganzen Bestand.');
|
||||
|
||||
if (v.abweichend > 0) {
|
||||
const los = knopf(`${v.abweichend} auffrischen`, 'btn btn-primary');
|
||||
los.onclick = async () => {
|
||||
los.disabled = true;
|
||||
try {
|
||||
await abweichendeAuffrischen();
|
||||
los.textContent = 'Läuft…';
|
||||
} catch (fehler) {
|
||||
zeileBestand.appendChild(fehlerzeile(meldung(fehler)));
|
||||
los.disabled = false;
|
||||
}
|
||||
};
|
||||
zeileBestand.querySelector('.knopfreihe')?.appendChild(los);
|
||||
}
|
||||
|
||||
// Direkt unter die Serversprache, denn dort wird die Frage gestellt.
|
||||
liste.insertBefore(zeileBestand, liste.children[1] ?? null);
|
||||
}).catch(() => { /* Ohne Verteilung fehlt nur die Zeile. */ });
|
||||
|
||||
// **Weicht sie ab, steht das deutlich dabei.**
|
||||
//
|
||||
// Eine eigene Sprache ändert nicht, was der Server holt — nur was man
|
||||
// selbst sieht. Steht sie auf Englisch, während der Server auf Deutsch
|
||||
// holt, sieht die ganze Mediathek englisch aus, und jedes „Metadaten
|
||||
// auffrischen" scheint wirkungslos. Genau in diese Falle läuft man,
|
||||
// ohne je zu erfahren, warum.
|
||||
const weichtAb = eigeneSprache !== null && eigeneSprache !== stand.language;
|
||||
const serversprache = sprachen.find(x => x.wert === stand.language)?.text ?? stand.language;
|
||||
|
||||
liste.appendChild(zeile(
|
||||
weichtAb ? 'Meine Sprache — weicht ab' : 'Meine Sprache',
|
||||
eigeneSprache ? (sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache)
|
||||
: 'Wie der Server',
|
||||
weichtAb
|
||||
? `Du siehst alles auf ${sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache}, `
|
||||
+ `obwohl der Server auf ${serversprache} holt. Titel ohne Übersetzung bleiben `
|
||||
+ `auf ${serversprache}. Auf „Wie der Server" stellen, um zu sehen, was wirklich `
|
||||
+ 'gespeichert ist.'
|
||||
: 'Gilt nur für dich. Was nicht übersetzt vorliegt, erscheint weiterhin in '
|
||||
+ 'der Sprache des Servers.',
|
||||
() => dialog({
|
||||
titel: 'Meine Sprache',
|
||||
text: 'Die Metadaten liegen in der Sprache des Servers vor. Eine andere '
|
||||
+ 'zu wählen heisst: du siehst, was übersetzt vorliegt — und für den '
|
||||
+ 'Rest die Serversprache. Ein Administrator kann die Übersetzungen '
|
||||
+ 'unten für alle Titel auf einmal holen.',
|
||||
felder: [{
|
||||
name: 'sprache', beschriftung: 'Sprache',
|
||||
auswahl: [{ wert: '', text: 'Wie der Server' }, ...sprachen],
|
||||
wert: eigeneSprache ?? '',
|
||||
}],
|
||||
bestaetigen: 'Übernehmen',
|
||||
tun: async wert => {
|
||||
const neu = wert('sprache');
|
||||
await spracheSetzen(neu || null);
|
||||
location.reload();
|
||||
},
|
||||
})));
|
||||
|
||||
liste.appendChild(zeile(
|
||||
'Übersetzungen holen',
|
||||
`Füllt eine zweite Sprache neben ${serversprache} auf`,
|
||||
`Der Bestand liegt auf ${serversprache} vor. Das hier legt zusätzlich `
|
||||
+ 'eine zweite Fassung an, für Benutzer, die eine andere Sprache gewählt '
|
||||
+ 'haben — am Bestand selbst ändert es nichts. Kostet je Titel eine '
|
||||
+ 'Anfrage an TMDB.',
|
||||
() => dialog({
|
||||
titel: 'Übersetzungen holen',
|
||||
text: `Die Titel des Servers bleiben auf ${serversprache}. Geholt wird `
|
||||
+ 'eine zusätzliche Fassung, die nur Benutzer mit genau dieser Sprache '
|
||||
+ 'zu sehen bekommen.',
|
||||
felder: [{
|
||||
name: 'sprache', beschriftung: 'Für welche Sprache',
|
||||
// **Nicht stur en-US.** Stand dort „Englisch", während der Server
|
||||
// auf Deutsch steht, las sich das wie eine Aussage über den
|
||||
// Server — und führte zu der Vermutung, die Spracheinstellung
|
||||
// wirke nicht. Vorgewählt wird die eigene Sprache, sonst die
|
||||
// erste, die nicht schon die des Servers ist.
|
||||
auswahl: sprachen,
|
||||
wert: eigeneSprache
|
||||
?? sprachen.find(x => x.wert !== stand.language)?.wert
|
||||
?? stand.language,
|
||||
}],
|
||||
bestaetigen: 'Holen',
|
||||
tun: async wert => {
|
||||
const bericht = await uebersetzungenHolen(wert('sprache'));
|
||||
alert(`${bericht.gesamt} Übersetzungen geholt, ${bericht.fehlend} ohne Treffer.`);
|
||||
},
|
||||
})));
|
||||
} catch (fehler) {
|
||||
liste.innerHTML = '';
|
||||
liste.appendChild(fehlerzeile(meldung(fehler)));
|
||||
}
|
||||
})();
|
||||
|
||||
return bereich;
|
||||
|
||||
function zeile(
|
||||
name: string, wert: string, hinweis: string | null, bearbeiten?: () => void,
|
||||
): HTMLElement {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'einstellung';
|
||||
|
||||
const oben = document.createElement('div');
|
||||
oben.className = 'knopfreihe';
|
||||
const text = document.createElement('div');
|
||||
text.className = 'zeile';
|
||||
text.style.flex = '1';
|
||||
text.innerHTML = `<strong>${esc(name)}</strong><span class="winzig">${esc(wert)}</span>`;
|
||||
oben.appendChild(text);
|
||||
|
||||
if (bearbeiten) {
|
||||
const b = knopf('Ändern', 'btn');
|
||||
b.prepend(icon('stift'));
|
||||
b.onclick = bearbeiten;
|
||||
oben.appendChild(b);
|
||||
}
|
||||
d.appendChild(oben);
|
||||
|
||||
if (hinweis) {
|
||||
const h = document.createElement('div');
|
||||
h.className = 'hinweis';
|
||||
h.textContent = hinweis;
|
||||
d.appendChild(h);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user