diff options
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | Readme.md | 7 | ||||
| -rw-r--r-- | img/kuchen.png | bin | 0 -> 377582 bytes | |||
| -rw-r--r-- | kuchenstand.css | 40 | ||||
| -rw-r--r-- | kuchenstand.js | 202 | ||||
| -rw-r--r-- | kuchenstand.php | 78 |
6 files changed, 336 insertions, 4 deletions
@@ -1,11 +1,12 @@ JS := node_modules/chart.js/dist/chart.umd.js node_modules/chart.js/dist/chart.umd.js.map \ node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js \ - index.js -PHP := index.php lade_daten.php config.php -CSS := index.css punktestand.css + index.js kuchenstand.js +PHP := index.php lade_daten.php config.php kuchenstand.php +CSS := index.css punktestand.css kuchenstand.css +IMGS := img/kuchen.png .PHONY: all -all: dist/js dist/php dist/css +all: dist/js dist/php dist/css dist/img dist: mkdir -p $@ @@ -14,6 +15,10 @@ dist/js: $(JS) dist mkdir -p $@ for js in $(JS); do cp $$js $@; done +dist/img: $(IMGS) dist + mkdir -p $@ + for img in $(IMGS); do cp $$img $@; done + .PHONY: dist/php dist/php: $(PHP) dist for php in $(PHP); do cp $$php ${@D}; done @@ -117,3 +117,10 @@ PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Punktestand. If not, see <https://www.gnu.org/licenses/>. + +### Kuchenbild + +Das Kuchenbild der kuchenstand Webseite ist unter der CC BY-NC 4.0 Lizenz unter +[1] veröffentlicht. + +[1]: https://pngimg.com/image/13101 diff --git a/img/kuchen.png b/img/kuchen.png Binary files differnew file mode 100644 index 0000000..6aa0188 --- /dev/null +++ b/img/kuchen.png diff --git a/kuchenstand.css b/kuchenstand.css new file mode 100644 index 0000000..02b7539 --- /dev/null +++ b/kuchenstand.css @@ -0,0 +1,40 @@ +#kuchen-wrapper { + display: flex; + flex-direction: column; + align-items: center; + margin: auto; + max-width: 50%; +} + +img.kuchen-hintergrund { + position: relative; + max-width: 100%; + opacity: 0.4; +} + +img.kuchen-vordergrund { + position: relative; +} + +#streak { + position: relative; +} + +.klassen-navigation { + list-style-type: none; + margin: auto; + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} + +ul.klassen-navigation li { + display: block; + padding: 14px 16px; + text-decoration: none; +} + +ul li:hover { + background-color: #111111; +} 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); + } +} diff --git a/kuchenstand.php b/kuchenstand.php new file mode 100644 index 0000000..fb41f96 --- /dev/null +++ b/kuchenstand.php @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de"> +<head> + <!-- Willkommen im Quellcode dieser Seite! Wenn du verstehen willst wie die + Seite funktioniert, findest du den Quellcode unter: + https://git.muhq.space/schule/punktestand + --> + <title>Kuchenstand</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="author" content="Florian Fischer" /> + + <!-- Setze ein leeres Bild als favicon --> + <link rel="icon" href="data:,"> + + <!-- Lade den allgemeinen CSS Code, der das Aussehen der Seite definiert --> + <link rel="stylesheet" href="index.css"/> + <!-- Lade den CSS Code, der das Aussehen der punktestand Seite geneauer definiert --> + <link rel="stylesheet" href="kuchenstand.css"/> + + <!-- Die Daten für den Jahrgang und das Schuljahr aus der Datenbank --> + <?php + // Lade die Daten für den Jahrgang und das Schuljahr aus der Datenbank + $QUERIES = array(); + parse_str($_SERVER['QUERY_STRING'], $QUERIES); + + // TODO: Berechne das aktuelle Schuljahr + function aktuelles_schuljahr() { + return "2526"; + } + + $SCHULJAHR = $QUERIES['schuljahr'] ?? aktuelles_schuljahr(); + $JAHRGANG = $QUERIES['jahrgang']; + // TODO: Behandle fehlenden jahrgangs URL query Parameter + + // Lade die lokale Konfigurationsdatei, die die globale Variable $CONFIG definiert + require_once('config.php'); + + // Lade die Funktion, um die Daten aus der Datenbank zu lesen + require('lade_daten.php'); + + // Lade alle Daten aus der Datenbank und schreibe sie als Javbascript Variablen + // in die Webseite mit der in lade_daten.php definierten Funktion. + $daten = schreibe_json($CONFIG, $SCHULJAHR, $JAHRGANG); + ?> + + <!-- Lade die benötigten Javascript Dateien --> + <script src="js/kuchenstand.js"></script> +</head> + +<body> + <h1>Kuchenstand Jahrgangsstufe <?php echo "$JAHRGANG $SCHULJAHR";?></h1> + + <ul class="klassen-navigation"> + <?php + foreach($daten["klassen"] as $klasse) { + echo "<li onclick=\"wechselKlasse('$klasse->bezeichner');\">$klasse->bezeichner</li>"; + } + ?> + </ul> + + <!-- Container für den Inhalt des Punktestands --> + <div id="content"> + <div id="kuchen-wrapper"> + <div id="kuchen"> + </div> + <span id="streak">Wähle eine Klasse aus</span> + </div> + + <!-- Liste aller Ergeignisse --> + <div class="ereignisse-wrapper"> + <h2>Ereignisse</h2> + <ul id="ereignisse"> + </ul> + </div> + </div> +</body> +</html> |
