Files
mindflux/src/main/resources/templates/admin/devlab-game.html
T
AliceDarkest b88962da6f Dev Lab: server-autoritatives Entwicklerlabor mit 6 Mechaniken + 189 Aufgaben
Dev Lab bündelt mehrere Disziplinen (SQL, Java/Debugging, Git, HTML, CSS, Regex,
PHP, REST) in 11 Spielen mit zusammen 189 Aufgaben. Im Gegensatz zur ersten,
rein clientseitigen Skizze jetzt server-autoritativ, DB-gestützt und im Admin
pflegbar – mit echter Mechanik-Vielfalt statt überall Lückentext:
cloze (Lücke tippen), choice (Multiple Choice), order (Bausteine zusammensetzen),
spot (Bug anklicken), match (Paare zuordnen), regex (Muster schreiben mit
Live-Treffer-Vorschau).

Prüfung und Score ausschließlich serverseitig – die Lösungen verlassen nie das
Backend; Eingabe-Platzhalter sind neutrale Labels (Lösungs-Leak behoben).
Fehler-Mechanik mit Versuchen + Hinweis + Auflösung, kein Game-Over; Score über
die generische game_scores-Tabelle (Kennung devlab, deck=Spiel-Slug).

- Entities DevLabGame/DevLabTask (JSON-payload) + Flyway V8__devlab.sql
- DevLabService mit sechs Mechanik-Checkern; API /api/devlab/** (CSRF-ausgenommen,
  devlab in KNOWN_GAMES)
- Admin /admin/devlab (Spiele + Aufgaben pflegen); polymorphe Frontend-Engine
  devlabgame.js + DB-getriebener Hub devlab.js
- Inhalte aus devlab/content.json (idempotenter Seeder); ersetzt die erste
  clientseitige Dev-Lab-Skizze (devlab.js/devlabgame.js/-game.html/-game.css neu)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:32:03 +02:00

109 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de" data-theme="dark"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${game.title} + ' – Dev Lab'">Dev-Lab-Spiel</title>
<link rel="stylesheet" href="/css/theme.css">
<script src="/js/theme.js"></script>
<style>
.dlg { max-width: 1000px; margin: 0 auto; padding: 0 1rem; width: 100%; }
.dlg .grid2 { display: grid; grid-template-columns: 160px 1fr; gap: 0.7rem; }
.dlg .field { margin-bottom: 0.7rem; display: flex; flex-direction: column; gap: 0.35rem; }
.dlg .field > label { color: var(--accent); font-weight: 700; font-size: 0.85rem; }
.dlg textarea.mf-input { font-family: "JetBrains Mono", monospace; resize: vertical; min-height: 8rem; font-size: 0.82rem; }
.dlg .task { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--secondary); border-radius: 12px; padding: 0.9rem 1.1rem; margin-bottom: 0.7rem; box-shadow: var(--g-soft); }
.dlg .task-head { display: flex; align-items: center; gap: 0.6rem; justify-content: space-between; flex-wrap: wrap; }
.dlg .ttype { background: var(--glow-w); color: var(--accent); font-weight: 800; border: 1px solid var(--border-accent); border-radius: 7px; padding: 0.14rem 0.55rem; font-size: 0.78rem; font-family: monospace; }
.dlg .tprompt { color: var(--heading); font-weight: 600; }
.dlg .hint { color: var(--muted); font-size: 0.82rem; margin-bottom: 0.8rem; line-height: 1.5; }
.dlg .hint code { color: var(--accent); }
.dlg .empty { color: var(--danger); }
.dlg details { margin-top: 0.5rem; }
.dlg summary { cursor: pointer; color: var(--accent); font-size: 0.85rem; }
@media (max-width: 600px) { .dlg .grid2 { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="mf-theme-float"><button class="mf-theme"></button></div>
<div class="mf-container" style="max-width:1040px">
<div class="mf-page-head">
<a th:href="@{/admin/devlab}" class="mf-back">⟵ Zurück zur Spieleliste</a>
</div>
<header style="margin-bottom:1.4rem">
<div class="mf-eyebrow">Dev Lab · Spiel</div>
<h1 style="color:var(--heading); font-size:1.75rem; font-weight:800; margin-top:.35rem" th:text="'⚗️ ' + ${game.title}">Spiel</h1>
<p style="color:var(--muted); margin-top:.3rem">
<span th:text="'/' + ${game.slug}">/slug</span> ·
<span th:text="${game.path}">syntax</span> ·
<span th:text="${game.language}">SQL</span>
</p>
</header>
<main class="dlg">
<h4 style="color:var(--accent); margin:0 0 0.7rem; text-transform:uppercase; letter-spacing:.08em; font-size:.8rem">Aufgaben</h4>
<div class="task" th:each="t : ${game.tasks}">
<div class="task-head">
<span><span class="ttype" th:text="${t.type}">cloze</span> <span class="tprompt" th:text="${t.prompt}">Titel</span></span>
<form th:action="@{'/admin/devlab/tasks/' + ${t.id} + '/delete'}" method="post" onsubmit="return confirm('Aufgabe löschen?');">
<input type="hidden" name="gameId" th:value="${game.id}">
<button type="submit" class="mf-btn danger">✕ Löschen</button>
</form>
</div>
<details>
<summary>Bearbeiten (JSON-Payload)</summary>
<form th:action="@{'/admin/devlab/tasks/' + ${t.id} + '/update'}" method="post" style="margin-top:.6rem">
<input type="hidden" name="gameId" th:value="${game.id}">
<div class="grid2">
<div class="field"><label>Punkte</label><input class="mf-input" type="number" name="points" th:value="${t.points}" min="10" max="1000"></div>
<div class="field"><label>Titel</label><input class="mf-input" type="text" name="prompt" th:value="${t.prompt}" required></div>
</div>
<div class="field"><label>Payload (JSON)</label><textarea class="mf-input" name="payload" th:text="${t.payload}" required></textarea></div>
<button type="submit" class="mf-btn">💾 Speichern</button>
</form>
</details>
</div>
<p th:if="${game.tasks.isEmpty()}" class="empty">Noch keine Aufgaben – unten hinzufügen. Ohne Aufgaben erscheint das Spiel nicht.</p>
<div class="mf-panel accent" style="margin-top:1.6rem">
<h4>Aufgabe hinzufügen</h4>
<p class="hint">Typ wählen und den passenden <strong>JSON-Payload</strong> eintragen. Pflichtfelder je Typ:
<code>cloze</code>: codeTemplate (<code>{{1}}</code>…), gaps[{position, placeholder, hint, caseSensitive, solutions[]}] ·
<code>choice</code>: question, options[], correctIndex ·
<code>order</code>: instruction, items[] (richtige Reihenfolge) ·
<code>spot</code>: tokens[], bugIndex, fix ·
<code>match</code>: instruction, pairs[[l,r]…] ·
<code>regex</code>: instruction, samples[{text, shouldMatch}], pattern.
Optional überall: narrative, hint, successText. Platzhalter nie die Lösung!</p>
<form th:action="@{'/admin/devlab/games/' + ${game.id} + '/tasks'}" method="post">
<div class="grid2">
<div class="field"><label>Typ</label>
<select class="mf-select" name="type">
<option value="cloze">cloze – Lücke tippen</option>
<option value="choice">choice – Multiple Choice</option>
<option value="order">order – Zusammensetzen</option>
<option value="spot">spot – Bug finden</option>
<option value="match">match – Zuordnen</option>
<option value="regex">regex – Muster schreiben</option>
</select>
</div>
<div class="field"><label>Titel</label><input class="mf-input" type="text" name="prompt" placeholder="kurzer Aufgabentitel" required></div>
</div>
<div class="field" style="max-width:160px"><label>Punkte</label><input class="mf-input" type="number" name="points" value="100" min="10" max="1000"></div>
<div class="field"><label>Payload (JSON)</label><textarea class="mf-input" name="payload" placeholder='{ "question": "...", "options": ["A","B"], "correctIndex": 0, "hint": "...", "successText": "..." }' required></textarea></div>
<button type="submit" class="mf-btn">➕ Aufgabe speichern</button>
</form>
</div>
</main>
</div>
<footer class="mf-footer">
<a href="#">Support</a><a href="#">Feedback</a>
<a href="/nutzungsbedingungen">Nutzungsbedingungen</a><a href="/richtlinien">Richtlinien</a>
<a href="/datenschutz">Datenschutz</a><a href="/impressum">Impressum</a>
</footer>
</body>
</html>