Add library filter for trackers

This commit is contained in:
schroda
2024-03-28 20:00:46 +01:00
parent 511b1f7c5e
commit 816cbee145
6 changed files with 49 additions and 6 deletions

View File

@@ -31,6 +31,7 @@ export const DefaultLibraryOptions: LibraryOptions = {
sortDesc: undefined, sortDesc: undefined,
sorts: undefined, sorts: undefined,
unread: undefined, unread: undefined,
tracker: {},
showTabSize: false, showTabSize: false,
}; };

View File

@@ -16,6 +16,8 @@ import { SortRadioInput } from '@/components/atoms/SortRadioInput';
import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput'; import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput';
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
import { OptionsTabs } from '@/components/molecules/OptionsTabs'; 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 } = { const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
filter: 'global.label.filter', filter: 'global.label.filter',
@@ -41,6 +43,9 @@ export const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { options, setOptions } = useLibraryOptionsContext(); const { options, setOptions } = useLibraryOptionsContext();
const trackerList = requestManager.useGetTrackerList();
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
const handleFilterChange = <T extends keyof LibraryOptions>(key: T, value: LibraryOptions[T]) => { const handleFilterChange = <T extends keyof LibraryOptions>(key: T, value: LibraryOptions[T]) => {
setOptions((v) => ({ ...v, [key]: value })); setOptions((v) => ({ ...v, [key]: value }));
}; };
@@ -65,6 +70,17 @@ export const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
checked={options.downloaded} checked={options.downloaded}
onChange={(c) => handleFilterChange('downloaded', c)} onChange={(c) => handleFilterChange('downloaded', c)}
/> />
<FormLabel sx={{ mt: 2 }}>{t('global.grid_layout.title')}</FormLabel>
{loggedInTrackers.map((tracker) => (
<ThreeStateCheckboxInput
key={tracker.id}
label={tracker.name}
checked={options.tracker[tracker.id]}
onChange={(checked) =>
handleFilterChange('tracker', { ...options.tracker, [tracker.id]: checked })
}
/>
))}
</> </>
); );
} }

View File

@@ -18,7 +18,10 @@ interface IProps {
export const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => { export const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => {
const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DefaultLibraryOptions); const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DefaultLibraryOptions);
const value = useMemo(() => ({ options, setOptions }), [options, setOptions]); const value = useMemo(
() => ({ options: { ...DefaultLibraryOptions, ...options }, setOptions }),
[options, setOptions],
);
return <LibraryOptionsContext.Provider value={value}>{children}</LibraryOptionsContext.Provider>; return <LibraryOptionsContext.Provider value={value}>{children}</LibraryOptionsContext.Provider>;
}; };

View File

@@ -8,9 +8,10 @@
import { StringParam, useQueryParam } from 'use-query-params'; import { StringParam, useQueryParam } from 'use-query-params';
import { useMemo } from 'react'; 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 { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
import { Trackers } from '@/lib/data/Trackers.ts';
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: TManga): boolean => { const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: TManga): boolean => {
switch (unread) { switch (unread) {
@@ -45,18 +46,37 @@ const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: TManga): b
return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element)); 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 = ( const filterManga = (
mangas: TManga[], mangas: TManga[],
query: NullAndUndefined<string>, query: NullAndUndefined<string>,
unread: NullAndUndefined<boolean>, unread: NullAndUndefined<boolean>,
downloaded: NullAndUndefined<boolean>, downloaded: NullAndUndefined<boolean>,
tracker: LibraryOptions['tracker'],
ignoreFilters: boolean, ignoreFilters: boolean,
): TManga[] => ): TManga[] =>
mangas.filter((manga) => { mangas.filter((manga) => {
const ignoreFiltersWhileSearching = ignoreFilters && query?.length; const ignoreFiltersWhileSearching = ignoreFilters && query?.length;
const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga); const matchesSearch = queryFilter(query, manga) || queryGenreFilter(query, manga);
const matchesFilters = const matchesFilters =
ignoreFiltersWhileSearching || (downloadedFilter(downloaded, manga) && unreadFilter(unread, manga)); ignoreFiltersWhileSearching ||
(downloadedFilter(downloaded, manga) && unreadFilter(unread, manga) && trackerFilter(tracker, manga));
return matchesSearch && matchesFilters; return matchesSearch && matchesFilters;
}); });
@@ -116,12 +136,12 @@ const sortManga = (
export const useGetVisibleLibraryMangas = (mangas: TManga[]) => { export const useGetVisibleLibraryMangas = (mangas: TManga[]) => {
const [query] = useQueryParam('query', StringParam); const [query] = useQueryParam('query', StringParam);
const { options } = useLibraryOptionsContext(); const { options } = useLibraryOptionsContext();
const { unread, downloaded } = options; const { unread, downloaded, tracker } = options;
const { settings } = useMetadataServerSettings(); const { settings } = useMetadataServerSettings();
const filteredMangas = useMemo( const filteredMangas = useMemo(
() => filterManga(mangas, query, unread, downloaded, settings.ignoreFilters), () => filterManga(mangas, query, unread, downloaded, tracker, settings.ignoreFilters),
[mangas, query, unread, downloaded, settings.ignoreFilters], [mangas, query, unread, downloaded, tracker, settings.ignoreFilters],
); );
const sortedMangas = useMemo( const sortedMangas = useMemo(
() => sortManga(filteredMangas, options.sorts, options.sortDesc), () => sortManga(filteredMangas, options.sorts, options.sortDesc),

View File

@@ -354,6 +354,7 @@
"label": { "label": {
"bookmarked": "Bookmarked", "bookmarked": "Bookmarked",
"downloaded": "Downloaded", "downloaded": "Downloaded",
"tracked": "Tracked",
"unread": "Unread" "unread": "Unread"
} }
}, },

View File

@@ -21,6 +21,7 @@ import {
MetaType, MetaType,
SourcePreferenceChangeInput, SourcePreferenceChangeInput,
} from '@/lib/graphql/generated/graphql.ts'; } from '@/lib/graphql/generated/graphql.ts';
import { TBaseTracker } from '@/lib/data/Trackers.ts';
type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State }; type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State };
@@ -391,6 +392,7 @@ export interface LibraryOptions {
sorts: NullAndUndefined<LibrarySortMode>; sorts: NullAndUndefined<LibrarySortMode>;
sortDesc: NullAndUndefined<boolean>; sortDesc: NullAndUndefined<boolean>;
showTabSize: boolean; showTabSize: boolean;
tracker: Record<TBaseTracker['id'], NullAndUndefined<boolean>>;
} }
export type UpdateCheck = { export type UpdateCheck = {