const EstAttachments = (() => { function render() { const { state, esc, number, byId } = EstApp; const rows = state.project.attachments || []; byId("work-content").innerHTML = `
첨부파일${rows.length}개

첨부파일은 이 작업의 JSON 저장에 포함됩니다.

${rows.map((row) => ``).join("")}
파일명크기 (바이트)작업
${esc(row.name)}${number(row.size)}
`; } function read(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(Error(`파일 읽기 실패: ${file.name}`)); reader.onload = () => resolve({ id: EstApp.uid(), name: file.name, type: file.type || "", size: file.size, data: String(reader.result).split(",", 2)[1], }); reader.readAsDataURL(file); }); } function requiredBytes(project, files) { const metadata = files.map((file) => ({ id: "0".repeat(36), name: file.name, type: file.type || "", size: file.size, data: "", })); const candidate = { ...project, id: project.id || "0".repeat(24), attachments: [...(project.attachments || []), ...metadata], }; return ( new Blob([JSON.stringify(candidate)]).size + files.reduce((sum, file) => sum + 4 * Math.ceil(file.size / 3), 0) ); } function install() { document.addEventListener("change", async (event) => { const input = event.target; if (input.dataset.attachmentAdd === undefined || !input.files.length) return; const { state } = EstApp, key = state.documentKey, revision = state.revision; try { const selected = [...input.files]; if (requiredBytes(state.project, selected) > 256 * 1024 * 1024) throw Error("첨부파일을 포함한 작업이 저장 한도 256MB를 초과합니다."); const files = await Promise.all(selected.map(read)); if (key !== state.documentKey || revision !== state.revision) throw Error("작업이 변경되었습니다. 첨부파일을 다시 추가하세요."); EstApp.change(() => (state.project.attachments ||= []).push(...files), false); } catch (error) { state.notice = error.message; EstApp.render(); } finally { input.value = ""; } }); document.addEventListener("click", (event) => { const node = event.target.closest("[data-attachment-download],[data-attachment-delete]"); if (!node) return; const { state } = EstApp, id = node.dataset.attachmentDownload ?? node.dataset.attachmentDelete; const index = (state.project.attachments || []).findIndex((item) => item.id === id); if (index < 0) return; if (node.dataset.attachmentDelete !== undefined) EstApp.change(() => state.project.attachments.splice(index, 1), false); else { const item = state.project.attachments[index], text = atob(item.data), bytes = new Uint8Array(text.length); for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i); EstApp.download(item.name, bytes, "application/octet-stream"); } }); } return { render, install, requiredBytes }; })();