Reduce requested manga data in queries

This commit is contained in:
schroda
2024-07-08 18:02:50 +02:00
parent 4b30e9bc48
commit 3d21266f92
47 changed files with 759 additions and 471 deletions

View File

@@ -23,17 +23,22 @@ import { UpdateChecker } from '@/components/library/UpdateChecker';
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
import { TManga } from '@/typings.ts';
import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx';
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
import { SelectionFAB } from '@/components/collection/SelectionFAB.tsx';
import { PARTIAL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
import { MangaActionMenuItems } from '@/components/manga/MangaActionMenuItems.tsx';
import { TabsMenu } from '@/components/tabs/TabsMenu.tsx';
import { TabsWrapper } from '@/components/tabs/TabsWrapper.tsx';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { GetCategoriesLibraryQuery, GetCategoriesLibraryQueryVariables } from '@/lib/graphql/generated/graphql.ts';
import {
GetCategoriesLibraryQuery,
GetCategoriesLibraryQueryVariables,
MangaChapterStatFieldsFragment,
MangaType,
} from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_LIBRARY } from '@/lib/graphql/queries/CategoryQuery.ts';
import { Mangas } from '@/lib/data/Mangas.ts';
import { MANGA_CHAPTER_STAT_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
const TitleWithSizeTag = styled('span')({
display: 'flex',
@@ -95,7 +100,7 @@ export function Library() {
handleSelectAll,
handleSelection,
clearSelection,
} = useSelectableCollection<TManga['id'], string>(mangas.length, {
} = useSelectableCollection<MangaType['id'], string>(mangas.length, {
itemIds: mangaIds,
currentKey: activeTab?.id.toString(),
});
@@ -107,14 +112,15 @@ export function Library() {
const selectedMangas = useMemo(
() =>
selectedItemIds.map(
(id) =>
requestManager.graphQLClient.client.cache.readFragment<TManga>({
id: requestManager.graphQLClient.client.cache.identify({ __typename: 'MangaType', id }),
fragment: PARTIAL_MANGA_FIELDS,
fragmentName: 'PARTIAL_MANGA_FIELDS',
})!,
),
selectedItemIds
.map((id) =>
Mangas.getFromCache<MangaChapterStatFieldsFragment>(
id,
MANGA_CHAPTER_STAT_FIELDS,
'MANGA_CHAPTER_STAT_FIELDS',
),
)
.filter((manga) => !!manga),
[selectedItemIds.length, mangas],
);

View File

@@ -24,6 +24,8 @@ import { MangaDetails } from '@/components/manga/MangaDetails';
import { MangaToolbarMenu } from '@/components/manga/MangaToolbarMenu';
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder';
import { GetMangaScreenQuery } from '@/lib/graphql/generated/graphql.ts';
import { GET_MANGA_SCREEN } from '@/lib/graphql/queries/MangaQuery.ts';
export const Manga: React.FC = () => {
const { t } = useTranslation();
@@ -32,7 +34,13 @@ export const Manga: React.FC = () => {
const { id } = useParams<{ id: string }>();
const autofetchedRef = useRef(false);
const { data, error: mangaError, loading: isLoading, networkStatus, refetch } = requestManager.useGetManga(id);
const {
data,
error: mangaError,
loading: isLoading,
networkStatus,
refetch,
} = requestManager.useGetManga<GetMangaScreenQuery>(GET_MANGA_SCREEN, id);
const isValidating = isNetworkRequestInFlight(networkStatus);
const manga = data?.manga;

View File

@@ -14,8 +14,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
import { TMigratableSource } from '@/components/MigrationCard.tsx';
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
import { MangaGrid } from '@/components/MangaGrid.tsx';
import { TPartialManga } from '@/typings.ts';
import { GridLayouts } from '@/components/source/GridLayouts.tsx';
import { useLocalStorage } from '@/util/useStorage.tsx';
import { GridLayout } from '@/components/context/LibraryOptionsContext.tsx';
@@ -23,6 +21,7 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
import { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/queries/SourceQuery.ts';
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
export const Migrate = () => {
const { t } = useTranslation();
@@ -117,11 +116,11 @@ export const Migrate = () => {
}
return (
<MangaGrid
<BaseMangaGrid
hasNextPage={false}
loadMore={() => {}}
isLoading={areMangasLoading}
mangas={(migratableSourceMangasData?.mangas.nodes ?? []) as TPartialManga[]}
mangas={migratableSourceMangasData?.mangas.nodes ?? []}
gridLayout={gridLayout}
mode="migrate.search"
/>

View File

@@ -11,7 +11,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import Box from '@mui/material/Box';
import { useTranslation } from 'react-i18next';
import { AllowedMetadataValueTypes, ChapterOffset, IReaderSettings, ReaderType, TManga } from '@/typings';
import { AllowedMetadataValueTypes, ChapterOffset, IReaderSettings, ReaderType } from '@/typings';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import {
checkAndHandleMissingStoredReaderSettings,
@@ -31,6 +31,7 @@ import { useDebounce } from '@/util/useDebounce.ts';
import {
GetChaptersReaderQuery,
GetChaptersReaderQueryVariables,
GetMangaReaderQuery,
UpdateChapterPatchInput,
} from '@/lib/graphql/generated/graphql.ts';
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
@@ -38,6 +39,8 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
import { Chapters } from '@/lib/data/Chapters.ts';
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
import { GET_CHAPTERS_READER } from '@/lib/graphql/queries/ChapterQuery.ts';
import { GET_MANGA_READER } from '@/lib/graphql/queries/MangaQuery.ts';
import { TMangaReader } from '@/lib/data/Mangas.ts';
type TChapter = GetChaptersReaderQuery['chapters']['nodes'][number];
@@ -94,7 +97,7 @@ export function Reader() {
lastReadAt: 0,
chapters: { totalCount: 0 },
trackRecords: { totalCount: 0 },
}) as unknown as TManga,
}) as unknown as TMangaReader,
[mangaId],
);
@@ -103,7 +106,7 @@ export function Reader() {
loading: isMangaLoading,
error: mangaError,
refetch: refetchManga,
} = requestManager.useGetManga(mangaId);
} = requestManager.useGetManga<GetMangaReaderQuery>(GET_MANGA_READER, mangaId);
const loadedChapter = useRef<TChapter | null>(null);
const isChapterLoaded =
Number(mangaId) === loadedChapter.current?.mangaId &&

View File

@@ -19,7 +19,6 @@ import { langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from '@/uti
import { translateExtensionLanguage } from '@/screens/util/Extensions';
import { AppbarSearch } from '@/components/util/AppbarSearch';
import { LangSelect } from '@/components/navbar/action/LangSelect';
import { MangaGrid } from '@/components/MangaGrid';
import { useDebounce } from '@/util/useDebounce.ts';
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx';
@@ -28,6 +27,7 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
@@ -166,7 +166,7 @@ const SourceSearchPreview = React.memo(
}
/>
) : (
<MangaGrid
<BaseMangaGrid
mangas={mangas}
isLoading={isLoading}
hasNextPage={false}

View File

@@ -20,7 +20,7 @@ import { styled } from '@mui/material/styles';
import FavoriteIcon from '@mui/icons-material/Favorite';
import NewReleasesIcon from '@mui/icons-material/NewReleases';
import FilterListIcon from '@mui/icons-material/FilterList';
import { IPos, TPartialManga, TranslationKey } from '@/typings';
import { IPos, TranslationKey } from '@/typings';
import {
requestManager,
AbortableApolloUseMutationPaginatedResponse,
@@ -30,7 +30,7 @@ import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsCon
import { SourceGridLayout } from '@/components/source/SourceGridLayout';
import { AppbarSearch } from '@/components/util/AppbarSearch';
import { SourceOptions } from '@/components/source/SourceOptions';
import { SourceMangaGrid } from '@/components/source/SourceMangaGrid';
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
import {
GetSourceBrowseQuery,
GetSourceBrowseQueryVariables,
@@ -45,6 +45,7 @@ import { getGridSnapshotKey } from '@/components/MangaGrid.tsx';
import { createUpdateSourceMetadata, getSourceMetadata } from '@/lib/metadata/sourceMetadata.ts';
import { makeToast } from '@/components/util/Toast.tsx';
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
const ContentTypeMenu = styled('div')(({ theme }) => ({
display: 'flex',
@@ -92,9 +93,9 @@ const SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY: { [contentType in SourceContentType]
[SourceContentType.SEARCH]: 'manga.error.label.no_matches',
};
const getUniqueMangas = (mangas: TPartialManga[]): TPartialManga[] => {
const mangaIdToManga: Record<TPartialManga['id'], TPartialManga> = {};
const uniqueMangas: TPartialManga[] = [];
const getUniqueMangas = <Manga extends MangaIdInfo>(mangas: Manga[]): Manga[] => {
const mangaIdToManga: Record<string, Manga> = {};
const uniqueMangas: Manga[] = [];
mangas.forEach((manga) => {
const isDuplicate = !!mangaIdToManga[manga.id];
@@ -470,7 +471,7 @@ export function SourceMangas() {
{t('global.button.filter')}
</ContentTypeButton>
</ContentTypeMenu>
<SourceMangaGrid
<BaseMangaGrid
key={contentType}
mangas={mangas}
hasNextPage={hasNextPage}
@@ -479,6 +480,8 @@ export function SourceMangas() {
messageExtra={messageExtra}
isLoading={isLoading}
gridLayout={options.SourcegridLayout}
mode="source"
inLibraryIndicator
/>
{contentType === SourceContentType.SEARCH && (
<SourceOptions

View File

@@ -15,8 +15,6 @@ import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Box from '@mui/material/Box';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { MangaGrid } from '@/components/MangaGrid.tsx';
import { TManga } from '@/typings';
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
import { useLocalStorage } from '@/util/useStorage.tsx';
import { GridLayout } from '@/components/context/LibraryOptionsContext.tsx';
@@ -28,20 +26,34 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
import { MangaCard } from '@/components/MangaCard.tsx';
import { StyledGroupedVirtuoso } from '@/components/virtuoso/StyledGroupedVirtuoso.tsx';
import { StyledGroupHeader } from '@/components/virtuoso/StyledGroupHeader.tsx';
import {
GetMangasDuplicatesQuery,
GetMangasDuplicatesQueryVariables,
MangaType,
} from '@/lib/graphql/generated/graphql.ts';
import { GET_MANGAS_DUPLICATES } from '@/lib/graphql/queries/MangaQuery.ts';
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
import { IMangaGridProps } from '@/components/MangaGrid.tsx';
const findDuplicatesByTitle = (libraryMangas: TManga[]): Record<string, TManga[]> => {
const findDuplicatesByTitle = <Manga extends Pick<MangaType, 'title'>>(
libraryMangas: Manga[],
): Record<string, Manga[]> => {
const titleToMangas = Object.groupBy(libraryMangas, ({ title }) => title.toLowerCase().trim());
return Object.fromEntries(
Object.entries(titleToMangas)
.filter((titleToMangaMap): titleToMangaMap is [string, TManga[]] => (titleToMangaMap[1]?.length ?? 0) > 1)
.filter((titleToMangaMap): titleToMangaMap is [string, Manga[]] => (titleToMangaMap[1]?.length ?? 0) > 1)
.map(([, mangas]) => [mangas[0].title, mangas]),
);
};
const findDuplicatesByTitleAndAlternativeTitles = (libraryMangas: TManga[]): Record<string, TManga[]> => {
const idToDuplicateStatus: Record<TManga['id'], boolean> = {};
const titleToMangas: Record<string, TManga[]> = {};
type TMangaDuplicate = Pick<MangaType, 'id' | 'title' | 'description'>;
const findDuplicatesByTitleAndAlternativeTitles = <Manga extends TMangaDuplicate>(
libraryMangas: Manga[],
): Record<string, Manga[]> => {
const idToDuplicateStatus: Record<MangaIdInfo['id'], boolean> = {};
const titleToMangas: Record<string, Manga[]> = {};
libraryMangas.forEach((mangaToCheck) =>
libraryMangas.forEach((libraryManga) => {
@@ -123,10 +135,13 @@ export const LibraryDuplicates = () => {
};
}, [t, gridLayout, checkAlternativeTitles]);
const { data, loading, error, refetch } = requestManager.useGetMangas({ condition: { inLibrary: true } });
const { data, loading, error, refetch } = requestManager.useGetMangas<
GetMangasDuplicatesQuery,
GetMangasDuplicatesQueryVariables
>(GET_MANGAS_DUPLICATES, { condition: { inLibrary: true } });
const mangasByTitle = useMemo(() => {
const libraryMangas: TManga[] = data?.mangas.nodes ?? [];
const libraryMangas: TMangaDuplicate[] = data?.mangas.nodes ?? [];
if (checkAlternativeTitles) {
return findDuplicatesByTitleAndAlternativeTitles(libraryMangas);
@@ -172,7 +187,7 @@ export const LibraryDuplicates = () => {
itemContent={(index) => (
<Box key={duplicatedMangas[index].id} sx={{ px: 1, pb: 1 }}>
<MangaCard
manga={duplicatedMangas[index]}
manga={duplicatedMangas[index] as IMangaGridProps['mangas'][number]}
gridLayout={gridLayout}
selected={null}
mode="duplicate"
@@ -188,12 +203,13 @@ export const LibraryDuplicates = () => {
<StyledGroupHeader sx={{ pt: index === 0 ? undefined : 0, pb: 0 }} variant="h5" isFirstItem={false}>
{title}
</StyledGroupHeader>
<MangaGrid
mangas={mangasByTitle[title]}
<BaseMangaGrid
mangas={mangasByTitle[title] as IMangaGridProps['mangas']}
hasNextPage={false}
loadMore={() => {}}
isLoading={false}
gridLayout={gridLayout}
inLibraryIndicator={false}
horizontal
mode="duplicate"
/>

View File

@@ -29,14 +29,23 @@ import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCe
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
import { ListItemLink } from '@/components/util/ListItemLink.tsx';
import { GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables } from '@/lib/graphql/generated/graphql.ts';
import {
GetCategoriesSettingsQuery,
GetCategoriesSettingsQueryVariables,
GetMangasBaseQuery,
GetMangasBaseQueryVariables,
} from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
try {
const nonLibraryMangas = await requestManager.getMangas({
filter: { inLibrary: { equalTo: false }, categoryId: { isNull: false } },
}).response;
const nonLibraryMangas = await requestManager.getMangas<GetMangasBaseQuery, GetMangasBaseQueryVariables>(
GET_MANGAS_BASE,
{
filter: { inLibrary: { equalTo: false }, categoryId: { isNull: false } },
},
).response;
const mangaIdsToRemove = Mangas.getIds(nonLibraryMangas.data.mangas.nodes);