/** * ⚡ UNIVERSAL MODULAR MONACO WEB-IDE ⚡ * ======================================= * Sprachunabhängige Entwicklungsumgebung mit: * - Dynamischer Monaco Editor Initialisierung (Python, JavaScript, SQL, Rust etc.) * - Delegierung an pluggbare Language Runner via RunnerRegistry * - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save) * - Reaktives Terminal, Auto-Save & Gamification Integration */ (function () { let monacoEditor = null; let currentChapterData = null; let currentLanguage = "python"; let autoSaveTimeout = null; function escapeHtml(text) { if (!text) return ""; return String(text) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function getActiveRunner() { 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"); if (!editorContainer) return; if (window.monaco) { createMonacoInstance(initialCode, langId); return; } const loaderScript = document.createElement("script"); loaderScript.src = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"; loaderScript.onload = () => { window.require.config({ paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" } }); window.require(["vs/editor/editor.main"], () => { createMonacoInstance(initialCode, langId); }); }; loaderScript.onerror = () => { editorContainer.innerHTML = ``; }; document.head.appendChild(loaderScript); } function createMonacoInstance(initialCode, langId) { const editorContainer = document.getElementById("monaco-editor-container"); if (!editorContainer) return; editorContainer.innerHTML = ""; const runner = getActiveRunner(); const monacoLang = runner ? runner.monacoLanguage : langId; monacoEditor = window.monaco.editor.create(editorContainer, { value: initialCode, language: monacoLang, theme: "vs-dark", fontSize: 14, fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace", automaticLayout: true, minimap: { enabled: false }, scrollBeyondLastLine: false, tabSize: 4, insertSpaces: true, lineNumbers: "on", bracketPairColorization: { enabled: true } }); monacoEditor.onDidChangeModelContent(() => { triggerAutoSave(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter, () => { runCode(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyMod.Shift | window.monaco.KeyCode.Enter, () => { runTests(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KeyS, () => { saveProgressDraft(); }); } window.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); saveProgressDraft(); } }); // 2. Code-Ausführung via aktivem Language-Runner async function runCode() { if (window.AUTH && !window.AUTH.isLoggedIn()) { window.AUTH.requireAuth({ allowClose: false, title: '🔒 Registrierung erforderlich', message: 'Um Code in der Web-IDE auszuführen, erstelle bitte einen kostenlosen Account oder melde dich an.' }); return; } const code = getEditorCode(); clearTerminal(); const runner = getActiveRunner(); if (!runner) { logToTerminal(`❌ Kein Language-Runner für '${currentLanguage}' registriert.\n`, "error"); return; } logToTerminal(`▶ Starte Ausführung mit ${runner.displayName} (Strg+Enter)...\n----------------------------------------\n`, "info"); const result = await runner.runCode(code, logToTerminal); if (result.success) { logToTerminal("\n----------------------------------------\n✨ Erfolgreich ausgeführt!\n", "success"); } else if (result.error) { suggestErrorInterpreter(result.error); } } // 3. Test-Ausführung via aktivem Language-Runner async function runTests() { if (window.AUTH && !window.AUTH.isLoggedIn()) { window.AUTH.requireAuth({ allowClose: false, title: '🔒 Registrierung erforderlich', message: 'Um automatisierte Tests auszuführen und Lernfortschritt zu speichern, erstelle bitte einen kostenlosen Account oder melde dich an.' }); return; } if (!currentChapterData || !currentChapterData.testCode) { logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error"); return; } const userCode = getEditorCode(); clearTerminal(); const runner = getActiveRunner(); if (!runner) { logToTerminal(`❌ Kein Test-Runner für '${currentLanguage}' verfügbar.\n`, "error"); return; } logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info"); const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal); if (result.success) { logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success"); triggerSuccessCelebration(); recordChapterSolved(); } else { logToTerminal(`\n⚠️ ${result.failures || 1} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning"); if (result.rawOutput) { suggestErrorInterpreter(result.rawOutput); } } } // 4. Hilfsfunktionen function getEditorCode() { if (monacoEditor) { return monacoEditor.getValue(); } const fallback = document.getElementById("fallback-code-editor"); return fallback ? fallback.value : ""; } function setEditorCode(code) { if (monacoEditor) { monacoEditor.setValue(code); } else { const fallback = document.getElementById("fallback-code-editor"); if (fallback) fallback.value = code; } } function logToTerminal(text, type = "stdout") { const term = document.getElementById("workspace-terminal-output"); if (!term) return; const span = document.createElement("span"); span.className = `term-line term-${type}`; span.innerText = text; term.appendChild(span); term.scrollTop = term.scrollHeight; } function clearTerminal() { const term = document.getElementById("workspace-terminal-output"); if (term) term.innerHTML = ""; } function triggerAutoSave() { clearTimeout(autoSaveTimeout); autoSaveTimeout = setTimeout(() => { saveProgressDraft(); }, 1500); } async function saveProgressDraft() { if (!currentChapterData || !currentChapterData.chapterId) return; const code = getEditorCode(); localStorage.setItem(`code_draft_${currentChapterData.chapterId}`, code); const token = localStorage.getItem("auth_token"); if (token) { try { await fetch("/api/progress/save", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ chapter_id: currentChapterData.chapterId, code_draft: code }) }); } catch (e) {} } const saveBadge = document.getElementById("editor-save-indicator"); if (saveBadge) { saveBadge.innerText = "💾 Gespeichert"; saveBadge.style.opacity = "1"; setTimeout(() => { saveBadge.style.opacity = "0.5"; }, 2000); } } async function recordChapterSolved() { if (!currentChapterData || !currentChapterData.chapterId) return; if (window.addXP) { window.addXP(100, currentChapterData.chapterId); } const token = localStorage.getItem("auth_token"); if (token) { try { await fetch("/api/progress/solve", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ chapter_id: currentChapterData.chapterId, xp_reward: 100 }) }); } catch (e) {} } } function triggerSuccessCelebration() { const banner = document.getElementById("success-modal-banner"); if (banner) { banner.style.display = "flex"; } } function suggestErrorInterpreter(traceback) { const errorTab = document.getElementById("tab-btn-interpreter"); const errorInput = document.getElementById("workspace-error-input"); if (errorInput) { errorInput.value = traceback; } if (errorTab) { errorTab.style.borderColor = "var(--danger)"; errorTab.innerHTML = "🩺 Fehler-Dolmetscher ⚠️"; } if (window.interpretWorkspaceError) { window.interpretWorkspaceError(); } } // 5. Init Workspace Controller window.initWorkspace = function (chapterData, language = "python") { currentChapterData = chapterData; currentLanguage = language || "python"; const savedDraft = localStorage.getItem(`code_draft_${chapterData.chapterId}`); const initialCode = savedDraft || chapterData.starterCode || ""; initMonacoEditor(initialCode, currentLanguage); document.getElementById("btn-run-code")?.addEventListener("click", runCode); document.getElementById("btn-run-tests")?.addEventListener("click", runTests); document.getElementById("btn-save-code")?.addEventListener("click", saveProgressDraft); document.getElementById("btn-clear-terminal")?.addEventListener("click", clearTerminal); document.getElementById("btn-reset-code")?.addEventListener("click", () => { if (confirm("Möchtest du deinen Code wirklich auf den Original-Startercode zurücksetzen?")) { setEditorCode(chapterData.starterCode || ""); } }); }; })();