import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Link, Outlet } from '@tanstack/react-router'; import clsx from 'clsx'; import { Button, Switch } from '@/components/form/form'; import { Show } from '@/components/primitives'; import { useReactPageShell } from '@/platform/shell/route-controllers'; import type { ImportOptionsPayload, ImportQueueEntry } from '../-import.types'; import { importOptionsQueryOptions, IMPORT_QUERY_KEY, saveImportOptions } from '../-import.api'; import { getQueueProgressPercent, getQueueStatusText, getStagingStatsText, } from '../-import.helpers'; import { useImportQueueWorkflow } from '../-import.store'; import styles from './import-page.module.css'; import { fallbackImage, RefreshIcon, useImportStaging } from './import-shared'; export function ImportPage() { useReactPageShell('import'); const { refreshStaging, scanning, scanProgress, stagingFiles, stagingPath, stagingQuery } = useImportStaging(); const isRefreshing = stagingQuery.isRefetching; const lastRefreshedAt = stagingQuery.dataUpdatedAt > 0 ? formatShortTime(stagingQuery.dataUpdatedAt) : null; // While a large staging folder is still scanning (#947), show progress instead of a count. const fileCountText = scanning ? scanProgress && scanProgress.total > 0 ? `Scanning ${scanProgress.scanned} of ${scanProgress.total} files…` : 'Scanning staging folder…' : getStagingStatsText(stagingFiles); return (
); } function formatShortTime(timestamp: number) { return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', }); } // 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 profile check on import
update({ folderArtistOverride: checked })} /> Use folder as artist
); } function ImportHeader({ error, fileCountText, loading, stagingPath, refreshing, lastRefreshedAt, onRefresh, }: { error: unknown; fileCountText: string; loading: boolean; stagingPath: string; refreshing: boolean; lastRefreshedAt: string | null; onRefresh: () => void; }) { return (

Import Music

{error ? 'Import folder: error' : `Import: ${stagingPath}`} {lastRefreshedAt ? `Last refreshed: ${lastRefreshedAt}` : null} {loading ? 'loading...' : fileCountText}
); } function ImportProcessingQueue() { const { clearFinishedJobs, queue } = useImportQueueWorkflow(); const hasFinished = queue.some((entry) => entry.status !== 'running'); return (
Processing
{queue.map((entry) => ( ))}
); } function ImportQueueItem({ entry }: { entry: ImportQueueEntry }) { const statusText = getQueueStatusText(entry); const statusClass = clsx({ [styles.error]: entry.status === 'error' || (entry.status === 'done' && entry.errors.length > 0), [styles.done]: entry.status === 'done', }); return (
{entry.imageUrl ? ( ) : (
)}
{entry.label}
{entry.sublabel}
{entry.errors.length > 0 && (
    {entry.errors.map((err, i) => (
  • {err}
  • ))}
)}
{statusText}
); } function ImportTabNav() { return ( ); }