confetti-box/web/request.js

241 lines
8.8 KiB
JavaScript

/*jshint esversion: 11 */
const TOO_LARGE_TEXT = "Too large!";
const ZERO_TEXT = "File is blank!";
const ERROR_TEXT = "Error!";
async function formSubmit() {
const form = document.getElementById("uploadForm");
const files = form.elements.fileUpload.files;
const duration = form.elements.duration.value;
const maxSize = form.elements.fileUpload.dataset.maxFilesize;
await sendFile(files, duration, maxSize);
// Reset the form file data since we've successfully submitted it
form.elements.fileUpload.value = "";
}
async function dragDropSubmit(evt) {
const form = document.getElementById("uploadForm");
const duration = form.elements.duration.value;
const maxSize = form.elements.fileUpload.dataset.maxFilesize;
evt.preventDefault();
const files = [];
if (evt.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
[...evt.dataTransfer.items].forEach((item, _) => {
// If dropped items aren't files, reject them
if (item.kind === "file") {
files.push(item.getAsFile());
}
});
} else {
// Use DataTransfer interface to access the file(s)
[...evt.dataTransfer.files].forEach((file, _) => {
files.push(file.name);
});
}
await sendFile(files, duration, maxSize);
}
async function pasteSubmit(evt) {
const form = document.getElementById("uploadForm");
const duration = form.elements.duration.value;
const maxSize = form.elements.fileUpload.dataset.maxFilesize;
const files = [];
const len = evt.clipboardData.files.length;
for (let i = 0; i < len; i++) {
const file = evt.clipboardData.files[i];
files.push(file);
}
await sendFile(files, duration, maxSize);
}
async function sendFile(files, duration, maxSize) {
for (const file of files) {
const [linkRow, progressBar, progressText] = await addNewToList(file.name);
if (file.size > maxSize) {
console.error("Provided file is too large", file.size, "bytes; max", maxSize, "bytes");
continue;
} else if (file.size == 0) {
console.error("Provided file has 0 bytes");
continue;
}
// Get preliminary upload information
let chunkedResponse;
try {
const response = await fetch("/upload/chunked", {
method: "POST",
body: JSON.stringify({
"name": file.name,
"size": file.size,
"expire_duration": parseInt(duration),
}),
});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
chunkedResponse = await response.json();
} catch (error) {
console.error(error);
}
// Upload the file in `chunk_size` chunks
let uploadArray = [];
const progressValues = [];
for (let start = 0; start < file.size; start += chunkedResponse.chunk_size) {
const chunk = file.slice(start, start + chunkedResponse.chunk_size)
const url = "/upload/chunked?uuid=" + chunkedResponse.uuid + "&offset=" + start;
const ID = progressValues.push(0);
let upload = new Promise(function (resolve, reject) {
let request = new XMLHttpRequest();
request.open("POST", url, true);
request.upload.addEventListener('progress',
(p) => {uploadProgress(p, progressBar, progressText, progressValues, file.size, ID);}, true
);
request.onload = () => {
if (this.status >= 200 && this.status < 300) {
resolve(request.response);
} else {
reject({status: this.status, statusText: request.statusText});
}
};
request.onerror = () => reject({status: this.status, statusText: request.statusText});
request.send(chunk);
});
uploadArray.push(upload);
}
console.log("Waiting for multiple uploads to complete");
console.log(await Promise.allSettled(uploadArray));
// Finish the request and update the progress box
const result = await fetch("/upload/chunked?uuid=" + chunkedResponse.uuid + "&finish");
uploadComplete(result, progressBar, progressText, linkRow);
}
}
async function addNewToList(origFileName) {
const uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
const linkRow = uploadedFilesDisplay.appendChild(document.createElement("div"));
const fileName = linkRow.appendChild(document.createElement("p"));
const progressBar = linkRow.appendChild(document.createElement("progress"));
const progressTxt = linkRow.appendChild(document.createElement("p"));
fileName.textContent = origFileName;
fileName.classList.add("file_name");
progressTxt.classList.add("status");
progressBar.max="100";
progressBar.value="0";
return [linkRow, progressBar, progressTxt];
}
const sumValues = obj => Object.values(obj).reduce((a, b) => a + b, 0);
function uploadProgress(progress, progressBar, progressText, progressValues, fileSize, ID) {
if (progress.lengthComputable) {
progressValues[ID] = progress.loaded;
const progressPercent = Math.floor((sumValues(progressValues) / fileSize) * 100);
if (progressPercent == 100) {
progressBar.removeAttribute("value");
progressText.textContent = "⏳";
} else {
progressBar.value = progressPercent;
progressText.textContent = progressPercent + "%";
}
}
}
async function uploadComplete(response, progressBar, progressText, linkRow) {
if (response.status === 200) {
const responseJson = await response.json();
console.log("Successfully uploaded file", responseJson);
makeFinished(progressBar, progressText, linkRow, responseJson);
} else if (response.status === 413) {
makeErrored(progressBar, progressText, linkRow, TOO_LARGE_TEXT);
} else {
makeErrored(progressBar, progressText, linkRow, ERROR_TEXT);
}
}
function makeErrored(progressBar, progressText, linkRow, errorMessage) {
progressText.textContent = errorMessage;
progressBar.style.display = "none";
linkRow.classList.add("upload_failed");
}
function makeFinished(progressBar, progressText, linkRow, response) {
progressText.textContent = "";
const link = progressText.appendChild(document.createElement("a"));
link.textContent = response.mmid;
link.href = "/f/" + response.mmid;
link.target = "_blank";
let button = linkRow.appendChild(document.createElement("button"));
button.textContent = "📝";
let buttonTimeout = null;
button.addEventListener('click', function(_e) {
const mmid = response.mmid;
if (buttonTimeout) {
clearTimeout(buttonTimeout);
}
navigator.clipboard.writeText(
window.location.protocol + "//" + window.location.host + "/f/" + mmid
);
button.textContent = "✅";
buttonTimeout = setTimeout(function() {
button.textContent = "📝";
}, 750);
});
progressBar.style.display = "none";
linkRow.classList.add("upload_done");
}
async function initEverything() {
const durationBox = document.getElementById("durationBox");
const durationButtons = durationBox.getElementsByTagName("button");
for (const b of durationButtons) {
b.addEventListener("click", function (_e) {
if (this.classList.contains("selected")) {
return;
}
document.getElementById("uploadForm").elements.duration.value = this.dataset.durationSeconds;
let selected = this.parentNode.getElementsByClassName("selected");
selected[0].classList.remove("selected");
this.classList.add("selected");
});
}
}
// This is the entrypoint for everything basically
document.addEventListener("DOMContentLoaded", function(_event) {
// Respond to form submissions
const form = document.getElementById("uploadForm");
form.addEventListener("submit", formSubmit);
// Respond to file paste events
window.addEventListener("paste", (event) => {
pasteSubmit(event)
});
// Respond to drag and drop stuff
let fileButton = document.getElementById("fileButton");
document.addEventListener("drop", (e) => {e.preventDefault();}, false);
document.addEventListener("dragover", (e) => {e.preventDefault()}, false);
fileButton.addEventListener("dragover", (e) => {e.preventDefault();}, false);
fileButton.addEventListener("drop", dragDropSubmit, false);
initEverything();
});