Skip to content

Commit 0a73496

Browse files
committed
Use gql for "mangas" III - global search
1 parent af9d49e commit 0a73496

File tree

5 files changed

+138
-27
lines changed

5 files changed

+138
-27
lines changed

src/components/source/SourceMangaGrid.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
*/
88

99
import { useTranslation } from 'react-i18next';
10-
import { IMangaCard } from '@/typings';
1110
import MangaGrid, { IMangaGridProps } from '@/components/MangaGrid';
11+
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
1212

13-
function filterManga(mangas: IMangaCard[]): IMangaCard[] {
13+
function filterManga(mangas: MangaType[]): MangaType[] {
1414
return mangas;
1515
}
1616

src/lib/graphql/mutations/SourceMutation.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77
*/
88

99
import gql from 'graphql-tag';
10-
import { FULL_MANGA_FIELDS, FULL_SOURCE_FIELDS } from '@/lib/graphql/Fragments';
10+
import { BASE_MANGA_FIELDS, FULL_SOURCE_FIELDS } from '@/lib/graphql/Fragments';
1111

1212
export const GET_SOURCE_MANGAS_FETCH = gql`
13-
${FULL_MANGA_FIELDS}
13+
${BASE_MANGA_FIELDS}
1414
mutation GET_SOURCE_MANGAS_FETCH($input: FetchSourceMangaInput!) {
1515
fetchSourceManga(input: $input) {
1616
clientMutationId
1717
hasNextPage
1818
mangas {
19-
...FULL_MANGA_FIELDS
19+
...BASE_MANGA_FIELDS
2020
}
2121
}
2222
}

src/lib/requests/RequestManager.ts

+115-15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
useQuery,
2222
} from '@apollo/client';
2323
import { OperationVariables } from '@apollo/client/core';
24+
import { useState } from 'react';
2425
import {
2526
BackupValidationResult,
2627
ICategory,
@@ -58,6 +59,9 @@ import {
5859
EnqueueChapterDownloadMutationVariables,
5960
EnqueueChapterDownloadsMutation,
6061
EnqueueChapterDownloadsMutationVariables,
62+
FetchSourceMangaInput,
63+
FetchSourceMangaType,
64+
FilterChangeInput,
6165
GetAboutQuery,
6266
GetAboutQueryVariables,
6367
GetExtensionsFetchMutation,
@@ -74,6 +78,8 @@ import {
7478
GetMangaFetchMutationVariables,
7579
GetMangaQuery,
7680
GetMangaQueryVariables,
81+
GetSourceMangasFetchMutation,
82+
GetSourceMangasFetchMutationVariables,
7783
GetSourceQuery,
7884
GetSourceQueryVariables,
7985
GetSourcesQuery,
@@ -214,6 +220,13 @@ type AbortableApolloUseMutationResponse<Data = any, Variables extends OperationV
214220
MutationTuple<Data, Variables>[0],
215221
MutationTuple<Data, Variables>[1] & AbortableRequest,
216222
];
223+
type AbortableApolloUseMutationPaginatedResponse<
224+
Data = any,
225+
Variables extends OperationVariables = OperationVariables,
226+
> = [
227+
(page: number) => Promise<FetchResult<Data>>,
228+
(MutationTuple<Data, Variables>[1] & AbortableRequest & { size: number; loadingMore: boolean })[],
229+
];
217230
type AbortableApolloMutationResponse<Data = any> = { response: Promise<FetchResult<Data>> } & AbortableRequest;
218231

219232
const isLoadingMore = (swrResult: SWRInfiniteResponse): boolean => {
@@ -625,6 +638,99 @@ export class RequestManager {
625638
return this.doRequestNew(GQLMethod.USE_QUERY, GET_SOURCE, { id }, options);
626639
}
627640

641+
public useGetSourceMangas(
642+
input: FetchSourceMangaInput,
643+
options?: MutationHookOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
644+
): AbortableApolloUseMutationPaginatedResponse<
645+
GetSourceMangasFetchMutation,
646+
GetSourceMangasFetchMutationVariables
647+
> {
648+
const createPaginatedResult = (
649+
result: AbortableApolloUseMutationResponse[1],
650+
page: number,
651+
): AbortableApolloUseMutationPaginatedResponse[1][number] => {
652+
const loading = result.loading || !result.called;
653+
return {
654+
...result,
655+
loading,
656+
size: page,
657+
loadingMore: loading && page > 1,
658+
};
659+
};
660+
661+
// TODO - implement caching
662+
// - ? global cache with revalidating (same as SWR does, revalidate each page starting with 1st until the first page is reached whose data didn't change)
663+
// - ? saving fetched mangas in location state and only "cache" when navigating prev/next
664+
const [mutate, result] = this.doRequestNew<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>(
665+
GQLMethod.USE_MUTATION,
666+
GET_SOURCE_MANGAS_FETCH,
667+
{ input },
668+
options,
669+
);
670+
671+
const [previousResults, setPreviousResults] = useState<AbortableApolloUseMutationPaginatedResponse[1]>([
672+
createPaginatedResult(result, input.page),
673+
]);
674+
675+
const [contentType, setContentType] = useState(input.type);
676+
const [query, setQuery] = useState(input.query);
677+
const [page, setPage] = useState(input.page);
678+
679+
const paginatedResult = createPaginatedResult(result, page);
680+
681+
// TODO - option "global cache with revalidating"
682+
// replace previousResults with cache
683+
// cache specific response
684+
// cache "base" key to specific page keys to be able to retrieve all necessary cached pages
685+
// get cached results
686+
// revalidate in background - revalidate first page -> result changed? revalidate every page until cached result and response is the same
687+
688+
// wrap "mutate" function to align with the expected type, which allows only passing a "page" argument
689+
const wrappedMutate = (newPage: number) => {
690+
const resetPreviousResultForInitialLoad = newPage < page;
691+
if (resetPreviousResultForInitialLoad) {
692+
setPreviousResults(previousResults.filter((prevResult) => prevResult.size <= newPage));
693+
}
694+
695+
if (newPage !== page) {
696+
setPage(newPage);
697+
}
698+
699+
return mutate({
700+
variables: {
701+
input: {
702+
...input,
703+
page: newPage,
704+
},
705+
},
706+
});
707+
};
708+
709+
const contentTypeChanged = contentType !== input.type;
710+
const queryChanged = query !== input.query;
711+
// instantly return empty results in case the provided variables changed - wait until the hook returns empty data,
712+
// otherwise, updating the previous results will revert the reset
713+
const resetPreviousResult = (queryChanged || contentTypeChanged) && !paginatedResult.data;
714+
let updatedResults = [
715+
...(resetPreviousResult ? [{ ...paginatedResult, size: page, loadingMore: false }] : previousResults),
716+
];
717+
718+
if (resetPreviousResult) {
719+
setContentType(input.type);
720+
setQuery(input.query);
721+
setPreviousResults([paginatedResult]);
722+
}
723+
724+
const resultChanged = previousResults[page - 1]?.loading !== paginatedResult.loading;
725+
const updatePreviousResult = resultChanged && !resetPreviousResult;
726+
if (updatePreviousResult) {
727+
updatedResults = [...previousResults.slice(0, page - 1), paginatedResult];
728+
setPreviousResults(updatedResults);
729+
}
730+
731+
return [wrappedMutate, updatedResult];
732+
}
733+
628734
public useGetSourcePopularMangas(
629735
sourceId: string,
630736
initialPages?: number,
@@ -683,21 +789,15 @@ export class RequestManager {
683789
}
684790

685791
public useSourceSearch(
686-
sourceId: string,
687-
searchTerm: string,
688-
initialPages?: number,
689-
swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
690-
): AbortableSWRInfiniteResponse<SourceSearchResult> {
691-
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
692-
swrOptions: {
693-
getEndpoint: (page, previousData) =>
694-
previousData?.hasNextPage ?? true
695-
? `source/${sourceId}/search?searchTerm=${searchTerm}&pageNum=${page + 1}`
696-
: null,
697-
initialSize: initialPages,
698-
...swrOptions,
699-
} as typeof swrOptions,
700-
});
792+
source: string,
793+
query: string,
794+
filters?: FilterChangeInput[],
795+
options?: MutationHookOptions<GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables>,
796+
): AbortableApolloUseMutationPaginatedResponse<
797+
GetSourceMangasFetchMutation,
798+
GetSourceMangasFetchMutationVariables
799+
> {
800+
return this.useGetSourceMangas({ type: FetchSourceMangaType.Search, source, query, filters, page: 1 }, options);
701801
}
702802

703803
public useSourceQuickSearch(

src/screens/SearchAll.tsx

+10-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import LangSelect from '@/components/navbar/action/LangSelect';
2121
import MangaGrid from '@/components/MangaGrid';
2222
import NavbarContext from '@/components/context/NavbarContext';
2323
import { useDebounce } from '@/components/manga/hooks';
24+
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
2425

2526
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
2627
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
@@ -99,15 +100,17 @@ const SourceSearchPreview = React.memo(
99100
const skipRequest = !searchString;
100101

101102
const { id, displayName, lang } = source;
102-
const {
103-
data: searchResult,
104-
isLoading,
105-
error,
106-
abortRequest,
107-
} = requestManager.useSourceQuickSearch(id, searchString ?? '', [], 1, { skipRequest });
108-
const mangas = !isLoading ? searchResult?.[0]?.mangaList ?? [] : [];
103+
const [loadPage, results] = requestManager.useSourceSearch(id, searchString ?? '', []);
104+
const { data: searchResult, loading: isLoading, error, abortRequest } = results[0]!;
105+
const mangas = (searchResult?.fetchSourceManga.mangas as MangaType[]) ?? [];
109106
const noMangasFound = !isLoading && !mangas.length;
110107

108+
useEffect(() => {
109+
if (!skipRequest) {
110+
loadPage(1);
111+
}
112+
}, [skipRequest, searchString]);
113+
111114
useEffect(() => {
112115
onSearchRequestFinished(source, isLoading, !noMangasFound, !searchString);
113116
}, [isLoading, noMangasFound, searchString]);

src/typings.ts

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import { ParseKeys } from 'i18next';
1212
import { Location } from 'react-router-dom';
1313
import { ExtensionType, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
1414

15+
export type RecursivePartial<T> = {
16+
[P in keyof T]?: T[P] extends (infer U)[]
17+
? RecursivePartial<U>[]
18+
: T[P] extends object | undefined
19+
? RecursivePartial<T[P]>
20+
: T[P];
21+
};
22+
1523
type GenericLocation<State = any> = Omit<Location, 'state'> & { state?: State };
1624

1725
declare module 'react-router-dom' {

0 commit comments

Comments
 (0)