83 lines
3.6 KiB
JavaScript
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 };
|
|
})();
|