diff --git a/src/components/context/LibraryOptionsContext.tsx b/src/components/context/LibraryOptionsContext.tsx index db2a2108..933765c6 100644 --- a/src/components/context/LibraryOptionsContext.tsx +++ b/src/components/context/LibraryOptionsContext.tsx @@ -31,6 +31,7 @@ export const DefaultLibraryOptions: LibraryOptions = { sortDesc: undefined, sorts: undefined, unread: undefined, + tracker: {}, showTabSize: false, }; diff --git a/src/components/library/LibraryOptionsPanel.tsx b/src/components/library/LibraryOptionsPanel.tsx index 44bcc771..2d37ce6a 100644 --- a/src/components/library/LibraryOptionsPanel.tsx +++ b/src/components/library/LibraryOptionsPanel.tsx @@ -16,6 +16,8 @@ import { SortRadioInput } from '@/components/atoms/SortRadioInput'; import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput'; import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { OptionsTabs } from '@/components/molecules/OptionsTabs'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; +import { Trackers } from '@/lib/data/Trackers.ts'; const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = { filter: 'global.label.filter', @@ -41,6 +43,9 @@ export const LibraryOptionsPanel: React.FC = ({ open, onClose }) => { const { t } = useTranslation(); const { options, setOptions } = useLibraryOptionsContext(); + const trackerList = requestManager.useGetTrackerList(); + const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []); + const handleFilterChange = (key: T, value: LibraryOptions[T]) => { setOptions((v) => ({ ...v, [key]: value })); }; @@ -65,6 +70,17 @@ export const LibraryOptionsPanel: React.FC = ({ open, onClose }) => { checked={options.downloaded} onChange={(c) => handleFilterChange('downloaded', c)} /> + {t('global.grid_layout.title')} + {loggedInTrackers.map((tracker) => ( + + handleFilterChange('tracker', { ...options.tracker, [tracker.id]: checked }) + } + /> + ))} ); } diff --git a/src/components/library/LibraryOptionsProvider.tsx b/src/components/library/LibraryOptionsProvider.tsx index 0788d1e8..a3da1567 100644 --- a/src/components/library/LibraryOptionsProvider.tsx +++ b/src/components/library/LibraryOptionsProvider.tsx @@ -18,7 +18,10 @@ interface IProps { export const LibraryOptionsContextProvider: React.FC = ({ children }) => { const [options, setOptions] = useLocalStorage('libraryOptions', DefaultLibraryOptions); - const value = useMemo(() => ({ options, setOptions }), [options, setOptions]); + const value = useMemo( + () => ({ options: { ...DefaultLibraryOptions, ...options }, setOptions }), + [options, setOptions], + ); return {children}; }; diff --git a/src/components/library/useGetVisibleLibraryMangas.ts b/src/components/library/useGetVisibleLibraryMangas.ts index 7170749a..2c8d168d 100644 --- a/src/components/library/useGetVisibleLibraryMangas.ts +++ b/src/components/library/useGetVisibleLibraryMangas.ts @@ -8,9 +8,10 @@ import { StringParam, useQueryParam } from 'use-query-params'; import { useMemo } from 'react'; -import { LibrarySortMode, NullAndUndefined, TManga } from '@/typings.ts'; +import { LibraryOptions, LibrarySortMode, NullAndUndefined, TManga } from '@/typings.ts'; import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx'; import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; +import { Trackers } from '@/lib/data/Trackers.ts'; const unreadFilter = (unread: NullAndUndefined, { unreadCount }: TManga): boolean => { switch (unread) { @@ -45,18 +46,37 @@ const queryGenreFilter = (query: NullAndUndefined, { genre }: TManga): b return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element)); }; +const trackerFilter = (trackFilters: LibraryOptions['tracker'], manga: TManga): boolean => + Object.entries(trackFilters) + .map(([trackFilterId, trackFilterState]) => { + const mangaTrackers = Trackers.getTrackers(manga.trackRecords.nodes); + const isTrackerBound = mangaTrackers.some((tracker) => tracker.id === Number(trackFilterId)); + + switch (trackFilterState) { + case true: + return isTrackerBound; + case false: + return !isTrackerBound; + default: + return true; + } + }) + .every((matchesFilter) => matchesFilter); + const filterManga = ( mangas: TManga[], query: NullAndUndefined, unread: NullAndUndefined, downloaded: NullAndUndefined, + tracker: LibraryOptions['tracker'], ignoreFilters: boolean, ): TManga[] => mangas.filter((manga) => { const ignoreFiltersWhileSearching = ignoreFilters && query?.length; const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga); const matchesFilters = - ignoreFiltersWhileSearching || (downloadedFilter(downloaded, manga) && unreadFilter(unread, manga)); + ignoreFiltersWhileSearching || + (downloadedFilter(downloaded, manga) && unreadFilter(unread, manga) && trackerFilter(tracker, manga)); return matchesSearch && matchesFilters; }); @@ -116,12 +136,12 @@ const sortManga = ( export const useGetVisibleLibraryMangas = (mangas: TManga[]) => { const [query] = useQueryParam('query', StringParam); const { options } = useLibraryOptionsContext(); - const { unread, downloaded } = options; + const { unread, downloaded, tracker } = options; const { settings } = useMetadataServerSettings(); const filteredMangas = useMemo( - () => filterManga(mangas, query, unread, downloaded, settings.ignoreFilters), - [mangas, query, unread, downloaded, settings.ignoreFilters], + () => filterManga(mangas, query, unread, downloaded, tracker, settings.ignoreFilters), + [mangas, query, unread, downloaded, tracker, settings.ignoreFilters], ); const sortedMangas = useMemo( () => sortManga(filteredMangas, options.sorts, options.sortDesc), diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index db3020a0..3c03616e 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -354,6 +354,7 @@ "label": { "bookmarked": "Bookmarked", "downloaded": "Downloaded", + "tracked": "Tracked", "unread": "Unread" } }, diff --git a/src/typings.ts b/src/typings.ts index c5133fe2..81322436 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -21,6 +21,7 @@ import { MetaType, SourcePreferenceChangeInput, } from '@/lib/graphql/generated/graphql.ts'; +import { TBaseTracker } from '@/lib/data/Trackers.ts'; type GenericLocation = Omit & { state?: State }; @@ -391,6 +392,7 @@ export interface LibraryOptions { sorts: NullAndUndefined; sortDesc: NullAndUndefined; showTabSize: boolean; + tracker: Record>; } export type UpdateCheck = {