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 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
<div class="panel-body">
|
||||
<div class="path-input-row">
|
||||
<input class="form-input" type="text" id="mountPath" placeholder="/Volumes/JUKEBOX or E:\\">
|
||||
<button type="button" class="button-primary" onclick="pickMountPath()">+</button>
|
||||
<button type="button" class="button-secondary" onclick="refreshSelectedDisk()">Refresh</button>
|
||||
</div>
|
||||
<div class="form-hint">Choose the directory where the removable disk is mounted. The app works with one selected disk at a time in standalone mode.</div>
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<div class="form-hint">Add one or more root folders with source files. After that, expand each root and enable or disable individual nested folders with checkboxes.</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button type="button" class="button-primary" onclick="addSourceRoot()">Add source folder</button>
|
||||
<input class="form-input" type="text" id="newSourcePath" placeholder="/media/movies">
|
||||
<button type="button" class="button-primary" onclick="addSourceRoot()">Add</button>
|
||||
<button type="button" class="button-secondary button-sm" onclick="reloadAllSourceTrees()">Refresh trees</button>
|
||||
</div>
|
||||
<div class="source-list">
|
||||
@@ -36,16 +37,11 @@
|
||||
<span class="form-hint">The new-only mode skips files already copied to this disk, even if they were later removed.</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="allowedFilesMode">Allowed file types</label>
|
||||
<select class="form-select" id="allowedFilesMode" style="width:auto;max-width:420px" onchange="updateAllowedFilesModeUI()">
|
||||
<option value="media_types">Audio, video, photo</option>
|
||||
<option value="extensions">Custom extensions list</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="mediaTypesGroup">
|
||||
<label class="form-label">Built-in media types</label>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
||||
<label class="form-label" style="margin:0">Allowed file types</label>
|
||||
<button type="button" class="button-secondary button-sm" id="editAllowedFilesButton" onclick="toggleAllowedFilesEditor()">Edit list</button>
|
||||
</div>
|
||||
<div style="display:grid;gap:8px">
|
||||
<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer">
|
||||
<input type="checkbox" id="mediaTypeAudio" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||
@@ -73,7 +69,10 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="extensionsGroup" style="display:none">
|
||||
<label class="form-label" for="allowedExtensions">Allowed extensions</label>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
||||
<label class="form-label" for="allowedExtensions" style="margin:0">Allowed extensions</label>
|
||||
<button type="button" class="button-secondary button-sm" onclick="toggleAllowedFilesEditor()">Use media types</button>
|
||||
</div>
|
||||
<textarea class="form-input" id="allowedExtensions" rows="5" placeholder=".mp3, .flac, .mp4"></textarea>
|
||||
<span class="form-hint">One extension per line or separated by commas. You can write <code>mp3</code> or <code>.mp3</code>.</span>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user