feat: upload - deel 02

This commit is contained in:
kodi
2026-03-13 16:21:51 +01:00
parent 8d1ff79912
commit 8fe9d0f436
7 changed files with 153 additions and 1 deletions
Binary file not shown.
@@ -49,6 +49,12 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('id="left-focus-line"', body)
self.assertIn('id="right-focus-line"', body)
self.assertIn('id="function-bar"', body)
self.assertIn('id="upload-btn"', body)
self.assertIn('id="upload-input"', body)
self.assertIn('id="upload-progress"', body)
self.assertIn('id="upload-target"', body)
self.assertIn('id="upload-current-file"', body)
self.assertIn('id="upload-count"', body)
self.assertIn('id="settings-btn"', body)
self.assertIn('id="rename-btn"', body)
self.assertIn('id="view-btn"', body)
@@ -131,6 +137,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertNotIn('id="tasks-panel"', body)
ordered_ids = [
'id="upload-btn"',
'id="settings-btn"',
'id="rename-btn"',
'id="view-btn"',
@@ -171,6 +178,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('function effectiveThemeKey(theme, colorMode)', app_js)
self.assertIn("document.documentElement.dataset.theme", app_js)
self.assertIn('document.getElementById("theme-toggle").onclick = toggleTheme;', app_js)
self.assertIn('document.getElementById("upload-btn").onclick = openUploadPicker;', app_js)
self.assertIn('document.getElementById("settings-btn").onclick = () => openSettings("general");', app_js)
self.assertIn('async function loadSettings()', app_js)
self.assertIn('await loadSettings();', app_js)
@@ -190,6 +198,13 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('applyTheme(settingsState.selectedTheme, settingsState.selectedColorMode);', app_js)
self.assertIn('settings.interfaceTab.onclick = () => setSettingsTab("interface");', app_js)
self.assertIn('"/api/settings"', app_js)
self.assertIn('function uploadElements()', app_js)
self.assertIn('function openUploadPicker()', app_js)
self.assertIn('async function handleUploadSelection(event)', app_js)
self.assertIn('uploadElements().input.onchange = handleUploadSelection;', app_js)
self.assertIn('"/api/files/upload"', app_js)
self.assertIn('Upload to: ${uploadState.targetPath}', app_js)
self.assertIn('Uploading ${total} file', app_js)
self.assertIn('`/api/files/thumbnail?', app_js)
self.assertIn("function iconTypeForEntry(entry)", app_js)
self.assertIn("function mediaIconSvg(type)", app_js)
+106
View File
@@ -57,6 +57,12 @@ let imageViewerState = {
path: null,
resizeHandler: null,
};
let uploadState = {
active: false,
targetPath: "",
files: [],
index: 0,
};
let settingsState = {
activeTab: "general",
logsLoaded: false,
@@ -302,6 +308,17 @@ function infoElements() {
};
}
function uploadElements() {
return {
button: document.getElementById("upload-btn"),
input: document.getElementById("upload-input"),
progress: document.getElementById("upload-progress"),
target: document.getElementById("upload-target"),
currentFile: document.getElementById("upload-current-file"),
count: document.getElementById("upload-count"),
};
}
async function apiRequest(method, url, body) {
const options = { method, headers: {} };
if (body !== undefined) {
@@ -317,6 +334,23 @@ async function apiRequest(method, url, body) {
return data;
}
async function uploadFileRequest(targetPath, file) {
const formData = new FormData();
formData.append("target_path", targetPath);
formData.append("file", file, file.name);
const response = await fetch("/api/files/upload", {
method: "POST",
body: formData,
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
const error = data.error || {};
throw new Error(error.message || `HTTP ${response.status}`);
}
return data;
}
async function refreshTasksSnapshot() {
try {
const data = await apiRequest("GET", "/api/tasks");
@@ -333,6 +367,76 @@ function createButton(text, onClick) {
return button;
}
function setUploadProgressVisible(visible) {
uploadElements().progress.classList.toggle("hidden", !visible);
}
function resetUploadProgress() {
const elements = uploadElements();
uploadState.active = false;
uploadState.targetPath = "";
uploadState.files = [];
uploadState.index = 0;
elements.button.disabled = false;
elements.target.textContent = "";
elements.currentFile.textContent = "";
elements.count.textContent = "";
setUploadProgressVisible(false);
}
function updateUploadProgress() {
const elements = uploadElements();
const total = uploadState.files.length;
const currentFile = uploadState.files[uploadState.index] || null;
elements.target.textContent = `Upload to: ${uploadState.targetPath}`;
elements.currentFile.textContent = currentFile
? `Uploading ${total} file${total === 1 ? "" : "s"} - Current file: ${currentFile.name}`
: `Uploading ${total} file${total === 1 ? "" : "s"}`;
elements.count.textContent = total > 0 ? `${Math.min(uploadState.index + 1, total)}/${total} files` : "";
elements.button.disabled = uploadState.active;
setUploadProgressVisible(uploadState.active);
}
function openUploadPicker() {
if (uploadState.active) {
return;
}
uploadState.targetPath = activePaneState().currentPath;
const elements = uploadElements();
elements.input.value = "";
elements.input.click();
}
async function handleUploadSelection(event) {
const files = Array.from(event.target.files || []);
event.target.value = "";
if (files.length === 0) {
return;
}
const targetPath = uploadState.targetPath || activePaneState().currentPath;
uploadState.active = true;
uploadState.targetPath = targetPath;
uploadState.files = files;
uploadState.index = 0;
setError("actions-error", "");
updateUploadProgress();
try {
for (let index = 0; index < files.length; index += 1) {
uploadState.index = index;
updateUploadProgress();
await uploadFileRequest(targetPath, files[index]);
}
await loadBrowsePane(state.activePane);
setStatus(`Upload: ${files.length} file${files.length === 1 ? "" : "s"} uploaded`);
} catch (err) {
setActionError("Upload", err);
} finally {
resetUploadProgress();
}
}
function setActivePane(pane) {
state.activePane = pane;
document.getElementById("left-pane").classList.toggle("active-pane", pane === "left");
@@ -2723,6 +2827,7 @@ function setupEvents() {
setupPaneEvents("right");
document.addEventListener("keydown", handleKeyboardShortcuts);
document.getElementById("theme-toggle").onclick = toggleTheme;
document.getElementById("upload-btn").onclick = openUploadPicker;
document.getElementById("settings-btn").onclick = () => openSettings("general");
document.getElementById("view-btn").onclick = openViewer;
document.getElementById("edit-btn").onclick = openEditor;
@@ -2731,6 +2836,7 @@ function setupEvents() {
document.getElementById("copy-btn").onclick = startCopySelected;
document.getElementById("move-btn").onclick = openF6Flow;
document.getElementById("mkdir-btn").onclick = createFolderForActivePane;
uploadElements().input.onchange = handleUploadSelection;
const rename = renameElements();
rename.closeButton.onclick = closeRenamePopup;
+24
View File
@@ -225,6 +225,30 @@ button:disabled {
background: var(--color-button-secondary-bg);
}
.upload-progress {
display: grid;
gap: 1px;
margin-top: 4px;
padding: 4px 8px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface-elevated);
color: var(--color-text-muted);
font-size: 12px;
line-height: 1.25;
}
.upload-progress-target,
.upload-progress-file {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.upload-progress-count {
color: var(--color-text-primary);
}
#theme-toggle-icon {
font-size: 14px;
line-height: 1;
+7
View File
@@ -85,6 +85,7 @@
<section id="footer-bar">
<div id="function-bar" class="toolbar compact-toolbar">
<button id="upload-btn" type="button"><span>Upload</span></button>
<button id="settings-btn" type="button"><span class="shortcut-hint">F1</span><span>Settings</span></button>
<button id="rename-btn" type="button" disabled><span class="shortcut-hint">F2</span><span>Rename</span></button>
<button id="view-btn" type="button" disabled><span class="shortcut-hint">F3</span><span>View</span></button>
@@ -94,6 +95,12 @@
<button id="mkdir-btn" type="button"><span class="shortcut-hint">F7</span><span>MKdir</span></button>
<button id="delete-btn" type="button" disabled><span class="shortcut-hint">F8</span><span>Delete</span></button>
</div>
<input id="upload-input" type="file" multiple hidden>
<div id="upload-progress" class="upload-progress hidden" aria-live="polite">
<div id="upload-target" class="upload-progress-target"></div>
<div id="upload-current-file" class="upload-progress-file"></div>
<div id="upload-count" class="upload-progress-count"></div>
</div>
<div id="actions-error" class="error"></div>
</section>
</div>