diff --git a/network-poc/static/index.html b/network-poc/static/index.html index 1b68594..7d86d18 100644 --- a/network-poc/static/index.html +++ b/network-poc/static/index.html @@ -1870,6 +1870,148 @@ if (container) container.style.display = 'none'; } + // Projektikortti: tiedostovälilehdet + kopioi + lataa ZIP + function renderProjectCard(files, projectName) { + const fileEntries = Object.entries(files); + if (fileEntries.length === 0) return; + + const cardId = 'proj-' + Date.now(); + const tabsHtml = fileEntries.map(([name], i) => + `${esc(name)}` + ).join(''); + + const panelsHtml = fileEntries.map(([name, code], i) => + `
+
+ +
+
${highlightCode(code)}
+
` + ).join(''); + + const allText = fileEntries.map(([name, code]) => `# --- ${name} ---\n${code}`).join('\n\n'); + + const cardHtml = ` +
+
+ ${esc(projectName || 'Projekti')} (${fileEntries.length} tiedostoa) + + + + +
+
${tabsHtml}
+
${panelsHtml}
+
`; + + const div = document.createElement('div'); + div.innerHTML = cardHtml; + termPanel.appendChild(div.firstElementChild); + termPanel.scrollTop = termPanel.scrollHeight; + } + + // Globaalit funktiot projektikortin interaktioille + window.switchProjectTab = function(cardId, idx) { + document.querySelectorAll(`.proj-tab[data-card="${cardId}"]`).forEach((tab, i) => { + tab.style.background = i === idx ? '#161b22' : 'transparent'; + tab.style.color = i === idx ? '#58a6ff' : '#8b949e'; + tab.style.border = i === idx ? '1px solid #30363d' : 'none'; + tab.style.borderBottom = i === idx ? 'none' : ''; + }); + document.querySelectorAll(`.proj-panel[data-card="${cardId}"]`).forEach((panel, i) => { + panel.style.display = i === idx ? '' : 'none'; + }); + }; + + window.copyFileContent = function(cardId, idx) { + const card = document.getElementById(cardId); + if (!card) return; + const files = JSON.parse(card.dataset.files); + const entries = Object.entries(files); + if (entries[idx]) { + navigator.clipboard.writeText(entries[idx][1]); + // Visuaalinen palaute + const btn = card.querySelectorAll(`.proj-panel[data-idx="${idx}"] button`)[0]; + if (btn) { const orig = btn.textContent; btn.textContent = '✓ Kopioitu'; setTimeout(() => btn.textContent = orig, 1500); } + } + }; + + window.copyAllFiles = function(cardId) { + const card = document.getElementById(cardId); + if (!card) return; + const files = JSON.parse(card.dataset.files); + const text = Object.entries(files).map(([name, code]) => `# --- ${name} ---\n${code}`).join('\n\n'); + navigator.clipboard.writeText(text); + const btn = card.querySelector('[onclick*="copyAllFiles"]'); + if (btn) { const orig = btn.textContent; btn.textContent = '✓ Kopioitu'; setTimeout(() => btn.textContent = orig, 1500); } + }; + + window.downloadZip = async function(cardId) { + const card = document.getElementById(cardId); + if (!card) return; + const files = JSON.parse(card.dataset.files); + + // Luodaan ZIP ilman ulkoisia kirjastoja (yksinkertainen uncompressed ZIP) + const entries = Object.entries(files); + const parts = []; + const centralDir = []; + let offset = 0; + + for (const [name, content] of entries) { + const nameBytes = new TextEncoder().encode(name); + const contentBytes = new TextEncoder().encode(content); + + // Local file header + const header = new Uint8Array(30 + nameBytes.length); + const view = new DataView(header.buffer); + view.setUint32(0, 0x04034b50, true); // Signature + view.setUint16(4, 20, true); // Version needed + view.setUint16(8, 0, true); // Method: store + view.setUint32(18, contentBytes.length, true); // Compressed size + view.setUint32(22, contentBytes.length, true); // Uncompressed size + view.setUint16(26, nameBytes.length, true); + header.set(nameBytes, 30); + + // Central directory entry + const cdEntry = new Uint8Array(46 + nameBytes.length); + const cdView = new DataView(cdEntry.buffer); + cdView.setUint32(0, 0x02014b50, true); + cdView.setUint16(4, 20, true); + cdView.setUint16(6, 20, true); + cdView.setUint32(20, contentBytes.length, true); + cdView.setUint32(24, contentBytes.length, true); + cdView.setUint16(28, nameBytes.length, true); + cdView.setUint32(42, offset, true); + cdEntry.set(nameBytes, 46); + + parts.push(header, contentBytes); + centralDir.push(cdEntry); + offset += header.length + contentBytes.length; + } + + const cdOffset = offset; + let cdSize = 0; + for (const cd of centralDir) { parts.push(cd); cdSize += cd.length; } + + // End of central directory + const eocd = new Uint8Array(22); + const eocdView = new DataView(eocd.buffer); + eocdView.setUint32(0, 0x06054b50, true); + eocdView.setUint16(8, entries.length, true); + eocdView.setUint16(10, entries.length, true); + eocdView.setUint32(12, cdSize, true); + eocdView.setUint32(16, cdOffset, true); + parts.push(eocd); + + const blob = new Blob(parts, { type: 'application/zip' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'project.zip'; + a.click(); + URL.revokeObjectURL(url); + }; + // Pipeline: manageri → koodari (per tiedosto) → testaaja → korjausluuppi async function kpnPipeline(task) { pipelineClear(); @@ -1984,6 +2126,7 @@ Write the corrected code.`; } termLog(`\n━━━ Pipeline valmis (${Object.keys(generatedFiles).length} tiedostoa) ━━━`); + renderProjectCard(generatedFiles, task); } // Yksinkertainen pipeline (vanha: manageri → koodari → testaaja)