Files
Aislo/tools/est-test/native-attachments.js

83 lines
3.6 KiB
JavaScript

const EstAttachments = (() => {
function render() {
const { state, esc, number, byId } = EstApp;
const rows = state.project.attachments || [];
byId("work-content").innerHTML =
`<div class="business-tools"><b>첨부파일</b><label>파일 추가 <input type="file" data-attachment-add multiple></label><span>${rows.length}개</span></div><p>첨부파일은 이 작업의 JSON 저장에 포함됩니다.</p><table class="sheet"><thead><tr><th>파일명</th><th>크기 (바이트)</th><th>작업</th></tr></thead><tbody>${rows.map((row) => `<tr><td>${esc(row.name)}</td><td>${number(row.size)}</td><td><button data-attachment-download="${esc(row.id)}">내려받기</button><button data-attachment-delete="${esc(row.id)}">삭제</button></td></tr>`).join("")}</tbody></table>`;
}
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 };
})();