Use gql for "mangas" II - category mangas
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user