From 70d301f78f3d95ab43cae04120ad4678d981f15b Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Thu, 21 May 2026 21:08:00 +0300 Subject: [PATCH] Remove native folder picker, use text input instead Native OS dialog (zenity/AppleScript/PowerShell) fails on Linux with "native folder picker is not supported on this platform". Replaced: - dashboard: removed the "+" button, users type mount path manually - settings: replaced "Add source folder" native dialog with inline text input field Co-Authored-By: Claude Sonnet 4.6 --- web/templates/dashboard.html | 22 ----------- web/templates/settings.html | 71 ++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/web/templates/dashboard.html b/web/templates/dashboard.html index 15fca0a..96aff70 100644 --- a/web/templates/dashboard.html +++ b/web/templates/dashboard.html @@ -5,7 +5,6 @@
-
Choose the directory where the removable disk is mounted. The app works with one selected disk at a time in standalone mode.
@@ -290,27 +289,6 @@ function startTaskPoll(taskID) { pollTask(taskID); } -async function pickFolder() { - const response = await fetch('/api/system/pick-folder', { method: 'POST' }); - const payload = await response.json(); - if (!response.ok) { - throw new Error(payload.error || 'Failed to choose folder'); - } - return payload.path || ''; -} - -async function pickMountPath() { - try { - const path = await pickFolder(); - if (!path) return; - document.getElementById('mountPath').value = path; - localStorage.setItem('jukebox.selectedMountPath', path); - await refreshSelectedDisk(); - } catch (error) { - toast(error.message || 'Failed to choose folder', 'error'); - } -} - async function refreshSelectedDisk() { const mountPath = document.getElementById('mountPath').value.trim(); if (!mountPath) { diff --git a/web/templates/settings.html b/web/templates/settings.html index b48706d..a275531 100644 --- a/web/templates/settings.html +++ b/web/templates/settings.html @@ -7,7 +7,8 @@
Add one or more root folders with source files. After that, expand each root and enable or disable individual nested folders with checkboxes.
- + +
@@ -36,16 +37,11 @@ The new-only mode skips files already copied to this disk, even if they were later removed.
-
- - -
-
- +
+ + +
@@ -127,6 +126,7 @@ const builtInMediaTypes = { }; let sourceRoots = []; let sourceConfig = {}; +let allowedFilesMode = 'media_types'; function escapeHTML(value) { return String(value || '').replace(/[&<>"']/g, (char) => ({ @@ -206,15 +206,6 @@ function collectSourcesForSave() { return items.sort((a, b) => a.path.localeCompare(b.path)); } -async function pickFolder() { - const response = await fetch('/api/system/pick-folder', { method: 'POST' }); - const payload = await response.json(); - if (!response.ok) { - throw new Error(payload.error || 'Failed to choose folder'); - } - return payload.path || ''; -} - async function loadSourceChildren(path) { if (!path || loadingNodes.has(path)) return; loadingNodes.add(path); @@ -262,21 +253,19 @@ function removeRoot(path) { } async function addSourceRoot() { - try { - const path = await pickFolder(); - if (!path) return; - if (sourceRoots.some((root) => isSamePath(root, path))) { - toast('This source folder is already added', 'error'); - return; - } - sourceRoots.push(path); - sourceRoots.sort((a, b) => a.localeCompare(b)); - sourceConfig[path] = true; - expandedNodes.add(path); - await loadSourceChildren(path); - } catch (error) { - toast(error.message || 'Failed to choose folder', 'error'); + const input = document.getElementById('newSourcePath'); + const path = (input?.value || '').trim(); + if (!path) return; + if (sourceRoots.some((root) => isSamePath(root, path))) { + toast('This source folder is already added', 'error'); + return; } + sourceRoots.push(path); + sourceRoots.sort((a, b) => a.localeCompare(b)); + sourceConfig[path] = true; + expandedNodes.add(path); + if (input) input.value = ''; + await loadSourceChildren(path); } async function reloadAllSourceTrees() { @@ -410,9 +399,13 @@ function selectedMediaTypes() { } function updateAllowedFilesModeUI() { - const mode = document.getElementById('allowedFilesMode').value || 'media_types'; - document.getElementById('mediaTypesGroup').style.display = mode === 'media_types' ? '' : 'none'; - document.getElementById('extensionsGroup').style.display = mode === 'extensions' ? '' : 'none'; + document.getElementById('mediaTypesGroup').style.display = allowedFilesMode === 'media_types' ? '' : 'none'; + document.getElementById('extensionsGroup').style.display = allowedFilesMode === 'extensions' ? '' : 'none'; +} + +function toggleAllowedFilesEditor() { + allowedFilesMode = allowedFilesMode === 'extensions' ? 'media_types' : 'extensions'; + updateAllowedFilesModeUI(); } function renderMediaTypeHints() { @@ -429,9 +422,9 @@ async function loadSettings() { document.getElementById('reserveGB').value = cfg.reserve_free_gb ?? 2; document.getElementById('destFolder').value = cfg.dest_folder || 'media'; document.getElementById('fileSelectMode').value = cfg.file_select_mode || 'new'; - document.getElementById('allowedFilesMode').value = cfg.allowed_files_mode || 'media_types'; document.getElementById('overwriteMode').value = cfg.overwrite_mode || 'skip'; document.getElementById('autoCopy').checked = !!cfg.auto_copy; + allowedFilesMode = cfg.allowed_files_mode || 'media_types'; document.getElementById('mediaTypeAudio').checked = (cfg.enabled_media_types || ['audio', 'video']).includes('audio'); document.getElementById('mediaTypeVideo').checked = (cfg.enabled_media_types || ['audio', 'video']).includes('video'); document.getElementById('mediaTypePhoto').checked = (cfg.enabled_media_types || []).includes('photo'); @@ -456,7 +449,7 @@ async function saveSettings(event) { reserve_free_gb: parseFloat(document.getElementById('reserveGB').value) || 2, dest_folder: document.getElementById('destFolder').value.trim() || 'media', file_select_mode: document.getElementById('fileSelectMode').value, - allowed_files_mode: document.getElementById('allowedFilesMode').value, + allowed_files_mode: allowedFilesMode, enabled_media_types: selectedMediaTypes(), allowed_extensions: parseExtensionsInput(document.getElementById('allowedExtensions').value), overwrite_mode: document.getElementById('overwriteMode').value,