added user feedback for actions like uploading or deleting
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let AppConfig = null;
|
let AppConfig = null;
|
||||||
let UI = {};
|
let ErrorTimeout = null;
|
||||||
|
let Files = {};
|
||||||
|
const UI = {};
|
||||||
|
|
||||||
async function appLoop() {
|
async function appLoop() {
|
||||||
if (AppConfig === null) {
|
if (AppConfig === null) {
|
||||||
@@ -65,9 +67,13 @@
|
|||||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
const files = await res.json();
|
const files = await res.json();
|
||||||
|
|
||||||
|
Files = {};
|
||||||
|
|
||||||
if (!UI.fileList) return;
|
if (!UI.fileList) return;
|
||||||
UI.fileList.innerHTML = "";
|
UI.fileList.innerHTML = "";
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
|
Files[file.Name] = true;
|
||||||
|
|
||||||
const size = humanReadableSize(file.Size);
|
const size = humanReadableSize(file.Size);
|
||||||
|
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
@@ -100,7 +106,11 @@
|
|||||||
),
|
),
|
||||||
{ method: "GET" }
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
if (!r.ok) throw new Error("Delete failed " + r.status);
|
if (r.ok) {
|
||||||
|
showSuccess("File deleted");
|
||||||
|
} else {
|
||||||
|
showError("Delete failed");
|
||||||
|
}
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -148,6 +158,7 @@
|
|||||||
document.body.appendChild(aLogo);
|
document.body.appendChild(aLogo);
|
||||||
|
|
||||||
const divDropzone = document.createElement("div");
|
const divDropzone = document.createElement("div");
|
||||||
|
divDropzone.className = "dropzone";
|
||||||
divDropzone.id = "dropzone";
|
divDropzone.id = "dropzone";
|
||||||
divDropzone.innerHTML = "Drag & drop files here or click to select";
|
divDropzone.innerHTML = "Drag & drop files here or click to select";
|
||||||
divDropzone.style.display = "none";
|
divDropzone.style.display = "none";
|
||||||
@@ -222,10 +233,91 @@
|
|||||||
return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s";
|
return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeFilename(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 filenameWithoutPathAndExtension =
|
||||||
|
lastDot !== -1
|
||||||
|
? filenameWithoutPath.slice(0, lastDot)
|
||||||
|
: filenameWithoutPath;
|
||||||
|
|
||||||
|
let cleanedFilename = filenameWithoutPathAndExtension
|
||||||
|
.replace(/ /g, "_")
|
||||||
|
.replace(/Ä/g, "Ae")
|
||||||
|
.replace(/ä/g, "ae")
|
||||||
|
.replace(/Ö/g, "Oe")
|
||||||
|
.replace(/ö/g, "oe")
|
||||||
|
.replace(/Ü/g, "Ue")
|
||||||
|
.replace(/ü/g, "ue")
|
||||||
|
.replace(/ß/g, "ss");
|
||||||
|
|
||||||
|
cleanedFilename = cleanedFilename.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
||||||
|
|
||||||
|
while (cleanedFilename.includes("__")) {
|
||||||
|
cleanedFilename = cleanedFilename.replace(/__+/g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanedFilename = cleanedFilename.replace(/^_+|_+$/g, "");
|
||||||
|
|
||||||
|
const maxLenFilename = 128;
|
||||||
|
if (cleanedFilename.length > maxLenFilename) {
|
||||||
|
cleanedFilename = cleanedFilename.slice(0, maxLenFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanedFilename + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(msg) {
|
||||||
|
const original = "Drag & drop files here or click to select";
|
||||||
|
UI.dropzone.innerHTML = msg;
|
||||||
|
UI.dropzone.classList.add("error");
|
||||||
|
|
||||||
|
if (ErrorTimeout) clearTimeout(ErrorTimeout);
|
||||||
|
|
||||||
|
ErrorTimeout = setTimeout(() => {
|
||||||
|
UI.dropzone.innerHTML = original;
|
||||||
|
UI.dropzone.classList.remove("error");
|
||||||
|
ErrorTimeout = null;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(msg) {
|
||||||
|
const original = "Drag & drop files here or click to select";
|
||||||
|
UI.dropzone.innerHTML = msg;
|
||||||
|
UI.dropzone.classList.add("success");
|
||||||
|
|
||||||
|
if (ErrorTimeout) clearTimeout(ErrorTimeout);
|
||||||
|
|
||||||
|
ErrorTimeout = setTimeout(() => {
|
||||||
|
UI.dropzone.innerHTML = original;
|
||||||
|
UI.dropzone.classList.remove("success");
|
||||||
|
ErrorTimeout = null;
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
function uploadFiles(fileListLike) {
|
function uploadFiles(fileListLike) {
|
||||||
const files = Array.from(fileListLike);
|
const files = Array.from(fileListLike);
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
|
||||||
|
for (const f of files) {
|
||||||
|
if (sanitizeFilename(f.name) == ".upload") {
|
||||||
|
showError("Invalid filename: .upload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sanitizeFilename(f.name) in Files) {
|
||||||
|
showError("File already exists: " + f.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let noErrorOccurred = true;
|
||||||
|
|
||||||
UI.overallProgressContainer.style.display = "block";
|
UI.overallProgressContainer.style.display = "block";
|
||||||
UI.overallProgress.value = 0;
|
UI.overallProgress.value = 0;
|
||||||
UI.overallStatus.textContent = "";
|
UI.overallStatus.textContent = "";
|
||||||
@@ -243,6 +335,9 @@
|
|||||||
UI.overallStatus.textContent = "";
|
UI.overallStatus.textContent = "";
|
||||||
UI.currentFileName.textContent = "";
|
UI.currentFileName.textContent = "";
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
|
if (noErrorOccurred) {
|
||||||
|
showSuccess("Upload successful");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,14 +377,21 @@
|
|||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
uploadedBytes += file.size;
|
uploadedBytes += file.size;
|
||||||
} else {
|
} else {
|
||||||
console.error("Upload failed with status", xhr.status);
|
if (xhr.status === 409) {
|
||||||
|
showError("File already exists: " + file.name);
|
||||||
|
noErrorOccurred = false;
|
||||||
|
} else {
|
||||||
|
showError("Upload failed: " + file.name);
|
||||||
|
noErrorOccurred = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
idx++;
|
idx++;
|
||||||
uploadNext();
|
uploadNext();
|
||||||
});
|
});
|
||||||
|
|
||||||
xhr.addEventListener("error", () => {
|
xhr.addEventListener("error", () => {
|
||||||
console.error("Network/server error during upload.");
|
showError("Network or server error during upload.");
|
||||||
|
noErrorOccurred = false;
|
||||||
idx++;
|
idx++;
|
||||||
uploadNext();
|
uploadNext();
|
||||||
});
|
});
|
||||||
|
@@ -9,7 +9,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dropzone */
|
/* Dropzone */
|
||||||
#dropzone {
|
.dropzone {
|
||||||
border: 2px dashed #888;
|
border: 2px dashed #888;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
color: #fefefe;
|
color: #fefefe;
|
||||||
@@ -20,10 +20,22 @@ body {
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dropzone:hover {
|
.dropzone:hover {
|
||||||
color: #0fff50;
|
color: #0fff50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropzone.error {
|
||||||
|
border: 2px solid #ff4d4d;
|
||||||
|
color: #ff4d4d;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone.success {
|
||||||
|
border: 2px solid #0fff50;
|
||||||
|
color: #0fff50;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* File list */
|
/* File list */
|
||||||
#file-list {
|
#file-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
Reference in New Issue
Block a user