Filipe: „diese kachel soll nicht mehr geschlossen sein, ABER wenn man
drauf drückt geht zuerst eine seite auf mit reihen/kategorien. links
eine reihe wo die nächsten geplanten videos schon bereit stehen. wan
und umd wie viel uhr … und das soll geschlossen sein außer für vanvan
und dogfather. in der mitte sollen die leute dan wenn eine sendung
läuft drauf drücken können und der sendung beitreten. auch bei der
vorschau schon und dan rechts will ich die letzte reihe/kategorie, da
sollen die leute alle mir videos anraten können … und nur ich und
vanvan sehen sie."
Haus: Team Dogi (crew.dogfather-universe.com). Die Agentur ist nicht
berührt -- pruef-haus-trennung 107/0, pruef-workspace-seiten 37/0.
WARUM EINE EIGENE SEITE UND KEIN VIERTER ZUSTAND DES SAALS
Der Saal meldet jeden an, der ihn öffnet („dabei"), baut Verbindungen
auf und hält den Ereignisstrom offen. Das ist richtig für jemanden,
der zusieht -- und falsch für jemanden, der nur nachsehen will, ob
heute Abend etwas läuft. Wäre das Foyer der vierte Zustand derselben
Seite, zählte jeder Blick als Zuschauer, und die Zahl im Regiepult
sähe nach Publikum aus, wo niemand zusieht.
Im Foyer wird nur gelesen: Stand holen, zeichnen, alle 20 Sekunden
nachfragen -- und nicht, solange der Reiter im Hintergrund liegt. Kein
Ereignisstrom: Der hielte eine Verbindung offen, und manche Browser
geben je Adresse nur sechs her; im Saal wird jede davon gebraucht.
DREI REIHEN
LINKS die Warteschlange, nach TERMIN sortiert statt nach Platz.
Was keinen hat, steht unten; was vorbei ist, bekommt
„· überfällig". Nur admin und rechte Hand -- und für alle
anderen steht sie gar nicht erst da, statt leer oder
gesperrt zu sein. Ein Kasten mit Schloss erzählt, dass es
etwas zu sehen gäbe. Entschieden wird das am Server; die
Seite fragt nicht nach der Rolle, sondern danach, ob die
Auskunft gekommen ist.
MITTE Lampe (Geschlossen / Gleich / Auf Sendung), Vorschaubild,
Titel, Satz -- und der Knopf, der NUR dasteht, wenn er auch
hineinführt. Er ist ein Link und kein Button: Wer ihn mit
der mittleren Maustaste antippt, bekommt einen neuen Reiter.
RECHTS Video anraten. Jeder darf schicken, niemand sieht fremde,
jeder sieht seine eigenen mit Stand. Vier Sperren, und jede
beantwortet etwas anderes: zu schnell (45 s), zu viele offen
(5 je Person), schon angeraten, steht schon bereit.
Am Telefon steht die MITTE oben. Dort ist „links" nur noch „vor allem
anderen", und das wäre die falsche Reihenfolge.
WAS SONST NOCH DAZUGEHÖRT
* Jede Zeile der Warteschlange im Saal hat jetzt ein Terminfeld.
* Die Kachel sagt nicht mehr „geschlossen" -- das Foyer ist immer
offen. „gleich" und „● live" bleiben: Sie sagen etwas, das man ohne
sie nicht weiß.
* reaktion.html steht in OHNE_KACHEL_UEBERALL. Die erlaubten Seiten
werden aus den Kachelzielen abgeleitet; als die Kachel aufs Foyer
zeigte, war der Saal plötzlich von keiner mehr genannt und
antwortete mit einer Umleitung.
DREI DOPPELTE ANTWORTEN BESEITIGT
* „Wie viele Vorschläge darf einer offen haben" stand VIERMAL:
dreimal als `je_person_max || 5` und einmal als Wort „fünf" im Satz
(plus ein fünftes Mal in meldung.js). Kommt jetzt vom Server.
* `maxlength="200"` im Formular neben VORSCHLAG_NOTIZ_MAX im Server.
Das Feld lernt die Grenze von der Auskunft.
* Die drei Foyer-Tafeln hatten eine eigene Fläche (Weiß mit 4,5 %) und
eine eigene Form. Beides gibt es im Haus längst: Material in
start.css (45 Klassen, gemessen), Form in module.css. Die eigene
Fläche war dazu fast durchsichtig -- über einem hellen
Hintergrundbild stand der Text ohne deckenden Grund. Gefunden hat
das nicht eine Prüfung, sondern das Bildschirmfoto.
EINE LÜCKE IN DER PRÜFUNG GESCHLOSSEN
In module.css steht dieselbe 48er-Klassenliste ACHTMAL. Sieben trugen
die Marke MODULLISTE und wurden verglichen; die achte -- die Umrandung
beim Tabben (:focus-visible) -- nicht. Sie war damit die Einzige, die
still hätte abweichen können, ausgerechnet bei der Regel, die nur
sieht, wer mit der Tastatur bedient. pruef-css-klassen erwartet jetzt
acht. Eine steigende Prüfzahl, und sie ist begründet.
UND EINER, DEN KEIN WERKZEUG FINDEN KANN
Der Satz oben lautete „Links steht, was ansteht; rechts kannst du
etwas anraten." Für die Community gibt es „links" gar nicht, und am
Telefon stehen die Reihen untereinander. Eine Richtung im Text ist
eine Aussage über das Aussehen -- und das ändert sich mit dem
Bildschirm und mit der Rolle. Jetzt steht dort, WAS geht, nicht WO.
GEMESSEN
mess-foyer (neu) 70 Messungen, 0 Fehler, 9 Bildschirmfotos
pruef-reaktion 538, 0 Fehler (vorher 478)
pruef-handy-teamdogi 263 Seitenaufrufe, 0 Befunde (vorher 244)
pruef-sackgassen 14, 0 Fehler
pruef-crew-adresse 173, 0 Fehler
pruef-haus-trennung 107, 0 Fehler
pruef-struktur 102, 0 Fehler
pruef-neue-seiten 109, 0 Fehler
pruef-portnummern 41, 0 Fehler
pruef-haus-seiten 38, 0 Fehler
pruef-workspace-seiten 37, 0 Fehler
pruef-breiten 45 Seiten auf fünf Breiten, 0 beanstandet
pruef-community-sicht 10, 0 Fehler
pruef-deutsche-texte 12, 0 Fehler
pruef-css-klassen grün
mess-foyer klickt den ganzen Weg durch: Ein Gast schickt ein Video,
DogFather sieht es mit Namen, nimmt es, und es muss links im Plan
stehen. Zum Schluss macht es sechs seiner Messungen absichtlich kaputt
und prüft, dass sie das merken.
NICHT BEHOBEN, WEIL NICHT MEINS: pruef-meldungen ist rot (28
Kennungen ohne Satz, 3 Rohanzeigen) -- gemessen im worktree auf dem
letzten Commit schon vorher, Zeichen für Zeichen dieselbe Liste. Die
Dateien gehören überwiegend der Agentur; das gehört in einen eigenen
Durchgang.
Co-Authored-By: Claude Opus 5 <[email protected]>
541 lines
21 KiB
JavaScript
541 lines
21 KiB
JavaScript
/* =====================================================================
|
||
DAS FOYER (08.10.2026)
|
||
|
||
Die Seite vor dem Saal. Drei Reihen:
|
||
|
||
LINKS was ansteht -- nur fuer DogFather und VanVan
|
||
MITTE der Weg hinein, sobald etwas laeuft oder gleich losgeht
|
||
RECHTS Videos anraten -- jeder darf, niemand sieht fremde
|
||
|
||
---------------------------------------------------------------------
|
||
HIER WIRD NUR GELESEN
|
||
|
||
Der Saal meldet jeden an, der ihn oeffnet, baut Verbindungen auf
|
||
und haelt den Ereignisstrom offen. Diese Seite tut nichts davon:
|
||
Sie holt den Stand, zeichnet ihn und fragt alle 20 Sekunden nach.
|
||
Erst der Knopf in der Mitte fuehrt hinein.
|
||
|
||
WARUM 20 SEKUNDEN UND KEIN EREIGNISSTROM: Ein Strom haelt eine
|
||
Verbindung offen, und manche Browser lassen nur sechs je Adresse
|
||
zu -- im Saal wird jede davon gebraucht. Das Foyer ist der
|
||
ruhigste Zustand des Hauses: kein Video, keine Kamera, kein Chat.
|
||
Eine Abfrage alle zwanzig Sekunden kostet dort nichts und kann
|
||
nicht haengenbleiben.
|
||
===================================================================== */
|
||
(() => {
|
||
'use strict';
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
const el = (art, klasse, text) => {
|
||
const n = document.createElement(art);
|
||
if (klasse) n.className = klasse;
|
||
if (text !== undefined) n.textContent = text;
|
||
return n;
|
||
};
|
||
|
||
let lage = null;
|
||
let raten = null;
|
||
|
||
async function hole(pfad, einstellungen) {
|
||
return fetch(pfad, { credentials: 'same-origin', ...(einstellungen || {}) });
|
||
}
|
||
|
||
function sagFehler(text) {
|
||
const f = $('fehler');
|
||
if (!f) return;
|
||
f.textContent = text;
|
||
f.hidden = false;
|
||
clearTimeout(sagFehler.uhr);
|
||
sagFehler.uhr = setTimeout(() => { f.hidden = true; }, 6000);
|
||
}
|
||
|
||
/** Maschinenworte werden uebersetzt -- dieselbe Tafel wie im ganzen
|
||
* Haus. Wo sie nichts kennt, steht ein Satz und kein Schluessel. */
|
||
function melde(antwort, ersatz) {
|
||
const wort = antwort && antwort.fehler;
|
||
const satz = (window.sagWas && wort) ? window.sagWas(wort) : null;
|
||
sagFehler(satz || ersatz || 'Das hat gerade nicht geklappt.');
|
||
}
|
||
|
||
/* =================================================================
|
||
ZEIT UND DATUM -- IMMER DEUTSCH, IMMER DIESELBE FORM
|
||
|
||
„Do, 9. Okt, 20:30". Nicht „09.10.2026 20:30": Wer einen Plan
|
||
ueberfliegt, sucht den WOCHENTAG, nicht die Jahreszahl -- und
|
||
das Jahr steht ohnehin nur dann dabei, wenn es ein anderes ist.
|
||
================================================================= */
|
||
function alsTermin(wert) {
|
||
if (!wert) return '';
|
||
const d = new Date(wert);
|
||
if (Number.isNaN(d.getTime())) return '';
|
||
const heute = new Date();
|
||
const gleichesJahr = d.getFullYear() === heute.getFullYear();
|
||
try {
|
||
return new Intl.DateTimeFormat('de-DE', {
|
||
weekday: 'short', day: 'numeric', month: 'short',
|
||
...(gleichesJahr ? {} : { year: 'numeric' }),
|
||
hour: '2-digit', minute: '2-digit',
|
||
}).format(d);
|
||
} catch {
|
||
return d.toLocaleString('de-DE');
|
||
}
|
||
}
|
||
|
||
/** Ist dieser Termin schon vorbei? */
|
||
const vorbei = (wert) => {
|
||
const d = wert ? new Date(wert) : null;
|
||
return !!(d && !Number.isNaN(d.getTime()) && d.getTime() < Date.now());
|
||
};
|
||
|
||
const vorschaubild = (video) => `https://i.ytimg.com/vi/${video}/mqdefault.jpg`;
|
||
|
||
/* =================================================================
|
||
DIE MITTE
|
||
================================================================= */
|
||
const WORTE = {
|
||
zu: 'Geschlossen',
|
||
vorbereitung: 'Gleich',
|
||
live: 'Auf Sendung',
|
||
};
|
||
|
||
function mitteZeichnen() {
|
||
const stand = lage?.stand || 'zu';
|
||
const lampe = $('lampe');
|
||
lampe.dataset.stand = stand;
|
||
$('lampe-wort').textContent = WORTE[stand] || '…';
|
||
|
||
/* ---- Das Bild ---- */
|
||
const kasten = $('saal-karte');
|
||
const bild = $('saal-bild');
|
||
const quelle = String(lage?.vorschau || '').trim()
|
||
|| (lage?.video ? vorschaubild(lage.video) : '');
|
||
if (quelle) {
|
||
if (bild.getAttribute('src') !== quelle) bild.setAttribute('src', quelle);
|
||
kasten.dataset.bild = 'ja';
|
||
/* Ein Bild, das 404 antwortet, steht genauso im Dokument wie
|
||
eines, das laedt -- auf dem Bildschirm ist an seiner Stelle
|
||
nichts. Dann lieber der Platzhaltersatz. */
|
||
bild.onerror = () => { kasten.dataset.bild = 'nein'; };
|
||
} else {
|
||
bild.removeAttribute('src');
|
||
kasten.dataset.bild = 'nein';
|
||
}
|
||
|
||
/* ---- Titel und Satz ---- */
|
||
const titel = lage?.titel || lage?.video_titel || '';
|
||
$('saal-titel').textContent = stand === 'zu' && !titel
|
||
? 'Gerade ist nichts angesetzt'
|
||
: (titel || 'Ohne Titel');
|
||
$('saal-titel').dataset.leer = (stand === 'zu' && !titel) ? 'ja' : 'nein';
|
||
|
||
const beginn = lage?.beginnt_am ? alsTermin(lage.beginnt_am) : '';
|
||
const satz = $('saal-satz');
|
||
if (stand === 'live') {
|
||
satz.textContent = 'Es läuft gerade. Du kannst jederzeit dazukommen — '
|
||
+ 'alle sehen dieselbe Stelle.';
|
||
} else if (stand === 'vorbereitung') {
|
||
satz.textContent = beginn
|
||
? `Der Wartebereich ist offen, Start um ${beginn}. Der Chat läuft schon.`
|
||
: 'Der Wartebereich ist offen. Der Chat läuft schon.';
|
||
} else {
|
||
satz.textContent = beginn
|
||
? `Angesetzt: ${beginn}. Vorher geht hier nichts auf — schau später noch mal rein.`
|
||
: 'Sobald DogFather oder VanVan aufmachen, kannst du hier hinein.';
|
||
}
|
||
$('saal-leer').textContent = stand === 'zu'
|
||
? 'Gerade läuft nichts.'
|
||
: 'Für diese Sendung gibt es kein Vorschaubild.';
|
||
|
||
/* ---- Der Knopf ----
|
||
ER STEHT NUR DA, WENN ER AUCH HINEINFUEHRT. Filipe: „in der
|
||
mitte sollen die leute dan wenn eine sendung läuft drauf
|
||
drücken können und der sendung beitreten. auch bei der
|
||
vorschau schon." Bei „zu" gibt es nichts zu betreten -- und
|
||
ein Knopf, der auf eine geschlossene Tuer fuehrt, ist
|
||
schlimmer als keiner. */
|
||
const knopf = $('saal-knopf');
|
||
const offen = stand === 'live' || stand === 'vorbereitung';
|
||
knopf.hidden = !offen;
|
||
$('saal-knopf-wort').textContent = stand === 'live'
|
||
? 'Jetzt beitreten' : 'In den Wartebereich';
|
||
knopf.dataset.stand = stand;
|
||
|
||
/* ---- Wie viele dabei sind ----
|
||
NUR DIE ZAHL, nie die Namen -- dieselbe Regel wie im Saal: Wer
|
||
zusieht, soll nicht ablesen koennen, wer sonst noch zusieht.
|
||
Und nur, wenn ueberhaupt jemand da ist: „0 dabei" ist keine
|
||
Auskunft, sondern eine Enttaeuschung. */
|
||
const zahl = $('saal-zahl');
|
||
const wie = Number(lage?.dabei) || 0;
|
||
zahl.hidden = !(offen && wie > 0);
|
||
zahl.textContent = wie === 1 ? '1 ist schon dabei' : `${wie} sind schon dabei`;
|
||
}
|
||
|
||
/* =================================================================
|
||
LINKS: DER PLAN (nur fuer die zwei)
|
||
================================================================= */
|
||
function planZeichnen() {
|
||
const reihe = $('reihe-plan');
|
||
/* `liste` bekommt nur, wer die Sendung fuehrt -- das entscheidet
|
||
der Server. Diese Seite fragt deshalb nicht nach der Rolle,
|
||
sondern danach, ob die Auskunft ueberhaupt gekommen ist. Eine
|
||
zweite Rollenliste im Browser waere die, die irgendwann von
|
||
der echten abweicht. */
|
||
const liste = Array.isArray(lage?.liste) ? lage.liste : null;
|
||
reihe.hidden = !liste;
|
||
if (!liste) return;
|
||
|
||
/* ==== NACH TERMIN, NICHT NACH PLATZ =========================
|
||
Im Pult ist die Reihenfolge die Aussage („was kommt als
|
||
Naechstes im Live"), hier der Termin („Donnerstag um acht").
|
||
Was keinen hat, steht hinten -- es liegt bereit, aber es ist
|
||
noch nichts verabredet. */
|
||
const sortiert = [
|
||
...liste.filter((z) => z.wann).sort((a, b) => String(a.wann).localeCompare(b.wann)),
|
||
...liste.filter((z) => !z.wann),
|
||
];
|
||
|
||
const kasten = $('plan-liste');
|
||
kasten.textContent = '';
|
||
if (!sortiert.length) {
|
||
kasten.append(el('li', 'plan-leer',
|
||
'Noch nichts vorbereitet. Im Saal unter „Warteschlange“ anhängen.'));
|
||
return;
|
||
}
|
||
for (const z of sortiert) {
|
||
const zeile = el('li', 'plan-zeile');
|
||
if (z.wann) zeile.dataset.termin = vorbei(z.wann) ? 'vorbei' : 'ja';
|
||
|
||
const b = document.createElement('img');
|
||
b.className = 'plan-zeile__bild';
|
||
b.src = vorschaubild(z.video);
|
||
b.alt = '';
|
||
b.loading = 'lazy';
|
||
b.decoding = 'async';
|
||
zeile.append(b);
|
||
|
||
const text = el('div', 'plan-zeile__text');
|
||
const name = el('span', 'plan-zeile__titel', z.titel || z.video);
|
||
/* Holt YouTube den Namen nicht heraus, steht dort die Kennung --
|
||
kein erfundener Titel. Dieselbe Regel wie in der
|
||
Warteschlange. */
|
||
name.dataset.roh = z.titel ? 'nein' : 'ja';
|
||
name.title = z.titel || ('Kennung ' + z.video);
|
||
text.append(name);
|
||
|
||
const wann = el('span', 'plan-zeile__wann',
|
||
z.wann ? alsTermin(z.wann) : 'noch ohne Termin');
|
||
if (z.wann && vorbei(z.wann)) wann.textContent += ' · überfällig';
|
||
text.append(wann);
|
||
zeile.append(text);
|
||
|
||
kasten.append(zeile);
|
||
}
|
||
}
|
||
|
||
/* =================================================================
|
||
RECHTS: ANRATEN
|
||
================================================================= */
|
||
const STAND_WORT = {
|
||
offen: 'wartet',
|
||
uebernommen: 'kommt dran',
|
||
abgelehnt: 'diesmal nicht',
|
||
};
|
||
|
||
function ratenZeichnen() {
|
||
if (!raten) return;
|
||
notizGrenze();
|
||
const darf = !!raten.darf_entscheiden;
|
||
const liste = raten.vorschlaege || [];
|
||
|
||
const zahl = $('raten-zahl');
|
||
zahl.hidden = !(darf && raten.offen > 0);
|
||
zahl.textContent = raten.offen === 1 ? '1 offen' : `${raten.offen} offen`;
|
||
|
||
$('raten-satz').textContent = darf
|
||
? 'Was das Rudel euch anträgt. Nur ihr zwei seht das hier.'
|
||
: 'Schick uns ein Video, das wir zusammen schauen sollen. '
|
||
+ 'Sehen tun das nur DogFather und VanVan.';
|
||
|
||
const kasten = $('raten-liste');
|
||
kasten.textContent = '';
|
||
if (!liste.length) {
|
||
kasten.append(el('li', 'raten-leer', darf
|
||
? 'Noch nichts angeraten.'
|
||
: 'Von dir ist noch nichts dabei.'));
|
||
}
|
||
|
||
for (const v of liste) {
|
||
const zeile = el('li', 'raten-zeile');
|
||
zeile.dataset.stand = v.stand;
|
||
|
||
const b = document.createElement('img');
|
||
b.className = 'raten-zeile__bild';
|
||
b.src = vorschaubild(v.video);
|
||
b.alt = '';
|
||
b.loading = 'lazy';
|
||
b.decoding = 'async';
|
||
zeile.append(b);
|
||
|
||
const text = el('div', 'raten-zeile__text');
|
||
const name = el('span', 'raten-zeile__titel', v.titel || v.video);
|
||
name.dataset.roh = v.titel ? 'nein' : 'ja';
|
||
name.title = v.titel || ('Kennung ' + v.video);
|
||
text.append(name);
|
||
|
||
const unter = el('span', 'raten-zeile__unter');
|
||
/* Beim Host steht, WER es war; beim Einsender waere das er
|
||
selbst -- und eine Zeile, die einem den eigenen Namen
|
||
vorliest, ist Platz ohne Auskunft. */
|
||
unter.textContent = [
|
||
darf && v.name ? v.name : '',
|
||
STAND_WORT[v.stand] || v.stand,
|
||
].filter(Boolean).join(' · ');
|
||
text.append(unter);
|
||
|
||
if (v.notiz) text.append(el('p', 'raten-zeile__notiz', v.notiz));
|
||
zeile.append(text);
|
||
|
||
const knoepfe = el('div', 'raten-zeile__knoepfe');
|
||
if (darf && v.stand === 'offen') {
|
||
const ja = el('button', 'raten-knopf raten-knopf--ja', 'Nehmen');
|
||
ja.type = 'button';
|
||
ja.title = 'In die Warteschlange legen';
|
||
ja.addEventListener('click', () => entscheiden(v.id, 'uebernehmen'));
|
||
const nein = el('button', 'raten-knopf', 'Diesmal nicht');
|
||
nein.type = 'button';
|
||
nein.addEventListener('click', () => entscheiden(v.id, 'ablehnen'));
|
||
knoepfe.append(ja, nein);
|
||
}
|
||
/* ==== WER DAS × BEKOMMT =================================
|
||
Die Leitung bei den ENTSCHIEDENEN (die offenen haben ihre
|
||
zwei Knoepfe), der Einsender bei seinen OFFENEN.
|
||
|
||
Der Einsender darf nur zurueckziehen, was noch nicht
|
||
entschieden ist: Was entschieden ist, ist Geschichte -- auch
|
||
fuer ihn. Koennte er einen abgelehnten loeschen, verschwaende
|
||
die Spur, dass ueberhaupt entschieden wurde, und derselbe
|
||
Vorschlag saehe beim naechsten Mal aus wie der erste. */
|
||
const wegErlaubt = darf ? v.stand !== 'offen' : v.stand === 'offen';
|
||
if (wegErlaubt) {
|
||
const weg = el('button', 'raten-knopf raten-knopf--weg', '×');
|
||
weg.type = 'button';
|
||
weg.title = darf ? 'Aus der Liste nehmen' : 'Zurückziehen';
|
||
weg.addEventListener('click', () => zurueckziehen(v.id));
|
||
knoepfe.append(weg);
|
||
}
|
||
if (knoepfe.children.length) zeile.append(knoepfe);
|
||
|
||
kasten.append(zeile);
|
||
}
|
||
|
||
/* ==== DIE GRENZE KOMMT VOM SERVER, UND ZWAR EINMAL =============
|
||
Hier stand sie VIERMAL: dreimal als `raten.je_person_max || 5`
|
||
und einmal als ausgeschriebenes Wort „fünf“ im Satz. Das sind
|
||
vier Antworten auf eine Frage. Wer die Grenze im Server auf
|
||
drei setzt, hat danach einen Knopf, der bei drei sperrt, und
|
||
einen Satz, der von fünf redet -- und niemand merkt es, weil
|
||
beides fuer sich genommen stimmig aussieht.
|
||
|
||
Gerechnet wird mit dem, was angekommen ist. Ist nichts
|
||
angekommen, steht hier auch keine Zahl: Eine erfundene Grenze
|
||
waere schlimmer als gar keine, weil man ihr glaubt.
|
||
|
||
Dass man sie VORHER erfaehrt, bleibt der Punkt -- eine
|
||
Grenze, von der man erst beim Anstossen erfaehrt, ist eine
|
||
Falle. */
|
||
const grenze = Number(raten.je_person_max) || 0;
|
||
const offenEigene = liste.filter((v) => v.stand === 'offen').length;
|
||
const rest = grenze ? Math.max(0, grenze - offenEigene) : null;
|
||
|
||
const fuss = $('raten-fuss');
|
||
if (darf) {
|
||
fuss.textContent = 'Was du nimmst, landet unten in der Warteschlange.';
|
||
} else if (rest === null) {
|
||
fuss.textContent = '';
|
||
} else if (rest > 0) {
|
||
fuss.textContent = `Du kannst noch ${rest} ${rest === 1 ? 'Video' : 'Videos'} anraten.`;
|
||
} else {
|
||
fuss.textContent = `Du hast gerade ${grenze} offene Vorschläge — `
|
||
+ 'warte, bis einer dran war.';
|
||
}
|
||
$('raten-los').disabled = !darf && rest === 0;
|
||
}
|
||
|
||
function restZeigen() {
|
||
const feld = $('raten-notiz');
|
||
const wort = $('raten-rest');
|
||
/* ==== KEINE ERSATZZAHL MEHR ====================================
|
||
Hier stand `feld.maxLength || 200`. Die 200 war eine Abschrift
|
||
von `VORSCHLAG_NOTIZ_MAX` im Server -- die dritte Stelle, an
|
||
der dieselbe Zahl stand. Jetzt lernt das Feld sie vom Server
|
||
(siehe `notizGrenze()`), und diese Funktion liest nur noch ab.
|
||
Ist sie noch nicht angekommen, meldet `maxLength` -1; dann
|
||
steht hier NICHTS. „noch -1“ waere schlimmer als keine Zahl. */
|
||
const max = feld.maxLength;
|
||
if (!(max > 0)) { wort.textContent = ''; return; }
|
||
const rest = max - feld.value.length;
|
||
/* DIE ZAHL STEHT ERST DA, WENN ES ENG WIRD. Ein Zaehler, der von
|
||
Anfang an mitlaeuft, macht aus einem Satz eine Aufgabe. */
|
||
wort.textContent = rest <= 40 ? `noch ${rest}` : '';
|
||
}
|
||
|
||
/** Die Laenge der Notiz kommt vom Server und wird dem Feld
|
||
* angelegt. Einmal, und nur wenn sie sich geaendert hat -- ein
|
||
* `maxLength`, das bei jedem Zeichnen neu gesetzt wird, setzt in
|
||
* manchen Browsern den Einfuegezeiger zurueck. */
|
||
function notizGrenze() {
|
||
const grenze = Number(raten?.notiz_max) || 0;
|
||
if (!grenze) return;
|
||
const feld = $('raten-notiz');
|
||
if (feld.maxLength === grenze) return;
|
||
feld.maxLength = grenze;
|
||
restZeigen();
|
||
}
|
||
|
||
async function abschicken(e) {
|
||
e.preventDefault();
|
||
const video = $('raten-video').value.trim();
|
||
if (!video) { sagFehler('Trag erst eine YouTube-Adresse ein.'); return; }
|
||
const knopf = $('raten-los');
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/reaktion/vorschlag', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ video, notiz: $('raten-notiz').value.trim() }),
|
||
});
|
||
if (!a.ok) { melde(await a.json().catch(() => null), 'Das ging gerade nicht.'); return; }
|
||
$('raten-video').value = '';
|
||
$('raten-notiz').value = '';
|
||
restZeigen();
|
||
sagFehler('Danke — angekommen. DogFather und VanVan sehen es.');
|
||
await ratenHolen();
|
||
} catch { sagFehler('Das ging gerade nicht.'); }
|
||
finally { knopf.disabled = false; }
|
||
}
|
||
|
||
async function entscheiden(id, was) {
|
||
/* ==== DIE ZWEI WEGE STEHEN AUSGESCHRIEBEN DA ===================
|
||
Vorher hiess es `/vorschlag/${id}/${was}` -- eine Zeile fuer
|
||
beide Wege, und im Quelltext stand nirgends, welche Woerter
|
||
fuer `was` ueberhaupt erlaubt sind. `pruef-struktur` hat
|
||
daraufhin „…/vorschlag/1/1“ gemeldet: Sie setzt in jeden
|
||
Platzhalter eine 1 ein und kann nicht wissen, dass der zweite
|
||
„uebernehmen“ oder „ablehnen“ heisst.
|
||
Das war zu Recht ein Fehler -- ein naechster Leser weiss es
|
||
genauso wenig. Jetzt stehen beide Adressen vollstaendig da,
|
||
ein Schreibfehler faellt beim Lesen auf, und die Pruefung
|
||
findet wieder, was sie pruefen soll. */
|
||
const weg = was === 'uebernehmen'
|
||
? `/workspace/api/reaktion/vorschlag/${id}/uebernehmen`
|
||
: `/workspace/api/reaktion/vorschlag/${id}/ablehnen`;
|
||
const a = await hole(weg, { method: 'POST' });
|
||
if (!a.ok) { melde(await a.json().catch(() => null), 'Das ging nicht.'); return; }
|
||
/* Beim Uebernehmen wandert es in die Warteschlange -- also muss
|
||
auch der Plan links nachziehen. Eine der beiden Reihen allein
|
||
zu zeichnen hiesse, dass links ein Video fehlt, das rechts
|
||
eben angenommen wurde. */
|
||
await Promise.all([standHolen(), ratenHolen()]);
|
||
}
|
||
|
||
async function zurueckziehen(id) {
|
||
const a = await hole(`/workspace/api/reaktion/vorschlag/${id}`, { method: 'DELETE' });
|
||
if (!a.ok) { melde(await a.json().catch(() => null), 'Das ging nicht.'); return; }
|
||
await ratenHolen();
|
||
}
|
||
|
||
/* =================================================================
|
||
HOLEN
|
||
================================================================= */
|
||
async function standHolen() {
|
||
try {
|
||
const a = await hole('/workspace/api/reaktion');
|
||
if (!a.ok) return;
|
||
lage = await a.json();
|
||
mitteZeichnen();
|
||
planZeichnen();
|
||
kopfSatz();
|
||
} catch { /* beim naechsten Takt wieder */ }
|
||
}
|
||
|
||
async function ratenHolen() {
|
||
try {
|
||
const a = await hole('/workspace/api/reaktion/vorschlaege');
|
||
if (!a.ok) return;
|
||
raten = await a.json();
|
||
ratenZeichnen();
|
||
} catch { /* die Reihe bleibt stehen, wie sie war */ }
|
||
}
|
||
|
||
/* ==== DER SATZ OBEN NENNT KEINE RICHTUNGEN MEHR ==================
|
||
|
||
Hier stand: „Gerade läuft nichts. Links steht, was ansteht;
|
||
rechts kannst du etwas anraten.“ Zwei Fehler in einem Satz, und
|
||
beide derselbe:
|
||
|
||
* „Links“ gibt es für die Community überhaupt nicht — der
|
||
Plan ist nur für DogFather und VanVan da. Der Satz zeigte
|
||
also ALLEN außer DogFather und VanVan auf einen leeren Rand.
|
||
* Und am Telefon stehen die Reihen übereinander. Dort ist
|
||
„links“ und „rechts“ auch für die zwei falsch.
|
||
|
||
Eine Richtung im Text ist eine Aussage über das Aussehen — und
|
||
das Aussehen ändert sich mit dem Bildschirm und mit der Rolle.
|
||
Das ist dieselbe Falle wie eine feste Pixelzahl: Sie stimmt, bis
|
||
jemand etwas verschiebt, und wird dann still falsch. Gesagt wird
|
||
deshalb, WAS geht, nicht WO es steht.
|
||
|
||
Dass der Plan da ist, wird nicht aus der Rolle geschlossen,
|
||
sondern daran gemessen, ob die Auskunft gekommen ist — dieselbe
|
||
Frage wie in `planZeichnen()`, und nur eine Antwort darauf. */
|
||
function kopfSatz() {
|
||
const stand = lage?.stand || 'zu';
|
||
const mitPlan = Array.isArray(lage?.liste);
|
||
$('kopf-satz').textContent = stand === 'live'
|
||
? 'Es läuft gerade — du kannst jederzeit dazukommen.'
|
||
: stand === 'vorbereitung'
|
||
? 'Gleich geht es los. Der Wartebereich ist schon offen.'
|
||
: mitPlan
|
||
? 'Gerade läuft nichts. Was ansteht, steht im Plan — und was '
|
||
+ 'das Rudel anträgt, wartet auf euch.'
|
||
: 'Gerade läuft nichts. Du kannst uns aber jederzeit ein Video anraten.';
|
||
}
|
||
|
||
/* =================================================================
|
||
LOS
|
||
================================================================= */
|
||
function los() {
|
||
$('raten-form').addEventListener('submit', abschicken);
|
||
$('raten-notiz').addEventListener('input', restZeigen);
|
||
restZeigen();
|
||
|
||
standHolen();
|
||
ratenHolen();
|
||
|
||
/* ==== ALLE ZWANZIG SEKUNDEN NACHFRAGEN ====================
|
||
Damit jemand, der das Foyer offen stehen laesst, den Start
|
||
mitbekommt. Ein Ereignisstrom waere genauer und haelt dafuer
|
||
eine Verbindung offen -- im Saal wird jede davon gebraucht
|
||
(manche Browser lassen nur sechs je Adresse zu).
|
||
|
||
UND NICHT, WENN DER REITER IM HINTERGRUND LIEGT. Ein Foyer,
|
||
das jemand seit gestern offen hat, soll den Server nicht alle
|
||
zwanzig Sekunden fragen. Beim Zurueckkommen wird sofort
|
||
einmal geholt -- dann steht der Stand von JETZT da und nicht
|
||
der von gestern. */
|
||
setInterval(() => {
|
||
if (document.visibilityState === 'visible') standHolen();
|
||
}, 20000);
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.visibilityState === 'visible') { standHolen(); ratenHolen(); }
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', los);
|
||
} else {
|
||
los();
|
||
}
|
||
})();
|