diff --git a/webui/src/routes/import/-import.types.ts b/webui/src/routes/import/-import.types.ts index 8828a3c5..73821b08 100644 --- a/webui/src/routes/import/-import.types.ts +++ b/webui/src/routes/import/-import.types.ts @@ -23,11 +23,21 @@ export interface ImportStagingFile { manual_match?: ImportTrackResult; } +/** While a large staging folder is still being scanned in the background (#947), the + * staging endpoints return `scanning: true` + progress instead of files/groups; the query + * polls until the scan completes and real data arrives. */ +export interface ImportScanProgress { + scanned: number; + total: number; +} + export interface ImportStagingFilesPayload { success: boolean; files?: ImportStagingFile[]; staging_path?: string; error?: string; + scanning?: boolean; + progress?: ImportScanProgress; } export interface ImportStagingGroup { @@ -47,6 +57,8 @@ export interface ImportStagingGroupsPayload { success: boolean; groups?: ImportStagingGroup[]; error?: string; + scanning?: boolean; + progress?: ImportScanProgress; } export interface ImportAlbumResult { diff --git a/webui/src/routes/import/-route.test.tsx b/webui/src/routes/import/-route.test.tsx index b8572b95..afd184e4 100644 --- a/webui/src/routes/import/-route.test.tsx +++ b/webui/src/routes/import/-route.test.tsx @@ -250,6 +250,19 @@ describe('import route', () => { expect(await screen.findByText('Import folder: error')).toBeInTheDocument(); }); + it('shows scan progress while a large staging folder is still scanning (#947)', async () => { + server.use( + http.get('/api/import/staging/files', () => + HttpResponse.json({ success: true, scanning: true, progress: { scanned: 5, total: 20 } }), + ), + ); + + renderImportRoute(); + + expect(await screen.findByTestId('import-page')).toBeInTheDocument(); + expect(await screen.findByText(/Scanning 5 of 20 files/)).toBeInTheDocument(); + }); + it('stores the active tab in nested route paths', async () => { const { history } = renderImportRoute(); diff --git a/webui/src/routes/import/-ui/import-page.tsx b/webui/src/routes/import/-ui/import-page.tsx index 840c8d5e..b33eadfe 100644 --- a/webui/src/routes/import/-ui/import-page.tsx +++ b/webui/src/routes/import/-ui/import-page.tsx @@ -21,17 +21,24 @@ import { fallbackImage, RefreshIcon, useImportStaging } from './import-shared'; export function ImportPage() { useReactPageShell('import'); - const { refreshStaging, stagingFiles, stagingPath, stagingQuery } = useImportStaging(); + 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 (
{ + if (!scanning) return undefined; + const id = window.setInterval(() => { + void refetch(); + }, 1500); + return () => window.clearInterval(id); + }, [scanning, refetch]); + return { refreshStaging: async () => { clearFinishedJobs(); @@ -28,6 +44,8 @@ export function useImportStaging() { // Keep the empty fallback stable so staging-driven effects do not loop while loading. stagingFiles: stagingQuery.data?.files ?? EMPTY_STAGING_FILES, stagingPath: stagingQuery.data?.staging_path || 'Not configured', + scanning, + scanProgress: stagingQuery.data?.progress ?? null, stagingQuery, }; }