feat: folder upload - deel 1
This commit is contained in:
+128
-1
@@ -69,6 +69,14 @@ let uploadState = {
|
||||
cancelled: false,
|
||||
conflictResolver: null,
|
||||
};
|
||||
let folderUploadPlanState = {
|
||||
targetPath: "",
|
||||
rootFolderName: "",
|
||||
entries: [],
|
||||
fileCount: 0,
|
||||
subfolderCount: 0,
|
||||
};
|
||||
let folderUploadPickerInput = null;
|
||||
let settingsState = {
|
||||
activeTab: "general",
|
||||
logsLoaded: false,
|
||||
@@ -325,6 +333,22 @@ function uploadElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function ensureFolderUploadPicker() {
|
||||
if (folderUploadPickerInput) {
|
||||
return folderUploadPickerInput;
|
||||
}
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.hidden = true;
|
||||
input.setAttribute("webkitdirectory", "");
|
||||
input.setAttribute("directory", "");
|
||||
input.onchange = handleFolderSelection;
|
||||
document.body.append(input);
|
||||
folderUploadPickerInput = input;
|
||||
return folderUploadPickerInput;
|
||||
}
|
||||
|
||||
function uploadConflictElements() {
|
||||
return {
|
||||
overlay: document.getElementById("upload-conflict-modal"),
|
||||
@@ -471,6 +495,16 @@ function resetUploadProgress() {
|
||||
setUploadProgressVisible(false);
|
||||
}
|
||||
|
||||
function showFolderUploadPlan(plan) {
|
||||
const elements = uploadElements();
|
||||
folderUploadPlanState = plan;
|
||||
elements.button.disabled = false;
|
||||
elements.target.textContent = `Upload to: ${plan.targetPath}`;
|
||||
elements.currentFile.textContent = `Folder: ${plan.rootFolderName} (plan only)`;
|
||||
elements.count.textContent = `${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"}${plan.subfolderCount > 0 ? ` • ${plan.subfolderCount} subfolder${plan.subfolderCount === 1 ? "" : "s"}` : ""}`;
|
||||
setUploadProgressVisible(true);
|
||||
}
|
||||
|
||||
function updateUploadProgress() {
|
||||
const elements = uploadElements();
|
||||
const total = uploadState.files.length;
|
||||
@@ -494,6 +528,22 @@ function openUploadPicker() {
|
||||
elements.input.click();
|
||||
}
|
||||
|
||||
function openFolderPicker() {
|
||||
if (uploadState.active) {
|
||||
return;
|
||||
}
|
||||
folderUploadPlanState = {
|
||||
targetPath: activePaneState().currentPath,
|
||||
rootFolderName: "",
|
||||
entries: [],
|
||||
fileCount: 0,
|
||||
subfolderCount: 0,
|
||||
};
|
||||
const input = ensureFolderUploadPicker();
|
||||
input.value = "";
|
||||
input.click();
|
||||
}
|
||||
|
||||
function isUploadConflictOpen() {
|
||||
const overlay = document.getElementById("upload-conflict-modal");
|
||||
return Boolean(overlay) && !overlay.classList.contains("hidden");
|
||||
@@ -522,6 +572,77 @@ function promptUploadConflict(fileName, targetPath, message) {
|
||||
});
|
||||
}
|
||||
|
||||
function countPlannedSubfolders(relativePaths) {
|
||||
const folderPaths = new Set();
|
||||
relativePaths.forEach((relativePath) => {
|
||||
const parts = relativePath.split("/").filter(Boolean);
|
||||
if (parts.length <= 1) {
|
||||
return;
|
||||
}
|
||||
let current = "";
|
||||
for (let index = 0; index < parts.length - 1; index += 1) {
|
||||
current = current ? `${current}/${parts[index]}` : parts[index];
|
||||
folderPaths.add(current);
|
||||
}
|
||||
});
|
||||
return folderPaths.size;
|
||||
}
|
||||
|
||||
function buildFolderUploadPlan(files, targetPath) {
|
||||
if (!files.length) {
|
||||
return null;
|
||||
}
|
||||
const plannedEntries = files.map((file) => {
|
||||
const webkitRelativePath = String(file.webkitRelativePath || "").replace(/\\/g, "/");
|
||||
const parts = webkitRelativePath.split("/").filter(Boolean);
|
||||
return {
|
||||
file,
|
||||
webkitRelativePath,
|
||||
rootFolderName: parts[0] || "",
|
||||
relativePath: parts.length > 1 ? parts.slice(1).join("/") : file.name,
|
||||
};
|
||||
});
|
||||
const rootFolderName = plannedEntries[0].rootFolderName;
|
||||
if (!rootFolderName) {
|
||||
throw new Error("Folder picker did not return a usable folder structure");
|
||||
}
|
||||
if (plannedEntries.some((entry) => entry.rootFolderName !== rootFolderName || !entry.relativePath)) {
|
||||
throw new Error("Folder picker returned multiple roots or invalid relative paths");
|
||||
}
|
||||
return {
|
||||
targetPath,
|
||||
rootFolderName,
|
||||
entries: plannedEntries.map((entry) => ({
|
||||
name: entry.file.name,
|
||||
size: entry.file.size,
|
||||
relativePath: entry.relativePath,
|
||||
})),
|
||||
fileCount: plannedEntries.length,
|
||||
subfolderCount: countPlannedSubfolders(plannedEntries.map((entry) => entry.relativePath)),
|
||||
};
|
||||
}
|
||||
|
||||
function handleFolderSelection(event) {
|
||||
const files = Array.from(event.target.files || []);
|
||||
event.target.value = "";
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const targetPath = folderUploadPlanState.targetPath || activePaneState().currentPath;
|
||||
const plan = buildFolderUploadPlan(files, targetPath);
|
||||
if (!plan) {
|
||||
return;
|
||||
}
|
||||
showFolderUploadPlan(plan);
|
||||
setError("actions-error", "");
|
||||
setStatus(`Folder upload plan ready: ${plan.rootFolderName} (${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"})`);
|
||||
} catch (err) {
|
||||
setUploadProgressVisible(false);
|
||||
setActionError("Folder upload", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUploadSelection(event) {
|
||||
const files = Array.from(event.target.files || []);
|
||||
event.target.value = "";
|
||||
@@ -3006,7 +3127,13 @@ function setupEvents() {
|
||||
setupPaneEvents("right");
|
||||
document.addEventListener("keydown", handleKeyboardShortcuts);
|
||||
document.getElementById("theme-toggle").onclick = toggleTheme;
|
||||
document.getElementById("upload-btn").onclick = openUploadPicker;
|
||||
document.getElementById("upload-btn").onclick = (event) => {
|
||||
if (event.altKey) {
|
||||
openFolderPicker();
|
||||
return;
|
||||
}
|
||||
openUploadPicker();
|
||||
};
|
||||
document.getElementById("settings-btn").onclick = () => openSettings("general");
|
||||
document.getElementById("view-btn").onclick = openViewer;
|
||||
document.getElementById("edit-btn").onclick = openEditor;
|
||||
|
||||
Reference in New Issue
Block a user