Monaco Editor -välilehti: selainpohjainen koodieditori agenttien tuottamalle koodille
Uusi "Editor"-välilehti jossa: - Monaco Editor (VS Coden ydin) CDN:stä, dark-teema - Tiedostopuu vasemmalla (klikataan tiedostoa) - Välilehdet ylhäällä (useita tiedostoja auki) - Kielitunnistus tiedostopäätteestä (Python, Rust, JS, ...) - "Avaa editorissa" -nappi projektikorteissa Monaco ladataan taustalla requestIdleCallback:llä — ei hidasta sivun käynnistymistä. Editor alustetaan vasta kun sitä tarvitaan. Projektikortin "Avaa editorissa" -nappi: 1. Avaa Editor-välilehden 2. Luo Monaco-mallit jokaiselle tiedostolle 3. Renderöi tiedostopuun ja välilehdet 4. Avaa ensimmäisen tiedoston editoriin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import AgentChat from "../components/AgentChat.astro";
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
window.mermaid = mermaid;
|
||||
</script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/editor/editor.main.css">
|
||||
<link rel="stylesheet" href="/src/styles/global.css">
|
||||
<style>
|
||||
/* Scoped styles can go here */
|
||||
@@ -42,6 +43,7 @@ import AgentChat from "../components/AgentChat.astro";
|
||||
<div class="main-tab" onclick="switchMainTab('builder')" data-i18n="tab_builder">Agent Builder</div>
|
||||
<div class="main-tab" onclick="switchMainTab('gallery')" data-i18n="tab_gallery">Galleria</div>
|
||||
<div class="main-tab" onclick="switchMainTab('guide')" data-i18n="tab_guide">Opas</div>
|
||||
<div class="main-tab" onclick="switchMainTab('editor')" data-i18n="tab_editor">Editor</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; opacity: 0.65; transform: scale(0.9); transform-origin: bottom right;">
|
||||
<div class="main-tab" onclick="switchMainTab('network')" data-i18n="tab_network">Laskentaverkko</div>
|
||||
@@ -580,6 +582,24 @@ ZIP-paketti sisältäen:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PANEELI: Editor -->
|
||||
<div id="panel-editor" class="main-panel">
|
||||
<div style="display:flex;height:calc(100vh - 160px);gap:0;border:1px solid var(--border-color);border-radius:6px;overflow:hidden">
|
||||
<!-- Tiedostopuu -->
|
||||
<div id="editor-filetree" style="width:200px;min-width:150px;background:#0d1117;border-right:1px solid var(--border-color);overflow-y:auto;font-family:'Courier New',monospace;font-size:13px">
|
||||
<div style="padding:10px 12px;color:#8b949e;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid var(--border-color)">Tiedostot</div>
|
||||
<div id="editor-file-list" style="padding:4px 0">
|
||||
<div style="padding:8px 16px;color:#8b949e;font-size:12px">Ei tiedostoja.<br><br>Generoi projekti:<br><code style="color:#58a6ff">kpn project "..."</code><br>ja klikkaa "Avaa editorissa"</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Monaco Editor -->
|
||||
<div style="flex:1;display:flex;flex-direction:column">
|
||||
<div id="editor-tabs" style="display:flex;background:#0d1117;border-bottom:1px solid var(--border-color);min-height:35px;align-items:flex-end;padding:0 8px;gap:2px;overflow-x:auto"></div>
|
||||
<div id="monaco-container" style="flex:1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module" is:inline>
|
||||
@@ -1683,6 +1703,7 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
||||
<span style="display:flex;gap:6px">
|
||||
<button onclick="copyAllFiles('${cardId}')" style="background:none;border:1px solid #30363d;color:#8b949e;font-size:11px;padding:2px 8px;border-radius:3px;cursor:pointer" title="Kopioi kaikki tiedostot leikepöydälle">Kopioi kaikki</button>
|
||||
<button onclick="downloadZip('${cardId}')" style="background:none;border:1px solid #30363d;color:#58a6ff;font-size:11px;padding:2px 8px;border-radius:3px;cursor:pointer" title="Lataa projekti ZIP-tiedostona">Lataa ZIP</button>
|
||||
<button onclick="openInEditor(JSON.parse(document.getElementById('${cardId}').dataset.files))" style="background:none;border:1px solid #3fb950;color:#3fb950;font-size:11px;padding:2px 8px;border-radius:3px;cursor:pointer" title="Avaa Monaco-editorissa">Avaa editorissa</button>
|
||||
${reportUrl ? `<a href="${reportUrl}" target="_blank" style="background:none;border:1px solid #a371f7;color:#a371f7;font-size:11px;padding:2px 8px;border-radius:3px;cursor:pointer;text-decoration:none">📄 Raportti</a>` : ''}
|
||||
</span>
|
||||
</div>
|
||||
@@ -4525,5 +4546,126 @@ uv run python crew.py "FastAPI + SQLite CRUD API"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Monaco Editor loader -->
|
||||
<script is:inline>
|
||||
// AMD-loader Monacolle
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl: function(workerId, label) {
|
||||
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
||||
self.MonacoEnvironment = { baseUrl: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/' };
|
||||
importScripts('https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/base/worker/workerMain.js');
|
||||
`)}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Kielitunnistus tiedostopäätteestä
|
||||
function langFromFilename(name) {
|
||||
const ext = name.split('.').pop().toLowerCase();
|
||||
const map = { py: 'python', rs: 'rust', js: 'javascript', ts: 'typescript', toml: 'toml', json: 'json', html: 'html', css: 'css', md: 'markdown', txt: 'plaintext', yaml: 'yaml', yml: 'yaml', sh: 'shell', sql: 'sql' };
|
||||
return map[ext] || 'plaintext';
|
||||
}
|
||||
|
||||
// Globaali editor-state
|
||||
window._editorFiles = {}; // { "main.py": "code...", ... }
|
||||
window._editorModels = {}; // Monaco-mallit per tiedosto
|
||||
window._activeFile = null;
|
||||
window._monacoEditor = null;
|
||||
window._monacoLoaded = false;
|
||||
|
||||
// Ladataan Monaco taustalla idle-aikana — ei blockata sivun latautumista
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(() => window.initMonaco(), { timeout: 5000 });
|
||||
} else {
|
||||
setTimeout(() => window.initMonaco(), 3000);
|
||||
}
|
||||
|
||||
window.initMonaco = async function() {
|
||||
if (window._monacoLoaded) return;
|
||||
window._monacoLoaded = true;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js';
|
||||
script.onload = () => {
|
||||
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' }});
|
||||
require(['vs/editor/editor.main'], () => resolve());
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
window._monacoEditor = monaco.editor.create(document.getElementById('monaco-container'), {
|
||||
value: '// Valitse tiedosto vasemmalta tai generoi projekti agentilla\n',
|
||||
language: 'plaintext',
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
minimap: { enabled: false },
|
||||
lineNumbers: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
padding: { top: 10 },
|
||||
});
|
||||
};
|
||||
|
||||
// Avaa tiedostot editoriin (kutsutaan projektikortin "Avaa editorissa" -napista)
|
||||
window.openInEditor = function(files) {
|
||||
window._editorFiles = { ...files };
|
||||
|
||||
// Vaihda Editor-välilehteen
|
||||
if (typeof switchMainTab === 'function') switchMainTab('editor');
|
||||
|
||||
// Alusta Monaco tarvittaessa
|
||||
window.initMonaco().then(() => {
|
||||
// Luo Monaco-mallit
|
||||
for (const [name, code] of Object.entries(files)) {
|
||||
const lang = langFromFilename(name);
|
||||
if (window._editorModels[name]) {
|
||||
window._editorModels[name].setValue(code);
|
||||
} else {
|
||||
window._editorModels[name] = monaco.editor.createModel(code, lang);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderöi tiedostopuu
|
||||
const fileList = document.getElementById('editor-file-list');
|
||||
fileList.innerHTML = Object.keys(files).map(name =>
|
||||
`<div class="editor-file-item" data-file="${name}" onclick="openEditorFile('${name}')" style="padding:6px 16px;cursor:pointer;color:#c9d1d9;font-size:13px;display:flex;align-items:center;gap:6px;border-left:2px solid transparent">`
|
||||
+ `<span style="color:#8b949e;font-size:11px">${langFromFilename(name) === 'python' ? '🐍' : langFromFilename(name) === 'rust' ? '🦀' : '📄'}</span>`
|
||||
+ `${name}</div>`
|
||||
).join('');
|
||||
|
||||
// Renderöi välilehdet
|
||||
const tabs = document.getElementById('editor-tabs');
|
||||
tabs.innerHTML = Object.keys(files).map(name =>
|
||||
`<div class="editor-tab" data-file="${name}" onclick="openEditorFile('${name}')" style="padding:6px 12px;cursor:pointer;font-size:12px;color:#8b949e;border:1px solid transparent;border-bottom:none;border-radius:4px 4px 0 0;white-space:nowrap">${name}</div>`
|
||||
).join('');
|
||||
|
||||
// Avaa ensimmäinen tiedosto
|
||||
const firstName = Object.keys(files)[0];
|
||||
if (firstName) openEditorFile(firstName);
|
||||
});
|
||||
};
|
||||
|
||||
// Vaihda aktiivinen tiedosto
|
||||
window.openEditorFile = function(name) {
|
||||
if (!window._editorModels[name] || !window._monacoEditor) return;
|
||||
window._activeFile = name;
|
||||
window._monacoEditor.setModel(window._editorModels[name]);
|
||||
|
||||
// Päivitä visuaalinen tila
|
||||
document.querySelectorAll('.editor-file-item').forEach(el => {
|
||||
const active = el.dataset.file === name;
|
||||
el.style.background = active ? '#161b22' : 'transparent';
|
||||
el.style.borderLeftColor = active ? '#58a6ff' : 'transparent';
|
||||
el.style.color = active ? '#e6edf3' : '#c9d1d9';
|
||||
});
|
||||
document.querySelectorAll('.editor-tab').forEach(el => {
|
||||
const active = el.dataset.file === name;
|
||||
el.style.background = active ? '#161b22' : 'transparent';
|
||||
el.style.color = active ? '#58a6ff' : '#8b949e';
|
||||
el.style.borderColor = active ? '#30363d' : 'transparent';
|
||||
el.style.borderBottomColor = active ? '#161b22' : 'transparent';
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user