Files
mindflux/src/main/resources/static/js/devlab.js
T
2026-06-30 18:03:55 +02:00

77 lines
3.9 KiB
JavaScript
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.
/* Dev Lab Hub – lädt den Spiele-Katalog aus /api/devlab/catalog (DB-gestützt) und gruppiert
die Spiele nach Trainingspfad. Spielkarten verlinken auf /devlab/games/{slug}. */
(function () {
const PATHS = [
{ id: 'syntax', marker: '{}', title: 'Syntax & Grundlagen', type: 'Grundlagenpfad',
copy: 'Sprachsyntax, Strukturen und Muster verstehen, bevor der Code komplex wird.',
mechanics: ['Lücken füllen', 'Zusammensetzen', 'Multiple Choice', 'Regex live'], badge: 'Syntax Core' },
{ id: 'debugging', marker: '</>', title: 'Debugging & Problemlösung', type: 'Ermittlungspfad',
copy: 'Fehler finden, Ursache erkennen und den passenden Reparaturschritt wählen.',
mechanics: ['Bug finden', 'Lücken füllen', 'Befehlsfolgen', 'Storyfälle'], badge: 'Bug Hunter' },
{ id: 'visual', marker: '[]', title: 'Praxis & Visualisierung', type: 'Werkstattpfad',
copy: 'Direkt sehen, was Regeln, Layouts, APIs und Kaskaden im System bewirken.',
mechanics: ['Zuordnen', 'Multiple Choice', 'Statuscodes', 'Kaskade'], badge: 'Visual Builder' }
];
const pathRoot = document.getElementById('dl-stations');
const gameRoot = document.getElementById('dl-games');
const mechanicsRoot = document.getElementById('dl-mechanics');
const ui = {
title: document.getElementById('dl-world-title'), type: document.getElementById('dl-world-type'),
icon: document.getElementById('dl-world-icon'), copy: document.getElementById('dl-world-copy'),
levels: document.getElementById('dl-levels'), xp: document.getElementById('dl-xp'),
badge: document.getElementById('dl-badge'), gameCount: document.getElementById('dl-game-count')
};
const esc = (v) => String(v).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
let byPath = {};
function renderStations(activeId) {
pathRoot.innerHTML = PATHS.map((p) => {
const games = byPath[p.id] || [];
return `<button type="button" class="dl-station${p.id === activeId ? ' active' : ''}" data-path="${p.id}">
<span class="dl-station-icon">${esc(p.marker)}</span>
<span><h3>${esc(p.title)}</h3><p>${esc(p.copy)}</p>
<span class="dl-station-meta"><span class="dl-chip">${games.length} Spiele</span><span class="dl-chip">${esc(p.type)}</span></span>
</span></button>`;
}).join('');
}
function renderPath(p) {
const games = byPath[p.id] || [];
const totalTasks = games.reduce((sum, g) => sum + (g.taskCount || 0), 0);
ui.title.textContent = p.title; ui.type.textContent = p.type; ui.icon.textContent = p.marker;
ui.copy.textContent = p.copy; ui.levels.textContent = games.length; ui.xp.textContent = totalTasks; ui.badge.textContent = p.badge;
ui.gameCount.textContent = `${games.length} Spiele`;
mechanicsRoot.innerHTML = p.mechanics.map((m) => `<span class="dl-chip">${esc(m)}</span>`).join('');
gameRoot.innerHTML = games.length ? games.map((g) => `
<article class="dl-game playable">
<div><h4>${esc(g.title)}</h4><p>${esc(g.description || '')}</p></div>
<div class="dl-game-actions">
<span class="dl-game-state">${g.taskCount} Aufgaben</span>
<a class="mf-btn" href="/devlab/games/${encodeURIComponent(g.slug)}">Starten</a>
</div>
</article>`).join('') : '<p class="dl-empty">Für diesen Pfad sind noch keine Spiele freigeschaltet.</p>';
renderStations(p.id);
}
pathRoot.addEventListener('click', (e) => {
const btn = e.target.closest('.dl-station');
if (!btn) return;
const p = PATHS.find((x) => x.id === btn.dataset.path);
if (p) renderPath(p);
});
fetch('/api/devlab/catalog')
.then((res) => res.json())
.then((data) => {
byPath = {};
(data.games || []).forEach((g) => {
const key = PATHS.some((p) => p.id === g.path) ? g.path : 'syntax';
(byPath[key] = byPath[key] || []).push(g);
});
renderPath(PATHS[0]);
})
.catch(() => renderPath(PATHS[0]));
})();