253 lines
8.0 KiB
JavaScript
253 lines
8.0 KiB
JavaScript
|
|
(function () {
|
||
|
|
const form = document.querySelector("#upload-form");
|
||
|
|
const dropZone = document.querySelector(".drop-zone");
|
||
|
|
const fileInput = document.querySelector("#file-input");
|
||
|
|
const fileSummary = document.querySelector("#file-summary");
|
||
|
|
const progress = document.querySelector("#upload-progress");
|
||
|
|
const uploadStatus = document.querySelector("#upload-status");
|
||
|
|
const result = document.querySelector("#upload-result");
|
||
|
|
const resultMeta = document.querySelector("#result-meta");
|
||
|
|
const resultList = document.querySelector("#result-list");
|
||
|
|
const uploadQueue = document.querySelector("#upload-queue");
|
||
|
|
const totalProgressBar = document.querySelector("#total-progress-bar");
|
||
|
|
const copyURL = document.querySelector("#copy-url");
|
||
|
|
const openBox = document.querySelector("#open-box");
|
||
|
|
const manageLink = document.querySelector("#manage-link");
|
||
|
|
|
||
|
|
if (!form || !dropZone || !fileInput) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let latestBoxURL = "";
|
||
|
|
let selectedFiles = [];
|
||
|
|
|
||
|
|
["dragenter", "dragover"].forEach((eventName) => {
|
||
|
|
dropZone.addEventListener(eventName, (event) => {
|
||
|
|
event.preventDefault();
|
||
|
|
dropZone.classList.add("is-dragging");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
["dragleave", "drop"].forEach((eventName) => {
|
||
|
|
dropZone.addEventListener(eventName, (event) => {
|
||
|
|
event.preventDefault();
|
||
|
|
dropZone.classList.remove("is-dragging");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
dropZone.addEventListener("drop", (event) => {
|
||
|
|
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
|
||
|
|
fileInput.files = event.dataTransfer.files;
|
||
|
|
updateSelectedState(event.dataTransfer.files);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
fileInput.addEventListener("change", () => updateSelectedState(fileInput.files));
|
||
|
|
|
||
|
|
form.addEventListener("submit", async (event) => {
|
||
|
|
event.preventDefault();
|
||
|
|
if (!fileInput.files || fileInput.files.length === 0) {
|
||
|
|
updateStatus("Choose at least one file first.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const submit = form.querySelector("button[type='submit']");
|
||
|
|
const formData = new FormData(form);
|
||
|
|
selectedFiles = Array.from(fileInput.files);
|
||
|
|
renderQueue(selectedFiles, "queued");
|
||
|
|
setLoading(true, submit);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const payload = await uploadWithProgress(form.action, formData, selectedFiles);
|
||
|
|
renderResult(payload);
|
||
|
|
form.reset();
|
||
|
|
updateSelectedState([]);
|
||
|
|
} catch (error) {
|
||
|
|
updateStatus(error.message || "Upload failed");
|
||
|
|
} finally {
|
||
|
|
setLoading(false, submit);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if (copyURL) {
|
||
|
|
copyURL.addEventListener("click", () => {
|
||
|
|
window.Warpbox.copyText(latestBoxURL, copyURL, "Copied");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateSelectedState(files) {
|
||
|
|
selectedFiles = Array.from(files || []);
|
||
|
|
const count = selectedFiles.length || 0;
|
||
|
|
const title = dropZone.querySelector(".drop-title");
|
||
|
|
if (title) {
|
||
|
|
title.textContent = count === 0 ? "Drop files to upload" : count === 1 ? "1 file selected" : `${count} files selected`;
|
||
|
|
}
|
||
|
|
if (fileSummary) {
|
||
|
|
fileSummary.textContent = count === 0 ? "Choose one or more files to begin." : `${count} file${count === 1 ? "" : "s"} ready.`;
|
||
|
|
}
|
||
|
|
if (count > 0) {
|
||
|
|
renderQueue(selectedFiles, "queued");
|
||
|
|
} else if (uploadQueue) {
|
||
|
|
uploadQueue.hidden = true;
|
||
|
|
uploadQueue.replaceChildren();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function setLoading(isLoading, submit) {
|
||
|
|
if (progress) {
|
||
|
|
progress.hidden = !isLoading;
|
||
|
|
}
|
||
|
|
if (submit) {
|
||
|
|
submit.disabled = isLoading;
|
||
|
|
submit.textContent = isLoading ? "Uploading..." : "Upload files";
|
||
|
|
}
|
||
|
|
updateStatus(isLoading ? "Transferring files..." : "");
|
||
|
|
setTotalProgress(isLoading ? 0 : 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateStatus(message) {
|
||
|
|
if (uploadStatus) {
|
||
|
|
uploadStatus.textContent = message;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderResult(payload) {
|
||
|
|
if (!result || !resultList || !resultMeta || !openBox) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
latestBoxURL = payload.boxUrl;
|
||
|
|
result.hidden = false;
|
||
|
|
openBox.href = payload.boxUrl;
|
||
|
|
resultMeta.textContent = `${payload.files.length} file${payload.files.length === 1 ? "" : "s"} · expires ${window.Warpbox.formatDate(payload.expiresAt)}`;
|
||
|
|
if (manageLink) {
|
||
|
|
const anchor = manageLink.querySelector("a");
|
||
|
|
manageLink.hidden = !payload.manageUrl;
|
||
|
|
if (anchor && payload.manageUrl) {
|
||
|
|
anchor.href = payload.manageUrl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resultList.replaceChildren();
|
||
|
|
payload.files.forEach((file) => {
|
||
|
|
resultList.append(createFileRow({
|
||
|
|
name: file.name,
|
||
|
|
meta: `${file.size} · ${file.url}`,
|
||
|
|
progress: 100,
|
||
|
|
status: "complete",
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function uploadWithProgress(url, formData, files) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const request = new XMLHttpRequest();
|
||
|
|
request.open("POST", url);
|
||
|
|
request.setRequestHeader("Accept", "application/json");
|
||
|
|
|
||
|
|
request.upload.addEventListener("progress", (event) => {
|
||
|
|
if (!event.lengthComputable) {
|
||
|
|
updateStatus("Uploading...");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const percent = Math.round((event.loaded / event.total) * 100);
|
||
|
|
updateStatus(`${percent}%`);
|
||
|
|
setTotalProgress(percent);
|
||
|
|
setFileProgress(files, percent);
|
||
|
|
});
|
||
|
|
|
||
|
|
request.addEventListener("load", () => {
|
||
|
|
let payload = {};
|
||
|
|
try {
|
||
|
|
payload = JSON.parse(request.responseText || "{}");
|
||
|
|
} catch (error) {
|
||
|
|
reject(new Error("Upload response could not be read"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (request.status < 200 || request.status >= 300) {
|
||
|
|
reject(new Error(payload.error || "Upload failed"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setTotalProgress(100);
|
||
|
|
setFileProgress(files, 100);
|
||
|
|
resolve(payload);
|
||
|
|
});
|
||
|
|
|
||
|
|
request.addEventListener("error", () => reject(new Error("Network error during upload")));
|
||
|
|
request.addEventListener("abort", () => reject(new Error("Upload aborted")));
|
||
|
|
request.send(formData);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderQueue(files, status) {
|
||
|
|
if (!uploadQueue) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
uploadQueue.hidden = files.length === 0;
|
||
|
|
uploadQueue.replaceChildren();
|
||
|
|
files.forEach((file) => {
|
||
|
|
uploadQueue.append(createFileRow({
|
||
|
|
name: file.name,
|
||
|
|
meta: window.Warpbox.formatBytes(file.size),
|
||
|
|
progress: status === "queued" ? 0 : 100,
|
||
|
|
status,
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function createFileRow(file) {
|
||
|
|
const row = document.createElement("div");
|
||
|
|
row.className = "result-item upload-file-row";
|
||
|
|
row.dataset.fileName = file.name;
|
||
|
|
|
||
|
|
const body = document.createElement("span");
|
||
|
|
const name = document.createElement("strong");
|
||
|
|
name.className = "file-name";
|
||
|
|
name.textContent = file.name;
|
||
|
|
name.title = file.name;
|
||
|
|
const meta = document.createElement("code");
|
||
|
|
meta.textContent = file.meta;
|
||
|
|
body.append(name, meta);
|
||
|
|
|
||
|
|
const side = document.createElement("div");
|
||
|
|
side.className = "file-progress-side";
|
||
|
|
const percent = document.createElement("span");
|
||
|
|
percent.className = "file-progress-percent";
|
||
|
|
percent.textContent = `${file.progress}%`;
|
||
|
|
const bar = document.createElement("div");
|
||
|
|
bar.className = "progress file-progress";
|
||
|
|
const fill = document.createElement("span");
|
||
|
|
fill.style.transform = `scaleX(${file.progress / 100})`;
|
||
|
|
bar.append(fill);
|
||
|
|
side.append(percent, bar);
|
||
|
|
|
||
|
|
row.append(body, side);
|
||
|
|
return row;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setTotalProgress(percent) {
|
||
|
|
if (totalProgressBar) {
|
||
|
|
totalProgressBar.style.transform = `scaleX(${Math.max(0, Math.min(100, percent)) / 100})`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function setFileProgress(files, totalPercent) {
|
||
|
|
if (!uploadQueue) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const count = files.length || 1;
|
||
|
|
const completedFloat = (Math.max(0, Math.min(100, totalPercent)) / 100) * count;
|
||
|
|
uploadQueue.querySelectorAll(".upload-file-row").forEach((row, index) => {
|
||
|
|
const progress = Math.max(0, Math.min(100, Math.round((completedFloat - index) * 100)));
|
||
|
|
const percent = row.querySelector(".file-progress-percent");
|
||
|
|
const fill = row.querySelector(".file-progress span");
|
||
|
|
if (percent) {
|
||
|
|
percent.textContent = `${progress}%`;
|
||
|
|
}
|
||
|
|
if (fill) {
|
||
|
|
fill.style.transform = `scaleX(${progress / 100})`;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
})();
|