aboutsummaryrefslogtreecommitdiff
path: root/kuchenstand.js
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-01-23 18:11:38 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2026-01-23 18:13:57 +0100
commit81acf4bb41d4d9297d1dcb9baa901b5bb0c3975e (patch)
tree43ee97784d50b2aeb8f66736128ced8912825b4e /kuchenstand.js
parentde186f81e3e84f706e79e3623cbc6226ebdb12fe (diff)
downloadpunktestand-81acf4bb41d4d9297d1dcb9baa901b5bb0c3975e.tar.gz
punktestand-81acf4bb41d4d9297d1dcb9baa901b5bb0c3975e.zip
füge eine einfache kuchenstand Webseite hinzu
Die kuchenstand Webseite betrachtet nur die konfigurierten Streak-Ereignisse und stellt diese als prozentualen Kuchen dar. TODO: Mache das Streak-Symbolbild konfigurierbar.
Diffstat (limited to 'kuchenstand.js')
-rw-r--r--kuchenstand.js202
1 files changed, 202 insertions, 0 deletions
diff --git a/kuchenstand.js b/kuchenstand.js
new file mode 100644
index 0000000..a3b8949
--- /dev/null
+++ b/kuchenstand.js
@@ -0,0 +1,202 @@
+/**
+ *
+ * @licstart The following is the entire license notice for the
+ * JavaScript code in this page.
+ *
+ * Copyright (C) 2025-2026 Florian Fischer
+ *
+ *
+ * The JavaScript code in this page is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GNU GPL) as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option)
+ * any later version. The code is distributed WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute non-source (e.g., minimized or compacted) forms of
+ * that code without the copy of the GNU GPL normally required by
+ * section 4, provided you include this license notice and a URL
+ * through which recipients can access the Corresponding Source.
+ *
+ * @licend The above is the entire license notice
+ * for the JavaScript code in this page.
+ *
+ */
+
+// Observer der auf 'resize' events unseres Kuchens reagiert und den zweiten
+// Kuchen und die Streak-Anzeige gegebenfalls anpasst.
+let kuchenResizeObserver;
+
+// Berechne die Streaks, wenn die Seite komplett geladen wurde.
+window.onload = function() {
+ berechneStreaks();
+ const params = new URLSearchParams(location.search);
+ // Ist in der URL die Klasse per Search-Parameter ?klasse=... spezifiziert?
+ const klasse = params.get('klasse');
+ if (klasse) {
+ wechselKlasse(klasse);
+ }
+}
+
+var streaks;
+
+// Berechne den Streak für alle Klassen.
+// Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable
+// aller Ereignisse, die für den Streak relevant sind, enthalten.
+// Ereignisse ohne positives Delta beenden einen Streak.
+function berechneStreaks() {
+ // Es gibt keine Streak-Ereignisse
+ if (!daten.streakEreignisse) {
+ return;
+ }
+
+ // Variable um den laufenden Streak per Klasse zu bestimmen
+ streaks = Object.fromEntries(daten.klassen.map(klasse => [klasse.bezeichner, 0]));
+
+ for (let ereignis of daten.streakEreignisse) {
+ const klasse = `${ereignis.Jahrgang}${ereignis.Bezeichner}`;
+
+ // Das Streak Ereignis bringt keine Punkte und unterbricht damit den Streak.
+ if (ereignis.Delta == 0) {
+ streaks[klasse] = 0;
+ // Erhöhe den Streak.
+ } else {
+ streaks[klasse] = streaks[klasse] + 1;
+ }
+ }
+}
+
+
+// Wechsel zur Ansicht der Klasse.
+function wechselKlasse(klasse) {
+ zeichne(klasse);
+ zeichneStreak(klasse);
+ listeStreakEreignisse(klasse);
+}
+
+// Zeichne die Kuchen Graphik für die aktuelle Klasse.
+function zeichne(klasse) {
+ // Wir haben keine Daten zum Zeichnen
+ if (!streaks) {
+ return;
+ }
+
+ const kuchenNode = document.getElementById("kuchen");
+ // Entferne alle bestehenden Kind-Objekte vom Kuchen-Element.
+ while (kuchenNode.firstChild) {
+ kuchenNode.removeChild(kuchenNode.firstChild);
+ }
+
+ const kuchenHintergrund = document.createElement("IMG");
+ kuchenHintergrund.src = "img/kuchen.png"
+ kuchenHintergrund.classList.add("kuchen-hintergrund");
+ // Füge das Hintergrundbild dem DOM hinzu
+ kuchenNode.appendChild(kuchenHintergrund);
+
+ const kuchenVordergrund = document.createElement("IMG");
+ kuchenVordergrund.src = "img/kuchen.png"
+ kuchenVordergrund.classList.add("kuchen-vordergrund");
+
+ // Passe die Größe des Kuchens anhand des Streaks an
+ const streak = streaks[klasse];
+ const proportion = streak/streak_anzahl * 100;
+ kuchenVordergrund.style.maxWidth = `${proportion}%`;
+ kuchenVordergrund.style.maxHeight = `${proportion}%`;
+
+ kuchenNode.appendChild(kuchenVordergrund);
+
+ // Erzeuge ein Observer der auf Größenveränderungen des Kuchens reagiert.
+ let streakNode = document.getElementById("streak");
+ kuchenResizeObserver = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ // Positioniere den kleinen Kuchen und die Streak-Anzeige passend zur
+ // echten Anzeigegröße.
+ kuchenVordergrund.style.top = `-${entry.contentRect.height/2 + kuchenVordergrund.offsetHeight/2}px`;
+ kuchenVordergrund.style.left = `${entry.contentRect.width/2 - kuchenVordergrund.offsetWidth/2}px`;
+ streakNode.style.top = `-${kuchenVordergrund.offsetHeight}px`
+ }
+ });
+
+ kuchenResizeObserver.observe(kuchenHintergrund);
+}
+
+// Zeichne den Streak für die aktuelle Klasse.
+function zeichneStreak(klasse) {
+ // Es gibt keine Streak-Ereignisse
+ if (!streaks) {
+ return;
+ }
+
+ const streak = streaks[klasse];
+
+ let streakNode = document.getElementById("streak");
+ // Schreibe den aktuellen Streak in das Streak span-Element
+ streakNode.innerHTML = `${streak}/${streak_anzahl}`;
+}
+
+// Erzeuge eine Liste aller Ereignisse der Klasse.
+// Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable aller
+// Ereignisse, die angezeigt werden sollen, enthalten.
+function listeStreakEreignisse(klasse) {
+ let liste = document.getElementById("ereignisse");
+ while (liste.firstChild) {
+ liste.removeChild(liste.firstChild);
+ }
+
+ // Erzeuge ein Mapping von Klassenbezeichner auf die entsprechende Klasse
+ let klassen = {};
+ daten.klassen.forEach(klasse => { klassen[klasse.bezeichner] = klasse });
+
+ for (let ereignis of daten.streakEreignisse) {
+ let klassenBezeichner = `${ereignis.Jahrgang}${ereignis.Bezeichner}`
+
+ // Das Ereignis ist nicht für diese Klasse
+ if (klassenBezeichner !== klasse) {
+ continue;
+ }
+
+ // Erzeuge ein Listenelement, das alle Infos des Ereignisses enthält
+ let ereignisNode = document.createElement("li");
+ ereignisNode.classList.add("ereignis");
+
+ // Setze den Hintergrund des Ereignisses auf 40% der Klassenfarbe
+ const farbe = klassen[klassenBezeichner].farbe;
+ if (klasse) {
+ ereignisNode.style.backgroundColor = `${farbe}40`;
+ }
+
+ // Span, das die Meta-Informationen (Klasse, Datum) eines Ereignisses enthält
+ let ereignisUrsache = document.createElement("span");
+ ereignisUrsache.innerHTML = `<b>${klassenBezeichner}</b>`
+ if (ereignis.Datum) {
+ ereignisUrsache.innerHTML += ` <small>${ereignis.Datum}</small>`
+ }
+ ereignisUrsache.classList.add("ereignis-klasse");
+ ereignisNode.appendChild(ereignisUrsache);
+
+ // Die Veränderung des Punktestands durch das Ereignis
+ let ereignisDelta = document.createElement("span");
+ ereignisDelta.innerHTML = `${ereignis.Delta}`;
+ ereignisDelta.classList.add("ereignis-delta");
+ // Ergänze positive und negative CSS Klassen
+ if (ereignis.Delta > 0) {
+ ereignisDelta.classList.add("positiv");
+ } else {
+ ereignisDelta.classList.add("negativ");
+ }
+ ereignisNode.appendChild(ereignisDelta);
+
+ // Die Beschreibung des Ereignisses
+ let ereignisBeschreibung = document.createElement("span");
+ ereignisBeschreibung.innerHTML = `<i>${ereignis.Beschreibung}</i>`;
+ ereignisBeschreibung.classList.add("ereignis-beschreibung");
+ ereignisNode.appendChild(ereignisBeschreibung);
+
+ // Füge das neue Ereignis-Listenelement der Ereignis-Liste hinzu.
+ // Jedes neue Element wird als erstes Kind eingefügt, damit
+ // neuere Ereignisse weiter oben in der Liste stehen
+ liste.insertBefore(ereignisNode, liste.firstChild);
+ }
+}