Liegestuetze: pro Stream mitzaehlen, wie viele gemacht sind

Herunterzaehlen mit -1/-5/-10 zaehlt als gemacht, Setzen bleibt eine
Korrektur und zaehlt nicht. Der Stand "gemacht" beginnt mit jedem neuen
Stream bei 0 (nicht beim Neustart des Hubs mitten im Stream), steht im
Overlay unter der offenen Zahl und laesst sich auf der Uebersicht
korrigieren.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZeasyAlexandClaude Opus 5.5 committed 2026-10-04 00:46:32 +02:00
1 parent eb9eab949e
commit 094a791da2
7 files changed
+213 -79

No files matched your search

+4 -4
View File
@@ -111,8 +111,8 @@ test('abgemeldete Zuhoerer bekommen nichts mehr', () => {
test('der Schnappschuss traegt den letzten Stand der Liegestuetze', () => {
const bus = createBus();
assert.equal(schnappschuss(bus).liegestuetze, 0);
bus.publish({ kind: 'liegestuetze', offen: 20, dazu: 10, name: 'Kevin' });
bus.publish({ kind: 'liegestuetze', offen: 15 });
assert.equal(schnappschuss(bus).liegestuetze, 15);
assert.deepEqual(schnappschuss(bus).liegestuetze, { offen: 0, gemacht: 0 });
bus.publish({ kind: 'liegestuetze', offen: 20, gemacht: 0, dazu: 10, name: 'Kevin' });
bus.publish({ kind: 'liegestuetze', offen: 15, gemacht: 5 });
assert.deepEqual(schnappschuss(bus).liegestuetze, { offen: 15, gemacht: 5 });
});
+8 -6
View File
@@ -66,12 +66,14 @@ export type StreamEvent = {
};
/**
* Neuer Stand der offenen Liegestuetze. `dazu` und `name` nur bei einer
* Einloesung - eine Korrektur von Hand soll nicht aussehen wie eine.
* Neuer Stand der Liegestuetze: offen und in diesem Stream gemacht. `dazu`
* und `name` nur bei einer Einloesung - eine Korrektur von Hand soll nicht
* aussehen wie eine.
*/
export type LiegestuetzeEvent = {
kind: 'liegestuetze';
offen: number;
gemacht: number;
dazu?: number;
name?: string;
};
@@ -105,8 +107,8 @@ export type OverlayEvent =
| { kind: 'beat'; t: number }
| {
kind: 'state'; stream: StreamEvent; chat: ChatEvent[]; alerts: AlertEvent[];
/** Offene Liegestuetze, damit der Zaehler nach dem Neuverbinden stimmt. */
liegestuetze: number;
/** Liegestuetze, damit der Zaehler nach dem Neuverbinden stimmt. */
liegestuetze: { offen: number; gemacht: number };
};
/**
@@ -134,7 +136,7 @@ export function createBus(): Bus {
let stream: StreamEvent = {
kind: 'stream', live: false, title: '', category: '', viewers: 0, startedAt: null,
};
let liegestuetze = 0;
let liegestuetze = { offen: 0, gemacht: 0 };
return {
publish(ev) {
@@ -149,7 +151,7 @@ export function createBus(): Bus {
} else if (ev.kind === 'stream') {
stream = ev;
} else if (ev.kind === 'liegestuetze') {
liegestuetze = ev.offen;
liegestuetze = { offen: ev.offen, gemacht: ev.gemacht };
} else if (ev.kind === 'clear') {
// Geloeschtes verschwindet auch aus dem Rueckblick, sonst holt der
// naechste Neustart der Browser-Source es zurueck ins Bild.
+47 -21
View File
@@ -65,9 +65,11 @@ export const PAGES = [
{ path: '/tts', file: 'tts.html', w: 600, h: 200,
title: 'Vorlesen', hint: 'Liest freigegebene Nachrichten vor. Braucht Ton in OBS.' },
{ path: '/liegestuetze', file: 'liegestuetze.html', w: 420, h: 200,
title: 'Liegestütze', hint: 'Offene Liegestütze aus Kanalpunkten. Zählt bei Einlösung hoch, korrigieren unten auf dieser Seite.' },
title: 'Liegestütze', hint: 'Offene und in diesem Stream gemachte Liegestütze. Zählt bei Einlösung hoch, bedienen unten auf dieser Seite.' },
] as const;
type LiegestuetzStand = { offen: number; gemacht: number };
export type OverlayServer = {
bus: Bus;
zugang: Zugang;
@@ -100,9 +102,12 @@ export type OverlayOptions = {
};
/** Der Liegestuetz-Zaehler. Ohne ihn fehlt der Abschnitt auf der Seite. */
liegestuetze?: {
stand: () => number;
aendern: (n: number) => number;
setzen: (n: number) => number;
stand: () => LiegestuetzStand;
/** Negativ heisst gemacht: das Abgezogene zaehlt fuer diesen Stream. */
aendern: (n: number) => LiegestuetzStand;
/** Korrekturen. Zaehlen nicht als gemacht. */
setzen: (n: number) => LiegestuetzStand;
gemachtSetzen: (n: number) => LiegestuetzStand;
};
};
@@ -295,16 +300,21 @@ async function handle(
const n = zahlAus(form.get('wert'));
const antwort = (text: string, code = 200): void =>
send(res, code, 'application/json; charset=utf-8',
JSON.stringify({ text, offen: opts.liegestuetze!.stand() }));
JSON.stringify({ text, ...opts.liegestuetze!.stand() }));
if (n === null) return antwort('Keine ganze Zahl.', 400);
if (was === 'aendern') {
const offen = opts.liegestuetze.aendern(n);
return antwort(`${n > 0 ? '+' : ''}${n}, jetzt ${offen} offen.`);
const neu = opts.liegestuetze.aendern(n);
return antwort(n < 0
? `${-n} gemacht. Noch ${neu.offen} offen, ${neu.gemacht} in diesem Stream.`
: `+${n}, jetzt ${neu.offen} offen.`);
}
if (n < 0) return antwort('Weniger als 0 geht nicht.', 400);
if (was === 'setzen') {
if (n < 0) return antwort('Weniger als 0 geht nicht.', 400);
return antwort(`Auf ${opts.liegestuetze.setzen(n)} gesetzt.`);
return antwort(`Offen auf ${opts.liegestuetze.setzen(n).offen} gesetzt.`);
}
if (was === 'gemacht-setzen') {
return antwort(`Gemacht auf ${opts.liegestuetze.gemachtSetzen(n).gemacht} gesetzt.`);
}
return antwort('Unbekannt.', 400);
}
@@ -469,7 +479,8 @@ const SEITEN_CSS = `
.wartend .hint { max-width: 60ch; word-break: break-word; }
.lz-stand { font: 700 3rem/1 "Segoe UI Variable Display", "Segoe UI", system-ui, sans-serif;
font-variant-numeric: tabular-nums; margin: .4rem 0 1rem; }
.lz-stand small { font-size: 1rem; font-weight: 400; color: #8b84a3; margin-left: .5rem; }
.lz-stand small { font-size: 1rem; font-weight: 400; color: #8b84a3; margin: 0 2rem 0 .5rem; }
.lz-stand .gemacht { color: #4ade80; }
button.lz { margin-left: 0; min-width: 3.2rem; }
input.lz { width: 6.5rem; padding: .2rem .6rem; font-size: .85rem; }
button.tts { margin-left: .4rem; }
@@ -513,9 +524,10 @@ Kanalpunkte zurück, sofern das verbundene Konto das darf.</p>
<p id="tts-meldung" class="meldung">&nbsp;</p>` : '';
const lzFeld = opts.liegestuetze ? `
<h2>Liegestütze</h2>
<p class="lead">Jede Einlösung zählt hoch. Hier herunterzählen, wenn sie gemacht
sind, oder den Stand korrigieren. Die Zuschauer sehen jede Änderung.</p>
<div class="lz-stand"><span id="lz-offen">${opts.liegestuetze.stand()}</span><small>offen</small></div>
<p class="lead">Jede Einlösung zählt hoch. Mit −1, −5, −10 herunterzählen, wenn
sie gemacht sind: das zählt für diesen Stream als gemacht. „Setzen“ ist nur
für Korrekturen und zählt nicht. Die Zuschauer sehen jede Änderung.</p>
<div class="lz-stand"><span id="lz-offen">${opts.liegestuetze.stand().offen}</span><small>offen</small><span id="lz-gemacht" class="gemacht">${opts.liegestuetze.stand().gemacht}</span><small>gemacht in diesem Stream</small></div>
<div class="gruppe">
<button class="lz" data-um="-10">−10</button>
<button class="lz" data-um="-5">−5</button>
@@ -526,8 +538,13 @@ sind, oder den Stand korrigieren. Die Zuschauer sehen jede Änderung.</p>
</div>
<form class="gruppe" id="lz-form">
<input class="lz" id="lz-wert" type="number" min="0" step="1" inputmode="numeric" placeholder="Anzahl">
<button type="submit" class="lz">setzen</button>
<button type="button" class="lz" id="lz-null">auf 0</button>
<button type="submit" class="lz">offen setzen</button>
<button type="button" class="lz" id="lz-null">offen auf 0</button>
</form>
<form class="gruppe" id="lz-gemacht-form">
<input class="lz" id="lz-gemacht-wert" type="number" min="0" step="1" inputmode="numeric" placeholder="Anzahl">
<button type="submit" class="lz">gemacht setzen</button>
<span class="hint">Beginnt bei jedem neuen Stream bei 0.</span>
</form>
<p id="lz-meldung" class="meldung">&nbsp;</p>` : '';
const base = opts.publicUrl || `http://127.0.0.1:${opts.port}`;
@@ -631,19 +648,21 @@ ${lzFeld}
? 'Stream läuft: ' + (ev.stream.title || 'ohne Titel')
: 'verbunden - kein Stream';
setzeLive(ev.stream.live);
zeigeLiegestuetze(ev.liegestuetze);
if (ev.liegestuetze) zeigeLiegestuetze(ev.liegestuetze);
} else if (ev.kind === 'stream') {
setzeLive(ev.live);
} else if (ev.kind === 'liegestuetze') {
zeigeLiegestuetze(ev.offen);
zeigeLiegestuetze(ev);
}
};
// Der Stand kommt über den Ereignisstrom, damit eine Einlösung auch hier
// sofort sichtbar ist und zwei offene Fenster nicht auseinanderlaufen.
function zeigeLiegestuetze(n) {
const el = document.getElementById('lz-offen');
if (el && typeof n === 'number') el.textContent = n.toLocaleString('de-DE');
function zeigeLiegestuetze(st) {
for (const [id, n] of [['lz-offen', st.offen], ['lz-gemacht', st.gemacht]]) {
const el = document.getElementById(id);
if (el && typeof n === 'number') el.textContent = n.toLocaleString('de-DE');
}
}
const lzMeldung = document.getElementById('lz-meldung');
async function lzRuf(was, wert) {
@@ -653,7 +672,7 @@ ${lzFeld}
body: new URLSearchParams({ was, wert: String(wert) }),
});
const body = await res.json().catch(() => ({ text: 'Fehler ' + res.status }));
zeigeLiegestuetze(body.offen);
zeigeLiegestuetze(body);
lzMeldung.textContent = body.text || '';
lzMeldung.className = 'meldung' + (res.ok ? ' ok' : ' fehler');
setTimeout(() => { lzMeldung.innerHTML = '&nbsp;'; lzMeldung.className = 'meldung'; }, 4000);
@@ -671,6 +690,13 @@ ${lzFeld}
feld.value = '';
});
document.getElementById('lz-null').addEventListener('click', () => lzRuf('setzen', 0));
document.getElementById('lz-gemacht-form').addEventListener('submit', (e) => {
e.preventDefault();
const feld = document.getElementById('lz-gemacht-wert');
if (feld.value === '') return;
lzRuf('gemacht-setzen', feld.value);
feld.value = '';
});
}
let sagAb;