Modularisiere Plattform (Phase 1 & 2): RunnerRegistry-Plugin-System, standardisierter Python-Adapter und deklaratives Kurs-Manifest (courses/python/course.json)

This commit is contained in:
Alex Mueller 2026-08-30 00:30:49 +02:00
parent 108da4c12a
commit 58ef8a3fb4
7 changed files with 443 additions and 211 deletions

View file

@ -13,5 +13,5 @@
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-29T23:32:59.778016" "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
} }

View file

@ -14,7 +14,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-29T23:32:59.778016" "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
}; };
window.GAMIFICATION_BADGES_KATALOG = { window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": { "rechenkonegin": {

View file

@ -1,20 +1,17 @@
/** /**
* ⚡ GEHÄRTETE MONACO WEB-IDE & PYODIDE RUNNER ENGINE ⚡ * ⚡ UNIVERSAL MODULAR MONACO WEB-IDE ⚡
* ===================================================== * =======================================
* Interaktive Entwicklungsumgebung mit: * Sprachunabhängige Entwicklungsumgebung mit:
* - Integriertem CODE_GUARD Sicherheits-Filter (Schadcode- & Exploit-Sperre) * - Dynamischer Monaco Editor Initialisierung (Python, JavaScript, SQL, Rust etc.)
* - Monaco Editor (VS Code Engine mit Autocomplete & Syntax-Highlighting) * - Delegierung an pluggbare Language Runner via RunnerRegistry
* - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save) * - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save)
* - In-Browser Pyodide WebAssembly Python Runner (95% Client-Execution) * - Reaktives Terminal, Auto-Save & Gamification Integration
* - Endlosschleifen-Timeout-Schutz (5000ms)
* - Reaktivität mit Fehler-Dolmetscher & Gamification
*/ */
(function () { (function () {
let monacoEditor = null; let monacoEditor = null;
let pyodideInstance = null;
let pyodideLoading = false;
let currentChapterData = null; let currentChapterData = null;
let currentLanguage = "python";
let autoSaveTimeout = null; let autoSaveTimeout = null;
function escapeHtml(text) { function escapeHtml(text) {
@ -27,13 +24,22 @@
.replace(/'/g, "'"); .replace(/'/g, "'");
} }
// 1. Initialisiere Monaco Editor via CDN function getActiveRunner() {
function initMonacoEditor(initialCode = "") { if (window.RunnerRegistry) {
const runner = window.RunnerRegistry.get(currentLanguage);
if (runner) return runner;
}
return null;
}
// 1. Initialisiere Monaco Editor
function initMonacoEditor(initialCode = "", langId = "python") {
currentLanguage = langId;
const editorContainer = document.getElementById("monaco-editor-container"); const editorContainer = document.getElementById("monaco-editor-container");
if (!editorContainer) return; if (!editorContainer) return;
if (window.monaco) { if (window.monaco) {
createMonacoInstance(initialCode); createMonacoInstance(initialCode, langId);
return; return;
} }
@ -44,7 +50,7 @@
paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" } paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" }
}); });
window.require(["vs/editor/editor.main"], () => { window.require(["vs/editor/editor.main"], () => {
createMonacoInstance(initialCode); createMonacoInstance(initialCode, langId);
}); });
}; };
loaderScript.onerror = () => { loaderScript.onerror = () => {
@ -53,14 +59,17 @@
document.head.appendChild(loaderScript); document.head.appendChild(loaderScript);
} }
function createMonacoInstance(initialCode) { function createMonacoInstance(initialCode, langId) {
const editorContainer = document.getElementById("monaco-editor-container"); const editorContainer = document.getElementById("monaco-editor-container");
if (!editorContainer) return; if (!editorContainer) return;
editorContainer.innerHTML = ""; editorContainer.innerHTML = "";
const runner = getActiveRunner();
const monacoLang = runner ? runner.monacoLanguage : langId;
monacoEditor = window.monaco.editor.create(editorContainer, { monacoEditor = window.monaco.editor.create(editorContainer, {
value: initialCode, value: initialCode,
language: "python", language: monacoLang,
theme: "vs-dark", theme: "vs-dark",
fontSize: 14, fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace", fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace",
@ -95,88 +104,29 @@
} }
}); });
// 2. Pyodide WebAssembly Initialisierung mit Sandbox-Bootstrap // 2. Code-Ausführung via aktivem Language-Runner
async function getPyodide() {
if (pyodideInstance) return pyodideInstance;
if (pyodideLoading) {
while (pyodideLoading) {
await new Promise((r) => setTimeout(r, 100));
}
return pyodideInstance;
}
pyodideLoading = true;
logToTerminal("⏳ Initialisiere geschützte Python Sandbox Engine... Bitte kurz warten.\n", "info");
try {
if (!window.loadPyodide) {
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
pyodideInstance = await window.loadPyodide({
stdout: (text) => logToTerminal(text + "\n", "stdout"),
stderr: (text) => logToTerminal(text + "\n", "stderr")
});
// Sandbox-Härtung im Pyodide-Interpreter aktivieren
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
}
logToTerminal("✅ Geschützte Python WebAssembly Sandbox bereitgestellt!\n\n", "success");
} catch (err) {
logToTerminal(`❌ Fehler beim Laden der Sandbox: ${err.message}\n`, "error");
} finally {
pyodideLoading = false;
}
return pyodideInstance;
}
// 3. Code-Ausführung mit Sicherheitsprüfung & Timeout
async function runCode() { async function runCode() {
const code = getEditorCode(); const code = getEditorCode();
clearTerminal(); clearTerminal();
// Layer 1: Code-Guard Sicherheits-Prüfung const runner = getActiveRunner();
if (window.CODE_GUARD) { if (!runner) {
const check = window.CODE_GUARD.validate(code); logToTerminal(`❌ Kein Language-Runner für '${currentLanguage}' registriert.\n`, "error");
if (!check.safe) {
logToTerminal(`\n${check.error}\n\n`, "error");
return; return;
} }
}
logToTerminal("▶ Starte Python-Skript (Strg+Enter)...\n----------------------------------------\n", "info"); logToTerminal(`▶ Starte Ausführung mit ${runner.displayName} (Strg+Enter)...\n----------------------------------------\n`, "info");
const py = await getPyodide(); const result = await runner.runCode(code, logToTerminal);
if (!py) return;
try { if (result.success) {
let timeoutHandle; logToTerminal("\n----------------------------------------\n✨ Erfolgreich ausgeführt!\n", "success");
const timeoutPromise = new Promise((_, reject) => { } else if (result.error) {
timeoutHandle = setTimeout(() => { suggestErrorInterpreter(result.error);
reject(new Error("Zeitüberschreitung: Dein Code lief länger als 5 Sekunden (Endlosschleife vermutet)."));
}, 5000);
});
const execPromise = py.runPythonAsync(code);
await Promise.race([execPromise, timeoutPromise]);
clearTimeout(timeoutHandle);
logToTerminal("\n----------------------------------------\n✨ Skript erfolgreich ausgeführt!\n", "success");
} catch (err) {
logToTerminal(`\n❌ Ausführungsfehler:\n${err.message}\n`, "error");
suggestErrorInterpreter(err.message);
} }
} }
// 4. Unittest-Ausführung mit Sicherheitsprüfung // 3. Test-Ausführung via aktivem Language-Runner
async function runTests() { async function runTests() {
if (!currentChapterData || !currentChapterData.testCode) { if (!currentChapterData || !currentChapterData.testCode) {
logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error"); logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error");
@ -186,78 +136,29 @@
const userCode = getEditorCode(); const userCode = getEditorCode();
clearTerminal(); clearTerminal();
// Layer 1: Code-Guard Sicherheits-Prüfung const runner = getActiveRunner();
if (window.CODE_GUARD) { if (!runner) {
const check = window.CODE_GUARD.validate(userCode); logToTerminal(`❌ Kein Test-Runner für '${currentLanguage}' verfügbar.\n`, "error");
if (!check.safe) {
logToTerminal(`\n${check.error}\n\n`, "error");
return; return;
} }
}
logToTerminal("🧪 Führe automatisierte Unittests aus (Strg+Shift+Enter)...\n----------------------------------------\n", "info"); logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info");
const py = await getPyodide(); const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal);
if (!py) return;
try { if (result.success) {
py.FS.writeFile("aufgabe.py", userCode); logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
const testRunnerScript = `
import unittest, io, sys
from unittest import TextTestRunner
for mod in list(sys.modules.keys()):
if mod in ('aufgabe', 'test_aufgabe'):
del sys.modules[mod]
import aufgabe
${currentChapterData.testCode}
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
stream = io.StringIO()
runner = TextTestRunner(stream=stream, verbosity=2)
result = runner.run(suite)
output_text = stream.getvalue()
success = result.wasSuccessful()
total_runs = result.testsRun
failures = len(result.failures)
errors = len(result.errors)
(success, total_runs, failures, errors, output_text)
`;
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
reject(new Error("Zeitüberschreitung: Unittest lief länger als 6 Sekunden."));
}, 6000);
});
const [isSuccess, totalRuns, fails, errs, outputText] = await Promise.race([
py.runPythonAsync(testRunnerScript),
timeoutPromise
]);
clearTimeout(timeoutHandle);
logToTerminal(outputText + "\n", "stdout");
if (isSuccess) {
logToTerminal(`\n🎉 HERVORRAGEND! Alle ${totalRuns} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
triggerSuccessCelebration(); triggerSuccessCelebration();
recordChapterSolved(); recordChapterSolved();
} else { } else {
logToTerminal(`\n⚠️ ${fails + errs} von ${totalRuns} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning"); logToTerminal(`\n⚠️ ${result.failures || 1} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning");
suggestErrorInterpreter(outputText); if (result.rawOutput) {
suggestErrorInterpreter(result.rawOutput);
} }
} catch (err) {
logToTerminal(`\n❌ Fehler beim Ausführen der Testsuite:\n${err.message}\n`, "error");
suggestErrorInterpreter(err.message);
} }
} }
// 5. Hilfsfunktionen // 4. Hilfsfunktionen
function getEditorCode() { function getEditorCode() {
if (monacoEditor) { if (monacoEditor) {
return monacoEditor.getValue(); return monacoEditor.getValue();
@ -378,14 +279,15 @@ errors = len(result.errors)
} }
} }
// 6. Init Workspace Controller // 5. Init Workspace Controller
window.initWorkspace = function (chapterData) { window.initWorkspace = function (chapterData, language = "python") {
currentChapterData = chapterData; currentChapterData = chapterData;
currentLanguage = language || "python";
const savedDraft = localStorage.getItem(`code_draft_${chapterData.chapterId}`); const savedDraft = localStorage.getItem(`code_draft_${chapterData.chapterId}`);
const initialCode = savedDraft || chapterData.starterCode || ""; const initialCode = savedDraft || chapterData.starterCode || "";
initMonacoEditor(initialCode); initMonacoEditor(initialCode, currentLanguage);
document.getElementById("btn-run-code")?.addEventListener("click", runCode); document.getElementById("btn-run-code")?.addEventListener("click", runCode);
document.getElementById("btn-run-tests")?.addEventListener("click", runTests); document.getElementById("btn-run-tests")?.addEventListener("click", runTests);

View file

@ -0,0 +1,202 @@
/**
* 🐍 PYTHON LANGUAGE RUNNER ADAPTER (Pyodide WebAssembly) 🐍
* ==========================================================
* Implementiert das standardisierte LanguageRunnerAdapter-Interface:
* - Pyodide WASM Engine mit In-Memory Python 3.12
* - CODE_GUARD Sicherheits-Prüfung & Sandbox-Bootstrap
* - Timeout-Schutz gegen Endlosschleifen
* - Unittest-Runner für aufgabe.py & test_aufgabe.py
* - Fehler-Dolmetscher Anbindung
*/
(function () {
let pyodideInstance = null;
let pyodideLoading = false;
const PythonAdapter = {
id: "python",
displayName: "Python 3.12 (Pyodide WebAssembly)",
icon: "🐍",
monacoLanguage: "python",
fileExtension: ".py",
testFileExtension: "test_aufgabe.py",
/**
* Lädt und initialisiert die Pyodide WebAssembly Engine
*/
async loadEngine(logCallback) {
if (pyodideInstance) return pyodideInstance;
if (pyodideLoading) {
while (pyodideLoading) {
await new Promise((r) => setTimeout(r, 100));
}
return pyodideInstance;
}
pyodideLoading = true;
if (logCallback) logCallback("⏳ Initialisiere Python WebAssembly Engine (Pyodide)... Bitte kurz warten.\n", "info");
try {
if (!window.loadPyodide) {
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
pyodideInstance = await window.loadPyodide();
// Sandbox-Härtung im Pyodide-Interpreter aktivieren
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
}
if (logCallback) logCallback("✅ Python WebAssembly Sandbox bereitgestellt!\n\n", "success");
} catch (err) {
if (logCallback) logCallback(`❌ Fehler beim Laden der Python Sandbox: ${err.message}\n`, "error");
throw err;
} finally {
pyodideLoading = false;
}
return pyodideInstance;
},
/**
* Sicherheitsprüfung vor Code-Ausführung
*/
validateSecurity(code) {
if (window.CODE_GUARD && window.CODE_GUARD.validate) {
return window.CODE_GUARD.validate(code);
}
return { safe: true };
},
/**
* Führt Python-Code aus (▶ Code ausführen)
*/
async runCode(code, logCallback) {
const securityCheck = this.validateSecurity(code);
if (!securityCheck.safe) {
logCallback(`\n${securityCheck.error}\n\n`, "error");
return { success: false, error: securityCheck.error };
}
const py = await this.loadEngine(logCallback);
if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
// Umleitung von stdout & stderr
py.setStdout({ batched: (text) => logCallback(text + "\n", "stdout") });
py.setStderr({ batched: (text) => logCallback(text + "\n", "stderr") });
try {
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
reject(new Error("Zeitüberschreitung: Dein Code lief länger als 5 Sekunden (Endlosschleife vermutet)."));
}, 5000);
});
const execPromise = py.runPythonAsync(code);
await Promise.race([execPromise, timeoutPromise]);
clearTimeout(timeoutHandle);
return { success: true };
} catch (err) {
logCallback(`\n❌ Ausführungsfehler:\n${err.message}\n`, "error");
return { success: false, error: err.message };
}
},
/**
* Führt automatisierte Unittests aus (🧪 Tests starten)
*/
async runTests(userCode, testCode, logCallback) {
const securityCheck = this.validateSecurity(userCode);
if (!securityCheck.safe) {
logCallback(`\n${securityCheck.error}\n\n`, "error");
return { success: false, error: securityCheck.error };
}
const py = await this.loadEngine(logCallback);
if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
try {
py.FS.writeFile("aufgabe.py", userCode);
const testRunnerScript = `
import unittest, io, sys
from unittest import TextTestRunner
for mod in list(sys.modules.keys()):
if mod in ('aufgabe', 'test_aufgabe'):
del sys.modules[mod]
import aufgabe
${testCode}
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
stream = io.StringIO()
runner = TextTestRunner(stream=stream, verbosity=2)
result = runner.run(suite)
output_text = stream.getvalue()
success = result.wasSuccessful()
total_runs = result.testsRun
failures = len(result.failures)
errors = len(result.errors)
(success, total_runs, failures, errors, output_text)
`;
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
reject(new Error("Zeitüberschreitung: Unittest lief länger als 6 Sekunden."));
}, 6000);
});
const [isSuccess, totalRuns, fails, errs, outputText] = await Promise.race([
py.runPythonAsync(testRunnerScript),
timeoutPromise
]);
clearTimeout(timeoutHandle);
logCallback(outputText + "\n", "stdout");
return {
success: Boolean(isSuccess),
total: totalRuns,
passed: totalRuns - (fails + errs),
failures: fails,
errors: errs,
rawOutput: outputText
};
} catch (err) {
logCallback(`\n❌ Fehler beim Ausführen der Testsuite:\n${err.message}\n`, "error");
return { success: false, error: err.message, rawOutput: err.message };
}
},
/**
* Übersetzt Python-Tracebacks
*/
translateError(traceback) {
if (window.INTERPRETER && window.INTERPRETER.translate) {
return window.INTERPRETER.translate(traceback);
}
return null;
}
};
// In Registry registrieren
if (window.RunnerRegistry) {
window.RunnerRegistry.register(PythonAdapter);
} else {
document.addEventListener("DOMContentLoaded", () => {
if (window.RunnerRegistry) window.RunnerRegistry.register(PythonAdapter);
});
}
})();

View file

@ -0,0 +1,52 @@
/**
* 🔌 UNIVERSAL LANGUAGE RUNNER REGISTRY 🔌
* =========================================
* Verwaltet austauschbare Language-Runner für polyglotte Code-Ausführung
* (Python, JavaScript, SQL, Rust etc.) in der Monaco Web-IDE.
*/
(function () {
const runners = new Map();
window.RunnerRegistry = {
/**
* Registriert einen neuen Language Runner Adapter
* @param {Object} adapter
*/
register(adapter) {
if (!adapter || !adapter.id) {
throw new Error("Runner Adapter muss eine gültige 'id' besitzen.");
}
runners.set(adapter.id.toLowerCase(), adapter);
console.log(`🔌 [RunnerRegistry] Runner '${adapter.displayName || adapter.id}' erfolgreich registriert.`);
},
/**
* Ruft einen registrierten Runner anhand der Sprach-ID ab
* @param {string} languageId
* @returns {Object|null}
*/
get(languageId) {
if (!languageId) return null;
return runners.get(languageId.toLowerCase()) || null;
},
/**
* Gibt alle registrierten Sprach-IDs zurück
* @returns {string[]}
*/
list() {
return Array.from(runners.keys());
},
/**
* Prüft, ob ein Runner für eine Sprache verfügbar ist
* @param {string} languageId
* @returns {boolean}
*/
has(languageId) {
if (!languageId) return false;
return runners.has(languageId.toLowerCase());
}
};
})();

View file

@ -0,0 +1,77 @@
{
"id": "python",
"title": "Python Software Engineering",
"language": "python",
"icon": "🐍",
"runner": "python",
"description": "Vom Einsteiger über professionelle Objektorientierung bis hin zu Data Engineering und Desktop-Apps in 33 Modulen.",
"tracks": [
{
"id": "lehrpfad_1_grundlagen",
"title": "🌱 Lehrpfad 1: Grundlagen der Programmierung",
"description": "Zahlen, Variablen, Bedingungen, Schleifen, Funktionen, Listen, Strings, Dicts & Sets.",
"certificateKey": "lehrpfad_1",
"chapters": [
{ "folder": "01_erste_schritte_taschenrechner", "title": "G01: Python als Taschenrechner", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "02_variablen_und_datentypen", "title": "G02: Variablen & Datentypen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "03_ein_und_ausgabe", "title": "G03: Interaktive Ein- & Ausgabe", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "04_verzweigungen_und_bedingungen", "title": "G04: Verzweigungen & Bedingungen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "05_schleifen_und_wiederholungen", "title": "G05: Schleifen & Wiederholungen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "06_funktionen_und_module", "title": "G06: Eigene Funktionen & Module", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "07_listen_und_sequenzen", "title": "G07: Listen & Sequenzen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "08_textverarbeitung_und_strings", "title": "G08: Textverarbeitung & Strings", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "09_dictionaries_und_sets", "title": "G09: Dictionaries & Sets", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "10_comprehensions_datum_algorithmen", "title": "G10: Comprehensions & Algorithmen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
]
},
{
"id": "lehrpfad_2_oop_einstieg",
"title": "🏗️ Lehrpfad 2: Einstieg in die Objektorientierung (OOP)",
"description": "Klassen, Konstruktor, self, Methoden, Tools (VS Code & Git) und Tamagotchi-Projekt.",
"certificateKey": "lehrpfad_2",
"chapters": [
{ "folder": "00_fehlersuche_und_grundlagen", "title": "OOP 00: Python-Fehlersuche", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "01_einstieg_klassen", "title": "OOP 01: Erste Klassen & Instanzen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "02_init_und_self", "title": "OOP 02: Konstruktor __init__ & self", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "03_methoden_und_verhalten", "title": "OOP 03: Methoden & Verhalten", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "04_str_und_darstellung", "title": "OOP 04: __str__ & String-Darstellung", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "04b_umstieg_vscode", "title": "Tooling 04b: Umstieg auf VS Code", "taskFile": "vscode_test.py", "testFile": "" },
{ "folder": "04c_git_und_versionskontrolle", "title": "Tooling 04c: Git & Versionskontrolle", "taskFile": "git_uebung.py", "testFile": "" },
{ "folder": "05_objekte_kombinieren", "title": "OOP 05: Objekte kombinieren", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "06_abschlussprojekt_tamagotchi", "title": "Projekt 06: Virtuelles Haustier (Tamagotchi)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
]
},
{
"id": "lehrpfad_3_fortgeschrittenes_oop",
"title": "🚀 Lehrpfad 3: Fortgeschrittenes OOP & GUIs",
"description": "Speicher-Referenzen, Dunder-Methoden, TDD, Vererbung, Polymorphie, Exceptions, JSON/CSV, Tkinter & Masterprojekt.",
"certificateKey": "lehrpfad_3",
"chapters": [
{ "folder": "07_referenzen_und_speicher", "title": "Adv 07: Referenzen & Speicher", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "08_operator_overloading_dunder", "title": "Adv 08: Operator Overloading & Dunder", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "09_eigene_unit_tests_schreiben", "title": "Adv 09: Eigene Unit Tests & TDD", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "10_vererbung_und_super", "title": "Adv 10: Vererbung & super()", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "11_polymorphie_und_interfaces", "title": "Adv 11: Polymorphie & Interfaces", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "12_exceptions_und_fehlerbehandlung", "title": "Adv 12: Exceptions & Fehlerbehandlung", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "13_persistenz_json_und_csv", "title": "Adv 13: Datei-Persistenz (JSON & CSV)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "14_gui_mit_tkinter", "title": "Adv 14: Desktop-GUIs mit Tkinter", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "15_parameter_und_container", "title": "Adv 15: Parameter & Eigene Container", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "16_master_abschlussprojekt", "title": "Master 16: Master-Abschlussprojekt", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
]
},
{
"id": "lehrpfad_4_professional_data_engineering",
"title": "🌟 Lehrpfad 4: Python Professional & Data Engineering",
"description": "Reguläre Ausdrücke (re), funktionale Idiome, Generatoren, Dataclasses, Type Hints, SQLite & REST-APIs.",
"certificateKey": "lehrpfad_4",
"chapters": [
{ "folder": "17_regulaere_ausdruecke_re", "title": "Pro 17: Reguläre Ausdrücke (re)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "18_funktionale_programmierung_lambda", "title": "Pro 18: Funktionale Programmierung (lambda)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "19_generatoren_und_itertools", "title": "Pro 19: Generatoren & itertools (yield)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "20_dataclasses_und_typehints", "title": "Pro 20: Dataclasses & Type Hints", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "21_datenbanken_und_sqlite", "title": "Pro 21: Relationale Datenbanken & SQLite", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
{ "folder": "22_web_apis_und_json_feeds", "title": "Pro 22: Web-APIs & JSON-Feeds", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
]
}
]
}

View file

@ -328,47 +328,19 @@
<script src="assets/certificate.js"></script> <script src="assets/certificate.js"></script>
<script src="assets/parsons.js"></script> <script src="assets/parsons.js"></script>
<script src="assets/code_guard.js"></script> <script src="assets/code_guard.js"></script>
<script src="assets/runners/runner_registry.js"></script>
<script src="assets/runners/python_runner.js"></script>
<script src="assets/monaco_ide.js"></script> <script src="assets/monaco_ide.js"></script>
<script> <script>
// Manifest aller Kapitel für sequentielle Navigation const urlParams = new URLSearchParams(window.location.search);
const CHAPTERS_MANIFEST = [ const courseId = urlParams.get("course") || "python";
{ track: "lehrpfad_1_grundlagen", folder: "01_erste_schritte_taschenrechner", title: "G01. Taschenrechner" }, const trackFolder = urlParams.get("track") || "lehrpfad_1_grundlagen";
{ track: "lehrpfad_1_grundlagen", folder: "02_variablen_und_datentypen", title: "G02. Variablen & Typen" }, const chapterFolder = urlParams.get("chapter") || "01_erste_schritte_taschenrechner";
{ track: "lehrpfad_1_grundlagen", folder: "03_ein_und_ausgabe", title: "G03. Ein- & Ausgabe" }, const chapterId = `${trackFolder}/${chapterFolder}`;
{ track: "lehrpfad_1_grundlagen", folder: "04_verzweigungen_und_bedingungen", title: "G04. Bedingungen" },
{ track: "lehrpfad_1_grundlagen", folder: "05_schleifen_und_wiederholungen", title: "G05. Schleifen" }, let CHAPTERS_MANIFEST = [];
{ track: "lehrpfad_1_grundlagen", folder: "06_funktionen_und_module", title: "G06. Funktionen & Module" }, let currentCourseManifest = null;
{ track: "lehrpfad_1_grundlagen", folder: "07_listen_und_sequenzen", title: "G07. Listen" },
{ track: "lehrpfad_1_grundlagen", folder: "08_textverarbeitung_und_strings", title: "G08. Strings" },
{ track: "lehrpfad_1_grundlagen", folder: "09_dictionaries_und_sets", title: "G09. Dicts & Sets" },
{ track: "lehrpfad_1_grundlagen", folder: "10_comprehensions_datum_algorithmen", title: "G10. Comprehensions" },
{ track: "lehrpfad_2_oop_einstieg", folder: "00_fehlersuche_und_grundlagen", title: "OOP 00. Debugging" },
{ track: "lehrpfad_2_oop_einstieg", folder: "01_einstieg_klassen", title: "OOP 01. Klassen" },
{ track: "lehrpfad_2_oop_einstieg", folder: "02_init_und_self", title: "OOP 02. Konstruktor" },
{ track: "lehrpfad_2_oop_einstieg", folder: "03_methoden_und_verhalten", title: "OOP 03. Methoden" },
{ track: "lehrpfad_2_oop_einstieg", folder: "04_str_und_darstellung", title: "OOP 04. __str__" },
{ track: "lehrpfad_2_oop_einstieg", folder: "04b_umstieg_vscode", title: "Tooling 04b. VS Code" },
{ track: "lehrpfad_2_oop_einstieg", folder: "04c_git_und_versionskontrolle", title: "Tooling 04c. Git" },
{ track: "lehrpfad_2_oop_einstieg", folder: "05_objekte_kombinieren", title: "OOP 05. Komposition" },
{ track: "lehrpfad_2_oop_einstieg", folder: "06_abschlussprojekt_tamagotchi", title: "Projekt 06. Tamagotchi" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "07_referenzen_und_speicher", title: "Adv 07. Speicher" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "08_operator_overloading_dunder", title: "Adv 08. Dunder" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "09_eigene_unit_tests_schreiben", title: "Adv 09. TDD Tests" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "10_vererbung_und_super", title: "Adv 10. Vererbung" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "11_polymorphie_und_interfaces", title: "Adv 11. Polymorphie" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "12_exceptions_und_fehlerbehandlung", title: "Adv 12. Exceptions" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "13_persistenz_json_und_csv", title: "Adv 13. Persistenz" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "14_gui_mit_tkinter", title: "Adv 14. Tkinter" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "15_parameter_und_container", title: "Adv 15. Container" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "16_master_abschlussprojekt", title: "Master 16. Abschlussprojekt" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "17_regulaere_ausdruecke_re", title: "Pro 17. Regex & Text Mining" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "18_funktionale_programmierung_lambda", title: "Pro 18. Functional & Lambda" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "19_generatoren_und_itertools", title: "Pro 19. Generatoren & Itertools" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "20_dataclasses_und_typehints", title: "Pro 20. Dataclasses & Decorators" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "21_datenbanken_und_sqlite", title: "Pro 21. SQLite & Relationale DBs" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "22_web_apis_und_json_feeds", title: "Pro 22. REST APIs & JSON Feeds" }
];
function switchTab(tabId) { function switchTab(tabId) {
document.querySelectorAll(".pane-tab-btn").forEach(b => b.classList.remove("active")); document.querySelectorAll(".pane-tab-btn").forEach(b => b.classList.remove("active"));
@ -385,22 +357,45 @@
if (el) el.style.display = el.style.display === "block" ? "none" : "block"; if (el) el.style.display = el.style.display === "block" ? "none" : "block";
} }
const urlParams = new URLSearchParams(window.location.search); async function loadCourseManifest() {
const trackFolder = urlParams.get("track") || "lehrpfad_1_grundlagen"; try {
const chapterFolder = urlParams.get("chapter") || "01_erste_schritte_taschenrechner"; const res = await fetch(`courses/${courseId}/course.json`);
const chapterId = `${trackFolder}/${chapterFolder}`; if (res.ok) {
currentCourseManifest = await res.json();
const list = [];
(currentCourseManifest.tracks || []).forEach(tr => {
(tr.chapters || []).forEach(ch => {
list.push({
track: tr.id,
folder: ch.folder,
title: ch.title,
taskFile: ch.taskFile || "aufgabe.py",
testFile: ch.testFile || "test_aufgabe.py"
});
});
});
CHAPTERS_MANIFEST = list;
}
} catch (e) {
console.warn("Konnte course.json nicht laden, nutze Fallback-Manifest:", e);
}
}
// Nächstes Kapitel ermitteln async function setupNextChapterButton() {
const currentIndex = CHAPTERS_MANIFEST.findIndex(c => c.folder === chapterFolder); const currentIndex = CHAPTERS_MANIFEST.findIndex(c => c.folder === chapterFolder);
const nextChapter = (currentIndex >= 0 && currentIndex < CHAPTERS_MANIFEST.length - 1) ? CHAPTERS_MANIFEST[currentIndex + 1] : null; const nextChapter = (currentIndex >= 0 && currentIndex < CHAPTERS_MANIFEST.length - 1) ? CHAPTERS_MANIFEST[currentIndex + 1] : null;
document.getElementById("btn-next-chapter")?.addEventListener("click", () => { const nextBtn = document.getElementById("btn-next-chapter");
if (nextBtn) {
nextBtn.onclick = () => {
if (nextChapter) { if (nextChapter) {
window.location.href = `workspace.html?track=${nextChapter.track}&chapter=${nextChapter.folder}`; window.location.href = `workspace.html?course=${courseId}&track=${nextChapter.track}&chapter=${nextChapter.folder}`;
} else { } else {
window.location.href = "index.html#zertifikate"; window.location.href = "index.html#zertifikate";
} }
}); };
}
}
async function loadWorkspaceData() { async function loadWorkspaceData() {
try { try {
@ -488,13 +483,17 @@
document.getElementById("hints-modal-overlay").style.display = "flex"; document.getElementById("hints-modal-overlay").style.display = "flex";
}); });
// 5. Monaco IDE initialisieren await loadCourseManifest();
setupNextChapterButton();
// 5. Monaco IDE initialisieren mit modularer Sprache
const lang = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
window.initWorkspace({ window.initWorkspace({
chapterId: chapterId, chapterId: chapterId,
starterCode: aufgabeCode, starterCode: aufgabeCode,
testCode: testCode, testCode: testCode,
solutionCode: loesungCode solutionCode: loesungCode
}); }, lang);
} catch (err) { } catch (err) {
document.getElementById("tab-content-theorie").innerHTML = `<div class="box box-danger">Fehler beim Laden: ${err.message}</div>`; document.getElementById("tab-content-theorie").innerHTML = `<div class="box box-danger">Fehler beim Laden: ${err.message}</div>`;