From 12dd973179e8599fd34782b7f958c09d9609343d Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:32:23 +0200 Subject: [PATCH] Use gql for "mangas" VI - source mangas filter --- src/components/source/SourceOptions.tsx | 76 ++++++++++--------- .../source/filters/CheckBoxFilter.tsx | 2 +- src/components/source/filters/GroupFilter.tsx | 9 ++- .../source/filters/SelectFilter.tsx | 64 +--------------- src/components/source/filters/SortFilter.tsx | 6 +- src/components/source/filters/TextFilter.tsx | 2 +- .../source/filters/TriStateFilter.tsx | 34 ++++++++- src/lib/requests/RequestManager.ts | 38 ---------- src/screens/SourceMangas.tsx | 71 +++++++---------- src/typings.ts | 26 +------ 10 files changed, 113 insertions(+), 215 deletions(-) diff --git a/src/components/source/SourceOptions.tsx b/src/components/source/SourceOptions.tsx index ec462b68..00f67678 100644 --- a/src/components/source/SourceOptions.tsx +++ b/src/components/source/SourceOptions.tsx @@ -10,7 +10,7 @@ import FilterListIcon from '@mui/icons-material/FilterList'; import { Button, Stack, Box } from '@mui/material'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ISourceFilters, IState } from '@/typings'; +import { SourceFilters } from '@/typings'; import OptionsPanel from '@/components/molecules/OptionsPanel'; import CheckBoxFilter from '@/components/source/filters/CheckBoxFilter'; import HeaderFilter from '@/components/source/filters/HeaderFilter'; @@ -25,14 +25,14 @@ import SeperatorFilter from '@/components/source/filters/SeparatorFilter'; import StyledFab from '@/components/util/StyledFab'; interface IFilters { - sourceFilter: ISourceFilters[]; + sourceFilter: SourceFilters[]; updateFilterValue: Function; group: number | undefined; update: any; } interface IFilters1 { - sourceFilter: ISourceFilters[]; + sourceFilter: SourceFilters[]; updateFilterValue: Function; resetFilterValue: Function; setTriggerUpdate: Function; @@ -42,85 +42,89 @@ interface IFilters1 { export function Options({ sourceFilter, group, updateFilterValue, update }: IFilters) { return ( - {sourceFilter.map((e: ISourceFilters, index) => { + {sourceFilter.map((e, index) => { let checkif = update.find( (el: { group: number | undefined; position: number }) => el.group === group && el.position === index, ); checkif = checkif ? checkif.state : checkif; switch (e.type) { - case 'CheckBox': + case 'CheckBoxFilter': return ( ); - case 'Group': + case 'GroupFilter': return ( ); - case 'Header': - return ; - case 'Select': + case 'HeaderFilter': + return ; + case 'SelectFilter': return ( ); - case 'Separator': - return ; - case 'Sort': + case 'SeparatorFilter': + return ; + case 'SortFilter': return ( ); - case 'Text': + case 'TextFilter': return ( ); - case 'TriState': + case 'TriStateFilter': return ( ); default: - return ; + throw new Error(`Unknown source filter "${e}"`); } })} diff --git a/src/components/source/filters/CheckBoxFilter.tsx b/src/components/source/filters/CheckBoxFilter.tsx index 125da405..8219ae71 100644 --- a/src/components/source/filters/CheckBoxFilter.tsx +++ b/src/components/source/filters/CheckBoxFilter.tsx @@ -27,7 +27,7 @@ const CheckBoxFilter: React.FC = (props: Props) => { const upd = update.filter( (e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group), ); - updateFilterValue([...upd, { position, state: event.target.checked.toString(), group }]); + updateFilterValue([...upd, { type: 'checkBoxState', position, state: event.target.checked, group }]); }; if (state !== undefined) { diff --git a/src/components/source/filters/GroupFilter.tsx b/src/components/source/filters/GroupFilter.tsx index 2a4db407..f1cf7589 100644 --- a/src/components/source/filters/GroupFilter.tsx +++ b/src/components/source/filters/GroupFilter.tsx @@ -10,11 +10,14 @@ import { ExpandLess, ExpandMore } from '@mui/icons-material'; import { Collapse, ListItemButton, ListItemText, Stack, Box } from '@mui/material'; import React from 'react'; // eslint-disable-next-line import/no-cycle -import { ISourceFilters } from '@/typings'; +import { SourceFilters } from '@/typings'; import { Options } from '@/components/source/SourceOptions'; +// type ExcludeByName = T extends { __typename?: K } ? never : T; +type PickByName = T extends { __typename?: K } ? T : never; + interface Props { - state: ISourceFilters[]; + state: PickByName['filters']; name: string; position: number; updateFilterValue: Function; @@ -36,7 +39,7 @@ const GroupFilter: React.FC = (props: Props) => { {/* Container is moved outside 2, so content has to go inside 4 */} { - const vall = values.map((e) => e.displayname).indexOf(`${event.target.value}`); - setval(vall); - const upd = update.filter( - (e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group), - ); - updateFilterValue([...upd, { position, state: vall.toString(), group }]); - }; - - const rett = values.map((e: Selected) => ( - - {e.displayname} - - )); - return ( - - {name} - - - ); - } - return null; -} - function noSelect( values: string[], name: string, @@ -84,7 +40,7 @@ function noSelect( const upd = update.filter( (e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group), ); - updateFilterValue([...upd, { position, state: vall.toString(), group }]); + updateFilterValue([...upd, { type: 'selectState', position, state: vall, group }]); }; const rett = values.map((value: string) => ( @@ -104,21 +60,7 @@ function noSelect( return null; } -const SelectFilter: React.FC = ({ - values, - name, - state, - selected, - position, - updateFilterValue, - update, - group, -}) => { - if (selected === undefined) { - return noSelect(values, name, state, position, updateFilterValue, update, group); - } - - return hasSelect(values, name, state, position, updateFilterValue, update, group); -}; +const SelectFilter: React.FC = ({ values, name, state, position, updateFilterValue, update, group }) => + noSelect(values, name, state, position, updateFilterValue, update, group); export default SelectFilter; diff --git a/src/components/source/filters/SortFilter.tsx b/src/components/source/filters/SortFilter.tsx index beb4f6a3..4748cf29 100644 --- a/src/components/source/filters/SortFilter.tsx +++ b/src/components/source/filters/SortFilter.tsx @@ -9,13 +9,13 @@ import { ExpandLess, ExpandMore } from '@mui/icons-material'; import { Collapse, ListItemButton, ListItemText, Stack, Box } from '@mui/material'; import React from 'react'; -import { IState } from '@/typings'; import SortRadioInput from '@/components/atoms/SortRadioInput'; +import { SortSelectionInput } from '@/lib/graphql/generated/graphql.ts'; interface Props { values: any; name: string; - state: IState; + state: SortSelectionInput; position: number; group: number | undefined; updateFilterValue: Function; @@ -45,7 +45,7 @@ const SortFilter: React.FC = (props: Props) => { const upd = update.filter( (e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group), ); - updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]); + updateFilterValue([...upd, { type: 'sortState', position, state: tmp, group }]); }; return ( diff --git a/src/components/source/filters/TextFilter.tsx b/src/components/source/filters/TextFilter.tsx index 7dd82065..7b62304b 100644 --- a/src/components/source/filters/TextFilter.tsx +++ b/src/components/source/filters/TextFilter.tsx @@ -29,7 +29,7 @@ const TextFilter: React.FC = (props) => { const upd = update.filter( (el: { position: number; group: number | undefined }) => !(position === el.position && group === el.group), ); - updateFilterValue([...upd, { position, state: inputText, group }]); + updateFilterValue([...upd, { type: 'textState', position, state: inputText, group }]); }, [inputText]); if (state !== undefined) { diff --git a/src/components/source/filters/TriStateFilter.tsx b/src/components/source/filters/TriStateFilter.tsx index e9270b25..70a53094 100644 --- a/src/components/source/filters/TriStateFilter.tsx +++ b/src/components/source/filters/TriStateFilter.tsx @@ -8,9 +8,10 @@ import React from 'react'; import ThreeStateCheckboxInput from '@/components/atoms/ThreeStateCheckboxInput'; +import { TriState } from '@/lib/graphql/generated/graphql.ts'; interface Props { - state: number; + state: TriState; name: string; position: number; group: number | undefined; @@ -18,9 +19,35 @@ interface Props { update: any; } +const convertTriStateToNumber = (triState: TriState): number => { + switch (triState) { + case TriState.Ignore: + return 0; + case TriState.Include: + return 1; + case TriState.Exclude: + return 2; + default: + throw new Error(`Unexpected TriState ${triState}`); + } +}; + +const convertNumberToTriState = (state: number): TriState => { + switch (state) { + case 0: + return TriState.Ignore; + case 1: + return TriState.Include; + case 2: + return TriState.Exclude; + default: + throw new Error(`Unexpected state number ${state}`); + } +}; + const TriStateFilter: React.FC = (props) => { const { state, name, position, group, updateFilterValue, update } = props; - const [val, setval] = React.useState(Number(state)); + const [val, setval] = React.useState(convertTriStateToNumber(state)); const handleChange = (checked: boolean | null | undefined) => { // eslint-disable-next-line no-nested-ternary @@ -32,8 +59,9 @@ const TriStateFilter: React.FC = (props) => { updateFilterValue([ ...upd, { + type: 'triState', position, - state: newState.toString(), + state: convertNumberToTriState(newState), group, }, ]); diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index 2c61c5d2..7721e253 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -28,10 +28,8 @@ import { BackupValidationResult, IChapter, IMangaChapter, - ISourceFilters, PaginatedList, SourcePreferences, - SourceSearchResult, } from '@/typings.ts'; import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts'; import storage from '@/util/localStorage.tsx'; @@ -1208,22 +1206,6 @@ export class RequestManager { return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } }); } - public useGetSourceFilters( - sourceId: string, - reset?: boolean, - swrOptions?: SWROptions, - ): AbortableSWRResponse { - return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/filters`, { swrOptions }); - } - - public setSourceFilters(sourceId: string, filters: { position: number; state: string }[]): AbortableAxiosResponse { - return this.doRequest(HttpMethod.POST, `source/${sourceId}/filters`, { data: filters }); - } - - public resetSourceFilters(sourceId: string): AbortableAxiosResponse { - return this.doRequest(HttpMethod.GET, `source/${sourceId}/filters?reset=true`); - } - public useSourceSearch( source: string, query?: string, @@ -1241,26 +1223,6 @@ export class RequestManager { ); } - public useSourceQuickSearch( - sourceId: string, - searchTerm: string, - filters: { position: number; state: string }[], - initialPages?: number, - swrOptions?: SWRInfiniteOptions, - ): AbortableSWRInfiniteResponse { - return this.doRequest(HttpMethod.SWR_POST_INFINITE, '', { - data: { searchTerm, filter: filters }, - swrOptions: { - getEndpoint: (page, previousData) => - previousData?.hasNextPage ?? true - ? `source/${sourceId}/quick-search?searchTerm=${searchTerm}&pageNum=${page + 1}` - : null, - initialSize: initialPages, - ...swrOptions, - } as typeof swrOptions, - }); - } - public useGetManga( mangaId: number | string, options?: QueryHookOptions, diff --git a/src/screens/SourceMangas.tsx b/src/screens/SourceMangas.tsx index 46d780ff..f3e3d1c4 100644 --- a/src/screens/SourceMangas.tsx +++ b/src/screens/SourceMangas.tsx @@ -74,6 +74,7 @@ export enum SourceContentType { } interface IPos { + type: 'selectState' | 'textState' | 'checkBoxState' | 'triState' | 'sortState'; position: number; state: any; group?: number; @@ -127,30 +128,30 @@ const useSourceManga = ( result = requestManager.useSourceSearch(sourceId, searchTerm ?? '', undefined, initialPages); break; case SourceContentType.FILTER: - result = requestManager.useSourceSearch(sourceId, undefined, [], initialPages); - // TODO - update filters to gql - // result = requestManager.useSourceQuickSearch( - // sourceId, - // '', - // filters.map((filter) => { - // const { position, state, group } = filter; - // - // const isPartOfGroup = group !== undefined; - // if (isPartOfGroup) { - // return { - // position: group, - // state: JSON.stringify({ - // position, - // state, - // }), - // }; - // } - // - // return filter; - // }), - // initialPages, - // { disableCache: true }, - // ); + result = requestManager.useSourceSearch( + sourceId, + undefined, + filters.map((filter) => { + const { position, state, group } = filter; + + const isPartOfGroup = group !== undefined; + if (isPartOfGroup) { + return { + position: group, + groupChange: { + position, + [filter.type]: state, + }, + }; + } + + return { + position, + [filter.type]: state, + }; + }), + initialPages, + ); break; default: throw new Error(`Unknown ContentType "${contentType}"`); @@ -222,10 +223,9 @@ export default function SourceMangas() { const mangas = (data?.fetchSourceManga.mangas as MangaType[]) ?? []; const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false; - const { data: filters = [], mutate: mutateFilters } = requestManager.useGetSourceFilters(sourceId); const { data: sourceData } = requestManager.useGetSource(sourceId); const source = sourceData?.source; - const [triggerDataRefresh, setTriggerDataRefresh] = useState(false); + const filters = source?.filters ?? []; const message = !isLoading ? t(SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY[contentType]) : undefined; const isLocalSource = sourceId === '0'; @@ -272,14 +272,6 @@ export default function SourceMangas() { const resetFilters = useCallback(async () => { setDialogFiltersToApply([]); setFiltersToApply([]); - try { - // required since previous implementation used to set the filters on server side (server caches them), thus, it has to be made sure that they are reset - await requestManager.resetSourceFilters(sourceId); - mutateFilters(); - } catch (error) { - // ignore - } - setTriggerDataRefresh(true); }, [sourceId]); useEffect( @@ -297,16 +289,6 @@ export default function SourceMangas() { [searchTerm, contentType], ); - // TODO - check when fixing filters - useEffect(() => { - if (!triggerDataRefresh) { - return; - } - - // refreshData(); - setTriggerDataRefresh(false); - }, [triggerDataRefresh]); - useEffect(() => { setTitle(source?.displayName ?? t('source.title')); setAction( @@ -378,7 +360,6 @@ export default function SourceMangas() { updateFilterValue={setDialogFiltersToApply} setTriggerUpdate={() => { setFiltersToApply(dialogFiltersToApply); - setTriggerDataRefresh(true); }} resetFilterValue={resetFilters} update={dialogFiltersToApply} diff --git a/src/typings.ts b/src/typings.ts index fc08e7ae..ca0669cc 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -10,7 +10,7 @@ import { OverridableComponent } from '@mui/material/OverridableComponent'; import { SvgIconTypeMap } from '@mui/material/SvgIcon/SvgIcon'; import { ParseKeys } from 'i18next'; import { Location } from 'react-router-dom'; -import { ExtensionType, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts'; +import { ExtensionType, GetSourceQuery, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts'; export type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] @@ -43,29 +43,7 @@ export interface ISource { displayName: string; } -export interface ISourceFilters { - type: string; - filter: ISourceFilter; -} - -export interface ISourceFilter { - name: string; - state: number | string | boolean | ISourceFilters[] | IState; - values?: string[]; - displayValues?: string[]; - selected?: ISelected; -} - -export interface ISelected { - displayname: string; - value: string; - _value: string; -} - -export interface IState { - ascending: boolean; - index: number; -} +export type SourceFilters = GetSourceQuery['source']['filters'][number]; export interface IMetadataMigration { appKeyPrefix?: { oldPrefix: string; newPrefix: string };