Reduce requested manga data in queries
This commit is contained in:
@@ -52,7 +52,7 @@ export const MangaCard = (props: MangaCardProps) => {
|
|||||||
mode === 'source',
|
mode === 'source',
|
||||||
);
|
);
|
||||||
|
|
||||||
const mangaLinkTo = getMangaLinkTo(mode, manga.id, manga.source?.id, manga.title);
|
const mangaLinkTo = getMangaLinkTo(mode, manga.id, manga.sourceId, manga.title);
|
||||||
|
|
||||||
const nextChapterIndexToRead = firstUnreadChapter?.sourceOrder ?? 1;
|
const nextChapterIndexToRead = firstUnreadChapter?.sourceOrder ?? 1;
|
||||||
const isLatestChapterRead = chapters?.totalCount === latestReadChapter?.sourceOrder;
|
const isLatestChapterRead = chapters?.totalCount === latestReadChapter?.sourceOrder;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ 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, useSessionStorage } from '@/util/useStorage.tsx';
|
import { useLocalStorage, useSessionStorage } from '@/util/useStorage.tsx';
|
||||||
import { TManga, TPartialManga } from '@/typings.ts';
|
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab.tsx';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab.tsx';
|
||||||
import { AppStorage } from '@/util/AppStorage.ts';
|
import { AppStorage } from '@/util/AppStorage.ts';
|
||||||
import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx';
|
import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx';
|
||||||
|
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' }}>
|
||||||
@@ -46,12 +46,14 @@ const GridItemContainerWithDimension = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TManga = MangaCardProps['manga'];
|
||||||
|
|
||||||
const createMangaCard = (
|
const createMangaCard = (
|
||||||
manga: TPartialManga,
|
manga: TManga,
|
||||||
gridLayout?: GridLayout,
|
gridLayout?: GridLayout,
|
||||||
inLibraryIndicator?: boolean,
|
inLibraryIndicator?: boolean,
|
||||||
isSelectModeActive: boolean = false,
|
isSelectModeActive: boolean = false,
|
||||||
selectedMangaIds?: TManga['id'][],
|
selectedMangaIds?: MangaType['id'][],
|
||||||
handleSelection?: DefaultGridProps['handleSelection'],
|
handleSelection?: DefaultGridProps['handleSelection'],
|
||||||
mode?: MangaCardProps['mode'],
|
mode?: MangaCardProps['mode'],
|
||||||
) => (
|
) => (
|
||||||
@@ -68,13 +70,13 @@ const createMangaCard = (
|
|||||||
|
|
||||||
type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
|
type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
mangas: TPartialManga[];
|
mangas: TManga[];
|
||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
|
GridItemContainer: (props: GridTypeMap['props'] & Partial<GridItemProps>) => JSX.Element;
|
||||||
gridLayout?: GridLayout;
|
gridLayout?: GridLayout;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
selectedMangaIds?: Required<TManga['id']>[];
|
selectedMangaIds?: Required<MangaType['id']>[];
|
||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaType['id']>['handleSelection'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const HorizontalGrid = forwardRef(
|
const HorizontalGrid = forwardRef(
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import DownloadIcon from '@mui/icons-material/Download';
|
|||||||
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
||||||
import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state';
|
import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state';
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { ChapterCard } from '@/components/chapter/ChapterCard.tsx';
|
import { ChapterCard } from '@/components/chapter/ChapterCard.tsx';
|
||||||
import { ResumeFab } from '@/components/manga/ResumeFAB.tsx';
|
import { ResumeFab } from '@/components/manga/ResumeFAB.tsx';
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
DownloadType,
|
DownloadType,
|
||||||
GetChaptersMangaQuery,
|
GetChaptersMangaQuery,
|
||||||
GetChaptersMangaQueryVariables,
|
GetChaptersMangaQueryVariables,
|
||||||
|
MangaScreenFieldsFragment,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { SelectableCollectionSelectAll } from '@/components/collection/SelectableCollectionSelectAll.tsx';
|
import { SelectableCollectionSelectAll } from '@/components/collection/SelectableCollectionSelectAll.tsx';
|
||||||
@@ -72,7 +72,10 @@ export interface IChapterWithMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: TManga;
|
manga: Pick<
|
||||||
|
MangaScreenFieldsFragment,
|
||||||
|
'id' | 'firstUnreadChapter' | 'chapters' | 'latestReadChapter' | 'unreadCount' | 'downloadCount'
|
||||||
|
>;
|
||||||
isRefreshing: boolean;
|
isRefreshing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { Mangas } from '@/lib/data/Mangas.ts';
|
import { Mangas } from '@/lib/data/Mangas.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const DownloadRange = {
|
const DownloadRange = {
|
||||||
NEXT_1: 1,
|
NEXT_1: 1,
|
||||||
@@ -25,7 +25,7 @@ export const ChaptersDownloadActionMenuItems = ({
|
|||||||
mangaIds,
|
mangaIds,
|
||||||
closeMenu,
|
closeMenu,
|
||||||
}: {
|
}: {
|
||||||
mangaIds: TManga['id'][];
|
mangaIds: MangaType['id'][];
|
||||||
closeMenu: () => void;
|
closeMenu: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -9,14 +9,12 @@
|
|||||||
import React, { useEffect, useLayoutEffect } from 'react';
|
import React, { useEffect, useLayoutEffect } 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 { TManga } from '@/typings';
|
|
||||||
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
import { IMangaGridProps, MangaGrid } from '@/components/MangaGrid';
|
import { IMangaGridProps, MangaGrid } from '@/components/MangaGrid';
|
||||||
|
|
||||||
interface LibraryMangaGridProps
|
interface LibraryMangaGridProps
|
||||||
extends Required<Pick<IMangaGridProps, 'isSelectModeActive' | 'selectedMangaIds' | 'handleSelection'>>,
|
extends Required<Pick<IMangaGridProps, 'isSelectModeActive' | 'selectedMangaIds' | 'handleSelection' | 'mangas'>>,
|
||||||
Pick<IMangaGridProps, 'retry' | 'message' | 'messageExtra'> {
|
Pick<IMangaGridProps, 'retry' | 'message' | 'messageExtra'> {
|
||||||
mangas: TManga[];
|
|
||||||
showFilteredOutMessage: boolean;
|
showFilteredOutMessage: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
import { StringParam, useQueryParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { LibraryOptions, LibrarySortMode, NullAndUndefined, TManga } from '@/typings.ts';
|
import { LibraryOptions, LibrarySortMode, NullAndUndefined } from '@/typings.ts';
|
||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
|
||||||
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { Trackers } from '@/lib/data/Trackers.ts';
|
import { ChapterType, MangaType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
const triStateFilter = (
|
const triStateFilter = (
|
||||||
triState: NullAndUndefined<boolean>,
|
triState: NullAndUndefined<boolean>,
|
||||||
@@ -35,22 +36,26 @@ const triStateFilterNumber = (triState: NullAndUndefined<boolean>, count?: numbe
|
|||||||
() => count === 0,
|
() => count === 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryFilter = (query: NullAndUndefined<string>, { title }: TManga): boolean => {
|
type TMangaQueryFilter = Pick<MangaType, 'title'>;
|
||||||
|
const queryFilter = (query: NullAndUndefined<string>, { title }: TMangaQueryFilter): 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 }: TManga): boolean => {
|
type TMangaQueryGenreFilter = Pick<MangaType, 'genre'>;
|
||||||
|
const queryGenreFilter = (query: NullAndUndefined<string>, { genre }: TMangaQueryGenreFilter): 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 trackerFilter = (trackFilters: LibraryOptions['tracker'], manga: TManga): boolean =>
|
type TMangaTrackerFilter = { trackRecords: { nodes: Pick<TrackRecordType, 'id' | 'trackerId'>[] } };
|
||||||
|
const trackerFilter = (trackFilters: LibraryOptions['tracker'], manga: TMangaTrackerFilter): boolean =>
|
||||||
Object.entries(trackFilters)
|
Object.entries(trackFilters)
|
||||||
.map(([trackFilterId, trackFilterState]) => {
|
.map(([trackFilterId, trackFilterState]) => {
|
||||||
const mangaTrackers = Trackers.getTrackers(manga.trackRecords.nodes);
|
const isTrackerBound = manga.trackRecords.nodes.some(
|
||||||
const isTrackerBound = mangaTrackers.some((tracker) => tracker.id === Number(trackFilterId));
|
(trackRecord) => trackRecord.trackerId === Number(trackFilterId),
|
||||||
|
);
|
||||||
|
|
||||||
return triStateFilter(
|
return triStateFilter(
|
||||||
trackFilterState,
|
trackFilterState,
|
||||||
@@ -60,15 +65,19 @@ const trackerFilter = (trackFilters: LibraryOptions['tracker'], manga: TManga):
|
|||||||
})
|
})
|
||||||
.every((matchesFilter) => matchesFilter);
|
.every((matchesFilter) => matchesFilter);
|
||||||
|
|
||||||
const filterManga = (
|
type TMangaFilter = TMangaQueryFilter &
|
||||||
mangas: TManga[],
|
TMangaQueryGenreFilter &
|
||||||
|
TMangaTrackerFilter &
|
||||||
|
Pick<MangaType, 'downloadCount' | 'unreadCount' | 'bookmarkCount'>;
|
||||||
|
const filterManga = <Manga extends TMangaFilter>(
|
||||||
|
mangas: Manga[],
|
||||||
query: NullAndUndefined<string>,
|
query: NullAndUndefined<string>,
|
||||||
unread: NullAndUndefined<boolean>,
|
unread: NullAndUndefined<boolean>,
|
||||||
downloaded: NullAndUndefined<boolean>,
|
downloaded: NullAndUndefined<boolean>,
|
||||||
bookmarked: NullAndUndefined<boolean>,
|
bookmarked: NullAndUndefined<boolean>,
|
||||||
tracker: LibraryOptions['tracker'],
|
tracker: LibraryOptions['tracker'],
|
||||||
ignoreFilters: boolean,
|
ignoreFilters: boolean,
|
||||||
): TManga[] =>
|
): Manga[] =>
|
||||||
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);
|
||||||
@@ -86,11 +95,16 @@ const sortByNumber = (a: number | string = 0, b: number | string = 0) => Number(
|
|||||||
|
|
||||||
const sortByString = (a: string, b: string): number => a.localeCompare(b);
|
const sortByString = (a: string, b: string): number => a.localeCompare(b);
|
||||||
|
|
||||||
const sortManga = (
|
type TMangaSort = Pick<MangaType, 'title' | 'inLibraryAt' | 'unreadCount'> & {
|
||||||
manga: TManga[],
|
lastReadChapter?: Pick<ChapterType, 'lastReadAt'> | null;
|
||||||
|
latestUploadedChapter?: Pick<ChapterType, 'uploadDate'> | null;
|
||||||
|
latestFetchedChapter?: Pick<ChapterType, 'fetchedAt'> | null;
|
||||||
|
};
|
||||||
|
const sortManga = <Manga extends TMangaSort>(
|
||||||
|
manga: Manga[],
|
||||||
sort: NullAndUndefined<LibrarySortMode>,
|
sort: NullAndUndefined<LibrarySortMode>,
|
||||||
desc: NullAndUndefined<boolean>,
|
desc: NullAndUndefined<boolean>,
|
||||||
): TManga[] => {
|
): Manga[] => {
|
||||||
const result = [...manga];
|
const result = [...manga];
|
||||||
|
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
@@ -125,7 +139,12 @@ const sortManga = (
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetVisibleLibraryMangas = (mangas: TManga[]) => {
|
export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangaFilter & TMangaSort>(
|
||||||
|
mangas: Manga[],
|
||||||
|
): {
|
||||||
|
visibleMangas: Manga[];
|
||||||
|
showFilteredOutMessage: boolean;
|
||||||
|
} => {
|
||||||
const [query] = useQueryParam('query', StringParam);
|
const [query] = useQueryParam('query', StringParam);
|
||||||
const { options } = useLibraryOptionsContext();
|
const { options } = useLibraryOptionsContext();
|
||||||
const { unread, downloaded, bookmarked, tracker } = options;
|
const { unread, downloaded, bookmarked, tracker } = options;
|
||||||
|
|||||||
@@ -19,8 +19,14 @@ import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import SyncIcon from '@mui/icons-material/Sync';
|
import SyncIcon from '@mui/icons-material/Sync';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
import { TManga } from '@/typings.ts';
|
import {
|
||||||
import { actionToTranslationKey, MangaAction, MangaDownloadInfo, Mangas, MangaUnreadInfo } from '@/lib/data/Mangas.ts';
|
actionToTranslationKey,
|
||||||
|
MangaAction,
|
||||||
|
MangaDownloadInfo,
|
||||||
|
MangaIdInfo,
|
||||||
|
Mangas,
|
||||||
|
MangaUnreadInfo,
|
||||||
|
} from '@/lib/data/Mangas.ts';
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { MenuItem } from '@/components/menu/MenuItem.tsx';
|
import { MenuItem } from '@/components/menu/MenuItem.tsx';
|
||||||
import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts';
|
import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts';
|
||||||
@@ -29,18 +35,19 @@ import { TrackManga } from '@/components/tracker/TrackManga.tsx';
|
|||||||
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
|
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
|
||||||
import { ChaptersDownloadActionMenuItems } from '@/components/chapter/ChaptersDownloadActionMenuItems.tsx';
|
import { ChaptersDownloadActionMenuItems } from '@/components/chapter/ChaptersDownloadActionMenuItems.tsx';
|
||||||
import { NestedMenuItem } from '@/components/menu/NestedMenuItem.tsx';
|
import { NestedMenuItem } from '@/components/menu/NestedMenuItem.tsx';
|
||||||
|
import { MangaChapterStatFieldsFragment, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const;
|
const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const;
|
||||||
|
|
||||||
type BaseProps = { onClose: (selectionModeState: boolean) => void; setHideMenu: (hide: boolean) => void };
|
type BaseProps = { onClose: (selectionModeState: boolean) => void; setHideMenu: (hide: boolean) => void };
|
||||||
|
|
||||||
export type SingleModeProps = {
|
export type SingleModeProps = {
|
||||||
manga: Pick<TManga, 'id' | 'title' | 'source' | 'trackRecords'> & MangaDownloadInfo & MangaUnreadInfo;
|
manga: Pick<MangaType, 'id' | 'title' | 'sourceId'> & MangaDownloadInfo & MangaUnreadInfo;
|
||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaType['id']>['handleSelection'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type SelectModeProps = {
|
type SelectModeProps = {
|
||||||
selectedMangas: TManga[];
|
selectedMangas: MangaChapterStatFieldsFragment[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props =
|
type Props =
|
||||||
@@ -82,7 +89,7 @@ export const MangaActionMenuItems = ({
|
|||||||
onClose(true);
|
onClose(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const performAction = (action: MangaAction, mangas: TManga[]) => {
|
const performAction = (action: MangaAction, mangas: MangaIdInfo[]) => {
|
||||||
Mangas.performAction(action, manga ? [manga.id] : Mangas.getIds(mangas), {
|
Mangas.performAction(action, manga ? [manga.id] : Mangas.getIds(mangas), {
|
||||||
wasManuallyMarkedAsRead: true,
|
wasManuallyMarkedAsRead: true,
|
||||||
}).catch(defaultPromiseErrorHandler(`MangaActionMenuItems:performAction(${action})`));
|
}).catch(defaultPromiseErrorHandler(`MangaActionMenuItems:performAction(${action})`));
|
||||||
@@ -150,7 +157,7 @@ export const MangaActionMenuItems = ({
|
|||||||
)}
|
)}
|
||||||
{isSingleMode && (
|
{isSingleMode && (
|
||||||
<Link
|
<Link
|
||||||
to={`/migrate/source/${manga?.source?.id}/manga/${manga?.id}/search?query=${manga?.title}`}
|
to={`/migrate/source/${manga?.sourceId}/manga/${manga?.id}/search?query=${manga?.title}`}
|
||||||
state={{ mangaTitle: manga?.title }}
|
state={{ mangaTitle: manga?.title }}
|
||||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,24 +8,37 @@
|
|||||||
|
|
||||||
import { LongPressPointerHandlers, LongPressResult } from 'use-long-press/lib/use-long-press.types';
|
import { LongPressPointerHandlers, LongPressResult } from 'use-long-press/lib/use-long-press.types';
|
||||||
import { PopupState } from 'material-ui-popup-state/hooks';
|
import { PopupState } from 'material-ui-popup-state/hooks';
|
||||||
import { TManga, TPartialManga } from '@/typings.ts';
|
|
||||||
import { GridLayout } from '@/components/context/LibraryOptionsContext.tsx';
|
import { GridLayout } from '@/components/context/LibraryOptionsContext.tsx';
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { useManageMangaLibraryState } from '@/components/manga/useManageMangaLibraryState.tsx';
|
import { useManageMangaLibraryState } from '@/components/manga/useManageMangaLibraryState.tsx';
|
||||||
|
import { MangaChapterCountInfo, MangaThumbnailInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
import { ChapterType, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { SingleModeProps } from '@/components/manga/MangaActionMenuItems.tsx';
|
||||||
|
|
||||||
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
||||||
|
|
||||||
|
type MangaCardBaseProps = Pick<MangaType, 'id' | 'title' | 'sourceId'> &
|
||||||
|
SingleModeProps['manga'] &
|
||||||
|
Partial<Pick<MangaType, 'inLibrary' | 'downloadCount' | 'unreadCount'>> &
|
||||||
|
MangaChapterCountInfo & {
|
||||||
|
latestReadChapter?: Pick<ChapterType, 'id' | 'sourceOrder'> | null;
|
||||||
|
firstUnreadChapter?: Pick<ChapterType, 'id' | 'sourceOrder'> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MangaCardSpecificProps = MangaCardBaseProps & MangaThumbnailInfo;
|
||||||
|
|
||||||
export interface MangaCardProps {
|
export interface MangaCardProps {
|
||||||
manga: TPartialManga;
|
manga: MangaCardBaseProps;
|
||||||
gridLayout?: GridLayout;
|
gridLayout?: GridLayout;
|
||||||
inLibraryIndicator?: boolean;
|
inLibraryIndicator?: boolean;
|
||||||
selected?: boolean | null;
|
selected?: boolean | null;
|
||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaType['id']>['handleSelection'];
|
||||||
mode?: MangaCardMode;
|
mode?: MangaCardMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SpecificMangaCardProps = MangaCardProps &
|
export type SpecificMangaCardProps = Omit<MangaCardProps, 'manga'> &
|
||||||
Pick<ReturnType<typeof useManageMangaLibraryState>, 'isInLibrary'> & {
|
Pick<ReturnType<typeof useManageMangaLibraryState>, 'isInLibrary'> & {
|
||||||
|
manga: MangaCardSpecificProps;
|
||||||
longPressBind: LongPressResult<LongPressPointerHandlers>;
|
longPressBind: LongPressResult<LongPressPointerHandlers>;
|
||||||
popupState: PopupState;
|
popupState: PopupState;
|
||||||
handleClick: (event: React.MouseEvent | React.TouchEvent) => void;
|
handleClick: (event: React.MouseEvent | React.TouchEvent) => void;
|
||||||
|
|||||||
@@ -10,22 +10,21 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
|
|||||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import React, { ComponentProps, useEffect, useMemo } from 'react';
|
import { ComponentProps, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { TManga } from '@/typings';
|
|
||||||
import { makeToast } from '@/components/util/Toast';
|
import { makeToast } from '@/components/util/Toast';
|
||||||
import { Mangas } from '@/lib/data/Mangas.ts';
|
import { Mangas, MangaThumbnailInfo, MangaTrackRecordInfo } from '@/lib/data/Mangas.ts';
|
||||||
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
import { CustomIconButton } from '@/components/atoms/CustomIconButton';
|
import { CustomIconButton } from '@/components/atoms/CustomIconButton';
|
||||||
import { TrackMangaButton } from '@/components/manga/TrackMangaButton.tsx';
|
import { TrackMangaButton } from '@/components/manga/TrackMangaButton.tsx';
|
||||||
import { useManageMangaLibraryState } from '@/components/manga/useManageMangaLibraryState.tsx';
|
import { useManageMangaLibraryState } from '@/components/manga/useManageMangaLibraryState.tsx';
|
||||||
import { Metadata as BaseMetadata } from '@/components/atoms/Metadata.tsx';
|
import { Metadata as BaseMetadata } from '@/components/atoms/Metadata.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const DetailsWrapper = styled('div')(({ theme }) => ({
|
const DetailsWrapper = styled('div')(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -156,10 +155,6 @@ const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
|||||||
return button;
|
return button;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
manga: TManga;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null): string {
|
function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null): string {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
return translate('global.label.unknown');
|
return translate('global.label.unknown');
|
||||||
@@ -172,7 +167,18 @@ function getValueOrUnknown(val?: string | null) {
|
|||||||
return val ?? translate('global.label.unknown');
|
return val ?? translate('global.label.unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
export const MangaDetails = ({
|
||||||
|
manga,
|
||||||
|
}: {
|
||||||
|
manga: Pick<
|
||||||
|
MangaType,
|
||||||
|
'id' | 'title' | 'author' | 'artist' | 'status' | 'inLibrary' | 'realUrl' | 'description' | 'genre'
|
||||||
|
> &
|
||||||
|
MangaThumbnailInfo &
|
||||||
|
MangaTrackRecordInfo & {
|
||||||
|
source?: Pick<SourceType, 'id' | 'displayName'> | null;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
|
|||||||
import { PopupState } from 'material-ui-popup-state/hooks';
|
import { PopupState } from 'material-ui-popup-state/hooks';
|
||||||
import { bindTrigger } from 'material-ui-popup-state';
|
import { bindTrigger } from 'material-ui-popup-state';
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
||||||
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
export const MangaOptionButton = forwardRef(
|
export const MangaOptionButton = forwardRef(
|
||||||
(
|
(
|
||||||
@@ -30,7 +30,7 @@ export const MangaOptionButton = forwardRef(
|
|||||||
}: {
|
}: {
|
||||||
id: number;
|
id: number;
|
||||||
selected?: boolean | null;
|
selected?: boolean | null;
|
||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<MangaType['id']>['handleSelection'];
|
||||||
asCheckbox?: boolean;
|
asCheckbox?: boolean;
|
||||||
popupState: PopupState;
|
popupState: PopupState;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import { Link } from 'react-router-dom';
|
|||||||
import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
|
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
|
||||||
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: TManga;
|
manga: Pick<MangaType, 'id' | 'inLibrary' | 'sourceId' | 'title'>;
|
||||||
onRefresh: () => any;
|
onRefresh: () => any;
|
||||||
refreshing: boolean;
|
refreshing: boolean;
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
|||||||
<>
|
<>
|
||||||
<Tooltip title={t('global.button.migrate')}>
|
<Tooltip title={t('global.button.migrate')}>
|
||||||
<Link
|
<Link
|
||||||
to={`/migrate/source/${manga.source?.id}/manga/${manga.id}/search?query=${manga.title}`}
|
to={`/migrate/source/${manga.sourceId}/manga/${manga.id}/search?query=${manga.title}`}
|
||||||
state={{ mangaTitle: manga.title }}
|
state={{ mangaTitle: manga.title }}
|
||||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
@@ -122,7 +122,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key="migrate"
|
key="migrate"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/migrate/source/${manga.source?.id}/manga/${manga.id}/search?query=${manga.title}`}
|
to={`/migrate/source/${manga.sourceId}/manga/${manga.id}/search?query=${manga.title}`}
|
||||||
state={{ mangaTitle: manga.title }}
|
state={{ mangaTitle: manga.title }}
|
||||||
style={{ textDecoration: 'none', color: 'inherit' }}
|
style={{ textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -16,20 +16,21 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { TrackManga } from '@/components/tracker/TrackManga.tsx';
|
import { TrackManga } from '@/components/tracker/TrackManga.tsx';
|
||||||
import { Trackers } from '@/lib/data/Trackers.ts';
|
import { Trackers } from '@/lib/data/Trackers.ts';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { CustomIconButton } from '@/components/atoms/CustomIconButton.tsx';
|
import { CustomIconButton } from '@/components/atoms/CustomIconButton.tsx';
|
||||||
import { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts';
|
import { GetTrackersSettingsQuery, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
|
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
|
||||||
|
import { MangaTrackRecordInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
export const TrackMangaButton = ({ manga }: { manga: TManga }) => {
|
export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick<MangaType, 'title'> }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
|
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
|
||||||
|
const trackers = trackerList.data?.trackers.nodes ?? [];
|
||||||
const mangaTrackers = manga.trackRecords.nodes;
|
const mangaTrackers = manga.trackRecords.nodes;
|
||||||
|
|
||||||
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
|
const loggedInTrackers = Trackers.getLoggedIn(trackers);
|
||||||
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackers));
|
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackers, trackers));
|
||||||
|
|
||||||
const handleClick = (openPopup: () => void) => {
|
const handleClick = (openPopup: () => void) => {
|
||||||
if (trackerList.error) {
|
if (trackerList.error) {
|
||||||
|
|||||||
@@ -16,19 +16,18 @@ import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings
|
|||||||
import { Categories } from '@/lib/data/Categories.ts';
|
import { Categories } from '@/lib/data/Categories.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { Mangas } from '@/lib/data/Mangas.ts';
|
import { Mangas } from '@/lib/data/Mangas.ts';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { awaitConfirmation } from '@/lib/ui/AwaitableDialog.tsx';
|
import { awaitConfirmation } from '@/lib/ui/AwaitableDialog.tsx';
|
||||||
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
|
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
|
||||||
|
|
||||||
export const useManageMangaLibraryState = (
|
export const useManageMangaLibraryState = (
|
||||||
manga: Pick<TManga, 'id' | 'title' | 'inLibrary'>,
|
manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>,
|
||||||
confirmRemoval: boolean = false,
|
confirmRemoval: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [isInLibrary, setIsInLibrary] = useState(manga.inLibrary);
|
const [isInLibrary, setIsInLibrary] = useState(!!manga.inLibrary);
|
||||||
|
|
||||||
const addToLibrary = useCallback(
|
const addToLibrary = useCallback(
|
||||||
(didSubmit: boolean, addToCategories: number[] = [], removeFromCategories: number[] = []) => {
|
(didSubmit: boolean, addToCategories: number[] = [], removeFromCategories: number[] = []) => {
|
||||||
@@ -162,6 +161,6 @@ export const useManageMangaLibraryState = (
|
|||||||
*
|
*
|
||||||
* To work around this issue, the currently known in library state gets returned here
|
* To work around this issue, the currently known in library state gets returned here
|
||||||
*/
|
*/
|
||||||
isInLibrary: Mangas.getFromCache<TManga>(manga.id)?.inLibrary ?? isInLibrary,
|
isInLibrary: Mangas.getFromCache(manga.id)?.inLibrary ?? isInLibrary,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AllowedMetadataValueTypes, ChapterOffset, IReaderSettings, TManga } from '@/typings';
|
import { AllowedMetadataValueTypes, ChapterOffset, IReaderSettings } from '@/typings';
|
||||||
import { ReaderSettingsOptions } from '@/components/reader/ReaderSettingsOptions';
|
import { ReaderSettingsOptions } from '@/components/reader/ReaderSettingsOptions';
|
||||||
import { useBackButton } from '@/util/useBackButton.ts';
|
import { useBackButton } from '@/util/useBackButton.ts';
|
||||||
import { Select } from '@/components/atoms/Select.tsx';
|
import { Select } from '@/components/atoms/Select.tsx';
|
||||||
import { getOptionForDirection } from '@/theme.ts';
|
import { getOptionForDirection } from '@/theme.ts';
|
||||||
import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MangaChapterCountInfo, MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
const Root = styled('div')({
|
const Root = styled('div')({
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
@@ -123,7 +124,7 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
settings: IReaderSettings;
|
settings: IReaderSettings;
|
||||||
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
|
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
|
||||||
manga: TManga;
|
manga: MangaIdInfo & MangaChapterCountInfo;
|
||||||
chapter: Pick<ChapterType, 'name' | 'sourceOrder' | 'pageCount'>;
|
chapter: Pick<ChapterType, 'name' | 'sourceOrder' | 'pageCount'>;
|
||||||
chapters: Pick<ChapterType, 'id' | 'sourceOrder' | 'name' | 'chapterNumber' | 'scanlator'>[];
|
chapters: Pick<ChapterType, 'id' | 'sourceOrder' | 'name' | 'chapterNumber' | 'scanlator'>[];
|
||||||
curPage: number;
|
curPage: number;
|
||||||
|
|||||||
@@ -25,8 +25,14 @@ import { CheckboxInput } from '@/components/atoms/CheckboxInput.tsx';
|
|||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { updateMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { updateMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
import {
|
||||||
|
GetCategoriesBaseQuery,
|
||||||
|
GetCategoriesBaseQueryVariables,
|
||||||
|
GetMangaCategoriesQuery,
|
||||||
|
GetMangaCategoriesQueryVariables,
|
||||||
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
|
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
|
||||||
|
import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts';
|
||||||
|
|
||||||
type BaseProps = {
|
type BaseProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -47,7 +53,11 @@ export type CategorySelectProps =
|
|||||||
| (BaseProps & PropertiesNever<SingleMangaModeProps> & MultiMangaModeProps);
|
| (BaseProps & PropertiesNever<SingleMangaModeProps> & MultiMangaModeProps);
|
||||||
|
|
||||||
const useGetMangaCategoryIds = (mangaId: number | undefined): number[] => {
|
const useGetMangaCategoryIds = (mangaId: number | undefined): number[] => {
|
||||||
const { data: mangaResult } = requestManager.useGetManga(mangaId ?? -1, { skip: mangaId === undefined });
|
const { data: mangaResult } = requestManager.useGetManga<GetMangaCategoriesQuery, GetMangaCategoriesQueryVariables>(
|
||||||
|
GET_MANGA_CATEGORIES,
|
||||||
|
mangaId ?? -1,
|
||||||
|
{ skip: mangaId === undefined },
|
||||||
|
);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (mangaId === undefined || !mangaResult) {
|
if (mangaId === undefined || !mangaResult) {
|
||||||
@@ -90,6 +100,7 @@ export function CategorySelect(props: CategorySelectProps) {
|
|||||||
const [doNotShowAddToLibraryDialogAgain, setDoNotShowAddToLibraryDialogAgain] = useState(false);
|
const [doNotShowAddToLibraryDialogAgain, setDoNotShowAddToLibraryDialogAgain] = useState(false);
|
||||||
|
|
||||||
const mangaCategoryIds = useGetMangaCategoryIds(mangaId);
|
const mangaCategoryIds = useGetMangaCategoryIds(mangaId);
|
||||||
|
|
||||||
const { data } = requestManager.useGetCategories<GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables>(
|
const { data } = requestManager.useGetCategories<GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables>(
|
||||||
GET_CATEGORIES_BASE,
|
GET_CATEGORIES_BASE,
|
||||||
);
|
);
|
||||||
|
|||||||
17
src/components/source/BaseMangaGrid.tsx
Normal file
17
src/components/source/BaseMangaGrid.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MangaGrid, IMangaGridProps } from '@/components/MangaGrid';
|
||||||
|
|
||||||
|
type TMangaBaseGrid = Omit<IMangaGridProps['mangas'][number], 'downloadCount' | 'unreadCount' | 'chapters'>;
|
||||||
|
|
||||||
|
export function BaseMangaGrid(props: Omit<IMangaGridProps, 'mangas'> & { mangas: TMangaBaseGrid[] }) {
|
||||||
|
const { mangas } = props;
|
||||||
|
|
||||||
|
return <MangaGrid {...props} mangas={mangas as IMangaGridProps['mangas']} />;
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { MangaGrid, IMangaGridProps } from '@/components/MangaGrid';
|
|
||||||
import { TPartialManga } from '@/typings.ts';
|
|
||||||
|
|
||||||
function filterManga(mangas: TPartialManga[]): TPartialManga[] {
|
|
||||||
return mangas;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SourceMangaGrid(props: IMangaGridProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { mangas, isLoading, hasNextPage, loadMore, message, messageExtra, gridLayout } = props;
|
|
||||||
|
|
||||||
const filteredManga = filterManga(mangas);
|
|
||||||
const showFilteredOutMessage = filteredManga.length === 0 && mangas.length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MangaGrid
|
|
||||||
mangas={filteredManga}
|
|
||||||
isLoading={isLoading}
|
|
||||||
hasNextPage={hasNextPage}
|
|
||||||
loadMore={loadMore}
|
|
||||||
message={showFilteredOutMessage ? t('manga.error.label.no_matches') : message}
|
|
||||||
messageExtra={messageExtra}
|
|
||||||
gridLayout={gridLayout}
|
|
||||||
inLibraryIndicator
|
|
||||||
mode="source"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -16,11 +16,12 @@ import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCe
|
|||||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||||
import { Trackers } from '@/lib/data/Trackers.ts';
|
import { Trackers } from '@/lib/data/Trackers.ts';
|
||||||
import { TrackerCard, TrackerMode } from '@/components/tracker/TrackerCard.tsx';
|
import { TrackerCard, TrackerMode } from '@/components/tracker/TrackerCard.tsx';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { GetTrackersBindQuery } from '@/lib/graphql/generated/graphql.ts';
|
import { GetMangaTrackRecordsQuery, GetTrackersBindQuery, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts';
|
import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
import { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts';
|
||||||
|
|
||||||
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
|
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
|
||||||
if (id === searchModeForTracker) {
|
if (id === searchModeForTracker) {
|
||||||
@@ -34,7 +35,7 @@ const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracke
|
|||||||
return TrackerMode.UNTRACKED;
|
return TrackerMode.UNTRACKED;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TrackManga = ({ manga }: { manga: Pick<TManga, 'id' | 'trackRecords'> }) => {
|
export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'title'> }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -43,26 +44,32 @@ export const TrackManga = ({ manga }: { manga: Pick<TManga, 'id' | 'trackRecords
|
|||||||
const trackerList = requestManager.useGetTrackerList<GetTrackersBindQuery>(GET_TRACKERS_BIND, {
|
const trackerList = requestManager.useGetTrackerList<GetTrackersBindQuery>(GET_TRACKERS_BIND, {
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
});
|
});
|
||||||
const mangaTrackers = manga.trackRecords.nodes;
|
const trackers = trackerList.data?.trackers.nodes ?? [];
|
||||||
|
|
||||||
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
|
const mangaTrackRecordsList = requestManager.useGetManga<GetMangaTrackRecordsQuery>(
|
||||||
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackers));
|
GET_MANGA_TRACK_RECORDS,
|
||||||
|
manga.id,
|
||||||
|
);
|
||||||
|
const mangaTrackRecords = mangaTrackRecordsList.data?.manga.trackRecords.nodes ?? [];
|
||||||
|
|
||||||
|
const loggedInTrackers = Trackers.getLoggedIn(trackers);
|
||||||
|
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackRecords, trackers));
|
||||||
const trackersInUseIds = Trackers.getIds(trackersInUse);
|
const trackersInUseIds = Trackers.getIds(trackersInUse);
|
||||||
|
|
||||||
const isSearchActive = searchModeForTracker !== undefined;
|
const isSearchActive = searchModeForTracker !== undefined;
|
||||||
const OptionalDialogContent = useMemo(() => (isSearchActive ? Box : DialogContent), [isSearchActive]);
|
const OptionalDialogContent = useMemo(() => (isSearchActive ? Box : DialogContent), [isSearchActive]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all(manga.trackRecords.nodes.map((trackRecord) => requestManager.fetchTrackBind(trackRecord.id))).catch(
|
Promise.all(mangaTrackRecords.map((trackRecord) => requestManager.fetchTrackBind(trackRecord.id))).catch(() =>
|
||||||
() => makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error'),
|
makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error'),
|
||||||
);
|
);
|
||||||
}, [manga.id]);
|
}, [mangaTrackRecords]);
|
||||||
|
|
||||||
const trackerComponents = useMemo(
|
const trackerComponents = useMemo(
|
||||||
() =>
|
() =>
|
||||||
loggedInTrackers.map((tracker) => {
|
loggedInTrackers.map((tracker) => {
|
||||||
const mode = getTrackerMode(tracker.id, trackersInUseIds, searchModeForTracker);
|
const mode = getTrackerMode(tracker.id, trackersInUseIds, searchModeForTracker);
|
||||||
const trackRecord = Trackers.getTrackRecordFor(tracker, manga.trackRecords.nodes);
|
const trackRecord = Trackers.getTrackRecordFor(tracker, mangaTrackRecords);
|
||||||
|
|
||||||
const isSearchForTracker = mode === TrackerMode.SEARCH;
|
const isSearchForTracker = mode === TrackerMode.SEARCH;
|
||||||
if (isSearchActive && !isSearchForTracker) {
|
if (isSearchActive && !isSearchForTracker) {
|
||||||
@@ -73,27 +80,39 @@ export const TrackManga = ({ manga }: { manga: Pick<TManga, 'id' | 'trackRecords
|
|||||||
<TrackerCard
|
<TrackerCard
|
||||||
key={tracker.id}
|
key={tracker.id}
|
||||||
tracker={tracker}
|
tracker={tracker}
|
||||||
mangaId={manga.id}
|
manga={manga}
|
||||||
trackRecord={trackRecord}
|
trackRecord={trackRecord}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
setSearchMode={(id) => setSearchModeForTracker(id)}
|
setSearchMode={(id) => setSearchModeForTracker(id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
[trackersInUseIds, searchModeForTracker, manga.id],
|
[trackersInUseIds, searchModeForTracker, mangaTrackRecords],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (trackerList.error) {
|
const error = trackerList.error ?? mangaTrackRecordsList.error;
|
||||||
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<EmptyViewAbsoluteCentered
|
<EmptyViewAbsoluteCentered
|
||||||
message={t('global.error.label.failed_to_load_data')}
|
message={t('global.error.label.failed_to_load_data')}
|
||||||
messageExtra={trackerList.error.message}
|
messageExtra={error.message}
|
||||||
retry={() => trackerList.refetch().catch(defaultPromiseErrorHandler('TrackManga::refetch'))}
|
retry={() => {
|
||||||
|
if (trackerList.error) {
|
||||||
|
trackerList.refetch().catch(defaultPromiseErrorHandler('TrackManga::refetch: trackerList'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaTrackRecordsList.error) {
|
||||||
|
mangaTrackRecordsList
|
||||||
|
.refetch()
|
||||||
|
.catch(defaultPromiseErrorHandler('TrackManga::refetch: mangaTrackRecordsList'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackerList.loading) {
|
const loading = trackerList.loading || mangaTrackRecordsList.loading;
|
||||||
|
if (loading) {
|
||||||
return <LoadingPlaceholder />;
|
return <LoadingPlaceholder />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { TrackerUntrackedCard } from '@/components/tracker/TrackerUntrackedCard.
|
|||||||
import { TrackerSearch } from '@/components/tracker/TrackerSearch.tsx';
|
import { TrackerSearch } from '@/components/tracker/TrackerSearch.tsx';
|
||||||
import { TrackerActiveCard } from '@/components/tracker/TrackerActiveCard.tsx';
|
import { TrackerActiveCard } from '@/components/tracker/TrackerActiveCard.tsx';
|
||||||
import { TTrackerBind, TTrackRecordBind } from '@/lib/data/Trackers.ts';
|
import { TTrackerBind, TTrackRecordBind } from '@/lib/data/Trackers.ts';
|
||||||
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
export enum TrackerMode {
|
export enum TrackerMode {
|
||||||
UNTRACKED,
|
UNTRACKED,
|
||||||
@@ -19,13 +21,13 @@ export enum TrackerMode {
|
|||||||
|
|
||||||
export const TrackerCard = ({
|
export const TrackerCard = ({
|
||||||
tracker,
|
tracker,
|
||||||
mangaId,
|
manga,
|
||||||
trackRecord,
|
trackRecord,
|
||||||
mode,
|
mode,
|
||||||
setSearchMode,
|
setSearchMode,
|
||||||
}: {
|
}: {
|
||||||
tracker: TTrackerBind;
|
tracker: TTrackerBind;
|
||||||
mangaId: number;
|
manga: MangaIdInfo & Pick<MangaType, 'title'>;
|
||||||
trackRecord?: TTrackRecordBind;
|
trackRecord?: TTrackRecordBind;
|
||||||
mode: TrackerMode;
|
mode: TrackerMode;
|
||||||
setSearchMode: (id?: number) => void;
|
setSearchMode: (id?: number) => void;
|
||||||
@@ -37,7 +39,7 @@ export const TrackerCard = ({
|
|||||||
if (mode === TrackerMode.SEARCH) {
|
if (mode === TrackerMode.SEARCH) {
|
||||||
return (
|
return (
|
||||||
<TrackerSearch
|
<TrackerSearch
|
||||||
mangaId={mangaId}
|
manga={manga}
|
||||||
tracker={tracker}
|
tracker={tracker}
|
||||||
trackedId={trackRecord?.remoteId}
|
trackedId={trackRecord?.remoteId}
|
||||||
closeSearchMode={() => setSearchMode(undefined)}
|
closeSearchMode={() => setSearchMode(undefined)}
|
||||||
@@ -46,7 +48,7 @@ export const TrackerCard = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode === TrackerMode.INFO && !trackRecord) {
|
if (mode === TrackerMode.INFO && !trackRecord) {
|
||||||
throw new Error(`TrackerCard: unable to find track record for tracker "${tracker.id}" of manga "${mangaId}"}`);
|
throw new Error(`TrackerCard: unable to find track record for tracker "${tracker.id}" of manga "${manga.id}"}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -31,24 +31,23 @@ import { TrackerMangaCard } from '@/components/tracker/TrackerMangaCard.tsx';
|
|||||||
import { DIALOG_PADDING } from '@/components/tracker/constants.ts';
|
import { DIALOG_PADDING } from '@/components/tracker/constants.ts';
|
||||||
import { getOptionForDirection } from '@/theme.ts';
|
import { getOptionForDirection } from '@/theme.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
export const TrackerSearch = ({
|
export const TrackerSearch = ({
|
||||||
mangaId,
|
manga,
|
||||||
tracker,
|
tracker,
|
||||||
closeSearchMode,
|
closeSearchMode,
|
||||||
trackedId,
|
trackedId,
|
||||||
}: {
|
}: {
|
||||||
mangaId: number;
|
manga: MangaIdInfo & Pick<MangaType, 'title'>;
|
||||||
tracker: Pick<TTrackerBase, 'id'>;
|
tracker: Pick<TTrackerBase, 'id'>;
|
||||||
closeSearchMode: () => void;
|
closeSearchMode: () => void;
|
||||||
trackedId?: string;
|
trackedId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// can't be undefined, since this can only be opened from the manga screen
|
const [searchString, setSearchString] = useState<string>(manga.title);
|
||||||
const manga = requestManager.useGetManga(mangaId);
|
|
||||||
|
|
||||||
const [searchString, setSearchString] = useState<string>(manga.data!.manga.title);
|
|
||||||
const [tmpSearchString, setTmpSearchString] = useState(searchString);
|
const [tmpSearchString, setTmpSearchString] = useState(searchString);
|
||||||
|
|
||||||
const [selectedTrackerRemoteId, setSelectedTrackerRemoteId] = useState<string | undefined>(trackedId);
|
const [selectedTrackerRemoteId, setSelectedTrackerRemoteId] = useState<string | undefined>(trackedId);
|
||||||
@@ -62,7 +61,7 @@ export const TrackerSearch = ({
|
|||||||
setSelectedTrackerRemoteId(trackedId);
|
setSelectedTrackerRemoteId(trackedId);
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
trackerSearch.abortRequest(new Error(`MangaTrackerSearchCard(${tracker.id}, ${mangaId}): search changed`));
|
trackerSearch.abortRequest(new Error(`MangaTrackerSearchCard(${tracker.id}, ${manga.id}): search changed`));
|
||||||
}, [searchString]);
|
}, [searchString]);
|
||||||
|
|
||||||
const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker();
|
const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker();
|
||||||
@@ -82,7 +81,7 @@ export const TrackerSearch = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindTracker({ variables: { mangaId, remoteId: selectedTrackerRemoteId, trackerId: tracker.id } })
|
bindTracker({ variables: { mangaId: manga.id, remoteId: selectedTrackerRemoteId, trackerId: tracker.id } })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
makeToast(t('manga.action.track.add.label.success'), 'success');
|
makeToast(t('manga.action.track.add.label.success'), 'success');
|
||||||
closeSearchMode();
|
closeSearchMode();
|
||||||
|
|||||||
@@ -9,12 +9,13 @@
|
|||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { DocumentNode } from '@apollo/client';
|
import { DocumentNode } from '@apollo/client';
|
||||||
import { ChapterOffset, TManga, TranslationKey } from '@/typings.ts';
|
import { ChapterOffset, TranslationKey } from '@/typings.ts';
|
||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { ChapterListFieldsFragment, ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
import { ChapterListFieldsFragment, ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { CHAPTER_LIST_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
|
import { CHAPTER_LIST_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
|
export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ export class Chapters {
|
|||||||
static async markAsRead(
|
static async markAsRead(
|
||||||
chapters: (ChapterDownloadInfo & ChapterBookmarkInfo)[],
|
chapters: (ChapterDownloadInfo & ChapterBookmarkInfo)[],
|
||||||
wasManuallyMarkedAsRead: boolean = false,
|
wasManuallyMarkedAsRead: boolean = false,
|
||||||
trackProgressMangaId?: TManga['id'],
|
trackProgressMangaId?: MangaIdInfo['id'],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { deleteChaptersManuallyMarkedRead, deleteChaptersWithBookmark, updateProgressManualMarkRead } =
|
const { deleteChaptersManuallyMarkedRead, deleteChaptersWithBookmark, updateProgressManualMarkRead } =
|
||||||
await getMetadataServerSettings();
|
await getMetadataServerSettings();
|
||||||
@@ -279,7 +280,7 @@ export class Chapters {
|
|||||||
}: Action extends 'mark_as_read'
|
}: Action extends 'mark_as_read'
|
||||||
? {
|
? {
|
||||||
wasManuallyMarkedAsRead: boolean;
|
wasManuallyMarkedAsRead: boolean;
|
||||||
trackProgressMangaId?: TManga['id'];
|
trackProgressMangaId?: MangaIdInfo['id'];
|
||||||
chapters: (ChapterDownloadInfo & ChapterBookmarkInfo & ChapterReadInfo)[];
|
chapters: (ChapterDownloadInfo & ChapterBookmarkInfo & ChapterReadInfo)[];
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
@@ -8,19 +8,26 @@
|
|||||||
|
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import { DocumentNode } from '@apollo/client/core';
|
import { DocumentNode } from '@apollo/client/core';
|
||||||
import { MetadataMigrationSettings, TManga, TranslationKey } from '@/typings.ts';
|
import { MetadataMigrationSettings, TranslationKey } from '@/typings.ts';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import {
|
||||||
ChapterConditionInput,
|
ChapterConditionInput,
|
||||||
|
GetMangasBaseQuery,
|
||||||
|
GetMangasBaseQueryVariables,
|
||||||
GetMangasChapterIdsWithStateQuery,
|
GetMangasChapterIdsWithStateQuery,
|
||||||
GetMangaToMigrateQuery,
|
GetMangaToMigrateQuery,
|
||||||
GetMangaToMigrateToFetchMutation,
|
GetMangaToMigrateToFetchMutation,
|
||||||
|
MangaBaseFieldsFragment,
|
||||||
|
MangaReaderFieldsFragment,
|
||||||
|
MangaType,
|
||||||
|
TrackRecordType,
|
||||||
UpdateMangaCategoriesPatchInput,
|
UpdateMangaCategoriesPatchInput,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { Chapters } from '@/lib/data/Chapters.ts';
|
import { Chapters } from '@/lib/data/Chapters.ts';
|
||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { FULL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
|
import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
|
||||||
|
import { MANGA_BASE_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
|
||||||
export type MangaAction =
|
export type MangaAction =
|
||||||
| 'download'
|
| 'download'
|
||||||
@@ -108,10 +115,16 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MangaChapterCountInfo = { chapters: Pick<TManga['chapters'], 'totalCount'> };
|
export type TMangaReader = MangaReaderFieldsFragment;
|
||||||
export type MangaDownloadInfo = Pick<TManga, 'downloadCount'> & MangaChapterCountInfo;
|
|
||||||
export type MangaUnreadInfo = Pick<TManga, 'unreadCount'> & MangaChapterCountInfo;
|
export type MangaIdInfo = Pick<MangaType, 'id'>;
|
||||||
export type MangaThumbnailInfo = Pick<TManga, 'thumbnailUrl' | 'thumbnailUrlLastFetched'>;
|
export type MangaChapterCountInfo = { chapters: Pick<MangaType['chapters'], 'totalCount'> };
|
||||||
|
export type MangaDownloadInfo = Pick<MangaType, 'downloadCount'> & MangaChapterCountInfo;
|
||||||
|
export type MangaUnreadInfo = Pick<MangaType, 'unreadCount'> & MangaChapterCountInfo;
|
||||||
|
export type MangaThumbnailInfo = Pick<MangaType, 'thumbnailUrl' | 'thumbnailUrlLastFetched'>;
|
||||||
|
export type MangaTrackRecordInfo = MangaIdInfo & {
|
||||||
|
trackRecords: { nodes: Pick<TrackRecordType, 'id' | 'trackerId'>[] };
|
||||||
|
};
|
||||||
|
|
||||||
export type MigrateMode = 'copy' | 'migrate';
|
export type MigrateMode = 'copy' | 'migrate';
|
||||||
|
|
||||||
@@ -162,14 +175,14 @@ type PerformActionOptions<Action extends MangaAction> = Action extends 'mark_as_
|
|||||||
: DefaultActionOption;
|
: DefaultActionOption;
|
||||||
|
|
||||||
export class Mangas {
|
export class Mangas {
|
||||||
static getIds(mangas: { id: number }[]): number[] {
|
static getIds(mangas: MangaIdInfo[]): number[] {
|
||||||
return mangas.map((manga) => manga.id);
|
return mangas.map((manga) => manga.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFromCache<T>(
|
static getFromCache<T = MangaBaseFieldsFragment>(
|
||||||
id: number,
|
id: MangaIdInfo['id'],
|
||||||
fragment: DocumentNode = FULL_MANGA_FIELDS,
|
fragment: DocumentNode = MANGA_BASE_FIELDS,
|
||||||
fragmentName: string = 'FULL_MANGA_FIELDS',
|
fragmentName: string = 'MANGA_BASE_FIELDS',
|
||||||
): T | null {
|
): T | null {
|
||||||
return requestManager.graphQLClient.client.cache.readFragment<T>({
|
return requestManager.graphQLClient.client.cache.readFragment<T>({
|
||||||
id: requestManager.graphQLClient.client.cache.identify({
|
id: requestManager.graphQLClient.client.cache.identify({
|
||||||
@@ -236,8 +249,10 @@ export class Mangas {
|
|||||||
return requestManager.getValidImgUrlFor(thumbnailUrl);
|
return requestManager.getValidImgUrlFor(thumbnailUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDuplicateLibraryMangas(title: string): ReturnType<typeof requestManager.getMangas> {
|
static getDuplicateLibraryMangas(
|
||||||
return requestManager.getMangas({
|
title: string,
|
||||||
|
): ReturnType<typeof requestManager.getMangas<GetMangasBaseQuery, GetMangasBaseQueryVariables>> {
|
||||||
|
return requestManager.getMangas<GetMangasBaseQuery, GetMangasBaseQueryVariables>(GET_MANGAS_BASE, {
|
||||||
condition: { inLibrary: true },
|
condition: { inLibrary: true },
|
||||||
filter: { title: { likeInsensitive: title } },
|
filter: { title: { likeInsensitive: title } },
|
||||||
});
|
});
|
||||||
@@ -429,7 +444,7 @@ export class Mangas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async migrate(
|
static async migrate(
|
||||||
mangaId: number,
|
mangaId: MangaIdInfo['id'],
|
||||||
mangaIdToMigrateTo: number,
|
mangaIdToMigrateTo: number,
|
||||||
{
|
{
|
||||||
mode,
|
mode,
|
||||||
|
|||||||
@@ -49,12 +49,13 @@ export type TTrackerSearch = TTrackerBase & Pick<TrackerType, 'authUrl'>;
|
|||||||
|
|
||||||
export type TTrackerBind = TTrackerBase & Pick<TrackerType, 'icon' | 'supportsTrackDeletion' | 'scores' | 'statuses'>;
|
export type TTrackerBind = TTrackerBase & Pick<TrackerType, 'icon' | 'supportsTrackDeletion' | 'scores' | 'statuses'>;
|
||||||
|
|
||||||
|
type TrackerIdInfo = Pick<TrackerType, 'id'>;
|
||||||
type LoggedInInfo = Pick<TrackerType, 'isLoggedIn' | 'isTokenExpired'>;
|
type LoggedInInfo = Pick<TrackerType, 'isLoggedIn' | 'isTokenExpired'>;
|
||||||
|
|
||||||
type TrackRecordTrackerInfo = { tracker: TTrackerBind };
|
type TrackRecordTrackerInfo = Pick<TrackRecordType, 'trackerId'>;
|
||||||
|
|
||||||
export class Trackers {
|
export class Trackers {
|
||||||
static getIds(trackers: { id: number }[]): number[] {
|
static getIds(trackers: TrackerIdInfo[]): number[] {
|
||||||
return trackers.map((tracker) => tracker.id);
|
return trackers.map((tracker) => tracker.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,16 +75,19 @@ export class Trackers {
|
|||||||
return trackers.filter(this.isLoggedIn);
|
return trackers.filter(this.isLoggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTrackers<TrackRecord extends TrackRecordTrackerInfo>(
|
static getTrackers<TrackRecord extends TrackRecordTrackerInfo, Tracker extends TrackerIdInfo>(
|
||||||
trackRecords: TrackRecord[],
|
trackRecords: TrackRecord[],
|
||||||
): TrackRecord['tracker'][] {
|
trackers: Tracker[],
|
||||||
return trackRecords.map((trackRecord) => trackRecord.tracker);
|
): Tracker[] {
|
||||||
|
return trackRecords
|
||||||
|
.map((trackRecord) => trackers.find((tracker) => tracker.id === trackRecord.trackerId))
|
||||||
|
.filter((tracker) => !!tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTrackRecordFor<TrackRecord extends TrackRecordTrackerInfo>(
|
static getTrackRecordFor<TrackRecord extends TrackRecordTrackerInfo>(
|
||||||
tracker: { id: number },
|
tracker: TrackerIdInfo,
|
||||||
trackRecords: TrackRecord[],
|
trackRecords: TrackRecord[],
|
||||||
): TrackRecord | undefined {
|
): TrackRecord | undefined {
|
||||||
return trackRecords.find((trackRecord) => trackRecord.tracker.id === tracker.id);
|
return trackRecords.find((trackRecord) => trackRecord.trackerId === tracker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
|
||||||
import { TRACKER_BIND_FIELDS } from '@/lib/graphql/fragments/TrackFragments.ts';
|
|
||||||
import { TRACK_RECORD_BIND_FIELDS } from '@/lib/graphql/fragments/TrackRecordFragments.ts';
|
|
||||||
|
|
||||||
export const PAGE_INFO = gql`
|
export const PAGE_INFO = gql`
|
||||||
fragment PAGE_INFO on PageInfo {
|
fragment PAGE_INFO on PageInfo {
|
||||||
@@ -27,147 +24,6 @@ export const GLOBAL_METADATA = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FULL_CATEGORY_FIELDS = gql`
|
|
||||||
fragment FULL_CATEGORY_FIELDS on CategoryType {
|
|
||||||
default
|
|
||||||
id
|
|
||||||
includeInUpdate
|
|
||||||
includeInDownload
|
|
||||||
name
|
|
||||||
order
|
|
||||||
meta {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
mangas {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FULL_TRACK_RECORD_FIELDS = gql`
|
|
||||||
${TRACK_RECORD_BIND_FIELDS}
|
|
||||||
${TRACKER_BIND_FIELDS}
|
|
||||||
|
|
||||||
fragment FULL_TRACK_RECORD_FIELDS on TrackRecordType {
|
|
||||||
...TRACK_RECORD_BIND_FIELDS
|
|
||||||
tracker {
|
|
||||||
...TRACKER_BIND_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const BASE_MANGA_FIELDS = gql`
|
|
||||||
${SOURCE_BASE_FIELDS}
|
|
||||||
${FULL_TRACK_RECORD_FIELDS}
|
|
||||||
fragment BASE_MANGA_FIELDS on MangaType {
|
|
||||||
artist
|
|
||||||
author
|
|
||||||
chaptersLastFetchedAt
|
|
||||||
description
|
|
||||||
genre
|
|
||||||
id
|
|
||||||
inLibrary
|
|
||||||
inLibraryAt
|
|
||||||
initialized
|
|
||||||
lastFetchedAt
|
|
||||||
meta {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
realUrl
|
|
||||||
source {
|
|
||||||
...SOURCE_BASE_FIELDS
|
|
||||||
}
|
|
||||||
status
|
|
||||||
thumbnailUrl
|
|
||||||
thumbnailUrlLastFetched
|
|
||||||
title
|
|
||||||
url
|
|
||||||
trackRecords {
|
|
||||||
totalCount
|
|
||||||
nodes {
|
|
||||||
...FULL_TRACK_RECORD_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const PARTIAL_MANGA_FIELDS = gql`
|
|
||||||
${BASE_MANGA_FIELDS}
|
|
||||||
${FULL_CATEGORY_FIELDS}
|
|
||||||
fragment PARTIAL_MANGA_FIELDS on MangaType {
|
|
||||||
...BASE_MANGA_FIELDS
|
|
||||||
unreadCount
|
|
||||||
downloadCount
|
|
||||||
bookmarkCount
|
|
||||||
categories {
|
|
||||||
nodes {
|
|
||||||
...FULL_CATEGORY_FIELDS
|
|
||||||
}
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
chapters {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FULL_CHAPTER_FIELDS = gql`
|
|
||||||
fragment FULL_CHAPTER_FIELDS on ChapterType {
|
|
||||||
chapterNumber
|
|
||||||
fetchedAt
|
|
||||||
id
|
|
||||||
isBookmarked
|
|
||||||
isDownloaded
|
|
||||||
isRead
|
|
||||||
lastPageRead
|
|
||||||
lastReadAt
|
|
||||||
mangaId
|
|
||||||
manga {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
inLibrary
|
|
||||||
thumbnailUrl
|
|
||||||
lastFetchedAt
|
|
||||||
}
|
|
||||||
meta {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
name
|
|
||||||
pageCount
|
|
||||||
realUrl
|
|
||||||
scanlator
|
|
||||||
sourceOrder
|
|
||||||
uploadDate
|
|
||||||
url
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FULL_MANGA_FIELDS = gql`
|
|
||||||
${PARTIAL_MANGA_FIELDS}
|
|
||||||
${FULL_CHAPTER_FIELDS}
|
|
||||||
fragment FULL_MANGA_FIELDS on MangaType {
|
|
||||||
...PARTIAL_MANGA_FIELDS
|
|
||||||
lastReadChapter {
|
|
||||||
...FULL_CHAPTER_FIELDS
|
|
||||||
}
|
|
||||||
latestReadChapter {
|
|
||||||
...FULL_CHAPTER_FIELDS
|
|
||||||
}
|
|
||||||
latestFetchedChapter {
|
|
||||||
...FULL_CHAPTER_FIELDS
|
|
||||||
}
|
|
||||||
latestUploadedChapter {
|
|
||||||
...FULL_CHAPTER_FIELDS
|
|
||||||
}
|
|
||||||
firstUnreadChapter {
|
|
||||||
...FULL_CHAPTER_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ABOUT_WEBUI = gql`
|
export const ABOUT_WEBUI = gql`
|
||||||
fragment ABOUT_WEBUI on AboutWebUI {
|
fragment ABOUT_WEBUI on AboutWebUI {
|
||||||
channel
|
channel
|
||||||
|
|||||||
@@ -7,19 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
import { MANGA_BASE_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
const MANGA_BASE_FIELDS = gql`
|
|
||||||
fragment MANGA_BASE_FIELDS on MangaType {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
|
|
||||||
thumbnailUrl
|
|
||||||
thumbnailUrlLastFetched
|
|
||||||
|
|
||||||
inLibrary
|
|
||||||
initialized
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CHAPTER_BASE_FIELDS = gql`
|
export const CHAPTER_BASE_FIELDS = gql`
|
||||||
fragment CHAPTER_BASE_FIELDS on ChapterType {
|
fragment CHAPTER_BASE_FIELDS on ChapterType {
|
||||||
|
|||||||
144
src/lib/graphql/fragments/MangaFragments.ts
Normal file
144
src/lib/graphql/fragments/MangaFragments.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const MANGA_BASE_FIELDS = gql`
|
||||||
|
fragment MANGA_BASE_FIELDS on MangaType {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
|
||||||
|
thumbnailUrl
|
||||||
|
thumbnailUrlLastFetched
|
||||||
|
inLibrary
|
||||||
|
initialized
|
||||||
|
sourceId
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MANGA_CHAPTER_STAT_FIELDS = gql`
|
||||||
|
fragment MANGA_CHAPTER_STAT_FIELDS on MangaType {
|
||||||
|
id
|
||||||
|
unreadCount
|
||||||
|
downloadCount
|
||||||
|
bookmarkCount
|
||||||
|
|
||||||
|
chapters {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MANGA_READER_FIELDS = gql`
|
||||||
|
${MANGA_BASE_FIELDS}
|
||||||
|
|
||||||
|
fragment MANGA_READER_FIELDS on MangaType {
|
||||||
|
...MANGA_BASE_FIELDS
|
||||||
|
|
||||||
|
meta {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
chapters {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
|
||||||
|
trackRecords {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MANGA_LIBRARY_FIELDS = gql`
|
||||||
|
${MANGA_BASE_FIELDS}
|
||||||
|
${MANGA_CHAPTER_STAT_FIELDS}
|
||||||
|
|
||||||
|
fragment MANGA_LIBRARY_FIELDS on MangaType {
|
||||||
|
...MANGA_BASE_FIELDS
|
||||||
|
...MANGA_CHAPTER_STAT_FIELDS
|
||||||
|
|
||||||
|
genre
|
||||||
|
lastFetchedAt
|
||||||
|
inLibraryAt
|
||||||
|
|
||||||
|
trackRecords {
|
||||||
|
totalCount
|
||||||
|
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
trackerId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUnreadChapter {
|
||||||
|
id
|
||||||
|
sourceOrder
|
||||||
|
}
|
||||||
|
lastReadChapter {
|
||||||
|
id
|
||||||
|
sourceOrder
|
||||||
|
lastReadAt
|
||||||
|
}
|
||||||
|
latestReadChapter {
|
||||||
|
id
|
||||||
|
sourceOrder
|
||||||
|
lastReadAt
|
||||||
|
}
|
||||||
|
latestFetchedChapter {
|
||||||
|
id
|
||||||
|
fetchedAt
|
||||||
|
}
|
||||||
|
latestUploadedChapter {
|
||||||
|
id
|
||||||
|
uploadDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MANGA_SCREEN_FIELDS = gql`
|
||||||
|
${MANGA_LIBRARY_FIELDS}
|
||||||
|
|
||||||
|
fragment MANGA_SCREEN_FIELDS on MangaType {
|
||||||
|
...MANGA_LIBRARY_FIELDS
|
||||||
|
|
||||||
|
artist
|
||||||
|
author
|
||||||
|
description
|
||||||
|
|
||||||
|
status
|
||||||
|
realUrl
|
||||||
|
|
||||||
|
sourceId
|
||||||
|
source {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
trackRecords {
|
||||||
|
totalCount
|
||||||
|
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
trackerId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS = gql`
|
||||||
|
${MANGA_BASE_FIELDS}
|
||||||
|
${MANGA_CHAPTER_STAT_FIELDS}
|
||||||
|
|
||||||
|
fragment MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS on MangaType {
|
||||||
|
...MANGA_BASE_FIELDS
|
||||||
|
...MANGA_CHAPTER_STAT_FIELDS
|
||||||
|
|
||||||
|
description
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { FULL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
|
import { MANGA_CHAPTER_STAT_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
|
||||||
const UPDATER_MANGA_FIELDS = gql`
|
const UPDATER_MANGA_FIELDS = gql`
|
||||||
fragment UPDATER_MANGA_FIELDS on MangaType {
|
fragment UPDATER_MANGA_FIELDS on MangaType {
|
||||||
@@ -19,7 +19,7 @@ const UPDATER_MANGA_FIELDS = gql`
|
|||||||
|
|
||||||
export const UPDATER_SUBSCRIPTION_FIELDS = gql`
|
export const UPDATER_SUBSCRIPTION_FIELDS = gql`
|
||||||
${UPDATER_MANGA_FIELDS}
|
${UPDATER_MANGA_FIELDS}
|
||||||
${FULL_MANGA_FIELDS}
|
${MANGA_CHAPTER_STAT_FIELDS}
|
||||||
|
|
||||||
fragment UPDATER_SUBSCRIPTION_FIELDS on UpdateStatus {
|
fragment UPDATER_SUBSCRIPTION_FIELDS on UpdateStatus {
|
||||||
isRunning
|
isRunning
|
||||||
@@ -28,7 +28,7 @@ export const UPDATER_SUBSCRIPTION_FIELDS = gql`
|
|||||||
mangas {
|
mangas {
|
||||||
totalCount
|
totalCount
|
||||||
nodes {
|
nodes {
|
||||||
...FULL_MANGA_FIELDS
|
...MANGA_CHAPTER_STAT_FIELDS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
|
import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
|
||||||
import {
|
import {
|
||||||
GetChaptersMangaQuery, GetDownloadStatusQueryVariables, GetGlobalMetadataQueryVariables,
|
GetChaptersMangaQuery, GetDownloadStatusQueryVariables, GetGlobalMetadataQueryVariables,
|
||||||
GetMangaQueryVariables, GetSourceBrowseQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
|
GetMangaScreenQueryVariables, GetSourceBrowseQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
|
||||||
} from "@/lib/graphql/generated/graphql.ts";
|
} from "@/lib/graphql/generated/graphql.ts";
|
||||||
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";
|
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";
|
||||||
export type AboutServerPayloadKeySpecifier = ('buildTime' | 'buildType' | 'discord' | 'github' | 'name' | 'revision' | 'version' | AboutServerPayloadKeySpecifier)[];
|
export type AboutServerPayloadKeySpecifier = ('buildTime' | 'buildType' | 'discord' | 'github' | 'name' | 'revision' | 'version' | AboutServerPayloadKeySpecifier)[];
|
||||||
@@ -583,7 +583,7 @@ export type QueryFieldPolicy = {
|
|||||||
extensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
extensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
getWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
getWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
||||||
lastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
lastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
manga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>>,
|
manga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>>,
|
||||||
mangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
mangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
meta?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>>,
|
meta?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>>,
|
||||||
metas?: FieldPolicy<any> | FieldReadFunction<any>,
|
metas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { FULL_MANGA_FIELDS } from '@/lib/graphql/Fragments';
|
import { MANGA_SCREEN_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
|
||||||
export const DELETE_MANGA_METADATA = gql`
|
export const DELETE_MANGA_METADATA = gql`
|
||||||
mutation DELETE_MANGA_METADATA($input: DeleteMangaMetaInput!) {
|
mutation DELETE_MANGA_METADATA($input: DeleteMangaMetaInput!) {
|
||||||
@@ -37,12 +37,13 @@ export const DELETE_MANGA_METADATA = gql`
|
|||||||
|
|
||||||
// makes the server fetch and return the manga
|
// makes the server fetch and return the manga
|
||||||
export const GET_MANGA_FETCH = gql`
|
export const GET_MANGA_FETCH = gql`
|
||||||
${FULL_MANGA_FIELDS}
|
${MANGA_SCREEN_FIELDS}
|
||||||
|
|
||||||
mutation GET_MANGA_FETCH($input: FetchMangaInput!) {
|
mutation GET_MANGA_FETCH($input: FetchMangaInput!) {
|
||||||
fetchManga(input: $input) {
|
fetchManga(input: $input) {
|
||||||
clientMutationId
|
clientMutationId
|
||||||
manga {
|
manga {
|
||||||
...FULL_MANGA_FIELDS
|
...MANGA_SCREEN_FIELDS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { BASE_MANGA_FIELDS } from '@/lib/graphql/Fragments';
|
|
||||||
import { SOURCE_SETTING_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
import { SOURCE_SETTING_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
||||||
|
import { MANGA_BASE_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
|
||||||
export const GET_SOURCE_MANGAS_FETCH = gql`
|
export const GET_SOURCE_MANGAS_FETCH = gql`
|
||||||
${BASE_MANGA_FIELDS}
|
${MANGA_BASE_FIELDS}
|
||||||
|
|
||||||
mutation GET_SOURCE_MANGAS_FETCH($input: FetchSourceMangaInput!) {
|
mutation GET_SOURCE_MANGAS_FETCH($input: FetchSourceMangaInput!) {
|
||||||
fetchSourceManga(input: $input) {
|
fetchSourceManga(input: $input) {
|
||||||
clientMutationId
|
clientMutationId
|
||||||
hasNextPage
|
hasNextPage
|
||||||
mangas {
|
mangas {
|
||||||
...BASE_MANGA_FIELDS
|
...MANGA_BASE_FIELDS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,15 +55,13 @@ export const TRACKER_BIND = gql`
|
|||||||
bindTrack(input: { mangaId: $mangaId, remoteId: $remoteId, trackerId: $trackerId }) {
|
bindTrack(input: { mangaId: $mangaId, remoteId: $remoteId, trackerId: $trackerId }) {
|
||||||
trackRecord {
|
trackRecord {
|
||||||
...TRACK_RECORD_BIND_FIELDS
|
...TRACK_RECORD_BIND_FIELDS
|
||||||
tracker {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
manga {
|
manga {
|
||||||
id
|
id
|
||||||
trackRecords {
|
trackRecords {
|
||||||
totalCount
|
totalCount
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
|
trackerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +81,7 @@ export const TRACKER_UNBIND = gql`
|
|||||||
totalCount
|
totalCount
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
|
trackerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +103,7 @@ export const TRACKER_UPDATE_BIND = gql`
|
|||||||
totalCount
|
totalCount
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
|
trackerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { FULL_MANGA_FIELDS, PAGE_INFO } from '@/lib/graphql/Fragments';
|
import { PAGE_INFO } from '@/lib/graphql/Fragments';
|
||||||
import {
|
import {
|
||||||
CATEGORY_BASE_FIELDS,
|
CATEGORY_BASE_FIELDS,
|
||||||
CATEGORY_LIBRARY_FIELDS,
|
CATEGORY_LIBRARY_FIELDS,
|
||||||
CATEGORY_SETTING_FIELDS,
|
CATEGORY_SETTING_FIELDS,
|
||||||
} from '@/lib/graphql/fragments/CategoryFragments.ts';
|
} from '@/lib/graphql/fragments/CategoryFragments.ts';
|
||||||
|
import { MANGA_LIBRARY_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
|
||||||
export const GET_CATEGORIES_BASE = gql`
|
export const GET_CATEGORIES_BASE = gql`
|
||||||
${CATEGORY_BASE_FIELDS}
|
${CATEGORY_BASE_FIELDS}
|
||||||
@@ -126,14 +127,15 @@ export const GET_CATEGORIES_SETTINGS = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_CATEGORY_MANGAS = gql`
|
export const GET_CATEGORY_MANGAS = gql`
|
||||||
${FULL_MANGA_FIELDS}
|
${MANGA_LIBRARY_FIELDS}
|
||||||
${PAGE_INFO}
|
${PAGE_INFO}
|
||||||
|
|
||||||
query GET_CATEGORY_MANGAS($id: Int!) {
|
query GET_CATEGORY_MANGAS($id: Int!) {
|
||||||
category(id: $id) {
|
category(id: $id) {
|
||||||
id
|
id
|
||||||
mangas {
|
mangas {
|
||||||
nodes {
|
nodes {
|
||||||
...FULL_MANGA_FIELDS
|
...MANGA_LIBRARY_FIELDS
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
...PAGE_INFO
|
...PAGE_INFO
|
||||||
|
|||||||
@@ -7,15 +7,68 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { FULL_CHAPTER_FIELDS, FULL_MANGA_FIELDS, PAGE_INFO } from '@/lib/graphql/Fragments';
|
import { PAGE_INFO } from '@/lib/graphql/Fragments';
|
||||||
|
import {
|
||||||
|
MANGA_BASE_FIELDS,
|
||||||
|
MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS,
|
||||||
|
MANGA_LIBRARY_FIELDS,
|
||||||
|
MANGA_READER_FIELDS,
|
||||||
|
MANGA_SCREEN_FIELDS,
|
||||||
|
} from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
import { TRACK_RECORD_BIND_FIELDS } from '@/lib/graphql/fragments/TrackRecordFragments.ts';
|
||||||
|
|
||||||
// returns the current manga from the database
|
// returns the current manga from the database
|
||||||
export const GET_MANGA = gql`
|
export const GET_MANGA_SCREEN = gql`
|
||||||
${FULL_MANGA_FIELDS}
|
${MANGA_SCREEN_FIELDS}
|
||||||
${FULL_CHAPTER_FIELDS}
|
|
||||||
query GET_MANGA($id: Int!) {
|
query GET_MANGA_SCREEN($id: Int!) {
|
||||||
manga(id: $id) {
|
manga(id: $id) {
|
||||||
...FULL_MANGA_FIELDS
|
...MANGA_SCREEN_FIELDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// returns the current manga from the database
|
||||||
|
export const GET_MANGA_READER = gql`
|
||||||
|
${MANGA_READER_FIELDS}
|
||||||
|
|
||||||
|
query GET_MANGA_READER($id: Int!) {
|
||||||
|
manga(id: $id) {
|
||||||
|
...MANGA_READER_FIELDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// returns the current manga from the database
|
||||||
|
export const GET_MANGA_TRACK_RECORDS = gql`
|
||||||
|
${TRACK_RECORD_BIND_FIELDS}
|
||||||
|
|
||||||
|
query GET_MANGA_TRACK_RECORDS($id: Int!) {
|
||||||
|
manga(id: $id) {
|
||||||
|
id
|
||||||
|
|
||||||
|
trackRecords {
|
||||||
|
totalCount
|
||||||
|
|
||||||
|
nodes {
|
||||||
|
...TRACK_RECORD_BIND_FIELDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// returns the current manga from the database
|
||||||
|
export const GET_MANGA_CATEGORIES = gql`
|
||||||
|
query GET_MANGA_CATEGORIES($id: Int!) {
|
||||||
|
manga(id: $id) {
|
||||||
|
id
|
||||||
|
categories {
|
||||||
|
totalCount
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -62,11 +115,11 @@ export const GET_MANGA_TO_MIGRATE = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// returns the current manga from the database
|
// returns the current manga from the database
|
||||||
export const GET_MANGAS = gql`
|
export const GET_MANGAS_BASE = gql`
|
||||||
${FULL_MANGA_FIELDS}
|
${MANGA_BASE_FIELDS}
|
||||||
${FULL_CHAPTER_FIELDS}
|
|
||||||
${PAGE_INFO}
|
${PAGE_INFO}
|
||||||
query GET_MANGAS(
|
|
||||||
|
query GET_MANGAS_BASE(
|
||||||
$after: Cursor
|
$after: Cursor
|
||||||
$before: Cursor
|
$before: Cursor
|
||||||
$condition: MangaConditionInput
|
$condition: MangaConditionInput
|
||||||
@@ -89,7 +142,83 @@ export const GET_MANGAS = gql`
|
|||||||
orderByType: $orderByType
|
orderByType: $orderByType
|
||||||
) {
|
) {
|
||||||
nodes {
|
nodes {
|
||||||
...FULL_MANGA_FIELDS
|
...MANGA_BASE_FIELDS
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PAGE_INFO
|
||||||
|
}
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// returns the current manga from the database
|
||||||
|
export const GET_MANGAS_LIBRARY = gql`
|
||||||
|
${MANGA_LIBRARY_FIELDS}
|
||||||
|
${PAGE_INFO}
|
||||||
|
|
||||||
|
query GET_MANGAS_LIBRARY(
|
||||||
|
$after: Cursor
|
||||||
|
$before: Cursor
|
||||||
|
$condition: MangaConditionInput
|
||||||
|
$filter: MangaFilterInput
|
||||||
|
$first: Int
|
||||||
|
$last: Int
|
||||||
|
$offset: Int
|
||||||
|
$orderBy: MangaOrderBy
|
||||||
|
$orderByType: SortOrder
|
||||||
|
) {
|
||||||
|
mangas(
|
||||||
|
after: $after
|
||||||
|
before: $before
|
||||||
|
condition: $condition
|
||||||
|
filter: $filter
|
||||||
|
first: $first
|
||||||
|
last: $last
|
||||||
|
offset: $offset
|
||||||
|
orderBy: $orderBy
|
||||||
|
orderByType: $orderByType
|
||||||
|
) {
|
||||||
|
nodes {
|
||||||
|
...MANGA_LIBRARY_FIELDS
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PAGE_INFO
|
||||||
|
}
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// returns the current manga from the database
|
||||||
|
export const GET_MANGAS_DUPLICATES = gql`
|
||||||
|
${MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS}
|
||||||
|
${PAGE_INFO}
|
||||||
|
|
||||||
|
query GET_MANGAS_DUPLICATES(
|
||||||
|
$after: Cursor
|
||||||
|
$before: Cursor
|
||||||
|
$condition: MangaConditionInput
|
||||||
|
$filter: MangaFilterInput
|
||||||
|
$first: Int
|
||||||
|
$last: Int
|
||||||
|
$offset: Int
|
||||||
|
$orderBy: MangaOrderBy
|
||||||
|
$orderByType: SortOrder
|
||||||
|
) {
|
||||||
|
mangas(
|
||||||
|
after: $after
|
||||||
|
before: $before
|
||||||
|
condition: $condition
|
||||||
|
filter: $filter
|
||||||
|
first: $first
|
||||||
|
last: $last
|
||||||
|
offset: $offset
|
||||||
|
orderBy: $orderBy
|
||||||
|
orderByType: $orderByType
|
||||||
|
) {
|
||||||
|
nodes {
|
||||||
|
...MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
...PAGE_INFO
|
...PAGE_INFO
|
||||||
@@ -106,9 +235,7 @@ export const GET_MIGRATABLE_SOURCE_MANGAS = gql`
|
|||||||
id
|
id
|
||||||
title
|
title
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
source {
|
sourceId
|
||||||
id
|
|
||||||
}
|
|
||||||
categories {
|
categories {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import {
|
|||||||
Metadata,
|
Metadata,
|
||||||
MetadataHolder,
|
MetadataHolder,
|
||||||
MetadataKeyValuePair,
|
MetadataKeyValuePair,
|
||||||
TManga,
|
|
||||||
} from '@/typings.ts';
|
} from '@/typings.ts';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { MetaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { MetaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { DEFAULT_DEVICE, getActiveDevice } from '@/util/device.ts';
|
import { DEFAULT_DEVICE, getActiveDevice } from '@/util/device.ts';
|
||||||
import { CategoryIdInfo } from '@/lib/data/Categories.ts';
|
import { CategoryIdInfo } from '@/lib/data/Categories.ts';
|
||||||
import { ChapterIdInfo } from '@/lib/data/Chapters.ts';
|
import { ChapterIdInfo } from '@/lib/data/Chapters.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
const APP_METADATA_KEY_PREFIX = 'webUI_';
|
const APP_METADATA_KEY_PREFIX = 'webUI_';
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ export const requestUpdateMetadataValue = async (
|
|||||||
await requestManager.setGlobalMetadata(metadataKey, value).response;
|
await requestManager.setGlobalMetadata(metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
case 'manga':
|
case 'manga':
|
||||||
await requestManager.setMangaMeta((metadataHolder as TManga).id, metadataKey, value).response;
|
await requestManager.setMangaMeta((metadataHolder as MangaIdInfo).id, metadataKey, value).response;
|
||||||
break;
|
break;
|
||||||
case 'source':
|
case 'source':
|
||||||
await requestManager.setSourceMeta((metadataHolder as Pick<SourceType, 'id'>).id, metadataKey, value)
|
await requestManager.setSourceMeta((metadataHolder as Pick<SourceType, 'id'>).id, metadataKey, value)
|
||||||
@@ -400,7 +400,7 @@ export const requestUpdateServerMetadata = async (keysToValues: MetadataKeyValue
|
|||||||
requestUpdateMetadata({}, 'global', keysToValues);
|
requestUpdateMetadata({}, 'global', keysToValues);
|
||||||
|
|
||||||
export const requestUpdateMangaMetadata = async (
|
export const requestUpdateMangaMetadata = async (
|
||||||
manga: TManga,
|
manga: MangaIdInfo & GqlMetaHolder,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues);
|
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Metadata, IReaderSettings, MetadataKeyValuePair, GqlMetaHolder, TManga } from '@/typings.ts';
|
import { Metadata, IReaderSettings, MetadataKeyValuePair, GqlMetaHolder } from '@/typings.ts';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import {
|
||||||
convertFromGqlMeta,
|
convertFromGqlMeta,
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
requestUpdateServerMetadata,
|
requestUpdateServerMetadata,
|
||||||
} from '@/lib/metadata/metadata.ts';
|
} from '@/lib/metadata/metadata.ts';
|
||||||
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
type UndefinedReaderSettings = {
|
type UndefinedReaderSettings = {
|
||||||
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
||||||
@@ -109,7 +110,7 @@ export const checkAndHandleMissingStoredReaderSettings = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metadataHolderType === 'manga') {
|
if (metadataHolderType === 'manga') {
|
||||||
await requestUpdateMangaMetadata(metadataHolder as TManga, settingsToUpdate);
|
await requestUpdateMangaMetadata(metadataHolder as MangaIdInfo & GqlMetaHolder, settingsToUpdate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,6 @@ import {
|
|||||||
GetMangaChaptersFetchMutationVariables,
|
GetMangaChaptersFetchMutationVariables,
|
||||||
GetMangaFetchMutation,
|
GetMangaFetchMutation,
|
||||||
GetMangaFetchMutationVariables,
|
GetMangaFetchMutationVariables,
|
||||||
GetMangaQuery,
|
|
||||||
GetMangaQueryVariables,
|
|
||||||
GetMangasQuery,
|
|
||||||
GetMangasQueryVariables,
|
|
||||||
GetRestoreStatusQuery,
|
GetRestoreStatusQuery,
|
||||||
GetRestoreStatusQueryVariables,
|
GetRestoreStatusQueryVariables,
|
||||||
GetServerSettingsQuery,
|
GetServerSettingsQuery,
|
||||||
@@ -197,6 +193,8 @@ import {
|
|||||||
GetChaptersUpdatesQueryVariables,
|
GetChaptersUpdatesQueryVariables,
|
||||||
GetSourcesListQuery,
|
GetSourcesListQuery,
|
||||||
GetSourcesListQueryVariables,
|
GetSourcesListQueryVariables,
|
||||||
|
GetMangasLibraryQuery,
|
||||||
|
GetMangasLibraryQueryVariables,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
||||||
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
||||||
@@ -223,9 +221,9 @@ import {
|
|||||||
UPDATE_MANGAS_CATEGORIES,
|
UPDATE_MANGAS_CATEGORIES,
|
||||||
} from '@/lib/graphql/mutations/MangaMutation.ts';
|
} from '@/lib/graphql/mutations/MangaMutation.ts';
|
||||||
import {
|
import {
|
||||||
GET_MANGA,
|
|
||||||
GET_MANGA_TO_MIGRATE,
|
GET_MANGA_TO_MIGRATE,
|
||||||
GET_MANGAS,
|
GET_MANGA_TRACK_RECORDS,
|
||||||
|
GET_MANGAS_LIBRARY,
|
||||||
GET_MIGRATABLE_SOURCE_MANGAS,
|
GET_MIGRATABLE_SOURCE_MANGAS,
|
||||||
} from '@/lib/graphql/queries/MangaQuery.ts';
|
} from '@/lib/graphql/queries/MangaQuery.ts';
|
||||||
import {
|
import {
|
||||||
@@ -283,7 +281,7 @@ import { DOWNLOAD_STATUS_SUBSCRIPTION } from '@/lib/graphql/subscriptions/Downlo
|
|||||||
import { UPDATER_SUBSCRIPTION } from '@/lib/graphql/subscriptions/UpdaterSubscription.ts';
|
import { UPDATER_SUBSCRIPTION } from '@/lib/graphql/subscriptions/UpdaterSubscription.ts';
|
||||||
import { GET_SERVER_SETTINGS } from '@/lib/graphql/queries/SettingsQuery.ts';
|
import { GET_SERVER_SETTINGS } from '@/lib/graphql/queries/SettingsQuery.ts';
|
||||||
import { UPDATE_SERVER_SETTINGS } from '@/lib/graphql/mutations/SettingsMutation.ts';
|
import { UPDATE_SERVER_SETTINGS } from '@/lib/graphql/mutations/SettingsMutation.ts';
|
||||||
import { BASE_MANGA_FIELDS, GLOBAL_METADATA } from '@/lib/graphql/Fragments.ts';
|
import { GLOBAL_METADATA } from '@/lib/graphql/Fragments.ts';
|
||||||
import { CLEAR_SERVER_CACHE } from '@/lib/graphql/mutations/ImageMutation.ts';
|
import { CLEAR_SERVER_CACHE } from '@/lib/graphql/mutations/ImageMutation.ts';
|
||||||
import { RESET_WEBUI_UPDATE_STATUS, UPDATE_WEBUI } from '@/lib/graphql/mutations/ServerInfoMutation.ts';
|
import { RESET_WEBUI_UPDATE_STATUS, UPDATE_WEBUI } from '@/lib/graphql/mutations/ServerInfoMutation.ts';
|
||||||
import { WEBUI_UPDATE_SUBSCRIPTION } from '@/lib/graphql/subscriptions/ServerInfoSubscription.ts';
|
import { WEBUI_UPDATE_SUBSCRIPTION } from '@/lib/graphql/subscriptions/ServerInfoSubscription.ts';
|
||||||
@@ -301,9 +299,11 @@ import {
|
|||||||
TRACKER_UPDATE_BIND,
|
TRACKER_UPDATE_BIND,
|
||||||
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
||||||
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||||
import { MetadataMigrationSettings, TManga } from '@/typings.ts';
|
import { MetadataMigrationSettings } from '@/typings.ts';
|
||||||
import { DOWNLOAD_STATUS_FIELDS } from '@/lib/graphql/fragments/DownloadFragments.ts';
|
import { DOWNLOAD_STATUS_FIELDS } from '@/lib/graphql/fragments/DownloadFragments.ts';
|
||||||
import { EXTENSION_LIST_FIELDS } from '@/lib/graphql/fragments/ExtensionFragments.ts';
|
import { EXTENSION_LIST_FIELDS } from '@/lib/graphql/fragments/ExtensionFragments.ts';
|
||||||
|
import { MANGA_BASE_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
enum GQLMethod {
|
enum GQLMethod {
|
||||||
QUERY = 'QUERY',
|
QUERY = 'QUERY',
|
||||||
@@ -1555,8 +1555,8 @@ export class RequestManager {
|
|||||||
(manga) =>
|
(manga) =>
|
||||||
this.graphQLClient.client.cache.readFragment<typeof manga>({
|
this.graphQLClient.client.cache.readFragment<typeof manga>({
|
||||||
id: this.graphQLClient.client.cache.identify(manga),
|
id: this.graphQLClient.client.cache.identify(manga),
|
||||||
fragment: BASE_MANGA_FIELDS,
|
fragment: MANGA_BASE_FIELDS,
|
||||||
fragmentName: 'BASE_MANGA_FIELDS',
|
fragmentName: 'MANGA_BASE_FIELDS',
|
||||||
}) ?? manga,
|
}) ?? manga,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -1630,18 +1630,20 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGetManga(
|
public useGetManga<Data, Variables extends OperationVariables = OperationVariables>(
|
||||||
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
||||||
mangaId: number | string,
|
mangaId: number | string,
|
||||||
options?: QueryHookOptions<GetMangaQuery, GetMangaQueryVariables>,
|
options?: QueryHookOptions<Data, Variables>,
|
||||||
): AbortableApolloUseQueryResponse<GetMangaQuery, GetMangaQueryVariables> {
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
||||||
return this.doRequest(GQLMethod.USE_QUERY, GET_MANGA, { id: Number(mangaId) }, options);
|
return this.doRequest(GQLMethod.USE_QUERY, document, { id: Number(mangaId) } as unknown as Variables, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getManga(
|
public getManga<Data, Variables extends OperationVariables = OperationVariables>(
|
||||||
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
||||||
mangaId: number | string,
|
mangaId: number | string,
|
||||||
options?: QueryOptions<GetMangaQueryVariables, GetMangaQuery>,
|
options?: QueryOptions<Variables, Data>,
|
||||||
): AbortabaleApolloQueryResponse<GetMangaQuery> {
|
): AbortabaleApolloQueryResponse<Data> {
|
||||||
return this.doRequest(GQLMethod.QUERY, GET_MANGA, { id: Number(mangaId) }, options);
|
return this.doRequest(GQLMethod.QUERY, document, { id: Number(mangaId) } as unknown as Variables, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMangaToMigrate(
|
public getMangaToMigrate(
|
||||||
@@ -1712,18 +1714,20 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGetMangas(
|
public useGetMangas<Data, Variables extends OperationVariables = OperationVariables>(
|
||||||
variables: GetMangasQueryVariables,
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
||||||
options?: QueryHookOptions<GetMangasQuery, GetMangasQueryVariables>,
|
variables: Variables,
|
||||||
): AbortableApolloUseQueryResponse<GetMangasQuery, GetMangasQueryVariables> {
|
options?: QueryHookOptions<Data, Variables>,
|
||||||
return this.doRequest(GQLMethod.USE_QUERY, GET_MANGAS, variables, options);
|
): AbortableApolloUseQueryResponse<Data, Variables> {
|
||||||
|
return this.doRequest(GQLMethod.USE_QUERY, document, variables, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMangas(
|
public getMangas<Data, Variables extends OperationVariables = OperationVariables>(
|
||||||
variables: GetMangasQueryVariables,
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
||||||
options?: QueryOptions<GetMangasQueryVariables, GetMangasQuery>,
|
variables: Variables,
|
||||||
): AbortabaleApolloQueryResponse<GetMangasQuery> {
|
options?: QueryOptions<Variables, Data>,
|
||||||
return this.doRequest(GQLMethod.QUERY, GET_MANGAS, variables, options);
|
): AbortabaleApolloQueryResponse<Data> {
|
||||||
|
return this.doRequest(GQLMethod.QUERY, document, variables, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGetMigratableSourceMangas(
|
public useGetMigratableSourceMangas(
|
||||||
@@ -2014,7 +2018,7 @@ export class RequestManager {
|
|||||||
|
|
||||||
public updateChapters(
|
public updateChapters(
|
||||||
ids: number[],
|
ids: number[],
|
||||||
patch: UpdateChapterPatchInput & { chapterIdsToDelete?: number[]; trackProgressMangaId?: TManga['id'] },
|
patch: UpdateChapterPatchInput & { chapterIdsToDelete?: number[]; trackProgressMangaId?: MangaIdInfo['id'] },
|
||||||
options?: MutationOptions<UpdateChaptersMutation, UpdateChaptersMutationVariables>,
|
options?: MutationOptions<UpdateChaptersMutation, UpdateChaptersMutationVariables>,
|
||||||
): AbortableApolloMutationResponse<UpdateChaptersMutation> {
|
): AbortableApolloMutationResponse<UpdateChaptersMutation> {
|
||||||
const { chapterIdsToDelete = [], trackProgressMangaId = -1, ...updatePatch } = patch;
|
const { chapterIdsToDelete = [], trackProgressMangaId = -1, ...updatePatch } = patch;
|
||||||
@@ -2144,8 +2148,8 @@ export class RequestManager {
|
|||||||
|
|
||||||
public useGetCategoryMangas(
|
public useGetCategoryMangas(
|
||||||
id: number,
|
id: number,
|
||||||
options?: QueryHookOptions<GetMangasQuery, GetMangasQueryVariables>,
|
options?: QueryHookOptions<GetMangasLibraryQuery, GetMangasLibraryQueryVariables>,
|
||||||
): AbortableApolloUseQueryResponse<GetMangasQuery, GetMangasQueryVariables> {
|
): AbortableApolloUseQueryResponse<GetMangasLibraryQuery, GetMangasLibraryQueryVariables> {
|
||||||
const isDefaultCategory = id === 0;
|
const isDefaultCategory = id === 0;
|
||||||
if (isDefaultCategory) {
|
if (isDefaultCategory) {
|
||||||
// hacky way of loading the default category mangas - some stuff won't work but since that is not used anyway, it won't be a problem
|
// hacky way of loading the default category mangas - some stuff won't work but since that is not used anyway, it won't be a problem
|
||||||
@@ -2165,10 +2169,10 @@ export class RequestManager {
|
|||||||
__typename: 'Query',
|
__typename: 'Query',
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
} as unknown as AbortableApolloUseQueryResponse<GetMangasQuery, GetMangasQueryVariables>;
|
} as unknown as AbortableApolloUseQueryResponse<GetMangasLibraryQuery, GetMangasLibraryQueryVariables>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.useGetMangas({ condition: { inLibrary: true, categoryIds: [id] } }, options);
|
return this.useGetMangas(GET_MANGAS_LIBRARY, { condition: { inLibrary: true, categoryIds: [id] } }, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteCategory(
|
public deleteCategory(
|
||||||
@@ -2597,7 +2601,7 @@ export class RequestManager {
|
|||||||
GQLMethod.MUTATION,
|
GQLMethod.MUTATION,
|
||||||
TRACKER_UNBIND,
|
TRACKER_UNBIND,
|
||||||
{ input: { recordId, deleteRemoteTrack } },
|
{ input: { recordId, deleteRemoteTrack } },
|
||||||
{ refetchQueries: [GET_MANGA], ...options },
|
{ refetchQueries: [GET_MANGA_TRACK_RECORDS], ...options },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,17 +23,22 @@ import { UpdateChecker } from '@/components/library/UpdateChecker';
|
|||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx';
|
import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx';
|
||||||
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
|
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
|
||||||
import { SelectionFAB } from '@/components/collection/SelectionFAB.tsx';
|
import { SelectionFAB } from '@/components/collection/SelectionFAB.tsx';
|
||||||
import { PARTIAL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
|
|
||||||
import { MangaActionMenuItems } from '@/components/manga/MangaActionMenuItems.tsx';
|
import { MangaActionMenuItems } from '@/components/manga/MangaActionMenuItems.tsx';
|
||||||
import { TabsMenu } from '@/components/tabs/TabsMenu.tsx';
|
import { TabsMenu } from '@/components/tabs/TabsMenu.tsx';
|
||||||
import { TabsWrapper } from '@/components/tabs/TabsWrapper.tsx';
|
import { TabsWrapper } from '@/components/tabs/TabsWrapper.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
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 { 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')({
|
const TitleWithSizeTag = styled('span')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -95,7 +100,7 @@ export function Library() {
|
|||||||
handleSelectAll,
|
handleSelectAll,
|
||||||
handleSelection,
|
handleSelection,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
} = useSelectableCollection<TManga['id'], string>(mangas.length, {
|
} = useSelectableCollection<MangaType['id'], string>(mangas.length, {
|
||||||
itemIds: mangaIds,
|
itemIds: mangaIds,
|
||||||
currentKey: activeTab?.id.toString(),
|
currentKey: activeTab?.id.toString(),
|
||||||
});
|
});
|
||||||
@@ -107,14 +112,15 @@ export function Library() {
|
|||||||
|
|
||||||
const selectedMangas = useMemo(
|
const selectedMangas = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selectedItemIds.map(
|
selectedItemIds
|
||||||
(id) =>
|
.map((id) =>
|
||||||
requestManager.graphQLClient.client.cache.readFragment<TManga>({
|
Mangas.getFromCache<MangaChapterStatFieldsFragment>(
|
||||||
id: requestManager.graphQLClient.client.cache.identify({ __typename: 'MangaType', id }),
|
id,
|
||||||
fragment: PARTIAL_MANGA_FIELDS,
|
MANGA_CHAPTER_STAT_FIELDS,
|
||||||
fragmentName: 'PARTIAL_MANGA_FIELDS',
|
'MANGA_CHAPTER_STAT_FIELDS',
|
||||||
})!,
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.filter((manga) => !!manga),
|
||||||
[selectedItemIds.length, mangas],
|
[selectedItemIds.length, mangas],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import { MangaDetails } from '@/components/manga/MangaDetails';
|
|||||||
import { MangaToolbarMenu } from '@/components/manga/MangaToolbarMenu';
|
import { MangaToolbarMenu } from '@/components/manga/MangaToolbarMenu';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder';
|
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 = () => {
|
export const Manga: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -32,7 +34,13 @@ export const Manga: React.FC = () => {
|
|||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const autofetchedRef = useRef(false);
|
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 isValidating = isNetworkRequestInFlight(networkStatus);
|
||||||
const manga = data?.manga;
|
const manga = data?.manga;
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|||||||
import { TMigratableSource } from '@/components/MigrationCard.tsx';
|
import { TMigratableSource } from '@/components/MigrationCard.tsx';
|
||||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.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 { GridLayouts } from '@/components/source/GridLayouts.tsx';
|
||||||
import { useLocalStorage } from '@/util/useStorage.tsx';
|
import { useLocalStorage } from '@/util/useStorage.tsx';
|
||||||
import { GridLayout } from '@/components/context/LibraryOptionsContext.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 { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/queries/SourceQuery.ts';
|
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||||
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
||||||
|
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
|
||||||
|
|
||||||
export const Migrate = () => {
|
export const Migrate = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -117,11 +116,11 @@ export const Migrate = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MangaGrid
|
<BaseMangaGrid
|
||||||
hasNextPage={false}
|
hasNextPage={false}
|
||||||
loadMore={() => {}}
|
loadMore={() => {}}
|
||||||
isLoading={areMangasLoading}
|
isLoading={areMangasLoading}
|
||||||
mangas={(migratableSourceMangasData?.mangas.nodes ?? []) as TPartialManga[]}
|
mangas={migratableSourceMangasData?.mangas.nodes ?? []}
|
||||||
gridLayout={gridLayout}
|
gridLayout={gridLayout}
|
||||||
mode="migrate.search"
|
mode="migrate.search"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r
|
|||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import {
|
||||||
checkAndHandleMissingStoredReaderSettings,
|
checkAndHandleMissingStoredReaderSettings,
|
||||||
@@ -31,6 +31,7 @@ import { useDebounce } from '@/util/useDebounce.ts';
|
|||||||
import {
|
import {
|
||||||
GetChaptersReaderQuery,
|
GetChaptersReaderQuery,
|
||||||
GetChaptersReaderQueryVariables,
|
GetChaptersReaderQueryVariables,
|
||||||
|
GetMangaReaderQuery,
|
||||||
UpdateChapterPatchInput,
|
UpdateChapterPatchInput,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.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 { Chapters } from '@/lib/data/Chapters.ts';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { GET_CHAPTERS_READER } from '@/lib/graphql/queries/ChapterQuery.ts';
|
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];
|
type TChapter = GetChaptersReaderQuery['chapters']['nodes'][number];
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ export function Reader() {
|
|||||||
lastReadAt: 0,
|
lastReadAt: 0,
|
||||||
chapters: { totalCount: 0 },
|
chapters: { totalCount: 0 },
|
||||||
trackRecords: { totalCount: 0 },
|
trackRecords: { totalCount: 0 },
|
||||||
}) as unknown as TManga,
|
}) as unknown as TMangaReader,
|
||||||
[mangaId],
|
[mangaId],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ export function Reader() {
|
|||||||
loading: isMangaLoading,
|
loading: isMangaLoading,
|
||||||
error: mangaError,
|
error: mangaError,
|
||||||
refetch: refetchManga,
|
refetch: refetchManga,
|
||||||
} = requestManager.useGetManga(mangaId);
|
} = requestManager.useGetManga<GetMangaReaderQuery>(GET_MANGA_READER, mangaId);
|
||||||
const loadedChapter = useRef<TChapter | null>(null);
|
const loadedChapter = useRef<TChapter | null>(null);
|
||||||
const isChapterLoaded =
|
const isChapterLoaded =
|
||||||
Number(mangaId) === loadedChapter.current?.mangaId &&
|
Number(mangaId) === loadedChapter.current?.mangaId &&
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from '@/uti
|
|||||||
import { translateExtensionLanguage } from '@/screens/util/Extensions';
|
import { translateExtensionLanguage } from '@/screens/util/Extensions';
|
||||||
import { AppbarSearch } from '@/components/util/AppbarSearch';
|
import { AppbarSearch } from '@/components/util/AppbarSearch';
|
||||||
import { LangSelect } from '@/components/navbar/action/LangSelect';
|
import { LangSelect } from '@/components/navbar/action/LangSelect';
|
||||||
import { MangaGrid } from '@/components/MangaGrid';
|
|
||||||
import { useDebounce } from '@/util/useDebounce.ts';
|
import { useDebounce } from '@/util/useDebounce.ts';
|
||||||
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
import { MangaCardProps } from '@/components/manga/MangaCard.types.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 { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
|
||||||
|
|
||||||
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
|
type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean };
|
||||||
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
type SourceToLoadingStateMap = Map<string, SourceLoadingState>;
|
||||||
@@ -166,7 +166,7 @@ const SourceSearchPreview = React.memo(
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<MangaGrid
|
<BaseMangaGrid
|
||||||
mangas={mangas}
|
mangas={mangas}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
hasNextPage={false}
|
hasNextPage={false}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { styled } from '@mui/material/styles';
|
|||||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
import { IPos, TPartialManga, TranslationKey } from '@/typings';
|
import { IPos, TranslationKey } from '@/typings';
|
||||||
import {
|
import {
|
||||||
requestManager,
|
requestManager,
|
||||||
AbortableApolloUseMutationPaginatedResponse,
|
AbortableApolloUseMutationPaginatedResponse,
|
||||||
@@ -30,7 +30,7 @@ import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsCon
|
|||||||
import { SourceGridLayout } from '@/components/source/SourceGridLayout';
|
import { SourceGridLayout } from '@/components/source/SourceGridLayout';
|
||||||
import { AppbarSearch } from '@/components/util/AppbarSearch';
|
import { AppbarSearch } from '@/components/util/AppbarSearch';
|
||||||
import { SourceOptions } from '@/components/source/SourceOptions';
|
import { SourceOptions } from '@/components/source/SourceOptions';
|
||||||
import { SourceMangaGrid } from '@/components/source/SourceMangaGrid';
|
import { BaseMangaGrid } from '@/components/source/BaseMangaGrid.tsx';
|
||||||
import {
|
import {
|
||||||
GetSourceBrowseQuery,
|
GetSourceBrowseQuery,
|
||||||
GetSourceBrowseQueryVariables,
|
GetSourceBrowseQueryVariables,
|
||||||
@@ -45,6 +45,7 @@ import { getGridSnapshotKey } from '@/components/MangaGrid.tsx';
|
|||||||
import { createUpdateSourceMetadata, getSourceMetadata } from '@/lib/metadata/sourceMetadata.ts';
|
import { createUpdateSourceMetadata, getSourceMetadata } from '@/lib/metadata/sourceMetadata.ts';
|
||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
|
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||||
|
import { MangaIdInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
|
||||||
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -92,9 +93,9 @@ const SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY: { [contentType in SourceContentType]
|
|||||||
[SourceContentType.SEARCH]: 'manga.error.label.no_matches',
|
[SourceContentType.SEARCH]: 'manga.error.label.no_matches',
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUniqueMangas = (mangas: TPartialManga[]): TPartialManga[] => {
|
const getUniqueMangas = <Manga extends MangaIdInfo>(mangas: Manga[]): Manga[] => {
|
||||||
const mangaIdToManga: Record<TPartialManga['id'], TPartialManga> = {};
|
const mangaIdToManga: Record<string, Manga> = {};
|
||||||
const uniqueMangas: TPartialManga[] = [];
|
const uniqueMangas: Manga[] = [];
|
||||||
|
|
||||||
mangas.forEach((manga) => {
|
mangas.forEach((manga) => {
|
||||||
const isDuplicate = !!mangaIdToManga[manga.id];
|
const isDuplicate = !!mangaIdToManga[manga.id];
|
||||||
@@ -470,7 +471,7 @@ export function SourceMangas() {
|
|||||||
{t('global.button.filter')}
|
{t('global.button.filter')}
|
||||||
</ContentTypeButton>
|
</ContentTypeButton>
|
||||||
</ContentTypeMenu>
|
</ContentTypeMenu>
|
||||||
<SourceMangaGrid
|
<BaseMangaGrid
|
||||||
key={contentType}
|
key={contentType}
|
||||||
mangas={mangas}
|
mangas={mangas}
|
||||||
hasNextPage={hasNextPage}
|
hasNextPage={hasNextPage}
|
||||||
@@ -479,6 +480,8 @@ export function SourceMangas() {
|
|||||||
messageExtra={messageExtra}
|
messageExtra={messageExtra}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
gridLayout={options.SourcegridLayout}
|
gridLayout={options.SourcegridLayout}
|
||||||
|
mode="source"
|
||||||
|
inLibraryIndicator
|
||||||
/>
|
/>
|
||||||
{contentType === SourceContentType.SEARCH && (
|
{contentType === SourceContentType.SEARCH && (
|
||||||
<SourceOptions
|
<SourceOptions
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import Menu from '@mui/material/Menu';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
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 { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
import { useLocalStorage } from '@/util/useStorage.tsx';
|
import { useLocalStorage } from '@/util/useStorage.tsx';
|
||||||
import { GridLayout } from '@/components/context/LibraryOptionsContext.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 { MangaCard } from '@/components/MangaCard.tsx';
|
||||||
import { StyledGroupedVirtuoso } from '@/components/virtuoso/StyledGroupedVirtuoso.tsx';
|
import { StyledGroupedVirtuoso } from '@/components/virtuoso/StyledGroupedVirtuoso.tsx';
|
||||||
import { StyledGroupHeader } from '@/components/virtuoso/StyledGroupHeader.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());
|
const titleToMangas = Object.groupBy(libraryMangas, ({ title }) => title.toLowerCase().trim());
|
||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(titleToMangas)
|
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]),
|
.map(([, mangas]) => [mangas[0].title, mangas]),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const findDuplicatesByTitleAndAlternativeTitles = (libraryMangas: TManga[]): Record<string, TManga[]> => {
|
type TMangaDuplicate = Pick<MangaType, 'id' | 'title' | 'description'>;
|
||||||
const idToDuplicateStatus: Record<TManga['id'], boolean> = {};
|
const findDuplicatesByTitleAndAlternativeTitles = <Manga extends TMangaDuplicate>(
|
||||||
const titleToMangas: Record<string, TManga[]> = {};
|
libraryMangas: Manga[],
|
||||||
|
): Record<string, Manga[]> => {
|
||||||
|
const idToDuplicateStatus: Record<MangaIdInfo['id'], boolean> = {};
|
||||||
|
const titleToMangas: Record<string, Manga[]> = {};
|
||||||
|
|
||||||
libraryMangas.forEach((mangaToCheck) =>
|
libraryMangas.forEach((mangaToCheck) =>
|
||||||
libraryMangas.forEach((libraryManga) => {
|
libraryMangas.forEach((libraryManga) => {
|
||||||
@@ -123,10 +135,13 @@ export const LibraryDuplicates = () => {
|
|||||||
};
|
};
|
||||||
}, [t, gridLayout, checkAlternativeTitles]);
|
}, [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 mangasByTitle = useMemo(() => {
|
||||||
const libraryMangas: TManga[] = data?.mangas.nodes ?? [];
|
const libraryMangas: TMangaDuplicate[] = data?.mangas.nodes ?? [];
|
||||||
|
|
||||||
if (checkAlternativeTitles) {
|
if (checkAlternativeTitles) {
|
||||||
return findDuplicatesByTitleAndAlternativeTitles(libraryMangas);
|
return findDuplicatesByTitleAndAlternativeTitles(libraryMangas);
|
||||||
@@ -172,7 +187,7 @@ export const LibraryDuplicates = () => {
|
|||||||
itemContent={(index) => (
|
itemContent={(index) => (
|
||||||
<Box key={duplicatedMangas[index].id} sx={{ px: 1, pb: 1 }}>
|
<Box key={duplicatedMangas[index].id} sx={{ px: 1, pb: 1 }}>
|
||||||
<MangaCard
|
<MangaCard
|
||||||
manga={duplicatedMangas[index]}
|
manga={duplicatedMangas[index] as IMangaGridProps['mangas'][number]}
|
||||||
gridLayout={gridLayout}
|
gridLayout={gridLayout}
|
||||||
selected={null}
|
selected={null}
|
||||||
mode="duplicate"
|
mode="duplicate"
|
||||||
@@ -188,12 +203,13 @@ export const LibraryDuplicates = () => {
|
|||||||
<StyledGroupHeader sx={{ pt: index === 0 ? undefined : 0, pb: 0 }} variant="h5" isFirstItem={false}>
|
<StyledGroupHeader sx={{ pt: index === 0 ? undefined : 0, pb: 0 }} variant="h5" isFirstItem={false}>
|
||||||
{title}
|
{title}
|
||||||
</StyledGroupHeader>
|
</StyledGroupHeader>
|
||||||
<MangaGrid
|
<BaseMangaGrid
|
||||||
mangas={mangasByTitle[title]}
|
mangas={mangasByTitle[title] as IMangaGridProps['mangas']}
|
||||||
hasNextPage={false}
|
hasNextPage={false}
|
||||||
loadMore={() => {}}
|
loadMore={() => {}}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
gridLayout={gridLayout}
|
gridLayout={gridLayout}
|
||||||
|
inLibraryIndicator={false}
|
||||||
horizontal
|
horizontal
|
||||||
mode="duplicate"
|
mode="duplicate"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -29,14 +29,23 @@ import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCe
|
|||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||||
import { ListItemLink } from '@/components/util/ListItemLink.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_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
|
||||||
|
import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
|
||||||
|
|
||||||
const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
|
const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const nonLibraryMangas = await requestManager.getMangas({
|
const nonLibraryMangas = await requestManager.getMangas<GetMangasBaseQuery, GetMangasBaseQueryVariables>(
|
||||||
|
GET_MANGAS_BASE,
|
||||||
|
{
|
||||||
filter: { inLibrary: { equalTo: false }, categoryId: { isNull: false } },
|
filter: { inLibrary: { equalTo: false }, categoryId: { isNull: false } },
|
||||||
}).response;
|
},
|
||||||
|
).response;
|
||||||
|
|
||||||
const mangaIdsToRemove = Mangas.getIds(nonLibraryMangas.data.mangas.nodes);
|
const mangaIdsToRemove = Mangas.getIds(nonLibraryMangas.data.mangas.nodes);
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import { ParseKeys } from 'i18next';
|
|||||||
import { Location } from 'react-router-dom';
|
import { Location } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
GetChaptersReaderQuery,
|
GetChaptersReaderQuery,
|
||||||
GetMangaQuery,
|
|
||||||
GetServerSettingsQuery,
|
GetServerSettingsQuery,
|
||||||
GetSourceBrowseQuery,
|
GetSourceBrowseQuery,
|
||||||
GetSourceSettingsQuery,
|
GetSourceSettingsQuery,
|
||||||
|
MangaReaderFieldsFragment,
|
||||||
MetaType,
|
MetaType,
|
||||||
SourcePreferenceChangeInput,
|
SourcePreferenceChangeInput,
|
||||||
TrackerType,
|
TrackerType,
|
||||||
@@ -84,13 +84,6 @@ export type AppMetadataKeys = MetadataServerSettingKeys | MangaMetadataKeys | Se
|
|||||||
|
|
||||||
export type MetadataKeyValuePair = [AppMetadataKeys, AllowedMetadataValueTypes];
|
export type MetadataKeyValuePair = [AppMetadataKeys, AllowedMetadataValueTypes];
|
||||||
|
|
||||||
export type TManga = GetMangaQuery['manga'];
|
|
||||||
|
|
||||||
export type TPartialManga = OptionalProperty<
|
|
||||||
TManga,
|
|
||||||
'unreadCount' | 'downloadCount' | 'bookmarkCount' | 'categories' | 'chapters'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface INavbarOverride {
|
export interface INavbarOverride {
|
||||||
status: boolean;
|
status: boolean;
|
||||||
value: any;
|
value: any;
|
||||||
@@ -187,7 +180,7 @@ export interface IReaderProps {
|
|||||||
curPage: number;
|
curPage: number;
|
||||||
initialPage: number;
|
initialPage: number;
|
||||||
settings: IReaderSettings;
|
settings: IReaderSettings;
|
||||||
manga: TManga;
|
manga: MangaReaderFieldsFragment;
|
||||||
chapter: GetChaptersReaderQuery['chapters']['nodes'][number];
|
chapter: GetChaptersReaderQuery['chapters']['nodes'][number];
|
||||||
nextChapter: () => void;
|
nextChapter: () => void;
|
||||||
prevChapter: () => void;
|
prevChapter: () => void;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const addImports = format(
|
|||||||
`import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
|
`import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
|
||||||
import {
|
import {
|
||||||
\tGetChaptersMangaQuery, GetDownloadStatusQueryVariables, GetGlobalMetadataQueryVariables,
|
\tGetChaptersMangaQuery, GetDownloadStatusQueryVariables, GetGlobalMetadataQueryVariables,
|
||||||
\tGetMangaQueryVariables, GetSourceBrowseQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
|
\tGetMangaScreenQueryVariables, GetSourceBrowseQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
|
||||||
} from "@/lib/graphql/generated/graphql.ts";
|
} from "@/lib/graphql/generated/graphql.ts";
|
||||||
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";`,
|
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";`,
|
||||||
);
|
);
|
||||||
@@ -93,7 +93,7 @@ const fixTypingOfQueryTypePolicies = format(
|
|||||||
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
\tgetWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
\tgetWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
|
||||||
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
\tmanga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>>,
|
\tmanga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaScreenQueryVariables>>,
|
||||||
\tmangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
\tmangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
\tmeta?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>>,
|
\tmeta?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetGlobalMetadataQueryVariables>>,
|
||||||
\tmetas?: FieldPolicy<any> | FieldReadFunction<any>,
|
\tmetas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||||
|
|||||||
Reference in New Issue
Block a user