Bij Codeurs hebben we om de paar maanden een 'show and tell'-moment - iedereen kiest een onderwerp en bereidt een korte presentatie voor. Deze keer wilde ik iets interactiever doen en een korte quiz voorbereiden over de React API. Na een snelle Google-zoekopdracht kon ik geen tool vinden om snel een quiz te maken. Bestaande tools vereisen vaak het aanmaken van een account om hun quiz-editor uit te proberen. Eenmaal ingelogd, vergt het creëren van quizvragen veel klikken en dit haalt je volledig uit de flow van het bedenken van vragen en antwoorden.
Ik besloot de quiz in Markdown te schrijven, deze om te zetten naar een PDF en antwoorden op post-it briefjes te verzamelen. Het beoordelen van de quiz was verwarrend, de vragen waren te moeilijk en uiteindelijk had alleen ik plezier. Maar dat terzijde voor deze blogpost. Waar ik naar op zoek was, was een tool waarmee ik snel een quiz kon maken en de vragen en antwoorden net zo eenvoudig kon bewerken als het aanpassen van een Markdown-document.
Zelf bouwen
Het uitgangspunt voor de editor is om een quiz te beschouwen als één document, niet als een verzameling vragen die afzonderlijk bewerkt worden. Dit zou een editor vereisen waarin de cursor natuurlijk kan bewegen tussen vragen en antwoorden. Met tekstselectie zou je dan vragen of antwoorden kunnen verplaatsen of verwijderen. Door op de enterknop te drukken, zou je een nieuwe vraag moeten kunnen maken.
Een quiz-editor maken
Tijdens de ontwikkeling van alinea heb ik wat ervaring opgedaan met Tiptap - een JavaScript-bibliotheek die je helpt bij het maken van aangepaste rich text editors. Het maakt onderliggend gebruik van ProseMirror, maar maakt het toegankelijk vanuit frontend frameworks zoals React of Vue.
Tiptap bevat een aantal handige plugins om van te starten, zodat je niet al je tijd hoeft te besteden aan het maken van een plugin om tekst vetgedrukt te maken. Om de quiz-editor te maken, begon ik met het maken van aangepaste Nodes. Nodes zijn secties binnen je document die je hun eigen opmaak en DOM-elementen kunt geven. Bijvoorbeeld een "slide" node die zijn eigen titel, tekst en antwoorden kan bevatten. De meest gecompliceerde plugin is degene die het gedrag van de enter-toets overneemt. Het vereist het doorzoeken van de ProseMirror state van het document en het invoegen van nieuwe slide, titel of antwoord nodes op basis van de huidige positie.
Samenwerken
Tiptap bevat een samenwerkingsplugin gebruik makend van Y.js. Y.js is een implementatie van CRDTs (Conflict-free Replicated Data Types) in JavaScript. CRDTs zijn een slimme manier om meerdere mensen dezelfde informatie te laten bewerken zonder conflicten, door bepaalde regels te volgen. Y.js bundelt de kracht van CRDTs in typen zoals maps of arrays die gedeeld worden met andere gebruikers. Alle gebruikers kunnen tegelijkertijd bewerken en naarmate de wijzigingen naar de andere gebruikers worden getransporteerd, eindigt iedereen met dezelfde gegevens. Y.js ondersteunt verschillende providers om het transport te vergemakkelijken. Dit omvat transport over WebSockets of WebRTC.
De WebRTC-optie is erg interessant, omdat het gebruikers in staat stelt om gegevens rechtstreeks tussen elkaar uit te wisselen, zonder een server. Technisch gezien is nog steeds een signaleringsserver nodig om verbindingen te faciliteren, maar de hoeveelheid werk die de server moet doen is zeer minimaal. Minimaal genoeg dat ik het op een gratis tier kon hosten.
Alle gegevens worden ook naar een lokale IndexedDB-database getransporteerd, dus er gaan geen gegevens verloren. Het maken van een nieuwe quiz creëert een URL met een unieke ID. Door de URL te delen, kan iemand anders samenwerken aan dezelfde quiz.
Quizzen
Nadat ik een initiële quiz-editor had gebouwd, schetste ik het volgende deel. In de context waarin ik ons de tool zag gebruiken, presenteren we het al op een groot scherm. Tijdens een quizsessie beantwoorden gebruikers een vraag op hun telefoon terwijl ze ook de vragen en algemene voortgang op het grote scherm kunnen bekijken. De interface is responsief en wordt ook weergegeven op de telefoons, dus een gedeeld scherm is technisch gezien niet eens nodig. Een quizsessie wordt gestart vanuit de editor. We kiezen willekeurige cijfers in de vorm van 999-999 als een quiz-pincode. Dit beperkt het aantal sessies dat tegelijkertijd kan worden geopend tot een miljoen, maar dat kan worden aangepast als we zien dat het enorm populair wordt.
Antwoorden verzamelen
De status van een quizsessie wordt vertegenwoordigd in een enkel Y.js-document. Gebruikers bewerken het document om hun antwoorden toe te voegen, die vervolgens worden gesynchroniseerd met iedereen. Hoewel Y.js een aanwezigheidsconcept heeft dat kan worden gebruikt om metagegevens zoals hun gebruikersnaam of online status te synchroniseren, heb ik ervoor gekozen om dit niet te gebruiken omdat het vluchtig is. Als iemand per ongeluk uitlogt (ze sluiten of vernieuwen hun tabblad), willen we nog steeds de gebruikersgegevens bewaren, zodat we de gebruiker aan het einde in de ranglijst kunnen opnemen. Het zou perfect zijn om de online aanwezigheid aan te geven, maar voor nu is er geen indicatie in de UI.
Hosten van de app
De frontend is gebouwd in TypeScript, waarbij Vite.js wordt gebruikt om de codebase in enkele statische assets te bundelen. Deze worden gehost op Cloudflare Pages, waar het hosten van statische pagina's volledig gratis is zonder bandbreedtebeperkingen.