import { useCallback, useEffect, useMemo, useState } from 'react' import { ChevronUp, Home } from 'lucide-react' import { filesAPI, type FileEntry, type FileListing } from '../api/files' import { libraryAPI } from '../api/library' import type { Library } from '../types' import { AutoOrganizeSettingsPanel } from './AutoOrganizeSettingsPanel' import { FileBrowserRoots } from './FileBrowserRoots' import { FileEntriesTable } from './FileEntriesTable' import { FileManagerToolbar } from './FileManagerToolbar' import { FileOperationsPanel } from './FileOperationsPanel' import { ManualOrganizePanel } from './ManualOrganizePanel' import { useAutoOrganizeSettings } from './useAutoOrganizeSettings' import { useFileOperations } from './useFileOperations' import { isCloudLibraryPath } from './fileManagerModel' import { useManualOrganize } from './useManualOrganize' // FileManagerPage provides a focused local storage view: // browse allowed roots, optionally recurse, and perform safe local operations. export function FileManagerPage() { const [libraries, setLibraries] = useState([]) const [path, setPath] = useState('') const [data, setData] = useState(null) const [error, setError] = useState('') const [loading, setLoading] = useState(true) const [recursive, setRecursive] = useState(false) const [scrapeAfter, setScrapeAfter] = useState(true) const autoOrganize = useAutoOrganizeSettings({ onScrapeAfterChange: setScrapeAfter }) const currentDir = useMemo(() => { if (data?.path) return data.path return path }, [data?.path, path]) const localLibraries = useMemo( () => libraries.filter((library) => !isCloudLibraryPath(library.path)), [libraries], ) const refresh = useCallback(() => { setLoading(true) setError('') filesAPI .list(path, recursive ? 5000 : 1000, recursive) .then(setData) .catch((err: unknown) => { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '加载失败' setError(msg) }) .finally(() => setLoading(false)) }, [path, recursive]) useEffect(() => { refresh() }, [refresh]) useEffect(() => { libraryAPI.list({ includeHidden: true }).then(setLibraries).catch(() => undefined) }, []) const fileOperations = useFileOperations({ currentDir, path, refresh }) const manualOrganize = useManualOrganize({ currentDir, localLibraries, selectedPath: fileOperations.selected?.path, selectedPaths: fileOperations.selectedPaths, scrapeAfter, onScrapeAfterChange: setScrapeAfter, onClearSelected: () => fileOperations.setSelectedPaths([]), refresh, }) const enter = (e: FileEntry) => { if (e.is_dir) setPath(e.path) } return (

文件管理

在下载目录中选择文件夹或视频,直接设置目标并整理入库。

void autoOrganize.save()} onRunNow={autoOrganize.runNow} onTabChange={autoOrganize.setActiveTab} onConfigChange={autoOrganize.changeConfig} /> {data?.path && ( fileOperations.setSelectedPaths([])} onLibraryChange={manualOrganize.setOrganizeLibraryID} onDestPathChange={manualOrganize.setOrganizeDestPath} onMediaTypeChange={manualOrganize.setOrganizeMediaType} onTransferModeChange={manualOrganize.setOrganizeTransferMode} onScanAfterChange={manualOrganize.setScanAfter} onScrapeAfterChange={manualOrganize.onScrapeAfterChange} onPreview={manualOrganize.preview} onRun={manualOrganize.run} /> )} {data?.path && ( )} {loading &&

加载中…

} {error &&
{error}
} {!loading && data && !data.path && data.roots && ( )} {!loading && data?.path && (
{data.parent && ( )}
{data.path}
)} {!loading && data?.entries && data.entries.length > 0 && ( fileOperations.setSelectedPaths(checked ? data.entries?.map((entry) => entry.path) ?? [] : [])} onToggleSelectedPath={fileOperations.toggleSelectedPath} onEnter={enter} onChoose={fileOperations.choose} /> )} {!loading && data?.entries && data.entries.length === 0 &&

空目录。

}
) }