From 0f7e15363bf80e543496da305f6e25cacf8a7824 Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 15 Jun 2026 18:34:42 +0200 Subject: [PATCH] feat(import-ui): surface quality-filter + folder-artist toggles on Import page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two import behaviour toggles previously lived only in Settings → Import. Mirror them onto the React Import page (above the processing queue) so they're visible and adjustable right where you import. - New ImportOptions component: two Switches ("Quality check on import", "Use folder as artist") with optimistic update + immediate save. - API: fetchImportOptions / saveImportOptions / importOptionsQueryOptions — read the whole settings blob, POST a partial {import: {...}} (the settings endpoint partial-merges, so the rest of config is untouched). Both default ON when absent, matching the backend defaults. - Same import.quality_filter_enabled / import.folder_artist_override config keys as the Settings page, so the two stay in sync. Co-Authored-By: Claude Sonnet 4.6 --- webui/src/routes/import/-import.api.ts | 42 +++++++++- webui/src/routes/import/-import.types.ts | 7 ++ .../routes/import/-ui/import-page.module.css | 19 +++++ webui/src/routes/import/-ui/import-page.tsx | 76 ++++++++++++++++++- 4 files changed, 141 insertions(+), 3 deletions(-) diff --git a/webui/src/routes/import/-import.api.ts b/webui/src/routes/import/-import.api.ts index 00de11e2..27a36a62 100644 --- a/webui/src/routes/import/-import.api.ts +++ b/webui/src/routes/import/-import.api.ts @@ -10,6 +10,7 @@ import type { ImportAutoImportResultsPayload, ImportAutoImportSettingsPayload, ImportAutoImportStatusPayload, + ImportOptionsPayload, ImportProcessPayload, ImportStagingFilesPayload, ImportStagingGroupsPayload, @@ -25,7 +26,7 @@ export const IMPORT_QUERY_KEY = ['import'] as const; // which left the progress bar stuck at 0 and showing "Failed" while files // imported fine (#772). Give the import-process calls a generous bound so the // responses actually arrive and the bar advances. Scoped to import only. -const IMPORT_REQUEST_TIMEOUT_MS = 300_000; // 5 min/track +const IMPORT_REQUEST_TIMEOUT_MS = 300_000; // 5 min/track export async function fetchImportStagingFiles(): Promise { return readJson(apiClient.get('import/staging/files')); @@ -225,6 +226,45 @@ export function autoImportResultsQueryOptions() { }); } +// --- Import behaviour toggles (mirrors Settings → Import) --- +// Both live under the `import.*` config namespace. GET reads the whole settings +// blob; POST /api/settings partial-merges, so we only send the import keys. +type SettingsBlob = { + import?: { quality_filter_enabled?: boolean; folder_artist_override?: boolean }; +}; + +export async function fetchImportOptions(): Promise { + const data = await readJson(apiClient.get('settings')); + const imp = data.import ?? {}; + return { + // Both default ON when the key is absent (matches the backend defaults). + qualityFilterEnabled: imp.quality_filter_enabled !== false, + folderArtistOverride: imp.folder_artist_override !== false, + }; +} + +export async function saveImportOptions( + options: ImportOptionsPayload, +): Promise<{ success: boolean; error?: string }> { + return readJson<{ success: boolean; error?: string }>( + apiClient.post('settings', { + json: { + import: { + quality_filter_enabled: options.qualityFilterEnabled, + folder_artist_override: options.folderArtistOverride, + }, + }, + }), + ); +} + +export function importOptionsQueryOptions() { + return queryOptions({ + queryKey: [...IMPORT_QUERY_KEY, 'import-options'], + queryFn: fetchImportOptions, + }); +} + export function invalidateImportQueries(queryClient: QueryClient) { return queryClient.invalidateQueries({ queryKey: IMPORT_QUERY_KEY }); } diff --git a/webui/src/routes/import/-import.types.ts b/webui/src/routes/import/-import.types.ts index a6298593..8828a3c5 100644 --- a/webui/src/routes/import/-import.types.ts +++ b/webui/src/routes/import/-import.types.ts @@ -241,3 +241,10 @@ export interface ImportSinglesQueueJob { } export type ImportQueueJob = ImportAlbumQueueJob | ImportSinglesQueueJob; + +// The two import behaviour toggles also shown in Settings → Import, mirrored +// onto the Import page for visibility. Both map to `import.*` config keys. +export interface ImportOptionsPayload { + qualityFilterEnabled: boolean; + folderArtistOverride: boolean; +} diff --git a/webui/src/routes/import/-ui/import-page.module.css b/webui/src/routes/import/-ui/import-page.module.css index 0d3c13c5..a6268d87 100644 --- a/webui/src/routes/import/-ui/import-page.module.css +++ b/webui/src/routes/import/-ui/import-page.module.css @@ -56,6 +56,25 @@ color: rgba(255, 255, 255, 0.5); } +.importOptionsRow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 22px; + padding: 10px 16px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + font-size: 13px; + color: rgba(255, 255, 255, 0.7); +} + +.importOption { + display: flex; + align-items: center; + gap: 8px; +} + .importStagingPath { flex: 1; overflow: hidden; diff --git a/webui/src/routes/import/-ui/import-page.tsx b/webui/src/routes/import/-ui/import-page.tsx index e519f32b..e4204c6c 100644 --- a/webui/src/routes/import/-ui/import-page.tsx +++ b/webui/src/routes/import/-ui/import-page.tsx @@ -1,12 +1,14 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Link, Outlet } from '@tanstack/react-router'; import clsx from 'clsx'; -import { Button } from '@/components/form/form'; +import { Button, Switch } from '@/components/form/form'; import { Show } from '@/components/primitives'; import { useReactPageShell } from '@/platform/shell/route-controllers'; -import type { ImportQueueEntry } from '../-import.types'; +import type { ImportOptionsPayload, ImportQueueEntry } from '../-import.types'; +import { importOptionsQueryOptions, IMPORT_QUERY_KEY, saveImportOptions } from '../-import.api'; import { getQueueProgressPercent, getQueueStatusText, @@ -36,6 +38,7 @@ export function ImportPage() { lastRefreshedAt={lastRefreshedAt} onRefresh={refreshStaging} /> +
@@ -54,6 +57,75 @@ function formatShortTime(timestamp: number) { }); } +// The two import behaviour toggles, also in Settings → Import, surfaced here so +// they're visible right where you import. Each writes its `import.*` config key +// on change (POST /api/settings partial-merges, so the rest of config is safe). +function ImportOptions() { + const queryClient = useQueryClient(); + const optionsQuery = useQuery(importOptionsQueryOptions()); + const saveMutation = useMutation({ + mutationFn: saveImportOptions, + onSuccess: () => { + window.showToast?.('Import options saved', 'success'); + }, + onError: (error: unknown) => { + window.showToast?.( + error instanceof Error ? error.message : 'Could not save import options', + 'error', + ); + // Re-sync the toggles to the server's actual state after a failed save. + void queryClient.invalidateQueries({ + queryKey: [...IMPORT_QUERY_KEY, 'import-options'], + }); + }, + }); + + const opts = optionsQuery.data; + if (!opts) return null; + + const update = (patch: Partial) => { + const next = { ...opts, ...patch }; + // Optimistic: reflect the toggle immediately, the mutation persists it. + queryClient.setQueryData([...IMPORT_QUERY_KEY, 'import-options'], next); + saveMutation.mutate(next); + }; + + return ( +
+
+ update({ qualityFilterEnabled: checked })} + /> + + Quality check on import + +
+
+ update({ folderArtistOverride: checked })} + /> + + Use folder as artist + +
+
+ ); +} + function ImportHeader({ error, fileCountText,