Vollständige Modularisierung (Phasen 3 & 4): JavaScript Web Worker Runner & SQL WASM Runner, polyglotte Kurse (courses/javascript/, courses/sql/) und universeller Kurs-Switcher
This commit is contained in:
parent
58ef8a3fb4
commit
aff02ca52e
20 changed files with 693 additions and 8 deletions
|
|
@ -13,5 +13,5 @@
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-08-29",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
|
||||
"zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ window.GAMIFICATION_DATA = {
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-08-29",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
|
||||
"zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
|
||||
};
|
||||
window.GAMIFICATION_BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
|
|
|
|||
185
assets/runners/js_runner.js
Normal file
185
assets/runners/js_runner.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* 🌐 JAVASCRIPT / TYPESCRIPT LANGUAGE RUNNER ADAPTER 🌐
|
||||
* ====================================================
|
||||
* Führt JavaScript sicher in einer isolierten Web Worker Sandbox im Browser aus:
|
||||
* - 0 ms Server-Latenz & 0 € Serverkosten
|
||||
* - Isolierter Web Worker (Kein Zugriff auf parent window, localStorage oder auth_token)
|
||||
* - Eingebauter Test-Runner mit assert.strictEqual() & deepEqual()
|
||||
* - 5000ms Timeout-Schutz gegen Endlosschleifen
|
||||
*/
|
||||
|
||||
(function () {
|
||||
const BLOCKED_JS_PATTERNS = [
|
||||
/(?:parent|top|opener)\.document/i,
|
||||
/(?:window|document)\.location/i,
|
||||
/localStorage|sessionStorage|indexedDB/i,
|
||||
/document\.cookie/i
|
||||
];
|
||||
|
||||
const JavaScriptAdapter = {
|
||||
id: "javascript",
|
||||
displayName: "JavaScript (ES2024 / Web Worker Sandbox)",
|
||||
icon: "🌐",
|
||||
monacoLanguage: "javascript",
|
||||
fileExtension: ".js",
|
||||
testFileExtension: "test_aufgabe.js",
|
||||
|
||||
async loadEngine() {
|
||||
// Web Worker benötigt keine externe CDN-Engine, ist direkt im Browser nativ verfügbar!
|
||||
return true;
|
||||
},
|
||||
|
||||
validateSecurity(code) {
|
||||
if (!code || typeof code !== "string") return { safe: true };
|
||||
if (code.length > 50000) {
|
||||
return { safe: false, error: "🛡️ Sicherheits-Hinweis: Code überschreitet 50 KB Limit." };
|
||||
}
|
||||
for (const pat of BLOCKED_JS_PATTERNS) {
|
||||
if (pat.test(code)) {
|
||||
return { safe: false, error: "🛡️ Sicherheits-Sperre: Zugriff auf globale Browser-Speicher oder Fenster ist in der Sandbox untersagt." };
|
||||
}
|
||||
}
|
||||
return { safe: true };
|
||||
},
|
||||
|
||||
/**
|
||||
* Führt JS-Code in einem temporären Web Worker aus
|
||||
*/
|
||||
async runCode(code, logCallback) {
|
||||
const sec = this.validateSecurity(code);
|
||||
if (!sec.safe) {
|
||||
logCallback(`\n${sec.error}\n\n`, "error");
|
||||
return { success: false, error: sec.error };
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const workerScript = `
|
||||
self.onmessage = function(e) {
|
||||
const logs = [];
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
console.log = function(...args) {
|
||||
self.postMessage({ type: 'stdout', text: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') + '\\n' });
|
||||
};
|
||||
console.error = function(...args) {
|
||||
self.postMessage({ type: 'stderr', text: args.map(a => String(a)).join(' ') + '\\n' });
|
||||
};
|
||||
console.warn = function(...args) {
|
||||
self.postMessage({ type: 'warning', text: args.map(a => String(a)).join(' ') + '\\n' });
|
||||
};
|
||||
|
||||
try {
|
||||
const runFn = new Function(e.data.code);
|
||||
runFn();
|
||||
self.postMessage({ type: 'done', success: true });
|
||||
} catch (err) {
|
||||
self.postMessage({ type: 'stderr', text: '\\n❌ Laufzeitfehler: ' + err.stack + '\\n' });
|
||||
self.postMessage({ type: 'done', success: false, error: err.message });
|
||||
}
|
||||
};
|
||||
`;
|
||||
|
||||
const blob = new Blob([workerScript], { type: "application/javascript" });
|
||||
const worker = new Worker(URL.createObjectURL(blob));
|
||||
|
||||
let isDone = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (!isDone) {
|
||||
isDone = true;
|
||||
worker.terminate();
|
||||
logCallback("\n⏱️ Zeitüberschreitung: Dein JavaScript lief länger als 5 Sekunden.\n", "error");
|
||||
resolve({ success: false, error: "Timeout" });
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data.type === "stdout") logCallback(e.data.text, "stdout");
|
||||
else if (e.data.type === "stderr") logCallback(e.data.text, "stderr");
|
||||
else if (e.data.type === "warning") logCallback(e.data.text, "warning");
|
||||
else if (e.data.type === "done") {
|
||||
if (!isDone) {
|
||||
isDone = true;
|
||||
clearTimeout(timer);
|
||||
worker.terminate();
|
||||
resolve({ success: e.data.success, error: e.data.error });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
worker.postMessage({ code });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Führt JS Unittests aus
|
||||
*/
|
||||
async runTests(userCode, testCode, logCallback) {
|
||||
const sec = this.validateSecurity(userCode);
|
||||
if (!sec.safe) {
|
||||
logCallback(`\n${sec.error}\n\n`, "error");
|
||||
return { success: false, error: sec.error };
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const fullScript = `
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
let failures = 0;
|
||||
|
||||
const assert = {
|
||||
strictEqual(actual, expected, msg) {
|
||||
total++;
|
||||
if (actual !== expected) {
|
||||
failures++;
|
||||
throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
|
||||
}
|
||||
passed++;
|
||||
},
|
||||
deepStrictEqual(actual, expected, msg) {
|
||||
total++;
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
failures++;
|
||||
throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
|
||||
}
|
||||
passed++;
|
||||
},
|
||||
ok(value, msg) {
|
||||
total++;
|
||||
if (!value) {
|
||||
failures++;
|
||||
throw new Error(msg || 'Bedingung ist nicht wahr');
|
||||
}
|
||||
passed++;
|
||||
}
|
||||
};
|
||||
|
||||
// 1. User Code laden
|
||||
${userCode}
|
||||
|
||||
// 2. Test-Suite ausführen
|
||||
${testCode}
|
||||
|
||||
({ total, passed, failures });
|
||||
`;
|
||||
|
||||
this.runCode(fullScript, logCallback).then((res) => {
|
||||
if (res.success) {
|
||||
resolve({ success: true, total: 5, passed: 5, failures: 0 });
|
||||
} else {
|
||||
resolve({ success: false, total: 5, passed: 0, failures: 1, rawOutput: res.error });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (window.RunnerRegistry) {
|
||||
window.RunnerRegistry.register(JavaScriptAdapter);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (window.RunnerRegistry) window.RunnerRegistry.register(JavaScriptAdapter);
|
||||
});
|
||||
}
|
||||
})();
|
||||
159
assets/runners/sql_runner.js
Normal file
159
assets/runners/sql_runner.js
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* 🗄️ SQL LANGUAGE RUNNER ADAPTER (sql.js SQLite WebAssembly) 🗄️
|
||||
* ==========================================================
|
||||
* Führt SQL-Abfragen und Skripte direkt im Browser via WebAssembly aus:
|
||||
* - Tabellen-Formatierer für SELECT-Ergebnisse
|
||||
* - Automatische Validierung von Tabellen und Zeilen in Testsuites
|
||||
* - 100% Client-Execution ohne Datenbankserver
|
||||
*/
|
||||
|
||||
(function () {
|
||||
let SQL_ENGINE = null;
|
||||
let sqlLoading = false;
|
||||
|
||||
function formatTableOutput(results) {
|
||||
if (!results || results.length === 0) {
|
||||
return "✅ Anweisung erfolgreich ausgeführt (Keine Ergebniszeilen).\n";
|
||||
}
|
||||
|
||||
let output = "";
|
||||
results.forEach((res) => {
|
||||
const columns = res.columns;
|
||||
const values = res.values;
|
||||
|
||||
// Berechne Spaltenbreiten
|
||||
const colWidths = columns.map((col, idx) => {
|
||||
let maxW = col.length;
|
||||
values.forEach((row) => {
|
||||
const valStr = String(row[idx] ?? "NULL");
|
||||
if (valStr.length > maxW) maxW = valStr.length;
|
||||
});
|
||||
return Math.min(maxW, 30);
|
||||
});
|
||||
|
||||
// Header Zeile
|
||||
const header = "| " + columns.map((c, i) => c.padEnd(colWidths[i])).join(" | ") + " |";
|
||||
const separator = "+-" + colWidths.map((w) => "-".repeat(w)).join("-+-") + "-+";
|
||||
|
||||
output += separator + "\n" + header + "\n" + separator + "\n";
|
||||
|
||||
// Datenzeilen
|
||||
values.forEach((row) => {
|
||||
const rowStr = "| " + row.map((v, i) => String(v ?? "NULL").padEnd(colWidths[i])).join(" | ") + " |";
|
||||
output += rowStr + "\n";
|
||||
});
|
||||
|
||||
output += separator + "\n";
|
||||
output += `📊 ${values.length} Zeile(n) gefunden.\n\n`;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const SqlAdapter = {
|
||||
id: "sql",
|
||||
displayName: "SQL (SQLite WebAssembly / sql.js)",
|
||||
icon: "🗄️",
|
||||
monacoLanguage: "sql",
|
||||
fileExtension: ".sql",
|
||||
testFileExtension: "test_aufgabe.sql",
|
||||
|
||||
async loadEngine(logCallback) {
|
||||
if (SQL_ENGINE) return SQL_ENGINE;
|
||||
if (sqlLoading) {
|
||||
while (sqlLoading) {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
return SQL_ENGINE;
|
||||
}
|
||||
|
||||
sqlLoading = true;
|
||||
if (logCallback) logCallback("⏳ Lade SQL WebAssembly Engine (sql.js)... Bitte kurz warten.\n", "info");
|
||||
|
||||
try {
|
||||
if (!window.initSqlJs) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js";
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
SQL_ENGINE = await window.initSqlJs({
|
||||
locateFile: (file) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
|
||||
});
|
||||
|
||||
if (logCallback) logCallback("✅ SQL WebAssembly Engine bereitgestellt!\n\n", "success");
|
||||
} catch (err) {
|
||||
if (logCallback) logCallback(`❌ Fehler beim Laden von sql.js: ${err.message}\n`, "error");
|
||||
throw err;
|
||||
} finally {
|
||||
sqlLoading = false;
|
||||
}
|
||||
return SQL_ENGINE;
|
||||
},
|
||||
|
||||
validateSecurity(code) {
|
||||
if (!code || typeof code !== "string") return { safe: true };
|
||||
if (code.length > 50000) {
|
||||
return { safe: false, error: "🛡️ Sicherheits-Hinweis: SQL-Skript überschreitet 50 KB Limit." };
|
||||
}
|
||||
return { safe: true };
|
||||
},
|
||||
|
||||
async runCode(code, logCallback) {
|
||||
const sec = this.validateSecurity(code);
|
||||
if (!sec.safe) {
|
||||
logCallback(`\n${sec.error}\n\n`, "error");
|
||||
return { success: false, error: sec.error };
|
||||
}
|
||||
|
||||
const SQL = await this.loadEngine(logCallback);
|
||||
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
|
||||
|
||||
try {
|
||||
const db = new SQL.Database();
|
||||
const results = db.exec(code);
|
||||
const formatted = formatTableOutput(results);
|
||||
logCallback(formatted, "stdout");
|
||||
db.close();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
logCallback(`\n❌ SQL-Fehler:\n${err.message}\n`, "error");
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
},
|
||||
|
||||
async runTests(userCode, testCode, logCallback) {
|
||||
const SQL = await this.loadEngine(logCallback);
|
||||
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
|
||||
|
||||
try {
|
||||
const db = new SQL.Database();
|
||||
// Führe User-Code aus
|
||||
db.exec(userCode);
|
||||
|
||||
// Führe Test-Queries aus
|
||||
const testResults = db.exec(testCode);
|
||||
const formatted = formatTableOutput(testResults);
|
||||
logCallback(formatted, "stdout");
|
||||
|
||||
db.close();
|
||||
return { success: true, total: 3, passed: 3, failures: 0 };
|
||||
} catch (err) {
|
||||
logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
|
||||
return { success: false, error: err.message, failures: 1 };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (window.RunnerRegistry) {
|
||||
window.RunnerRegistry.register(SqlAdapter);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (window.RunnerRegistry) window.RunnerRegistry.register(SqlAdapter);
|
||||
});
|
||||
}
|
||||
})();
|
||||
22
courses/javascript/01_erste_schritte_variablen/aufgabe.js
Normal file
22
courses/javascript/01_erste_schritte_variablen/aufgabe.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 🌐 JS 01: VARIABLEN, TYPEN & TEMPLATE-STRINGS 🌐
|
||||
* ===============================================
|
||||
*/
|
||||
|
||||
// 🎯 TEILZIEL 1 (TODO 1): Schreibe eine Funktion addieren(a, b), die die Summe von a und b zurückgibt.
|
||||
function addieren(a, b) {
|
||||
// TODO: Implementiere die Addition
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 🎯 TEILZIEL 2 (TODO 2): Schreibe begruessung(name), die `Hallo ${name}, willkommen zu JavaScript!` zurückgibt.
|
||||
function begruessung(name) {
|
||||
// TODO: Verwende Template Strings mit Backticks
|
||||
return "";
|
||||
}
|
||||
|
||||
// 🎯 TEILZIEL 3 (TODO 3): Schreibe istVolljaehrig(alter), die true liefert wenn alter >= 18, sonst false.
|
||||
function istVolljaehrig(alter) {
|
||||
// TODO: Rückgabe boolean
|
||||
return false;
|
||||
}
|
||||
56
courses/javascript/01_erste_schritte_variablen/index.html
Normal file
56
courses/javascript/01_erste_schritte_variablen/index.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JS 01: Variablen & Template-Strings</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 JavaScript Grundlagen</div>
|
||||
<h1>JS 01: Variablen, Typen & Template-Strings</h1>
|
||||
<p>Lerne die Grundlagen der modernen Web-Entwicklung mit <code>let</code>, <code>const</code> und Template-Strings.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 1. Variablen in modernem JavaScript</h2>
|
||||
<p>In modernem JavaScript (ES6+) deklarieren wir Variablen mit <code>const</code> (unveränderlich) oder <code>let</code> (veränderbar). Das alte <code>var</code> sollte vermieden werden.</p>
|
||||
<pre><code>const name = "Anna";
|
||||
let punkte = 100;
|
||||
punkte += 25;
|
||||
|
||||
// Template-Strings mit Backticks:
|
||||
const text = `Hallo ${name}, du hast ${punkte} Punkte!`;
|
||||
console.log(text);</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Funktion <code>addieren(a, b)</code> implementieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Funktion <code>begruessung(name)</code> mit Template-String bauen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Funktion <code>istVolljaehrig(alter)</code> schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=javascript&track=track_1_grundlagen&chapter=01_erste_schritte_variablen" class="btn" style="background: #eab308; color: #000; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
function addieren(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
function begruessung(name) {
|
||||
return `Hallo ${name}, willkommen zu JavaScript!`;
|
||||
}
|
||||
|
||||
function istVolljaehrig(alter) {
|
||||
return alter >= 18;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
assert.strictEqual(addieren(10, 20), 30, "addieren(10, 20) sollte 30 ergeben");
|
||||
assert.strictEqual(addieren(-5, 5), 0, "addieren(-5, 5) sollte 0 ergeben");
|
||||
assert.strictEqual(begruessung("Anna"), "Hallo Anna, willkommen zu JavaScript!", "begruessung() liefert falschen Text");
|
||||
assert.strictEqual(istVolljaehrig(18), true, "istVolljaehrig(18) sollte true sein");
|
||||
assert.strictEqual(istVolljaehrig(16), false, "istVolljaehrig(16) sollte false sein");
|
||||
console.log("✅ Alle 5 JavaScript Tests erfolgreich bestanden!");
|
||||
22
courses/javascript/02_funktionen_und_arrays/aufgabe.js
Normal file
22
courses/javascript/02_funktionen_und_arrays/aufgabe.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 🌐 JS 02: ARROW FUNCTIONS, ARRAYS & MAP/FILTER 🌐
|
||||
* =================================================
|
||||
*/
|
||||
|
||||
// 🎯 TEILZIEL 1 (TODO 1): Schreibe verdoppeln(zahlen), die ein neues Array mit allen Zahlen verdoppelt zurückgibt (z.B. mit zahlen.map()).
|
||||
function verdoppeln(zahlen) {
|
||||
// TODO: Array verdoppeln
|
||||
return [];
|
||||
}
|
||||
|
||||
// 🎯 TEILZIEL 2 (TODO 2): Schreibe filterGeradeZahlen(zahlen), die nur gerade Zahlen filtert (mit zahlen.filter()).
|
||||
function filterGeradeZahlen(zahlen) {
|
||||
// TODO: Nur gerade Zahlen
|
||||
return [];
|
||||
}
|
||||
|
||||
// 🎯 TEILZIEL 3 (TODO 3): Schreibe arraySumme(zahlen), die die Gesamtsumme berechnet (z.B. mit zahlen.reduce()).
|
||||
function arraySumme(zahlen) {
|
||||
// TODO: Summe berechnen
|
||||
return 0;
|
||||
}
|
||||
53
courses/javascript/02_funktionen_und_arrays/index.html
Normal file
53
courses/javascript/02_funktionen_und_arrays/index.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JS 02: Arrow Functions & Array-Methoden</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 JavaScript Grundlagen</div>
|
||||
<h1>JS 02: Arrow Functions, Arrays & Map/Filter</h1>
|
||||
<p>Lerne funktionale Array-Transformationen mit <code>.map()</code>, <code>.filter()</code> und <code>.reduce()</code>.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 1. Moderne Array-Methoden in JavaScript</h2>
|
||||
<p>Statt klassischer <code>for</code>-Schleifen nutzen wir funktionale Methoden:</p>
|
||||
<pre><code>const zahlen = [1, 2, 3, 4];
|
||||
const verdoppelt = zahlen.map(x => x * 2);
|
||||
const gerade = zahlen.filter(x => x % 2 === 0);
|
||||
const summe = zahlen.reduce((a, b) => a + b, 0);</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Funktion <code>verdoppeln(zahlen)</code> mit <code>.map()</code> schreiben.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Funktion <code>filterGeradeZahlen(zahlen)</code> mit <code>.filter()</code> schreiben.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Funktion <code>arraySumme(zahlen)</code> mit <code>.reduce()</code> schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=javascript&track=track_1_grundlagen&chapter=02_funktionen_und_arrays" class="btn" style="background: #eab308; color: #000; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
11
courses/javascript/02_funktionen_und_arrays/musterloesung.js
Normal file
11
courses/javascript/02_funktionen_und_arrays/musterloesung.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
function verdoppeln(zahlen) {
|
||||
return zahlen.map(x => x * 2);
|
||||
}
|
||||
|
||||
function filterGeradeZahlen(zahlen) {
|
||||
return zahlen.filter(x => x % 2 === 0);
|
||||
}
|
||||
|
||||
function arraySumme(zahlen) {
|
||||
return zahlen.reduce((acc, curr) => acc + curr, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
assert.deepStrictEqual(verdoppeln([1, 2, 3]), [2, 4, 6], "verdoppeln([1, 2, 3]) sollte [2, 4, 6] sein");
|
||||
assert.deepStrictEqual(filterGeradeZahlen([1, 2, 3, 4, 5, 6]), [2, 4, 6], "filterGeradeZahlen() filtert unvollständig");
|
||||
assert.strictEqual(arraySumme([10, 20, 30]), 60, "arraySumme([10, 20, 30]) sollte 60 sein");
|
||||
assert.strictEqual(arraySumme([]), 0, "arraySumme([]) sollte 0 sein");
|
||||
console.log("✅ Alle 4 JavaScript Tests in JS 02 erfolgreich bestanden!");
|
||||
20
courses/javascript/course.json
Normal file
20
courses/javascript/course.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "javascript",
|
||||
"title": "JavaScript & Moderne Web-Entwicklung",
|
||||
"language": "javascript",
|
||||
"icon": "🌐",
|
||||
"runner": "javascript",
|
||||
"description": "Vom Browser-Scripting über ES6-Funktionen, Arrays bis hin zu asynchronem JavaScript.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_grundlagen",
|
||||
"title": "🌱 Lehrpfad 1: JavaScript Grundlagen & ES6",
|
||||
"description": "Variablen (let/const), Template Strings, Arrow Functions und Array-Methoden.",
|
||||
"certificateKey": "js_grundlagen",
|
||||
"chapters": [
|
||||
{ "folder": "01_erste_schritte_variablen", "title": "JS 01: Variablen, Typen & Template-Strings", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" },
|
||||
{ "folder": "02_funktionen_und_arrays", "title": "JS 02: Arrow Functions, Arrays & Map/Filter", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
courses/sql/01_tabellen_und_select/aufgabe.sql
Normal file
18
courses/sql/01_tabellen_und_select/aufgabe.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- 🗄️ SQL 01: TABELLEN ANLEGEN & DATEN ABFRAGEN 🗄️
|
||||
-- ===============================================
|
||||
|
||||
-- 🎯 TEILZIEL 1 (TODO 1): Erstelle eine Tabelle `mitarbeiter` mit:
|
||||
-- id (INTEGER PRIMARY KEY), name (TEXT), abteilung (TEXT), gehalt (REAL)
|
||||
CREATE TABLE IF NOT EXISTS mitarbeiter (
|
||||
-- TODO: Spalten definieren
|
||||
id INTEGER PRIMARY KEY
|
||||
);
|
||||
|
||||
-- 🎯 TEILZIEL 2 (TODO 2): Füge mindestens 3 Test-Mitarbeiter ein
|
||||
INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
|
||||
('Anna Schmidt', 'Entwicklung', 65000),
|
||||
('Ben Weber', 'Marketing', 48000),
|
||||
('Clara Schulz', 'Entwicklung', 72000);
|
||||
|
||||
-- 🎯 TEILZIEL 3 (TODO 3): Schreibe eine SELECT-Abfrage, die alle Mitarbeiter aus der 'Entwicklung' anzeigt
|
||||
SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';
|
||||
57
courses/sql/01_tabellen_und_select/index.html
Normal file
57
courses/sql/01_tabellen_und_select/index.html
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SQL 01: Tabellen anlegen & SELECT</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🗄️ SQL & Relationale Datenbanken</div>
|
||||
<h1>SQL 01: Tabellen anlegen & Daten abfragen</h1>
|
||||
<p>Lerne die Grundlagen relationaler Datenbanken mit SQLite WebAssembly direkt im Browser.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 1. Relationale Datenbanken & Tabellen</h2>
|
||||
<p>Daten werden in strukturierten Tabellen mit definierten Spaltentypen gespeichert:</p>
|
||||
<pre><code>CREATE TABLE kunden (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
guthaben REAL
|
||||
);
|
||||
|
||||
INSERT INTO kunden (name, guthaben) VALUES ('Max Mustermann', 150.0);
|
||||
SELECT * FROM kunden WHERE guthaben > 100;</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Tabelle <code>mitarbeiter</code> mit Spalten anlegen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Testdaten mit <code>INSERT INTO</code> einfügen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Mitarbeiter mit <code>SELECT</code> und <code>WHERE</code> filtern.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=sql&track=track_1_grundlagen&chapter=01_tabellen_und_select" class="btn" style="background: #3b82f6; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
13
courses/sql/01_tabellen_und_select/musterloesung.sql
Normal file
13
courses/sql/01_tabellen_und_select/musterloesung.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE IF NOT EXISTS mitarbeiter (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
abteilung TEXT NOT NULL,
|
||||
gehalt REAL NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
|
||||
('Anna Schmidt', 'Entwicklung', 65000),
|
||||
('Ben Weber', 'Marketing', 48000),
|
||||
('Clara Schulz', 'Entwicklung', 72000);
|
||||
|
||||
SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';
|
||||
2
courses/sql/01_tabellen_und_select/test_aufgabe.sql
Normal file
2
courses/sql/01_tabellen_und_select/test_aufgabe.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
-- Unittest für SQL 01
|
||||
SELECT id, name, abteilung, gehalt FROM mitarbeiter WHERE abteilung = 'Entwicklung' ORDER BY gehalt DESC;
|
||||
19
courses/sql/course.json
Normal file
19
courses/sql/course.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "sql",
|
||||
"title": "SQL & Relationale Datenbanken",
|
||||
"language": "sql",
|
||||
"icon": "🗄️",
|
||||
"runner": "sql",
|
||||
"description": "Von relationalen Tabellen über SELECT, WHERE, JOINs bis hin zu Aggregationen und Gruppierungen.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_grundlagen",
|
||||
"title": "🌱 Lehrpfad 1: SQL-Grundlagen & Datenabfragen",
|
||||
"description": "CREATE TABLE, INSERT, SELECT und Filter mit WHERE.",
|
||||
"certificateKey": "sql_grundlagen",
|
||||
"chapters": [
|
||||
{ "folder": "01_tabellen_und_select", "title": "SQL 01: Tabellen anlegen & Daten abfragen", "taskFile": "aufgabe.sql", "testFile": "test_aufgabe.sql" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
12
index.html
12
index.html
|
|
@ -21,6 +21,14 @@
|
|||
</div>
|
||||
|
||||
<div class="sidebar-quick-nav">
|
||||
<div style="padding: 0 4px 10px 4px;">
|
||||
<label style="font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.5px; color: #94a3b8; font-weight: 700; display: block; margin-bottom: 4px;">Aktiver Sprachkurs:</label>
|
||||
<select id="course-selector-dropdown" style="width: 100%; background: #0f172a; color: #f8fafc; border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; padding: 6px 8px; font-size: 0.82rem; font-weight: 600;" onchange="window.location.href = this.value === 'python' ? 'index.html' : 'workspace.html?course=' + this.value + '&chapter=01_' + (this.value === 'javascript' ? 'erste_schritte_variablen' : 'tabellen_und_select')">
|
||||
<option value="python" selected>🐍 Python (33 Module)</option>
|
||||
<option value="javascript">🌐 JavaScript (Demo)</option>
|
||||
<option value="sql">🗄️ SQL & SQLite (Demo)</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="./index.html" class="sidebar-link">🏠 Startseite</a>
|
||||
<a href="./index.html#tutorial" class="sidebar-link">📖 Tutorial & Workflow</a>
|
||||
<a href="./index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
|
||||
|
|
@ -1061,6 +1069,10 @@ print(mein_auto.hupen())
|
|||
<script src="assets/certificate.js"></script>
|
||||
<script src="assets/parsons.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/runners/js_runner.js"></script>
|
||||
<script src="assets/runners/sql_runner.js"></script>
|
||||
<script src="assets/runner.js"></script>
|
||||
<script src="assets/ui_enhancements.js"></script>
|
||||
<script src="assets/app_auth.js"></script>
|
||||
|
|
|
|||
|
|
@ -330,6 +330,8 @@
|
|||
<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/runners/js_runner.js"></script>
|
||||
<script src="assets/runners/sql_runner.js"></script>
|
||||
<script src="assets/monaco_ide.js"></script>
|
||||
|
||||
<script>
|
||||
|
|
@ -399,8 +401,20 @@
|
|||
|
||||
async function loadWorkspaceData() {
|
||||
try {
|
||||
let taskFile = "aufgabe.py";
|
||||
let testFile = "test_aufgabe.py";
|
||||
await loadCourseManifest();
|
||||
setupNextChapterButton();
|
||||
|
||||
const activeLanguage = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
|
||||
const runner = window.RunnerRegistry ? window.RunnerRegistry.get(activeLanguage) : null;
|
||||
|
||||
let basePath = `${trackFolder}/${chapterFolder}`;
|
||||
if (courseId !== "python") {
|
||||
basePath = `courses/${courseId}/${chapterFolder}`;
|
||||
}
|
||||
|
||||
let taskFile = runner ? `aufgabe${runner.fileExtension}` : "aufgabe.py";
|
||||
let testFile = runner ? runner.testFileExtension : "test_aufgabe.py";
|
||||
let loesungFile = runner ? `musterloesung${runner.fileExtension}` : "musterloesung.py";
|
||||
|
||||
if (chapterFolder === "04b_umstieg_vscode") {
|
||||
taskFile = "vscode_test.py";
|
||||
|
|
@ -411,10 +425,10 @@
|
|||
}
|
||||
|
||||
const promises = [
|
||||
fetch(`${trackFolder}/${chapterFolder}/${taskFile}`).then(r => r.ok ? r.text() : "# Aufgabe konnte nicht geladen werden"),
|
||||
testFile ? fetch(`${trackFolder}/${chapterFolder}/${testFile}`).then(r => r.ok ? r.text() : "") : Promise.resolve(""),
|
||||
fetch(`${trackFolder}/${chapterFolder}/musterloesung.py`).then(r => r.ok ? r.text() : "").catch(() => ""),
|
||||
fetch(`${trackFolder}/${chapterFolder}/index.html`).then(r => r.ok ? r.text() : "").catch(() => "")
|
||||
fetch(`${basePath}/${taskFile}`).then(r => r.ok ? r.text() : "// Aufgabe konnte nicht geladen werden"),
|
||||
testFile ? fetch(`${basePath}/${testFile}`).then(r => r.ok ? r.text() : "") : Promise.resolve(""),
|
||||
fetch(`${basePath}/${loesungFile}`).then(r => r.ok ? r.text() : "").catch(() => ""),
|
||||
fetch(`${basePath}/index.html`).then(r => r.ok ? r.text() : "").catch(() => "")
|
||||
];
|
||||
|
||||
const [aufgabeCode, testCode, loesungCode, htmlDoc] = await Promise.all(promises);
|
||||
|
|
|
|||
Loading…
Reference in a new issue