feat: folder upload - deel 2
This commit is contained in:
+140
-3
@@ -70,6 +70,7 @@ let uploadState = {
|
||||
conflictResolver: null,
|
||||
};
|
||||
let folderUploadPlanState = {
|
||||
targetPane: "left",
|
||||
targetPath: "",
|
||||
rootFolderName: "",
|
||||
entries: [],
|
||||
@@ -500,7 +501,7 @@ function showFolderUploadPlan(plan) {
|
||||
folderUploadPlanState = plan;
|
||||
elements.button.disabled = false;
|
||||
elements.target.textContent = `Upload to: ${plan.targetPath}`;
|
||||
elements.currentFile.textContent = `Folder: ${plan.rootFolderName} (plan only)`;
|
||||
elements.currentFile.textContent = `Folder: ${plan.rootFolderName}`;
|
||||
elements.count.textContent = `${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"}${plan.subfolderCount > 0 ? ` • ${plan.subfolderCount} subfolder${plan.subfolderCount === 1 ? "" : "s"}` : ""}`;
|
||||
setUploadProgressVisible(true);
|
||||
}
|
||||
@@ -533,6 +534,7 @@ function openFolderPicker() {
|
||||
return;
|
||||
}
|
||||
folderUploadPlanState = {
|
||||
targetPane: state.activePane,
|
||||
targetPath: activePaneState().currentPath,
|
||||
rootFolderName: "",
|
||||
entries: [],
|
||||
@@ -610,9 +612,11 @@ function buildFolderUploadPlan(files, targetPath) {
|
||||
throw new Error("Folder picker returned multiple roots or invalid relative paths");
|
||||
}
|
||||
return {
|
||||
targetPane: folderUploadPlanState.targetPane || state.activePane,
|
||||
targetPath,
|
||||
rootFolderName,
|
||||
entries: plannedEntries.map((entry) => ({
|
||||
file: entry.file,
|
||||
name: entry.file.name,
|
||||
size: entry.file.size,
|
||||
relativePath: entry.relativePath,
|
||||
@@ -622,7 +626,139 @@ function buildFolderUploadPlan(files, targetPath) {
|
||||
};
|
||||
}
|
||||
|
||||
function handleFolderSelection(event) {
|
||||
function folderDirectoryPaths(plan) {
|
||||
const paths = new Set([`${plan.targetPath}/${plan.rootFolderName}`]);
|
||||
plan.entries.forEach((entry) => {
|
||||
const parts = entry.relativePath.split("/").filter(Boolean);
|
||||
if (parts.length <= 1) {
|
||||
return;
|
||||
}
|
||||
let current = `${plan.targetPath}/${plan.rootFolderName}`;
|
||||
for (let index = 0; index < parts.length - 1; index += 1) {
|
||||
current = `${current}/${parts[index]}`;
|
||||
paths.add(current);
|
||||
}
|
||||
});
|
||||
return Array.from(paths).sort((left, right) => left.split("/").length - right.split("/").length);
|
||||
}
|
||||
|
||||
async function ensureFolderDirectoryExists(path) {
|
||||
const segments = path.split("/");
|
||||
const name = segments.pop();
|
||||
const parentPath = segments.join("/");
|
||||
try {
|
||||
await apiRequest("POST", "/api/files/mkdir", {
|
||||
parent_path: parentPath,
|
||||
name,
|
||||
});
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err.code !== "already_exists") {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await apiRequest("GET", `/api/browse?${new URLSearchParams({ path }).toString()}`);
|
||||
}
|
||||
|
||||
function updateFolderUploadProgress(plan, currentName, index) {
|
||||
const elements = uploadElements();
|
||||
elements.target.textContent = `Folder upload to: ${plan.targetPath}/${plan.rootFolderName}`;
|
||||
elements.currentFile.textContent = currentName ? `Current file: ${currentName}` : `Folder: ${plan.rootFolderName}`;
|
||||
elements.count.textContent = `${Math.min(index + 1, plan.fileCount)}/${plan.fileCount} files`;
|
||||
elements.button.disabled = true;
|
||||
setUploadProgressVisible(true);
|
||||
}
|
||||
|
||||
async function executeFolderUploadPlan(plan) {
|
||||
uploadState.active = true;
|
||||
uploadState.targetPath = `${plan.targetPath}/${plan.rootFolderName}`;
|
||||
uploadState.overwriteAll = false;
|
||||
uploadState.skipAll = false;
|
||||
uploadState.successfulCount = 0;
|
||||
uploadState.skippedCount = 0;
|
||||
uploadState.cancelled = false;
|
||||
uploadState.files = plan.entries.map((entry) => entry.file);
|
||||
uploadState.index = 0;
|
||||
setError("actions-error", "");
|
||||
showFolderUploadPlan(plan);
|
||||
|
||||
try {
|
||||
const directories = folderDirectoryPaths(plan);
|
||||
for (const directoryPath of directories) {
|
||||
await ensureFolderDirectoryExists(directoryPath);
|
||||
}
|
||||
|
||||
outer:
|
||||
for (let index = 0; index < plan.entries.length; index += 1) {
|
||||
const entry = plan.entries[index];
|
||||
const relativeParts = entry.relativePath.split("/").filter(Boolean);
|
||||
const fileName = relativeParts[relativeParts.length - 1];
|
||||
const parentSegments = relativeParts.slice(0, -1);
|
||||
const targetPath = parentSegments.length
|
||||
? `${plan.targetPath}/${plan.rootFolderName}/${parentSegments.join("/")}`
|
||||
: `${plan.targetPath}/${plan.rootFolderName}`;
|
||||
|
||||
uploadState.index = index;
|
||||
updateFolderUploadProgress(plan, entry.relativePath, index);
|
||||
let overwrite = uploadState.overwriteAll;
|
||||
while (true) {
|
||||
try {
|
||||
await uploadFileRequest(targetPath, entry.file, 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(fileName, 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 || uploadState.skippedCount > 0) {
|
||||
await loadBrowsePane(plan.targetPane);
|
||||
}
|
||||
if (uploadState.cancelled) {
|
||||
setStatus(`Folder upload: ${uploadState.successfulCount}/${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"} uploaded before cancel`);
|
||||
} else if (uploadState.skippedCount > 0) {
|
||||
setStatus(`Folder upload: ${uploadState.successfulCount} uploaded, ${uploadState.skippedCount} skipped`);
|
||||
} else {
|
||||
setStatus(`Folder upload: ${uploadState.successfulCount} file${uploadState.successfulCount === 1 ? "" : "s"} uploaded`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (uploadState.successfulCount > 0 || uploadState.skippedCount > 0) {
|
||||
await loadBrowsePane(plan.targetPane);
|
||||
}
|
||||
setActionError("Folder upload", err);
|
||||
} finally {
|
||||
resetUploadProgress();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFolderSelection(event) {
|
||||
const files = Array.from(event.target.files || []);
|
||||
event.target.value = "";
|
||||
if (files.length === 0) {
|
||||
@@ -636,7 +772,8 @@ function handleFolderSelection(event) {
|
||||
}
|
||||
showFolderUploadPlan(plan);
|
||||
setError("actions-error", "");
|
||||
setStatus(`Folder upload plan ready: ${plan.rootFolderName} (${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"})`);
|
||||
setStatus(`Folder upload: preparing ${plan.rootFolderName} (${plan.fileCount} file${plan.fileCount === 1 ? "" : "s"})`);
|
||||
await executeFolderUploadPlan(plan);
|
||||
} catch (err) {
|
||||
setUploadProgressVisible(false);
|
||||
setActionError("Folder upload", err);
|
||||
|
||||
Reference in New Issue
Block a user