Projektin ZIP-lataus projektikorttiin
Lataa .zip -nappi renderöidään projektikortin headeriin. ZIP rakennetaan selaimessa ilman ulkoisia kirjastoja (CRC-32 + ZIP-rakenne inline). Kansiorakenne säilyy: prompts/*.md -tiedostot menevät alihakemistoon.
This commit is contained in:
@@ -1341,6 +1341,7 @@ OUTPUT FORMAT:
|
||||
`<span style="color:var(--purple);font-weight:600">${esc(name||'Projekti')} <span style="color:#8b949e;font-weight:normal">(${entries.length})</span></span>` +
|
||||
`<span style="display:flex;gap:6px">` +
|
||||
`<button class="btn btn-muted" onclick="copyAllProjectFiles('${id}')">Kopioi kaikki</button>` +
|
||||
`<button class="btn btn-muted" onclick="downloadProjectZip('${id}','${esc(name||'projekti')}')">Lataa .zip</button>` +
|
||||
`<button class="btn btn-green" onclick="openInEditor(window._projectFiles['${id}'])">Avaa editorissa</button>` +
|
||||
`</span></div>` +
|
||||
`<div class="project-tabs">${tabs}</div>${panels}</div>`;
|
||||
@@ -1360,6 +1361,84 @@ OUTPUT FORMAT:
|
||||
const text = Object.entries(files).map(([n,c]) => '# --- ' + n + ' ---\n' + c).join('\n\n');
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
window.downloadProjectZip = function(id, name) {
|
||||
const files = window._projectFiles[id];
|
||||
if (!files) return;
|
||||
const enc = new TextEncoder();
|
||||
const entries = Object.entries(files);
|
||||
const localParts = [];
|
||||
const centralParts = [];
|
||||
let offset = 0;
|
||||
|
||||
for (const [fname, content] of entries) {
|
||||
const nameBytes = enc.encode(fname);
|
||||
const dataBytes = enc.encode(content);
|
||||
const crc = crc32(dataBytes);
|
||||
|
||||
// Local file header
|
||||
const local = new Uint8Array(30 + nameBytes.length + dataBytes.length);
|
||||
const lv = new DataView(local.buffer);
|
||||
lv.setUint32(0, 0x04034b50, true); // signature
|
||||
lv.setUint16(4, 20, true); // version needed
|
||||
lv.setUint16(8, 8, true); // UTF-8 flag
|
||||
lv.setUint32(14, crc, true); // CRC-32
|
||||
lv.setUint32(18, dataBytes.length, true); // compressed size
|
||||
lv.setUint32(22, dataBytes.length, true); // uncompressed size
|
||||
lv.setUint16(26, nameBytes.length, true); // filename length
|
||||
local.set(nameBytes, 30);
|
||||
local.set(dataBytes, 30 + nameBytes.length);
|
||||
localParts.push(local);
|
||||
|
||||
// Central directory entry
|
||||
const central = new Uint8Array(46 + nameBytes.length);
|
||||
const cv = new DataView(central.buffer);
|
||||
cv.setUint32(0, 0x02014b50, true); // signature
|
||||
cv.setUint16(4, 20, true); // version made by
|
||||
cv.setUint16(6, 20, true); // version needed
|
||||
cv.setUint16(8, 8, true); // UTF-8 flag
|
||||
cv.setUint32(16, crc, true);
|
||||
cv.setUint32(20, dataBytes.length, true);
|
||||
cv.setUint32(24, dataBytes.length, true);
|
||||
cv.setUint16(28, nameBytes.length, true);
|
||||
cv.setUint32(42, offset, true); // local header offset
|
||||
central.set(nameBytes, 46);
|
||||
centralParts.push(central);
|
||||
|
||||
offset += local.length;
|
||||
}
|
||||
|
||||
const centralSize = centralParts.reduce((s, c) => s + c.length, 0);
|
||||
// End of central directory
|
||||
const eocd = new Uint8Array(22);
|
||||
const ev = new DataView(eocd.buffer);
|
||||
ev.setUint32(0, 0x06054b50, true);
|
||||
ev.setUint16(8, entries.length, true);
|
||||
ev.setUint16(10, entries.length, true);
|
||||
ev.setUint32(12, centralSize, true);
|
||||
ev.setUint32(16, offset, true);
|
||||
|
||||
const blob = new Blob([...localParts, ...centralParts, eocd], { type: 'application/zip' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = (name || 'projekti').replace(/[^a-z0-9_-]/gi, '_') + '.zip';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// CRC-32 (ZIP-standardin mukainen)
|
||||
const crc32Table = new Uint32Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
let c = i;
|
||||
for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
|
||||
crc32Table[i] = c;
|
||||
}
|
||||
function crc32(data) {
|
||||
let crc = 0xFFFFFFFF;
|
||||
for (let i = 0; i < data.length; i++) crc = crc32Table[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8);
|
||||
return (crc ^ 0xFFFFFFFF) >>> 0;
|
||||
}
|
||||
|
||||
window.switchProjTab = function(id,i) {
|
||||
document.querySelectorAll(`.project-tab[data-card="${id}"]`).forEach((t,j) => t.classList.toggle('active', j===i));
|
||||
document.querySelectorAll(`.proj-panel[data-card="${id}"]`).forEach((p,j) => p.style.display = j===i ? '' : 'none');
|
||||
|
||||
Reference in New Issue
Block a user