Use gql for "mangas" II - category mangas

This commit is contained in:
schroda
2023-09-06 21:47:13 +02:00
parent 89dc053f00
commit af9d49e5e9
5 changed files with 47 additions and 33 deletions

View File

@@ -12,10 +12,10 @@ import Typography from '@mui/material/Typography';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Avatar, Box, CardContent, styled } from '@mui/material'; import { Avatar, Box, CardContent, styled } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IMangaCard } from '@/typings';
import requestManager from '@/lib/requests/RequestManager.ts'; import requestManager from '@/lib/requests/RequestManager.ts';
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
import SpinnerImage from '@/components/util/SpinnerImage'; import SpinnerImage from '@/components/util/SpinnerImage';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
const BottomGradient = styled('div')({ const BottomGradient = styled('div')({
position: 'absolute', position: 'absolute',
@@ -66,7 +66,7 @@ const BadgeContainer = styled('div')({
}); });
interface IProps { interface IProps {
manga: IMangaCard; manga: MangaType;
gridLayout?: GridLayout; gridLayout?: GridLayout;
inLibraryIndicator?: boolean; inLibraryIndicator?: boolean;
} }
@@ -75,10 +75,11 @@ const MangaCard = (props: IProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
manga: { id, title, thumbnailUrl, downloadCount, unreadCount: unread, inLibrary }, manga: { id, title, thumbnailUrl: tmpThumbnailUrl, downloadCount, unreadCount: unread, inLibrary },
gridLayout, gridLayout,
inLibraryIndicator, inLibraryIndicator,
} = props; } = props;
const thumbnailUrl = tmpThumbnailUrl ?? 'nonExistingMangaUrl';
const { const {
options: { showUnreadBadge, showDownloadBadge }, options: { showUnreadBadge, showDownloadBadge },
} = useLibraryOptionsContext(); } = useLibraryOptionsContext();

View File

@@ -11,12 +11,12 @@ import Grid, { GridTypeMap } from '@mui/material/Grid';
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { GridItemProps, VirtuosoGrid, VirtuosoGridHandle } from 'react-virtuoso'; import { GridItemProps, VirtuosoGrid, VirtuosoGridHandle } from 'react-virtuoso';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { IMangaCard } from '@/typings';
import EmptyView from '@/components/util/EmptyView'; import EmptyView from '@/components/util/EmptyView';
import LoadingPlaceholder from '@/components/util/LoadingPlaceholder'; import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
import MangaCard from '@/components/MangaCard'; import MangaCard from '@/components/MangaCard';
import { GridLayout } from '@/components/context/LibraryOptionsContext'; import { GridLayout } from '@/components/context/LibraryOptionsContext';
import useLocalStorage from '@/util/useLocalStorage'; import useLocalStorage from '@/util/useLocalStorage';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => ( const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}> <Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
@@ -40,13 +40,13 @@ const GridItemContainerWithDimension = (
); );
}; };
const createMangaCard = (manga: IMangaCard, gridLayout?: GridLayout, inLibraryIndicator?: boolean) => ( const createMangaCard = (manga: MangaType, gridLayout?: GridLayout, inLibraryIndicator?: boolean) => (
<MangaCard key={manga.id} manga={manga} gridLayout={gridLayout} inLibraryIndicator={inLibraryIndicator} /> <MangaCard key={manga.id} manga={manga} gridLayout={gridLayout} inLibraryIndicator={inLibraryIndicator} />
); );
type DefaultGridProps = { type DefaultGridProps = {
isLoading: boolean; isLoading: boolean;
mangas: IMangaCard[]; mangas: MangaType[];
inLibraryIndicator?: boolean; inLibraryIndicator?: boolean;
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element; GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
gridLayout?: GridLayout; gridLayout?: GridLayout;
@@ -150,7 +150,7 @@ const VerticalGrid = ({
}; };
export interface IMangaGridProps { export interface IMangaGridProps {
mangas: IMangaCard[]; mangas: MangaType[];
isLoading: boolean; isLoading: boolean;
message?: string; message?: string;
messageExtra?: JSX.Element; messageExtra?: JSX.Element;

View File

@@ -9,12 +9,13 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { StringParam, useQueryParam } from 'use-query-params'; import { StringParam, useQueryParam } from 'use-query-params';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IMangaCard, LibrarySortMode, NullAndUndefined } from '@/typings'; import { LibrarySortMode, NullAndUndefined } from '@/typings';
import { useSearchSettings } from '@/util/searchSettings'; import { useSearchSettings } from '@/util/searchSettings';
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
import MangaGrid from '@/components/MangaGrid'; import MangaGrid from '@/components/MangaGrid';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: IMangaCard): boolean => { const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: MangaType): boolean => {
switch (unread) { switch (unread) {
case true: case true:
return !!unreadCount && unreadCount >= 1; return !!unreadCount && unreadCount >= 1;
@@ -25,7 +26,7 @@ const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: IManga
} }
}; };
const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: IMangaCard): boolean => { const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: MangaType): boolean => {
switch (downloaded) { switch (downloaded) {
case true: case true:
return !!downloadCount && downloadCount >= 1; return !!downloadCount && downloadCount >= 1;
@@ -36,24 +37,24 @@ const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount
} }
}; };
const queryFilter = (query: NullAndUndefined<string>, { title }: IMangaCard): boolean => { const queryFilter = (query: NullAndUndefined<string>, { title }: MangaType): boolean => {
if (!query) return true; if (!query) return true;
return title.toLowerCase().includes(query.toLowerCase()); return title.toLowerCase().includes(query.toLowerCase());
}; };
const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: IMangaCard): boolean => { const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: MangaType): boolean => {
if (!query) return true; if (!query) return true;
const queries = query.split(',').map((str) => str.toLowerCase().trim()); const queries = query.split(',').map((str) => str.toLowerCase().trim());
return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element)); return queries.every((element) => genre.map((el) => el.toLowerCase()).includes(element));
}; };
const filterManga = ( const filterManga = (
mangas: IMangaCard[], mangas: MangaType[],
query: NullAndUndefined<string>, query: NullAndUndefined<string>,
unread: NullAndUndefined<boolean>, unread: NullAndUndefined<boolean>,
downloaded: NullAndUndefined<boolean>, downloaded: NullAndUndefined<boolean>,
ignoreFilters: boolean, ignoreFilters: boolean,
): IMangaCard[] => ): MangaType[] =>
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);
@@ -63,19 +64,20 @@ const filterManga = (
return matchesSearch && matchesFilters; return matchesSearch && matchesFilters;
}); });
const sortByUnread = (a: IMangaCard, b: IMangaCard): number => (a.unreadCount ?? 0) - (b.unreadCount ?? 0); const sortByUnread = (a: MangaType, b: MangaType): number => (a.unreadCount ?? 0) - (b.unreadCount ?? 0);
const sortByTitle = (a: IMangaCard, b: IMangaCard): number => a.title.localeCompare(b.title); const sortByTitle = (a: MangaType, b: MangaType): number => a.title.localeCompare(b.title);
const sortByDateAdded = (a: IMangaCard, b: IMangaCard): number => a.inLibraryAt - b.inLibraryAt; const sortByDateAdded = (a: MangaType, b: MangaType): number => Number(a.inLibraryAt) - Number(b.inLibraryAt);
const sortByLastRead = (a: IMangaCard, b: IMangaCard): number => b.lastReadAt - a.lastReadAt; const sortByLastRead = (a: MangaType, b: MangaType): number =>
Number(b.lastReadChapter?.lastReadAt ?? 0) - Number(a.lastReadChapter?.lastReadAt ?? 0);
const sortManga = ( const sortManga = (
manga: IMangaCard[], manga: MangaType[],
sort: NullAndUndefined<LibrarySortMode>, sort: NullAndUndefined<LibrarySortMode>,
desc: NullAndUndefined<boolean>, desc: NullAndUndefined<boolean>,
): IMangaCard[] => { ): MangaType[] => {
const result = [...manga]; const result = [...manga];
switch (sort) { switch (sort) {
@@ -103,7 +105,7 @@ const sortManga = (
}; };
interface LibraryMangaGridProps { interface LibraryMangaGridProps {
mangas: IMangaCard[]; mangas: MangaType[];
isLoading: boolean; isLoading: boolean;
message?: string; message?: string;
} }

View File

@@ -25,7 +25,6 @@ import {
BackupValidationResult, BackupValidationResult,
ICategory, ICategory,
IChapter, IChapter,
IManga,
IMangaChapter, IMangaChapter,
ISourceFilters, ISourceFilters,
PaginatedList, PaginatedList,
@@ -65,6 +64,8 @@ import {
GetExtensionsFetchMutationVariables, GetExtensionsFetchMutationVariables,
GetCategoriesQuery, GetCategoriesQuery,
GetCategoriesQueryVariables, GetCategoriesQueryVariables,
GetCategoryMangasQuery,
GetCategoryMangasQueryVariables,
GetExtensionsQuery, GetExtensionsQuery,
GetExtensionsQueryVariables, GetExtensionsQueryVariables,
GetGlobalMetadatasQuery, GetGlobalMetadatasQuery,
@@ -918,8 +919,11 @@ export class RequestManager {
); );
} }
public useGetCategoryMangas(categoryId: number, swrOptions?: SWROptions<IManga[]>): AbortableSWRResponse<IManga[]> { public useGetCategoryMangas(
return this.doRequest(HttpMethod.SWR_GET, `category/${categoryId}`, { swrOptions }); id: number,
options?: QueryHookOptions<GetCategoryMangasQuery, GetCategoryMangasQueryVariables>,
): AbortableApolloUseQueryResponse<GetCategoryMangasQuery, GetCategoryMangasQueryVariables> {
return this.doRequestNew(GQLMethod.USE_QUERY, GET_CATEGORY_MANGAS, { id }, options);
} }
public deleteCategory(categoryId: number): AbortableApolloMutationResponse<DeleteCategoryMutation> { public deleteCategory(categoryId: number): AbortableApolloMutationResponse<DeleteCategoryMutation> {

View File

@@ -20,6 +20,7 @@ import LibraryMangaGrid from '@/components/library/LibraryMangaGrid';
import AppbarSearch from '@/components/util/AppbarSearch'; import AppbarSearch from '@/components/util/AppbarSearch';
import UpdateChecker from '@/components/library/UpdateChecker'; import UpdateChecker from '@/components/library/UpdateChecker';
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
const StyledGridWrapper = styled(Box)(({ theme }) => ({ const StyledGridWrapper = styled(Box)(({ theme }) => ({
// TabsMenu height + TabsMenu bottom padding - grid item top padding // TabsMenu height + TabsMenu bottom padding - grid item top padding
@@ -63,8 +64,14 @@ export default function Library() {
const { options } = useLibraryOptionsContext(); const { options } = useLibraryOptionsContext();
const [lastLibraryUpdate, setLastLibraryUpdate] = useState(Date.now()); const [lastLibraryUpdate, setLastLibraryUpdate] = useState(Date.now());
const { data, error: tabsError, loading: areCategoriesLoading } = requestManager.useGetCategories(); const {
const tabsData = data?.categories.nodes.filter((category) => category.id !== 0); data: categoriesResponse,
error: tabsError,
loading: areCategoriesLoading,
} = requestManager.useGetCategories();
const tabsData = categoriesResponse?.categories.nodes.filter(
(category) => category.id !== 0 || (category.id === 0 && category.mangas.totalCount),
);
const tabs = tabsData ?? []; const tabs = tabsData ?? [];
const librarySize = useMemo( const librarySize = useMemo(
() => tabs.map((tab) => tab.mangas.totalCount).reduce((prev, curr) => prev + curr, 0), () => tabs.map((tab) => tab.mangas.totalCount).reduce((prev, curr) => prev + curr, 0),
@@ -75,11 +82,11 @@ export default function Library() {
const activeTab = tabs.find((tab) => tab.order === tabSearchParam) ?? tabs[0]; const activeTab = tabs.find((tab) => tab.order === tabSearchParam) ?? tabs[0];
const { const {
data: mangaData, data: categoryMangaResponse,
error: mangaError, error: mangaError,
isLoading: mangaLoading, loading: mangaLoading,
} = requestManager.useGetCategoryMangas(activeTab?.id, { skipRequest: !activeTab }); } = requestManager.useGetCategoryMangas(activeTab?.id, { skip: !activeTab });
const mangas = mangaData ?? []; const mangas = (categoryMangaResponse?.category.mangas.nodes as unknown as MangaType[]) ?? [];
const { setTitle, setAction } = useContext(NavbarContext); const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { useEffect(() => {
@@ -105,14 +112,14 @@ export default function Library() {
}, [t, librarySize, areCategoriesLoading, options]); }, [t, librarySize, areCategoriesLoading, options]);
const handleTabChange = (newTab: number) => { const handleTabChange = (newTab: number) => {
setTabSearchParam(newTab === 0 ? undefined : newTab); setTabSearchParam(newTab);
}; };
if (tabsError != null) { if (tabsError != null) {
return ( return (
<EmptyView <EmptyView
message={t('category.error.label.request_failure')} message={t('category.error.label.request_failure')}
messageExtra={(tabsError?.message as any) ?? tabsError} messageExtra={tabsError.message ?? tabsError}
/> />
); );
} }
@@ -171,7 +178,7 @@ export default function Library() {
(mangaError ? ( (mangaError ? (
<EmptyView <EmptyView
message={t('manga.error.label.request_failure')} message={t('manga.error.label.request_failure')}
messageExtra={mangaError?.message ?? mangaError} messageExtra={mangaError.message ?? mangaError}
/> />
) : ( ) : (
<LibraryMangaGrid <LibraryMangaGrid