Twig ist verdammt cool!

Wie ich das UI des Huebi Charity Spendendashboards überarbeitet habe.

Twig ist verdammt cool!
(Sticker von @Ikaika)

Es ist nicht lange her, seitdem ich über das Huebi Charity Spendendashboard geschrieben habe. Zwischenzeitlich habe ich hin und wieder kleinere Dinge verändert, und dabei auch eine neue Templating-Engine ausprobiert. Über diese Dinge möchte ich heute mal schreiben.

Kleinere Bugfixes und Code-Dinge möchte ich heute mal weglassen - daher fange ich direkt mit etwas Großem an:

Neue Snapshots, jetzt komplett anders!

Ich habe ein bisschen mit der TipeeeAPI experimentiert und herausgefunden, dass man auch die Donations von vor vielen Jahren abfragen kann. Dabei kam mir die Idee, auch die Donations des allerersten Huebi Charity Livestreams – dessen VODs heute nicht mehr verfügbar sind – abzufragen, um meinem Dashboard noch mehr Relevanz zu verleihen. Da ich allerdings auch am Überlegen war, die Struktur der Datenbank etwas anzupassen (die Zeit nicht mehr in Stunden zu speichern), habe ich erstmal alle Donations einzeln in die Datenbank gespeichert. Also... Nur die Daten, die ich wirklich benötigt habe. Darunter:

  • Stream ID
  • Erstell-Datum (& Zeit)
  • Menge
  • Währung
  • Benutzername

Diese Daten habe ich zu allen Donations von allen Streams gesammelt. (Grüße gehen raus an die TipeeeAPI! 👋) Hier hat mich sofort interessiert: Wie läuft das mit den Währungen? Macht Tipeee das ganz automatisch, oder muss ich da jetzt ernsthaft vor jedem Stream die Json-Datei mit den Konvertierungs-Raten aktualisieren? Da ist mir aufgefallen: Alles ist EUR. Dabei war ich mir sicher, dass viele auch mit anderen Währungen bezahlt haben. Anscheinend konvertiert Tipeee das automatisch. Yay!

Ein Sticker von meinem Drachen, wie er glücklich Konfetti schmeißt.
(Sticker von @Ikaika)

Dann gings jetzt um die neue Generierung der Snapshots! Dafür könnte ich jetzt wieder Tipeee sehr oft abfragen, oder... ich frage einfach meine Datenbank ab!

SELECT
  stream_id,
  SUM(amount) AS SUM
FROM
  donations
WHERE
  stream_id = 8
  AND created_at <= "2024-01-07T12:00:00+01:00"
GROUP BY
  stream_id;

Das habe ich dann für alle 15 Minuten, für jeden Stream gemacht (insgesamt 768 Anfragen) und schon waren die neuen Snapshots fertig. Ja, die Werte unterscheiden sich jetzt etwas vom Vorherigen - allerdings empfinde ich das als eine Verbesserung, da die Daten jetzt auf die richtigen Donations passen. Oder was findet ihr?

Die Logik der Charts anzupassen (von Stunden zu Minuten) war jetzt nicht viel Arbeit, daher gehe ich hier nicht weiter drauf ein.

Neue Streamarchiv-Auswahl

Ich hatte im letzten Post gefragt, ob jemand eine bessere Idee für das Design des Stream-Archivs hätte. Es hat sich zwar keiner gemeldet, aber ich kam selbst auf eine gute Idee: Ich könnte das Design der Chips aus Material Design 3 übernehmen.

Material Design-Style Chips

Ich finde, das passt besser als ein einfacher Dropdown, und funktionieren tut es auch endlich überall. Anscheinend hatte das Dropdown nicht so zuverlässig auf dem Handy funktioniert - ups!

Ein Kumpel meinte dann noch, dass es etwas verwirrend sein kann, dass nach 2021 auf einmal 7, und dann 8 kommt. Daher habe ich für die Streams, wo der Stream-Name nicht mit dem Jahr übereinstimmt, noch extra das Jahr in den Namen hinzugefügt.

Erstes Mal Twig

Ich hatte bereits einiges an Erfahrung mit Templating-Engines, insbesondere durch Laravel's Blade, und wollte deshalb etwas Ähnliches in mein Projekt integrieren. Allerdings ging ich damals davon aus, dass man für eine Templating-Engine ein umfangreiches Framework benötigt. Da ich mein Projekt nicht komplett neu entwickeln wollte, habe ich kurzerhand meine eigene Templating-Engine entworfen. Hier ein kleiner Ausschnitt davon:

<?php

use HuebiCharityDashboard\StreamManager;

require_once __DIR__ . '/../classes/HuebiCharityDashboard/StreamManager.php';

$templateVars["visualizeStream"] = (
    isset($_GET['stream'])
    ? StreamManager::getStream(htmlspecialchars($_GET['stream']))
    : null
) ?? StreamManager::getLatestStream();
$templateVars["latestSnapshot"] = $templateVars["visualizeStream"]->getLatestSnapshot();
$templateVars["nowDate"] = new DateTime();

$templateVars["allStreams"] = StreamManager::getAllStreams();
$templateVars["latestStream"] = StreamManager::getLatestStream();

// Show page
include __DIR__ . '/../views/dashboard.php';

Der Controller

<section>
    <?php
    $latestSum = $templateVars["latestSnapshot"] != null ? $templateVars["latestSnapshot"]->value : 0;
    ?>
    <?php if ($templateVars["nowDate"] < $templateVars["visualizeStream"]->end): ?>
        <h3>Aktuelle Spendensumme</h3>
    <?php else: ?>
        <h3>Endsumme</h3>
    <?php endif; ?>
    <h3 id="moneyValue">
        <?= number_format($latestSum, 2, ",", "."); ?> €
    </h3>
    <?php if ($templateVars["visualizeStream"]->start < $templateVars["nowDate"] && $templateVars["visualizeStream"]->end > $templateVars["nowDate"]): ?>
        <p id="updateInfo">(wird alle 15 Minuten aktualisiert)</p>
    <?php endif; ?>
</section>

<!-- CSS Stuff here -->

Die "Templating"-Datei von dem "Aktuelle Spendensumme"-Modul

Sie ist nicht sehr hübsch und gehört eher verbrannt, aber... das war halt das, was ich damals machen konnte. Es funktionierte.

Ein Sticker von meinem Drachen, der die Schultern zuckt.
(Sticker von @Ikaika)

Irgendwann hat es mich dann aber doch nochmal interessiert, ob es da nicht irgendwie eine Templating-Engine für Vanilla PHP gibt weshalb ich dann im Internet geschaut habe: Hallo Twig!

The flexible, fast, and secure template engine for PHP

Twig lässt sich sehr einfach per Composer installieren. Da ich sowieso schon Composer verwende, habe ich es eben mal ausprobiert.

Angefangen mit einem einfachen Test:

$loader = new FilesystemLoader(__DIR__ . '/../templates');
$twig = new Environment($loader);

$twig->render('Dashboard.html.twig', [...]);

Beispielcode für Twig

Scheint alles relativ easy. Cool! Also habe ich angefangen, das komplette Dashboard mit Twig umzubauen. Die oben gezeigten Dateien sehen jetzt (gekürzt) so aus:

<?php

use HuebiCharityDashboard\StreamManager;

require_once __DIR__ . '/../classes/HuebiCharityDashboard/StreamManager.php';
require_once __DIR__ . '/../helpers/perHourChartDataHelper.php';
require_once __DIR__ . '/../helpers/fullChartDataHelper.php';

$latestStream = StreamManager::getLatestStream();
$allStreams = StreamManager::getAllStreams();
$visualizeStream = (
    isset($_GET['stream'])
    ? StreamManager::getStream(htmlspecialchars($_GET['stream']))
    : null
) ?? $latestStream;

echo Core::getInstance()->twig->render(
    'Dashboard.html.twig',
    [
        'visualizeStream' => $visualizeStream,
        'latestSnapshot' => $visualizeStream->getLatestSnapshot(),
        'nowDate' => new DateTime(),
        'allStreams' => $allStreams,
        'latestStream' => $latestStream,
        'perHourChartData' => generatePerHourChartData($visualizeStream),
        'fullChartData' => generateFullChartData($visualizeStream, $allStreams)
    ]
);

Der Controller

{% set latestSum = 0 %}
{% if latestSnapshot != null %}
    {% set latestSum = latestSnapshot.value %}
{% endif %}

<section>
    {% if nowDate < visualizeStream.end %}
        <h3>Aktuelle Spendensumme</h3>
    {% else %}
        <h3>Endsumme</h3>
    {% endif %}

    <h3 id="moneyValue">{{ latestSum|number_format(2, ",", ".") }} €</h3>

    {% if isLive %}
        <p id="updateInfo">(wird alle 15 Minuten aktualisiert)</p>
    {% endif %}
</section>

<!-- CSS Stuff here -->

Die Twig Templating-Datei von dem "Aktuelle Spendensumme"-Modul

Ich finds deutlich übersichtlicher, oder was meint ihr?

Ein weiterer Vorteil ist, dass ich keine duplizierten Dinge wie z.B. den <head> Tag im Code habe. Den hatte ich vorher doppelt drin, da ich einen für das Dashboard, und einen für die Fehlerseite hatte. Jetzt ist das dank Komponenten (bzw. einfachen includes, lol) kein Problem mehr! 😁

Dafür habe ich jetzt ungefähr 2,5 Stunden gebraucht - dabei habe ich aber auch sehr viel neues gelernt. Meiner Meinung nach hat es sich gelohnt!

Ein Sticker von meinem Drachen, der sehr Floofy und Glücklich ist.
(Sticker von @Ikaika)

White-Mode muss sein!

Der eine oder andere Programmierer wird sich jetzt sicherlich fragen, was mit mir passiert ist. Wieso implementiere ich einen White-Mode in eine Webseite? Das tut doch in den Augen weh!

Sagen wir es so... Nicht jeder findet einen Dark-Mode angenehm. Manch einer braucht einen White-Mode, um Text vernünftig lesen zu können. Barrierefreiheit ist wichtig, daher habe ich - wie auch auf diesem Ghost-Blog - ein White-Mode eingebaut, welcher sich nach der Präferenz des Benutzers einstellt. (Um genauer zu sein: Ich nutze die CSS Funktion prefers-color-scheme.) So muss ich mich nicht um einen Schalter kümmern, und der Benutzer hat ein angenehmes Erlebnis, da er schon den für sich passenden Modus sieht.

Das Huebi Charity Spendendashboard im Light-Mode

Das Ergebnis, finde ich, sieht gar nicht mal so schlecht aus. Jeder Text ist lesbar, und auch die Diagramme kann man gut erkennen. (Wobei ich da nicht mal die Farben anpassen kann, huch.)

Fazit

Ich denke, ich bin gut auf den nächsten Huebi Charity Livestream vorbereitet - auch wenn dieser noch nicht angekündigt wurde. Wer sich mehr für meine Implementation von Twig oder für die einzelnen Bugfixes und kleinen Änderungen, die ich hier nicht erwähnt habe, interessiert, der kann sich gerne das Repository selbst anschauen. Der Code der Webseite ist weiterhin Open Source! 😄