140 lines
4.8 KiB
JavaScript
140 lines
4.8 KiB
JavaScript
|
|
async function createBox() {
|
||
|
|
const response = await fetch("/box", {
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
retention_key: el.expiry?.value || defaultRetention,
|
||
|
|
password: el.password?.value || "",
|
||
|
|
allow_zip: isOneTimeDownloadSelected() || !el.allowZip || el.allowZip.checked,
|
||
|
|
files: files.map((item) => ({ name: item.displayName, size: item.file.size })),
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await readJSON(response);
|
||
|
|
if (!response.ok) throw new Error(result.error || "Could not create upload box");
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function readJSON(response) {
|
||
|
|
try {
|
||
|
|
return await response.json();
|
||
|
|
} catch (_) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function markFileStatus(item, status) {
|
||
|
|
if (!item.boxID || !item.boxFile) return;
|
||
|
|
try {
|
||
|
|
await fetch(`/box/${item.boxID}/files/${item.boxFile.id}/status`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({ status }),
|
||
|
|
});
|
||
|
|
} catch (_) {
|
||
|
|
// Best effort only. The upload endpoint also marks hard failures.
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function setFileFailed(item, message) {
|
||
|
|
item.failed = true;
|
||
|
|
item.uploaded = false;
|
||
|
|
item.error = message || "Failed to upload";
|
||
|
|
item.loaded = item.file.size;
|
||
|
|
item.row?.classList.remove("is-working", "is-uploaded");
|
||
|
|
item.row?.classList.add("is-failed");
|
||
|
|
if (item.row) item.row.title = item.error;
|
||
|
|
setRowProgress(item, 100);
|
||
|
|
updateOverallProgress();
|
||
|
|
}
|
||
|
|
|
||
|
|
function markCompletedImpact(item) {
|
||
|
|
const key = item.boxFile?.id || item.displayName;
|
||
|
|
if (completedImpactKeys.has(key)) return;
|
||
|
|
completedImpactKeys.add(key);
|
||
|
|
flashProgressBar(item.row?.querySelector(".upload-progress-bar"));
|
||
|
|
}
|
||
|
|
|
||
|
|
function uploadFile(item, onComplete) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const xhr = new XMLHttpRequest();
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append("file", item.file, item.displayName);
|
||
|
|
|
||
|
|
xhr.open("POST", item.boxFile.upload_path);
|
||
|
|
|
||
|
|
xhr.upload.addEventListener("loadstart", () => {
|
||
|
|
item.loaded = 0;
|
||
|
|
item.failed = false;
|
||
|
|
item.uploaded = false;
|
||
|
|
item.row?.classList.remove("is-failed", "is-uploaded");
|
||
|
|
item.row?.classList.add("is-working");
|
||
|
|
setRowProgress(item, 2);
|
||
|
|
updateOverallProgress();
|
||
|
|
});
|
||
|
|
|
||
|
|
xhr.upload.addEventListener("progress", (event) => {
|
||
|
|
if (!event.lengthComputable) return;
|
||
|
|
item.loaded = Math.min(event.loaded, item.file.size);
|
||
|
|
const percent = (event.loaded / event.total) * 100;
|
||
|
|
setRowProgress(item, percent >= 100 ? 99 : percent);
|
||
|
|
updateOverallProgress();
|
||
|
|
});
|
||
|
|
|
||
|
|
xhr.addEventListener("load", async () => {
|
||
|
|
if (xhr.status < 200 || xhr.status >= 300) {
|
||
|
|
let message = "Upload failed";
|
||
|
|
try {
|
||
|
|
message = JSON.parse(xhr.responseText).error || message;
|
||
|
|
} catch (_) {}
|
||
|
|
setFileFailed(item, message);
|
||
|
|
await markFileStatus(item, "failed");
|
||
|
|
reject(new Error(message));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
item.uploaded = true;
|
||
|
|
item.failed = false;
|
||
|
|
item.loaded = item.file.size;
|
||
|
|
item.row?.classList.remove("is-working", "is-failed");
|
||
|
|
item.row?.classList.add("is-uploaded");
|
||
|
|
if (item.row) item.row.title = "Uploaded";
|
||
|
|
setRowProgress(item, 100);
|
||
|
|
markCompletedImpact(item);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = JSON.parse(xhr.responseText);
|
||
|
|
if (result.file) {
|
||
|
|
item.boxFile = result.file;
|
||
|
|
const icon = item.row?.querySelector(".upload-file-icon");
|
||
|
|
if (icon && result.file.thumbnail_path) {
|
||
|
|
item.row.classList.add("has-thumbnail");
|
||
|
|
icon.src = result.file.thumbnail_path;
|
||
|
|
} else if (icon && result.file.icon_path && !item.previewURL) {
|
||
|
|
icon.src = result.file.icon_path;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (_) {}
|
||
|
|
|
||
|
|
updateOverallProgress();
|
||
|
|
onComplete();
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
|
||
|
|
xhr.addEventListener("error", async () => {
|
||
|
|
setFileFailed(item, "Network error while uploading");
|
||
|
|
await markFileStatus(item, "failed");
|
||
|
|
reject(new Error("Network error while uploading"));
|
||
|
|
});
|
||
|
|
|
||
|
|
xhr.addEventListener("abort", async () => {
|
||
|
|
setFileFailed(item, "Upload cancelled");
|
||
|
|
await markFileStatus(item, "failed");
|
||
|
|
reject(new Error("Upload cancelled"));
|
||
|
|
});
|
||
|
|
|
||
|
|
markFileStatus(item, "uploading");
|
||
|
|
xhr.send(formData);
|
||
|
|
});
|
||
|
|
}
|