From 5899f11b94c0399814539addaa6a5d495166f6d0 Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Tue, 16 Sep 2025 18:00:23 -0500 Subject: [PATCH] N/A --- README.md | 7 ++ .../components/library/LibraryScanDrawer.jsx | 14 ++- frontend/src/pages/Library.jsx | 119 ++++++++---------- 3 files changed, 69 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 9b359e25..5a86ee8f 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,13 @@ Here’s how you can join the party: --- +## πŸ”§ Configuration Notes + +- 🎞️ **TMDB Metadata** – Add your TMDB API key in *Settings β†’ Media Libraries* to enable poster, backdrop, cast, and synopsis lookups for the local library scanner. +- πŸ› οΈ **FFmpeg & ffprobe** – Ensure FFmpeg (including `ffprobe`) is available to capture technical metadata. Dispatcharr will fall back to MediaInfo when ffprobe is missing, but FFmpeg is still recommended for best results. + +--- + ## πŸ“š Roadmap & Documentation - πŸ“š **Roadmap:** Coming soon! diff --git a/frontend/src/components/library/LibraryScanDrawer.jsx b/frontend/src/components/library/LibraryScanDrawer.jsx index aee116e1..09837412 100644 --- a/frontend/src/components/library/LibraryScanDrawer.jsx +++ b/frontend/src/components/library/LibraryScanDrawer.jsx @@ -16,6 +16,10 @@ import useLibraryStore from '../../store/library'; dayjs.extend(relativeTime); +const EMPTY_SCAN_LIST = []; + + + const statusColor = { pending: 'gray', running: 'blue', @@ -27,15 +31,15 @@ const statusColor = { }; const LibraryScanDrawer = ({ opened, onClose, libraryId }) => { - const fetchScans = useLibraryStore((s) => s.fetchScans); - const scansLoading = useLibraryStore((s) => s.scansLoading); - const scans = useLibraryStore((s) => s.scans[libraryId || 'all'] || []); + const scansLoading = useLibraryStore((state) => state.scansLoading); + const scans = + useLibraryStore((state) => state.scans[libraryId || 'all']) ?? EMPTY_SCAN_LIST; useEffect(() => { if (opened) { - fetchScans(libraryId); + useLibraryStore.getState().fetchScans(libraryId); } - }, [opened, libraryId, fetchScans]); + }, [opened, libraryId]); return ( { const [playbackModalOpen, setPlaybackModalOpen] = useState(false); const [formSubmitting, setFormSubmitting] = useState(false); - const { - libraries, - loading: librariesLoading, - fetchLibraries, - createLibrary, - updateLibrary, - deleteLibrary, - triggerScan, - selectedLibraryId, - setSelectedLibrary, - filters: libraryFilters, - setFilters: setLibraryFilters, - } = useLibraryStore( - (state) => ({ - libraries: state.libraries, - loading: state.loading, - fetchLibraries: state.fetchLibraries, - createLibrary: state.createLibrary, - updateLibrary: state.updateLibrary, - deleteLibrary: state.deleteLibrary, - triggerScan: state.triggerScan, - selectedLibraryId: state.selectedLibraryId, - setSelectedLibrary: state.setSelectedLibrary, - filters: state.filters, - setFilters: state.setFilters, - }), - shallow - ); + // ---- Library store (one selector per field/action) ---- + const libraries = useLibraryStore((s) => s.libraries); + const librariesLoading = useLibraryStore((s) => s.loading); + const fetchLibraries = useLibraryStore((s) => s.fetchLibraries); + const createLibrary = useLibraryStore((s) => s.createLibrary); + const updateLibrary = useLibraryStore((s) => s.updateLibrary); + const deleteLibrary = useLibraryStore((s) => s.deleteLibrary); + const triggerScan = useLibraryStore((s) => s.triggerScan); + const selectedLibraryId = useLibraryStore((s) => s.selectedLibraryId); + const setSelectedLibrary = useLibraryStore((s) => s.setSelectedLibrary); + const libraryFilters = useLibraryStore((s) => s.filters); + const setLibraryFilters = useLibraryStore((s) => s.setFilters); - const { - items, - loading: itemsLoading, - total, - page, - pageSize, - setPage, - filters: itemFilters, - setFilters: setItemFilters, - fetchItems, - openItem, - closeItem, - } = useMediaLibraryStore( - (state) => ({ - items: state.items, - loading: state.loading, - total: state.total, - page: state.page, - pageSize: state.pageSize, - setPage: state.setPage, - filters: state.filters, - setFilters: state.setFilters, - fetchItems: state.fetchItems, - openItem: state.openItem, - closeItem: state.closeItem, - }), - shallow - ); + // ---- Media store (one selector per field/action) ---- + const items = useMediaLibraryStore((s) => s.items); + const itemsLoading = useMediaLibraryStore((s) => s.loading); + const total = useMediaLibraryStore((s) => s.total); + const page = useMediaLibraryStore((s) => s.page); + const pageSize = useMediaLibraryStore((s) => s.pageSize); + const setPage = useMediaLibraryStore((s) => s.setPage); + const itemFilters = useMediaLibraryStore((s) => s.filters); + const setItemFilters = useMediaLibraryStore((s) => s.setFilters); + const fetchItems = useMediaLibraryStore((s) => s.fetchItems); + const openItem = useMediaLibraryStore((s) => s.openItem); + const closeItem = useMediaLibraryStore((s) => s.closeItem); + // ---- Effects ---- + + // Initial library load (run once on mount) useEffect(() => { fetchLibraries(); - }, [fetchLibraries]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // Debounced media search change -> update filters & reset page useEffect(() => { setItemFilters({ search: debouncedSearch }); setPage(1); - }, [debouncedSearch, setItemFilters, setPage]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedSearch]); + // Debounced library search change -> update filters & refresh libraries useEffect(() => { setLibraryFilters({ search: debouncedLibrarySearch }); fetchLibraries(); - }, [debouncedLibrarySearch, setLibraryFilters, fetchLibraries]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedLibrarySearch]); + + // Fetch items when selected library or paging/filter inputs change + useEffect(() => { + if (selectedLibraryId) { + fetchItems(selectedLibraryId); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + selectedLibraryId, + page, + pageSize, + itemFilters.type, + itemFilters.status, + itemFilters.year, + itemFilters.search, + ]); const selectedLibrary = useMemo( () => libraries.find((lib) => lib.id === selectedLibraryId) || null, [libraries, selectedLibraryId] ); - useEffect(() => { - if (selectedLibraryId) { - fetchItems(selectedLibraryId); - } - }, [selectedLibraryId, fetchItems, page, pageSize, itemFilters.type, itemFilters.status, itemFilters.year, itemFilters.search]); - + // ---- Handlers ---- const handleCreateOrUpdate = async (payload) => { setFormSubmitting(true); try { @@ -196,6 +182,7 @@ const LibraryPage = () => { } }; + // ---- Render ---- return (