refactor: rename functions and restructure them
This commit is contained in:
@@ -9,28 +9,32 @@
|
|||||||
errorTimeout: null,
|
errorTimeout: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function appLoop() {
|
// ===== app ==============================
|
||||||
|
|
||||||
|
async function appInit() {
|
||||||
|
uiBuildElements();
|
||||||
|
uiCacheElements();
|
||||||
|
uiBindEvents();
|
||||||
|
|
||||||
|
await configLoad();
|
||||||
|
appUpdate();
|
||||||
|
|
||||||
|
setInterval(appUpdate, 5 * 1000);
|
||||||
|
setInterval(configLoad, 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appUpdate() {
|
||||||
if (state.config === null) {
|
if (state.config === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUI();
|
uiUpdate();
|
||||||
fetchFiles();
|
fileListFetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initApp() {
|
// ===== config ===========================
|
||||||
addUIElementsToBody();
|
|
||||||
getUIElements();
|
|
||||||
addEventListeners();
|
|
||||||
|
|
||||||
await loadAppConfig();
|
async function configLoad() {
|
||||||
appLoop();
|
|
||||||
|
|
||||||
setInterval(appLoop, 5 * 1000);
|
|
||||||
setInterval(loadAppConfig, 60 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadAppConfig() {
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/config/", { cache: "no-store" });
|
const res = await fetch("/config/", { cache: "no-store" });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -43,31 +47,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchFiles() {
|
// ===== files ============================
|
||||||
if (state.config.Modes.Sinkhole) {
|
|
||||||
clearFileList();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
async function fileDeleteClickHandler(event, file) {
|
||||||
const files = await fetchFileList();
|
|
||||||
state.files = {};
|
|
||||||
clearFileList();
|
|
||||||
renderFileList(files);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("fetchFiles failed:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchFileList() {
|
|
||||||
const res = await fetch(state.config.Endpoints.Files, {
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDeleteClick(event, file) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!confirm(`Do you really want to delete "${file.Name}"?`)) return;
|
if (!confirm(`Do you really want to delete "${file.Name}"?`)) return;
|
||||||
|
|
||||||
@@ -81,24 +63,136 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showSuccess("File deleted: " + file.Name);
|
uiShowSuccess("File deleted: " + file.Name);
|
||||||
} else {
|
} else {
|
||||||
showError("Delete failed");
|
uiShowError("Delete failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchFiles();
|
fileListFetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError("Delete failed");
|
uiShowError("Delete failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEventListeners() {
|
async function fileListFetch() {
|
||||||
|
if (state.config.Modes.Sinkhole) {
|
||||||
|
fileListClear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = await fileListRequest();
|
||||||
|
state.files = {};
|
||||||
|
fileListClear();
|
||||||
|
fileListRender(files);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("fileListFetch failed:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileListRequest() {
|
||||||
|
const res = await fetch(state.config.Endpoints.Files, {
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileListClear() {
|
||||||
|
if (state.ui.fileList) state.ui.fileList.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileListRender(files) {
|
||||||
|
if (!state.ui.fileList) return;
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
state.files[file.Name] = true;
|
||||||
|
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.appendChild(uiCreateDownloadLink(file));
|
||||||
|
|
||||||
|
if (!state.config.Modes.Readonly) {
|
||||||
|
li.appendChild(uiCreateDeleteLink(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ui.fileList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileSanitizeName(dirtyFilename) {
|
||||||
|
if (!dirtyFilename || dirtyFilename.trim() === "") {
|
||||||
|
return "upload.bin";
|
||||||
|
}
|
||||||
|
|
||||||
|
const filenameWithoutPath = dirtyFilename.split(/[\\/]/).pop();
|
||||||
|
|
||||||
|
const lastDot = filenameWithoutPath.lastIndexOf(".");
|
||||||
|
const extension = lastDot !== -1 ? filenameWithoutPath.slice(lastDot) : "";
|
||||||
|
let nameOnly =
|
||||||
|
lastDot !== -1
|
||||||
|
? filenameWithoutPath.slice(0, lastDot)
|
||||||
|
: filenameWithoutPath;
|
||||||
|
|
||||||
|
const charMap = {
|
||||||
|
Ä: "Ae",
|
||||||
|
ä: "ae",
|
||||||
|
Ö: "Oe",
|
||||||
|
ö: "oe",
|
||||||
|
Ü: "Ue",
|
||||||
|
ü: "ue",
|
||||||
|
ß: "ss",
|
||||||
|
};
|
||||||
|
|
||||||
|
let cleanedFilename = nameOnly.replace(/./g, (char) => {
|
||||||
|
if (charMap[char]) {
|
||||||
|
return charMap[char];
|
||||||
|
}
|
||||||
|
if (char === " ") {
|
||||||
|
return "_";
|
||||||
|
}
|
||||||
|
return char;
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanedFilename = cleanedFilename.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
||||||
|
|
||||||
|
while (cleanedFilename.includes("__")) {
|
||||||
|
cleanedFilename = cleanedFilename.replace(/__+/g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanedFilename = cleanedFilename.replace(/^_+|_+$/g, "");
|
||||||
|
|
||||||
|
const MAX_LEN = 128;
|
||||||
|
if (cleanedFilename.length > MAX_LEN) {
|
||||||
|
cleanedFilename = cleanedFilename.slice(0, MAX_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanedFilename + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileValidateBeforeUpload(files) {
|
||||||
|
for (const f of files) {
|
||||||
|
const safeName = fileSanitizeName(f.name);
|
||||||
|
if (safeName === ".upload") {
|
||||||
|
uiShowError("Invalid filename: .upload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (safeName in state.files) {
|
||||||
|
uiShowError("File already exists: " + f.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ui ===============================
|
||||||
|
|
||||||
|
function uiBindEvents() {
|
||||||
state.ui.dropzone.addEventListener("click", () =>
|
state.ui.dropzone.addEventListener("click", () =>
|
||||||
state.ui.fileInput.click()
|
state.ui.fileInput.click()
|
||||||
);
|
);
|
||||||
state.ui.fileInput.addEventListener("change", () => {
|
state.ui.fileInput.addEventListener("change", () => {
|
||||||
if (state.ui.fileInput.files.length > 0)
|
if (state.ui.fileInput.files.length > 0)
|
||||||
uploadFiles(state.ui.fileInput.files);
|
uploadStart(state.ui.fileInput.files);
|
||||||
});
|
});
|
||||||
state.ui.dropzone.addEventListener("dragover", (e) => {
|
state.ui.dropzone.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -110,11 +204,11 @@
|
|||||||
state.ui.dropzone.addEventListener("drop", (e) => {
|
state.ui.dropzone.addEventListener("drop", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
state.ui.dropzone.style.borderColor = "#888";
|
state.ui.dropzone.style.borderColor = "#888";
|
||||||
if (e.dataTransfer.files.length > 0) uploadFiles(e.dataTransfer.files);
|
if (e.dataTransfer.files.length > 0) uploadStart(e.dataTransfer.files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUIElementsToBody() {
|
function uiBuildElements() {
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
|
|
||||||
const aLogo = document.createElement("a");
|
const aLogo = document.createElement("a");
|
||||||
@@ -170,44 +264,7 @@
|
|||||||
document.body.appendChild(divSinkholeModeInfo);
|
document.body.appendChild(divSinkholeModeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearFileList() {
|
function uiCacheElements() {
|
||||||
if (state.ui.fileList) state.ui.fileList.innerHTML = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDownloadLink(file) {
|
|
||||||
const size = humanReadableSize(file.Size);
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.className = "download-link";
|
|
||||||
link.href = state.config.Endpoints.FilesGet.replace(
|
|
||||||
":filename",
|
|
||||||
encodeURIComponent(file.Name)
|
|
||||||
);
|
|
||||||
link.textContent = `${file.Name} (${size})`;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDeleteLink(file) {
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.className = "delete-link";
|
|
||||||
link.href = "#";
|
|
||||||
link.textContent = " [Delete]";
|
|
||||||
link.title = "Delete file";
|
|
||||||
link.addEventListener("click", (e) => handleDeleteClick(e, file));
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishUpload(success) {
|
|
||||||
state.ui.overallProgressContainer.style.display = "none";
|
|
||||||
state.ui.overallProgress.value = 0;
|
|
||||||
state.ui.overallStatus.textContent = "";
|
|
||||||
state.ui.currentFileName.textContent = "";
|
|
||||||
fetchFiles();
|
|
||||||
if (success) {
|
|
||||||
showSuccess("Upload successful");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUIElements() {
|
|
||||||
state.ui.currentFileName = document.getElementById("currentFileName");
|
state.ui.currentFileName = document.getElementById("currentFileName");
|
||||||
state.ui.dropzone = document.getElementById("dropzone");
|
state.ui.dropzone = document.getElementById("dropzone");
|
||||||
state.ui.fileInput = document.getElementById("fileInput");
|
state.ui.fileInput = document.getElementById("fileInput");
|
||||||
@@ -220,7 +277,29 @@
|
|||||||
state.ui.sinkholeModeInfo = document.getElementById("sinkholeModeInfo");
|
state.ui.sinkholeModeInfo = document.getElementById("sinkholeModeInfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanReadableSize(bytes) {
|
function uiCreateDeleteLink(file) {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.className = "delete-link";
|
||||||
|
link.href = "#";
|
||||||
|
link.textContent = " [Delete]";
|
||||||
|
link.title = "Delete file";
|
||||||
|
link.addEventListener("click", (e) => fileDeleteClickHandler(e, file));
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uiCreateDownloadLink(file) {
|
||||||
|
const size = uiFormatSize(file.Size);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.className = "download-link";
|
||||||
|
link.href = state.config.Endpoints.FilesGet.replace(
|
||||||
|
":filename",
|
||||||
|
encodeURIComponent(file.Name)
|
||||||
|
);
|
||||||
|
link.textContent = `${file.Name} (${size})`;
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uiFormatSize(bytes) {
|
||||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (bytes >= 1024 && i < units.length - 1) {
|
while (bytes >= 1024 && i < units.length - 1) {
|
||||||
@@ -230,7 +309,7 @@
|
|||||||
return `${bytes.toFixed(1)} ${units[i]}`;
|
return `${bytes.toFixed(1)} ${units[i]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanReadableSpeed(bytesPerSec) {
|
function uiFormatSpeed(bytesPerSec) {
|
||||||
if (!isFinite(bytesPerSec) || bytesPerSec <= 0) return "—";
|
if (!isFinite(bytesPerSec) || bytesPerSec <= 0) return "—";
|
||||||
if (bytesPerSec < 1024) return bytesPerSec.toFixed(0) + " B/s";
|
if (bytesPerSec < 1024) return bytesPerSec.toFixed(0) + " B/s";
|
||||||
if (bytesPerSec < 1024 * 1024)
|
if (bytesPerSec < 1024 * 1024)
|
||||||
@@ -238,85 +317,18 @@
|
|||||||
return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s";
|
return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s";
|
||||||
}
|
}
|
||||||
|
|
||||||
function initUIProgress() {
|
function uiInitProgress() {
|
||||||
state.ui.overallProgressContainer.style.display = "block";
|
state.ui.overallProgressContainer.style.display = "block";
|
||||||
state.ui.overallProgress.value = 0;
|
state.ui.overallProgress.value = 0;
|
||||||
state.ui.overallStatus.textContent = "";
|
state.ui.overallStatus.textContent = "";
|
||||||
state.ui.currentFileName.textContent = "";
|
state.ui.currentFileName.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFileList(files) {
|
function uiShowError(msg) {
|
||||||
if (!state.ui.fileList) return;
|
uiShowMessage(msg, "error", 2000);
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
state.files[file.Name] = true;
|
|
||||||
|
|
||||||
const li = document.createElement("li");
|
|
||||||
li.appendChild(createDownloadLink(file));
|
|
||||||
|
|
||||||
if (!state.config.Modes.Readonly) {
|
|
||||||
li.appendChild(createDeleteLink(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ui.fileList.appendChild(li);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeFilename(dirtyFilename) {
|
function uiShowMessage(msg, type, duration = 2000) {
|
||||||
if (!dirtyFilename || dirtyFilename.trim() === "") {
|
|
||||||
return "upload.bin";
|
|
||||||
}
|
|
||||||
|
|
||||||
const filenameWithoutPath = dirtyFilename.split(/[\\/]/).pop();
|
|
||||||
|
|
||||||
const lastDot = filenameWithoutPath.lastIndexOf(".");
|
|
||||||
const extension = lastDot !== -1 ? filenameWithoutPath.slice(lastDot) : "";
|
|
||||||
let nameOnly =
|
|
||||||
lastDot !== -1
|
|
||||||
? filenameWithoutPath.slice(0, lastDot)
|
|
||||||
: filenameWithoutPath;
|
|
||||||
|
|
||||||
const charMap = {
|
|
||||||
Ä: "Ae",
|
|
||||||
ä: "ae",
|
|
||||||
Ö: "Oe",
|
|
||||||
ö: "oe",
|
|
||||||
Ü: "Ue",
|
|
||||||
ü: "ue",
|
|
||||||
ß: "ss",
|
|
||||||
};
|
|
||||||
|
|
||||||
let cleanedFilename = nameOnly.replace(/./g, (char) => {
|
|
||||||
if (charMap[char]) {
|
|
||||||
return charMap[char];
|
|
||||||
}
|
|
||||||
if (char === " ") {
|
|
||||||
return "_";
|
|
||||||
}
|
|
||||||
return char;
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanedFilename = cleanedFilename.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
||||||
|
|
||||||
while (cleanedFilename.includes("__")) {
|
|
||||||
cleanedFilename = cleanedFilename.replace(/__+/g, "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanedFilename = cleanedFilename.replace(/^_+|_+$/g, "");
|
|
||||||
|
|
||||||
const MAX_LEN = 128;
|
|
||||||
if (cleanedFilename.length > MAX_LEN) {
|
|
||||||
cleanedFilename = cleanedFilename.slice(0, MAX_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanedFilename + extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(msg) {
|
|
||||||
showMessage(msg, "error", 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(msg, type, duration = 2000) {
|
|
||||||
state.ui.dropzone.innerHTML = msg;
|
state.ui.dropzone.innerHTML = msg;
|
||||||
state.ui.dropzone.classList.add(type);
|
state.ui.dropzone.classList.add(type);
|
||||||
|
|
||||||
@@ -329,11 +341,11 @@
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSuccess(msg) {
|
function uiShowSuccess(msg) {
|
||||||
showMessage(msg, "success", 1500);
|
uiShowMessage(msg, "success", 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUI() {
|
function uiUpdate() {
|
||||||
if (state.config.Modes.Readonly) {
|
if (state.config.Modes.Readonly) {
|
||||||
state.ui.dropzone.style.display = "none";
|
state.ui.dropzone.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
@@ -349,75 +361,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFiles(fileListLike) {
|
function uiUpdateProgress(totalUploaded, totalSize, startTime) {
|
||||||
const files = Array.from(fileListLike);
|
|
||||||
if (files.length === 0) return;
|
|
||||||
|
|
||||||
if (!validateFiles(files)) return;
|
|
||||||
|
|
||||||
initUIProgress();
|
|
||||||
|
|
||||||
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
||||||
let uploadedBytes = 0;
|
|
||||||
let currentIndex = 0;
|
|
||||||
const startTime = Date.now();
|
|
||||||
let allSuccessful = true;
|
|
||||||
|
|
||||||
function uploadNext() {
|
|
||||||
if (currentIndex >= files.length) {
|
|
||||||
finishUpload(allSuccessful);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = files[currentIndex];
|
|
||||||
state.ui.currentFileName.textContent = file.name;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
const form = new FormData();
|
|
||||||
form.append("uploadfile", file);
|
|
||||||
|
|
||||||
xhr.upload.addEventListener("progress", (e) => {
|
|
||||||
if (e.lengthComputable) {
|
|
||||||
updateProgressUI(uploadedBytes + e.loaded, totalSize, startTime);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.addEventListener("load", () => {
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
uploadedBytes += file.size;
|
|
||||||
} else if (xhr.status === 409) {
|
|
||||||
showError("File already exists: " + file.name);
|
|
||||||
allSuccessful = false;
|
|
||||||
} else {
|
|
||||||
showError("Upload failed: " + file.name);
|
|
||||||
allSuccessful = false;
|
|
||||||
}
|
|
||||||
currentIndex++;
|
|
||||||
uploadNext();
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.addEventListener("error", () => {
|
|
||||||
showError("Network or server error during upload.");
|
|
||||||
allSuccessful = false;
|
|
||||||
currentIndex++;
|
|
||||||
uploadNext();
|
|
||||||
});
|
|
||||||
|
|
||||||
xhr.open("POST", state.config.Endpoints.Upload);
|
|
||||||
xhr.send(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchFiles();
|
|
||||||
uploadNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProgressUI(totalUploaded, totalSize, startTime) {
|
|
||||||
const percent = (totalUploaded / totalSize) * 100;
|
const percent = (totalUploaded / totalSize) * 100;
|
||||||
state.ui.overallProgress.value = percent;
|
state.ui.overallProgress.value = percent;
|
||||||
|
|
||||||
const elapsed = (Date.now() - startTime) / 1000;
|
const elapsed = (Date.now() - startTime) / 1000;
|
||||||
const speed = totalUploaded / elapsed;
|
const speed = totalUploaded / elapsed;
|
||||||
const speedStr = humanReadableSpeed(speed);
|
const speedStr = uiFormatSpeed(speed);
|
||||||
|
|
||||||
const remainingBytes = totalSize - totalUploaded;
|
const remainingBytes = totalSize - totalUploaded;
|
||||||
const etaSec = speed > 0 ? remainingBytes / speed : Infinity;
|
const etaSec = speed > 0 ? remainingBytes / speed : Infinity;
|
||||||
@@ -433,20 +383,82 @@
|
|||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFiles(files) {
|
// ===== upload ===========================
|
||||||
for (const f of files) {
|
|
||||||
const safeName = sanitizeFilename(f.name);
|
function uploadFinish(success) {
|
||||||
if (safeName === ".upload") {
|
state.ui.overallProgressContainer.style.display = "none";
|
||||||
showError("Invalid filename: .upload");
|
state.ui.overallProgress.value = 0;
|
||||||
return false;
|
state.ui.overallStatus.textContent = "";
|
||||||
}
|
state.ui.currentFileName.textContent = "";
|
||||||
if (safeName in state.files) {
|
fileListFetch();
|
||||||
showError("File already exists: " + f.name);
|
if (success) {
|
||||||
return false;
|
uiShowSuccess("Upload successful");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initApp);
|
function uploadStart(fileListLike) {
|
||||||
|
const files = Array.from(fileListLike);
|
||||||
|
if (files.length === 0) return;
|
||||||
|
|
||||||
|
if (!fileValidateBeforeUpload(files)) return;
|
||||||
|
|
||||||
|
uiInitProgress();
|
||||||
|
|
||||||
|
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
||||||
|
let uploadedBytes = 0;
|
||||||
|
let currentIndex = 0;
|
||||||
|
const startTime = Date.now();
|
||||||
|
let allSuccessful = true;
|
||||||
|
|
||||||
|
function uploadNext() {
|
||||||
|
if (currentIndex >= files.length) {
|
||||||
|
uploadFinish(allSuccessful);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = files[currentIndex];
|
||||||
|
state.ui.currentFileName.textContent = file.name;
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("uploadfile", file);
|
||||||
|
|
||||||
|
xhr.upload.addEventListener("progress", (e) => {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
uiUpdateProgress(uploadedBytes + e.loaded, totalSize, startTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener("load", () => {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
uploadedBytes += file.size;
|
||||||
|
} else if (xhr.status === 409) {
|
||||||
|
uiShowError("File already exists: " + file.name);
|
||||||
|
allSuccessful = false;
|
||||||
|
} else {
|
||||||
|
uiShowError("Upload failed: " + file.name);
|
||||||
|
allSuccessful = false;
|
||||||
|
}
|
||||||
|
currentIndex++;
|
||||||
|
uploadNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener("error", () => {
|
||||||
|
uiShowError("Network or server error during upload.");
|
||||||
|
allSuccessful = false;
|
||||||
|
currentIndex++;
|
||||||
|
uploadNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.open("POST", state.config.Endpoints.Upload);
|
||||||
|
xhr.send(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileListFetch();
|
||||||
|
uploadNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== init ============================
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", appInit);
|
||||||
})();
|
})();
|
||||||
|
Reference in New Issue
Block a user