feat: upload - deel 03.02 - Skipp all toegevoegd

This commit is contained in:
kodi
2026-03-13 18:30:10 +01:00
parent 8fe9d0f436
commit 360815498e
13 changed files with 463 additions and 19 deletions
+185 -6
View File
@@ -62,6 +62,12 @@ let uploadState = {
targetPath: "",
files: [],
index: 0,
overwriteAll: false,
skipAll: false,
successfulCount: 0,
skippedCount: 0,
cancelled: false,
conflictResolver: null,
};
let settingsState = {
activeTab: "general",
@@ -319,6 +325,72 @@ function uploadElements() {
};
}
function uploadConflictElements() {
return {
overlay: document.getElementById("upload-conflict-modal"),
title: document.getElementById("upload-conflict-title"),
target: document.getElementById("upload-conflict-target"),
fileName: document.getElementById("upload-conflict-file-name"),
message: document.getElementById("upload-conflict-message"),
overwriteButton: document.getElementById("upload-conflict-overwrite-btn"),
overwriteAllButton: document.getElementById("upload-conflict-overwrite-all-btn"),
skipButton: document.getElementById("upload-conflict-skip-btn"),
skipAllButton: document.getElementById("upload-conflict-skip-all-btn"),
cancelButton: document.getElementById("upload-conflict-cancel-btn"),
};
}
function ensureUploadConflictModal() {
if (document.getElementById("upload-conflict-modal")) {
return uploadConflictElements();
}
const overlay = document.createElement("div");
overlay.id = "upload-conflict-modal";
overlay.className = "popup-overlay hidden";
const card = document.createElement("div");
card.className = "popup-card";
card.setAttribute("role", "dialog");
card.setAttribute("aria-modal", "true");
card.setAttribute("aria-labelledby", "upload-conflict-title");
const title = document.createElement("h3");
title.id = "upload-conflict-title";
title.textContent = "Upload conflict";
const target = document.createElement("div");
target.id = "upload-conflict-target";
target.className = "popup-meta";
const fileName = document.createElement("div");
fileName.id = "upload-conflict-file-name";
fileName.className = "popup-meta";
const message = document.createElement("div");
message.id = "upload-conflict-message";
message.className = "popup-meta";
const actions = document.createElement("div");
actions.className = "popup-actions";
const overwriteButton = createButton("Overwrite", () => resolveUploadConflict("overwrite"));
overwriteButton.id = "upload-conflict-overwrite-btn";
const overwriteAllButton = createButton("Overwrite all", () => resolveUploadConflict("overwrite_all"));
overwriteAllButton.id = "upload-conflict-overwrite-all-btn";
const skipButton = createButton("Skip", () => resolveUploadConflict("skip"));
skipButton.id = "upload-conflict-skip-btn";
const skipAllButton = createButton("Skip all", () => resolveUploadConflict("skip_all"));
skipAllButton.id = "upload-conflict-skip-all-btn";
const cancelButton = createButton("Cancel", () => resolveUploadConflict("cancel"));
cancelButton.id = "upload-conflict-cancel-btn";
actions.append(overwriteButton, overwriteAllButton, skipButton, skipAllButton, cancelButton);
card.append(title, target, fileName, message, actions);
overlay.append(card);
document.body.append(overlay);
return uploadConflictElements();
}
async function apiRequest(method, url, body) {
const options = { method, headers: {} };
if (body !== undefined) {
@@ -334,9 +406,19 @@ async function apiRequest(method, url, body) {
return data;
}
async function uploadFileRequest(targetPath, file) {
function createApiError(response, data) {
const error = data.error || {};
const err = new Error(error.message || `HTTP ${response.status}`);
err.code = error.code || null;
err.status = response.status;
err.details = error.details || {};
return err;
}
async function uploadFileRequest(targetPath, file, overwrite = false) {
const formData = new FormData();
formData.append("target_path", targetPath);
formData.append("overwrite", overwrite ? "true" : "false");
formData.append("file", file, file.name);
const response = await fetch("/api/files/upload", {
@@ -345,8 +427,7 @@ async function uploadFileRequest(targetPath, file) {
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
const error = data.error || {};
throw new Error(error.message || `HTTP ${response.status}`);
throw createApiError(response, data);
}
return data;
}
@@ -377,6 +458,12 @@ function resetUploadProgress() {
uploadState.targetPath = "";
uploadState.files = [];
uploadState.index = 0;
uploadState.overwriteAll = false;
uploadState.skipAll = false;
uploadState.successfulCount = 0;
uploadState.skippedCount = 0;
uploadState.cancelled = false;
uploadState.conflictResolver = null;
elements.button.disabled = false;
elements.target.textContent = "";
elements.currentFile.textContent = "";
@@ -407,6 +494,34 @@ function openUploadPicker() {
elements.input.click();
}
function isUploadConflictOpen() {
const overlay = document.getElementById("upload-conflict-modal");
return Boolean(overlay) && !overlay.classList.contains("hidden");
}
function resolveUploadConflict(choice) {
const resolver = uploadState.conflictResolver;
if (!resolver) {
return;
}
uploadState.conflictResolver = null;
const elements = uploadConflictElements();
elements.overlay.classList.add("hidden");
resolver(choice);
}
function promptUploadConflict(fileName, targetPath, message) {
const elements = ensureUploadConflictModal();
elements.title.textContent = "Upload conflict";
elements.target.textContent = `Upload to: ${targetPath}`;
elements.fileName.textContent = `File: ${fileName}`;
elements.message.textContent = message || "Target path already exists.";
elements.overlay.classList.remove("hidden");
return new Promise((resolve) => {
uploadState.conflictResolver = resolve;
});
}
async function handleUploadSelection(event) {
const files = Array.from(event.target.files || []);
event.target.value = "";
@@ -419,18 +534,71 @@ async function handleUploadSelection(event) {
uploadState.targetPath = targetPath;
uploadState.files = files;
uploadState.index = 0;
uploadState.overwriteAll = false;
uploadState.skipAll = false;
uploadState.successfulCount = 0;
uploadState.skippedCount = 0;
uploadState.cancelled = false;
setError("actions-error", "");
updateUploadProgress();
try {
outer:
for (let index = 0; index < files.length; index += 1) {
uploadState.index = index;
updateUploadProgress();
await uploadFileRequest(targetPath, files[index]);
let overwrite = uploadState.overwriteAll;
while (true) {
try {
await uploadFileRequest(targetPath, files[index], overwrite);
uploadState.successfulCount += 1;
break;
} catch (err) {
if (err.code !== "already_exists") {
throw err;
}
if (uploadState.skipAll) {
uploadState.skippedCount += 1;
break;
}
const choice = await promptUploadConflict(files[index].name, targetPath, err.message);
if (choice === "overwrite") {
overwrite = true;
continue;
}
if (choice === "overwrite_all") {
uploadState.overwriteAll = true;
overwrite = true;
continue;
}
if (choice === "skip") {
uploadState.skippedCount += 1;
break;
}
if (choice === "skip_all") {
uploadState.skipAll = true;
uploadState.skippedCount += 1;
break;
}
uploadState.cancelled = true;
break outer;
}
}
}
if (uploadState.successfulCount > 0) {
await loadBrowsePane(state.activePane);
}
if (uploadState.cancelled) {
setStatus(`Upload: ${uploadState.successfulCount}/${files.length} file${files.length === 1 ? "" : "s"} uploaded before cancel`);
} else if (uploadState.skippedCount > 0) {
setStatus(`Upload: ${uploadState.successfulCount} uploaded, ${uploadState.skippedCount} skipped`);
} else {
setStatus(`Upload: ${uploadState.successfulCount} file${uploadState.successfulCount === 1 ? "" : "s"} uploaded`);
}
await loadBrowsePane(state.activePane);
setStatus(`Upload: ${files.length} file${files.length === 1 ? "" : "s"} uploaded`);
} catch (err) {
if (uploadState.successfulCount > 0) {
await loadBrowsePane(state.activePane);
}
setActionError("Upload", err);
} finally {
resetUploadProgress();
@@ -1558,6 +1726,10 @@ function isBatchMovePopupOpen() {
return !batchMoveElements().overlay.classList.contains("hidden");
}
function isUploadConflictModalOpen() {
return isUploadConflictOpen();
}
function isViewerOpen() {
return !viewerElements().overlay.classList.contains("hidden");
}
@@ -2664,6 +2836,13 @@ function handleKeyboardShortcuts(event) {
}
return;
}
if (isUploadConflictModalOpen()) {
if (event.key === "Escape") {
event.preventDefault();
resolveUploadConflict("cancel");
}
return;
}
if (isMovePopupOpen()) {
if (event.key === "Escape") {
event.preventDefault();