[Codegen] Use gql for "loading chapters"
This commit is contained in:
@@ -27,16 +27,15 @@ import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IChapter, IDownloadChapter } from '@/typings';
|
||||
import { IDownloadChapter } from '@/typings';
|
||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||
import { getUploadDateString } from '@/util/date';
|
||||
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
||||
import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ChapterType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
interface IProps {
|
||||
chapter: IChapter;
|
||||
chapter: ChapterType;
|
||||
chapterIds: number[];
|
||||
triggerChaptersUpdate: () => void;
|
||||
downloadChapter: IDownloadChapter | undefined;
|
||||
showChapterNumber: boolean;
|
||||
onSelect: (selected: boolean) => void;
|
||||
@@ -47,15 +46,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
chapter,
|
||||
chapterIds,
|
||||
triggerChaptersUpdate,
|
||||
downloadChapter: dc,
|
||||
showChapterNumber,
|
||||
onSelect,
|
||||
selected,
|
||||
} = props;
|
||||
const { chapter, chapterIds, downloadChapter: dc, showChapterNumber, onSelect, selected } = props;
|
||||
const isSelecting = selected !== null;
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
@@ -82,12 +73,10 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
requestManager
|
||||
.updateChapter(chapter.id, {
|
||||
[key]: value,
|
||||
lastPageRead: key === 'isRead' ? 0 : undefined,
|
||||
})
|
||||
.response.then(() => triggerChaptersUpdate());
|
||||
requestManager.updateChapter(chapter.id, {
|
||||
[key]: value,
|
||||
lastPageRead: key === 'isRead' ? 0 : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const downloadChapter = () => {
|
||||
@@ -96,7 +85,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
};
|
||||
|
||||
const deleteChapter = () => {
|
||||
requestManager.deleteDownloadedChapter(chapter.id).response.then(() => triggerChaptersUpdate());
|
||||
requestManager.deleteDownloadedChapter(chapter.id);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
@@ -113,8 +102,8 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isDownloaded = chapter.downloaded;
|
||||
const canBeDownloaded = !chapter.downloaded && dc === undefined;
|
||||
const { isDownloaded } = chapter;
|
||||
const canBeDownloaded = !chapter.isDownloaded && dc === undefined;
|
||||
|
||||
return (
|
||||
<li>
|
||||
@@ -126,9 +115,9 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
>
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
to={`/manga/${chapter.mangaId}/chapter/${chapter.index}`}
|
||||
to={`/manga/${chapter.manga.id}/chapter/${chapter.sourceOrder}`}
|
||||
style={{
|
||||
color: theme.palette.text[chapter.read ? 'disabled' : 'primary'],
|
||||
color: theme.palette.text[chapter.isRead ? 'disabled' : 'primary'],
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
@@ -143,7 +132,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
>
|
||||
<Stack direction="column" flex={1}>
|
||||
<Typography variant="h5" component="h2">
|
||||
{chapter.bookmarked && (
|
||||
{chapter.isBookmarked && (
|
||||
<BookmarkIcon
|
||||
color="primary"
|
||||
sx={{ mr: 0.5, position: 'relative', top: '0.15em' }}
|
||||
@@ -153,7 +142,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
</Typography>
|
||||
<Typography variant="caption">{chapter.scanlator}</Typography>
|
||||
<Typography variant="caption">
|
||||
{getUploadDateString(chapter.uploadDate)}
|
||||
{getUploadDateString(Number(chapter.uploadDate ?? 0))}
|
||||
{isDownloaded && ` • ${t('chapter.status.label.downloaded')}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
@@ -192,24 +181,24 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||
<ListItemText>{t('chapter.action.download.add.label.action')}</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={() => sendChange('isBookmarked', !chapter.bookmarked)}>
|
||||
<MenuItem onClick={() => sendChange('isBookmarked', !chapter.isBookmarked)}>
|
||||
<ListItemIcon>
|
||||
{chapter.bookmarked && <BookmarkRemove fontSize="small" />}
|
||||
{!chapter.bookmarked && <BookmarkAdd fontSize="small" />}
|
||||
{chapter.isBookmarked && <BookmarkRemove fontSize="small" />}
|
||||
{!chapter.isBookmarked && <BookmarkAdd fontSize="small" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{chapter.bookmarked && t('chapter.action.bookmark.remove.label.action')}
|
||||
{!chapter.bookmarked && t('chapter.action.bookmark.add.label.action')}
|
||||
{chapter.isBookmarked && t('chapter.action.bookmark.remove.label.action')}
|
||||
{!chapter.isBookmarked && t('chapter.action.bookmark.add.label.action')}
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => sendChange('isRead', !chapter.read)}>
|
||||
<MenuItem onClick={() => sendChange('isRead', !chapter.isRead)}>
|
||||
<ListItemIcon>
|
||||
{chapter.read && <RemoveDone fontSize="small" />}
|
||||
{!chapter.read && <Done fontSize="small" />}
|
||||
{chapter.isRead && <RemoveDone fontSize="small" />}
|
||||
{!chapter.isRead && <Done fontSize="small" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{chapter.read && t('chapter.action.mark_as_read.remove.label.action')}
|
||||
{!chapter.read && t('chapter.action.mark_as_read.add.label.action.current')}
|
||||
{chapter.isRead && t('chapter.action.mark_as_read.remove.label.action')}
|
||||
{!chapter.isRead && t('chapter.action.mark_as_read.add.label.action.current')}
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => sendChange('markPrevRead', true)}>
|
||||
|
||||
@@ -11,7 +11,7 @@ import Typography from '@mui/material/Typography';
|
||||
import React, { ComponentProps, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IChapter, IDownloadChapter, IQueue, TranslationKey } from '@/typings';
|
||||
import { IDownloadChapter, IQueue, TranslationKey } from '@/typings';
|
||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||
import useSubscription from '@/components/library/useSubscription';
|
||||
import ChapterCard from '@/components/manga/ChapterCard';
|
||||
@@ -22,7 +22,7 @@ import makeToast from '@/components/util/Toast';
|
||||
import ChaptersToolbarMenu from '@/components/manga/ChaptersToolbarMenu';
|
||||
import SelectionFAB from '@/components/manga/SelectionFAB';
|
||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
||||
import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ChapterType, MangaType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
const StyledVirtuoso = styled(Virtuoso)(({ theme }) => ({
|
||||
listStyle: 'none',
|
||||
@@ -69,25 +69,29 @@ const actionsStrings: {
|
||||
};
|
||||
|
||||
export interface IChapterWithMeta {
|
||||
chapter: IChapter;
|
||||
chapter: ChapterType;
|
||||
downloadChapter: IDownloadChapter | undefined;
|
||||
selected: boolean | null;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
mangaId: string;
|
||||
manga: MangaType;
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selection, setSelection] = useState<number[] | null>(null);
|
||||
const prevQueueRef = useRef<IDownloadChapter[]>();
|
||||
const queue = useSubscription<IQueue>('downloads').data?.queue;
|
||||
|
||||
const [options, dispatch] = useChapterOptions(mangaId);
|
||||
const { data: chaptersData, mutate, isLoading } = requestManager.useGetMangaChapters(mangaId);
|
||||
const chapters = useMemo(() => chaptersData ?? [], [chaptersData]);
|
||||
const [options, dispatch] = useChapterOptions(manga.id);
|
||||
const { data: chaptersData, loading: isLoading, refetch } = requestManager.useGetMangaChapters(manga.id);
|
||||
const chapters = useMemo(
|
||||
() => (chaptersData?.chapters.nodes as ChapterType[]) ?? [],
|
||||
[chaptersData?.chapters.nodes],
|
||||
);
|
||||
const mangaChapterIds = useMemo(() => chapters.map((chapter) => chapter.id), [chapters]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -102,26 +106,17 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
});
|
||||
|
||||
if (changedDownloads.length > 0) {
|
||||
mutate();
|
||||
refetch();
|
||||
}
|
||||
}
|
||||
|
||||
prevQueueRef.current = queue;
|
||||
}, [queue]);
|
||||
|
||||
const visibleChapters = useMemo(
|
||||
() => filterAndSortChapters(chapters, options), //
|
||||
[chapters, options],
|
||||
);
|
||||
const visibleChapters = useMemo(() => filterAndSortChapters(chapters, options), [chapters, options]);
|
||||
|
||||
const firstUnreadChapter = useMemo(
|
||||
() =>
|
||||
chapters
|
||||
.slice()
|
||||
.reverse()
|
||||
.find((chapter) => !chapter.read),
|
||||
[chapters],
|
||||
);
|
||||
const nextChapterIndexToRead = (manga.lastReadChapter?.sourceOrder ?? 0) + 1;
|
||||
const isLatestChapterRead = manga.chapters.totalCount === manga.lastReadChapter?.sourceOrder;
|
||||
|
||||
const handleSelection = (index: number) => {
|
||||
const chapter = visibleChapters[index];
|
||||
@@ -174,11 +169,49 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
|
||||
actionPromise
|
||||
.then(() => makeToast(t(actionsStrings[action].success, { count: chapterIds.length }), 'success'))
|
||||
.then(() => mutate())
|
||||
.catch(() => makeToast(t(actionsStrings[action].error, { count: chapterIds.length }), 'error'));
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
const noChaptersFound = chapters.length === 0;
|
||||
const noChaptersMatchingFilter = !noChaptersFound && visibleChapters.length === 0;
|
||||
|
||||
const chaptersWithMeta: IChapterWithMeta[] = useMemo(
|
||||
() =>
|
||||
visibleChapters.map((chapter) => {
|
||||
const downloadChapter = queue?.find(
|
||||
(cd) => cd.chapterIndex === chapter.sourceOrder && cd.mangaId === chapter.manga.id,
|
||||
);
|
||||
const selected = selection?.includes(chapter.id) ?? null;
|
||||
return {
|
||||
chapter,
|
||||
downloadChapter,
|
||||
selected,
|
||||
};
|
||||
}),
|
||||
[queue, selection, visibleChapters],
|
||||
);
|
||||
|
||||
const selectedChapters = useMemo(() => {
|
||||
if (!selection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
|
||||
}, [selection, chapters]);
|
||||
|
||||
const chapterListFAB = useMemo(() => {
|
||||
if (selectedChapters) {
|
||||
return <SelectionFAB selectedChapters={selectedChapters} onAction={handleFabAction} />;
|
||||
}
|
||||
|
||||
if (!isLatestChapterRead) {
|
||||
return <ResumeFab chapterIndex={nextChapterIndexToRead} mangaId={manga.id} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [selectedChapters, isLatestChapterRead]);
|
||||
|
||||
if (isLoading || (noChaptersFound && isRefreshing)) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -192,24 +225,6 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const noChaptersFound = chapters.length === 0;
|
||||
const noChaptersMatchingFilter = !noChaptersFound && visibleChapters.length === 0;
|
||||
|
||||
const chaptersWithMeta: IChapterWithMeta[] = visibleChapters.map((chapter) => {
|
||||
const downloadChapter = queue?.find(
|
||||
(cd) => cd.chapterIndex === chapter.index && cd.mangaId === chapter.mangaId,
|
||||
);
|
||||
const selected = selection?.includes(chapter.id) ?? null;
|
||||
return {
|
||||
chapter,
|
||||
downloadChapter,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
|
||||
const selectedChapters =
|
||||
selection === null ? null : chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="column" sx={{ position: 'relative' }}>
|
||||
@@ -273,7 +288,6 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
{...chaptersWithMeta[index]}
|
||||
chapterIds={mangaChapterIds}
|
||||
showChapterNumber={options.showChapterNumber}
|
||||
triggerChaptersUpdate={() => mutate()}
|
||||
onSelect={() => handleSelection(index)}
|
||||
/>
|
||||
);
|
||||
@@ -282,11 +296,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
||||
overscan={window.innerHeight * 0.5}
|
||||
/>
|
||||
</Stack>
|
||||
{selectedChapters !== null ? (
|
||||
<SelectionFAB selectedChapters={selectedChapters} onAction={handleFabAction} />
|
||||
) : (
|
||||
firstUnreadChapter && <ResumeFab chapter={firstUnreadChapter} mangaId={mangaId} />
|
||||
)}
|
||||
{chapterListFAB}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,25 +9,21 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PlayArrow } from '@mui/icons-material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IChapter } from '@/typings';
|
||||
import StyledFab from '@/components/util/StyledFab';
|
||||
|
||||
interface ResumeFABProps {
|
||||
chapter: IChapter;
|
||||
mangaId: string;
|
||||
chapterIndex: number;
|
||||
mangaId: number;
|
||||
}
|
||||
|
||||
export default function ResumeFab(props: ResumeFABProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
chapter: { index },
|
||||
mangaId,
|
||||
} = props;
|
||||
const { chapterIndex, mangaId } = props;
|
||||
return (
|
||||
<StyledFab component={Link} variant="extended" color="primary" to={`/manga/${mangaId}/chapter/${index}`}>
|
||||
<StyledFab component={Link} variant="extended" color="primary" to={`/manga/${mangaId}/chapter/${chapterIndex}`}>
|
||||
<PlayArrow />
|
||||
{index === 1 ? t('global.button.start') : t('global.button.resume')}
|
||||
{chapterIndex === 1 ? t('global.button.start') : t('global.button.resume')}
|
||||
</StyledFab>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,38 +63,38 @@ const SelectionFAB: React.FC<SelectionFABProps> = (props) => {
|
||||
<SelectionFABActionItem
|
||||
action="download"
|
||||
matchingChapters={selectedChapters.filter(
|
||||
({ chapter: c, downloadChapter: dc }) => !c.downloaded && dc === undefined,
|
||||
({ chapter: c, downloadChapter: dc }) => !c.isDownloaded && dc === undefined,
|
||||
)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.download.add.button.selected')}
|
||||
/>
|
||||
<SelectionFABActionItem
|
||||
action="delete"
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.downloaded)}
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.isDownloaded)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.download.delete.button.selected')}
|
||||
/>
|
||||
<SelectionFABActionItem
|
||||
action="bookmark"
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => !chapter.bookmarked)}
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => !chapter.isBookmarked)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.bookmark.add.button.selected')}
|
||||
/>
|
||||
<SelectionFABActionItem
|
||||
action="unbookmark"
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.bookmarked)}
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.isBookmarked)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.bookmark.remove.button.selected')}
|
||||
/>
|
||||
<SelectionFABActionItem
|
||||
action="mark_as_read"
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => !chapter.read)}
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => !chapter.isRead)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.mark_as_read.add.button.selected')}
|
||||
/>
|
||||
<SelectionFABActionItem
|
||||
action="mark_as_unread"
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.read)}
|
||||
matchingChapters={selectedChapters.filter(({ chapter }) => chapter.isRead)}
|
||||
onClick={handleAction}
|
||||
title={t('chapter.action.mark_as_read.remove.button.selected')}
|
||||
/>
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { mutate } from 'swr';
|
||||
import requestManager, { RequestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||
|
||||
export const useRefreshManga = (mangaId: string) => {
|
||||
const [fetchingOnline, setFetchingOnline] = useState(false);
|
||||
@@ -16,14 +15,8 @@ export const useRefreshManga = (mangaId: string) => {
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setFetchingOnline(true);
|
||||
await Promise.all([
|
||||
requestManager.getMangaFetch(mangaId).response.then((res) => {
|
||||
mutate(`${RequestManager.API_VERSION}manga/${mangaId}`, res, { revalidate: false });
|
||||
}),
|
||||
requestManager.getMangaChapters(mangaId, true).response.then((res) =>
|
||||
mutate(`${RequestManager.API_VERSION}manga/${mangaId}/chapters`, res, {
|
||||
revalidate: false,
|
||||
}),
|
||||
),
|
||||
requestManager.getMangaFetch(mangaId, { awaitRefetchQueries: true }).response,
|
||||
requestManager.getMangaChaptersFetch(mangaId, { awaitRefetchQueries: true }).response,
|
||||
]).finally(() => setFetchingOnline(false));
|
||||
}, [mangaId]);
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
ChapterListOptions,
|
||||
ChapterOptionsReducerAction,
|
||||
ChapterSortMode,
|
||||
IChapter,
|
||||
NullAndUndefined,
|
||||
TranslationKey,
|
||||
} from '@/typings';
|
||||
import { useReducerLocalStorage } from '@/util/useLocalStorage';
|
||||
import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
const defaultChapterOptions: ChapterListOptions = {
|
||||
active: false,
|
||||
@@ -46,7 +46,7 @@ function chapterOptionsReducer(state: ChapterListOptions, actions: ChapterOption
|
||||
}
|
||||
}
|
||||
|
||||
export function unreadFilter(unread: NullAndUndefined<boolean>, { read: isChapterRead }: IChapter) {
|
||||
export function unreadFilter(unread: NullAndUndefined<boolean>, { isRead: isChapterRead }: ChapterType) {
|
||||
switch (unread) {
|
||||
case true:
|
||||
return !isChapterRead;
|
||||
@@ -57,7 +57,7 @@ export function unreadFilter(unread: NullAndUndefined<boolean>, { read: isChapte
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFilter(downloaded: NullAndUndefined<boolean>, { downloaded: chapterDownload }: IChapter) {
|
||||
function downloadFilter(downloaded: NullAndUndefined<boolean>, { isDownloaded: chapterDownload }: ChapterType) {
|
||||
switch (downloaded) {
|
||||
case true:
|
||||
return chapterDownload;
|
||||
@@ -68,7 +68,7 @@ function downloadFilter(downloaded: NullAndUndefined<boolean>, { downloaded: cha
|
||||
}
|
||||
}
|
||||
|
||||
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { bookmarked: chapterBookmarked }: IChapter) {
|
||||
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { isBookmarked: chapterBookmarked }: ChapterType) {
|
||||
switch (bookmarked) {
|
||||
case true:
|
||||
return chapterBookmarked;
|
||||
@@ -79,7 +79,7 @@ function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { bookmarked: c
|
||||
}
|
||||
}
|
||||
|
||||
export function filterAndSortChapters(chapters: IChapter[], options: ChapterListOptions): IChapter[] {
|
||||
export function filterAndSortChapters(chapters: ChapterType[], options: ChapterListOptions): ChapterType[] {
|
||||
const filtered = options.active
|
||||
? chapters.filter(
|
||||
(chp) =>
|
||||
@@ -88,14 +88,17 @@ export function filterAndSortChapters(chapters: IChapter[], options: ChapterList
|
||||
bookmarkedFilter(options.bookmarked, chp),
|
||||
)
|
||||
: [...chapters];
|
||||
const Sorted = options.sortBy === 'fetchedAt' ? filtered.sort((a, b) => a.fetchedAt - b.fetchedAt) : filtered;
|
||||
const Sorted =
|
||||
options.sortBy === 'fetchedAt'
|
||||
? filtered.sort((a, b) => Number(a.fetchedAt ?? 0) - Number(b.fetchedAt ?? 0))
|
||||
: filtered;
|
||||
if (options.reverse) {
|
||||
Sorted.reverse();
|
||||
}
|
||||
return Sorted;
|
||||
}
|
||||
|
||||
export const useChapterOptions = (mangaId: string) =>
|
||||
export const useChapterOptions = (mangaId: number) =>
|
||||
useReducerLocalStorage<ChapterListOptions, ChapterOptionsReducerAction>(
|
||||
chapterOptionsReducer,
|
||||
`${mangaId}filterOptions`,
|
||||
|
||||
@@ -24,9 +24,9 @@ import ListItemText from '@mui/material/ListItemText';
|
||||
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChapterOffset, IChapter, IReaderSettings } from '@/typings';
|
||||
import { ChapterOffset, IReaderSettings } from '@/typings';
|
||||
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
|
||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ChapterType, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
const Root = styled('div')(({ theme }) => ({
|
||||
top: 0,
|
||||
@@ -115,8 +115,8 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
|
||||
interface IProps {
|
||||
settings: IReaderSettings;
|
||||
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
|
||||
manga: Pick<MangaType, 'id'>;
|
||||
chapter: IChapter;
|
||||
manga: MangaType;
|
||||
chapter: ChapterType;
|
||||
curPage: number;
|
||||
scrollToPage: (page: number) => void;
|
||||
openNextChapter: (offset: ChapterOffset, setHistory: (nextChapterIndex: number) => void) => Promise<void>;
|
||||
@@ -304,7 +304,7 @@ export default function ReaderNavBar(props: IProps) {
|
||||
<IconButton
|
||||
title={t('reader.button.previous_chapter')}
|
||||
sx={{ gridArea: 'pre' }}
|
||||
disabled={disableChapterNavButtons || chapter.index <= 1}
|
||||
disabled={disableChapterNavButtons || chapter.sourceOrder <= 1}
|
||||
onClick={() =>
|
||||
openNextChapter(ChapterOffset.PREV, (prevChapterIndex) => {
|
||||
navigate(`/manga/${manga.id}/chapter/${prevChapterIndex}`, {
|
||||
@@ -322,11 +322,11 @@ export default function ReaderNavBar(props: IProps) {
|
||||
<FormControl
|
||||
sx={{ gridArea: 'current' }}
|
||||
size="small"
|
||||
disabled={disableChapterNavButtons || chapter.index < 1}
|
||||
disabled={disableChapterNavButtons || chapter.sourceOrder < 1}
|
||||
>
|
||||
<Select
|
||||
MenuProps={MenuProps}
|
||||
value={chapter.index >= 1 ? chapter.index : ''}
|
||||
value={chapter.sourceOrder >= 1 ? chapter.sourceOrder : ''}
|
||||
displayEmpty
|
||||
onChange={({ target: { value: selectedChapter } }) => {
|
||||
navigate(`/manga/${manga.id}/chapter/${selectedChapter}`, {
|
||||
@@ -338,7 +338,7 @@ export default function ReaderNavBar(props: IProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
{Array(Math.max(0, chapter.chapterCount))
|
||||
{Array(Math.max(0, manga.chapters.totalCount))
|
||||
.fill(1)
|
||||
.map((ignoreValue, index) => (
|
||||
<MenuItem key={`Chapter#${index + 1}`} value={index + 1}>{`${t(
|
||||
@@ -352,8 +352,8 @@ export default function ReaderNavBar(props: IProps) {
|
||||
sx={{ gridArea: 'next' }}
|
||||
disabled={
|
||||
disableChapterNavButtons ||
|
||||
chapter.index < 1 ||
|
||||
chapter.index >= chapter.chapterCount
|
||||
chapter.sourceOrder < 1 ||
|
||||
chapter.sourceOrder >= manga.chapters.totalCount
|
||||
}
|
||||
onClick={() => {
|
||||
openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FieldPolicy, FieldReadFunction, TypePolicies, TypePolicy } from '@apollo/client/cache';
|
||||
import { GetChaptersQuery } from "@/lib/graphql/generated/graphql.ts";
|
||||
export type AboutPayloadKeySpecifier = ('buildTime' | 'buildType' | 'discord' | 'github' | 'name' | 'revision' | 'version' | AboutPayloadKeySpecifier)[];
|
||||
export type AboutPayloadFieldPolicy = {
|
||||
buildTime?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
@@ -491,7 +492,7 @@ export type QueryFieldPolicy = {
|
||||
categories?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
category?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
chapter?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
chapters?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
chapters?: FieldPolicy<GetChaptersQuery['chapters']> | FieldReadFunction<GetChaptersQuery['chapters']>,
|
||||
checkForServerUpdates?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
checkForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
downloadStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
||||
@@ -2378,7 +2378,7 @@ export type GetSourceMangasFetchMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetSourceMangasFetchMutation = { __typename?: 'Mutation', fetchSourceManga: { __typename?: 'FetchSourceMangaPayload', clientMutationId?: string | null, hasNextPage: boolean, mangas: Array<{ __typename?: 'MangaType', unreadCount: number, downloadCount: number, artist?: string | null, author?: string | null, chaptersLastFetchedAt?: any | null, description?: string | null, genre: Array<string>, id: number, inLibrary: boolean, inLibraryAt: any, initialized: boolean, lastFetchedAt?: any | null, realUrl?: string | null, status: MangaStatus, thumbnailUrl?: string | null, title: string, url: string, lastReadChapter?: { __typename?: 'ChapterType', chapterNumber: number, fetchedAt: any, id: number, isBookmarked: boolean, isDownloaded: boolean, isRead: boolean, lastPageRead: number, lastReadAt: any, name: string, pageCount: number, realUrl?: string | null, scanlator?: string | null, sourceOrder: number, uploadDate: any, url: string, manga: { __typename?: 'MangaType', unreadCount: number, downloadCount: number, artist?: string | null, author?: string | null, chaptersLastFetchedAt?: any | null, description?: string | null, genre: Array<string>, id: number, inLibrary: boolean, inLibraryAt: any, initialized: boolean, lastFetchedAt?: any | null, realUrl?: string | null, status: MangaStatus, thumbnailUrl?: string | null, title: string, url: string, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', default: boolean, id: number, includeInUpdate: IncludeInUpdate, name: string, order: number, meta: Array<{ __typename?: 'CategoryMetaType', key: string, value: string }>, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, meta: Array<{ __typename?: 'MangaMetaType', key: string, value: string }>, source?: { __typename?: 'SourceType', displayName: string, iconUrl: string, id: any, isConfigurable: boolean, isNsfw: boolean, lang: string, name: string, supportsLatest: boolean } | null }, meta: Array<{ __typename?: 'ChapterMetaType', key: string, value: string }> } | null, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', default: boolean, id: number, includeInUpdate: IncludeInUpdate, name: string, order: number, meta: Array<{ __typename?: 'CategoryMetaType', key: string, value: string }>, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> }, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, meta: Array<{ __typename?: 'MangaMetaType', key: string, value: string }>, source?: { __typename?: 'SourceType', displayName: string, iconUrl: string, id: any, isConfigurable: boolean, isNsfw: boolean, lang: string, name: string, supportsLatest: boolean } | null }> } };
|
||||
export type GetSourceMangasFetchMutation = { __typename?: 'Mutation', fetchSourceManga: { __typename?: 'FetchSourceMangaPayload', clientMutationId?: string | null, hasNextPage: boolean, mangas: Array<{ __typename?: 'MangaType', artist?: string | null, author?: string | null, chaptersLastFetchedAt?: any | null, description?: string | null, genre: Array<string>, id: number, inLibrary: boolean, inLibraryAt: any, initialized: boolean, lastFetchedAt?: any | null, realUrl?: string | null, status: MangaStatus, thumbnailUrl?: string | null, title: string, url: string, meta: Array<{ __typename?: 'MangaMetaType', key: string, value: string }>, source?: { __typename?: 'SourceType', displayName: string, iconUrl: string, id: any, isConfigurable: boolean, isNsfw: boolean, lang: string, name: string, supportsLatest: boolean } | null }> } };
|
||||
|
||||
export type UpdateSourcePreferencesMutationVariables = Exact<{
|
||||
input: UpdateSourcePreferenceInput;
|
||||
|
||||
@@ -11,12 +11,14 @@ import useSWR, { Middleware, SWRConfiguration, SWRResponse } from 'swr';
|
||||
import useSWRInfinite, { SWRInfiniteConfiguration, SWRInfiniteResponse } from 'swr/infinite';
|
||||
import {
|
||||
ApolloError,
|
||||
ApolloQueryResult,
|
||||
DocumentNode,
|
||||
FetchResult,
|
||||
MutationHookOptions,
|
||||
MutationOptions,
|
||||
MutationTuple,
|
||||
QueryHookOptions,
|
||||
QueryOptions,
|
||||
QueryResult,
|
||||
TypedDocumentNode,
|
||||
useMutation,
|
||||
@@ -24,12 +26,13 @@ import {
|
||||
} from '@apollo/client';
|
||||
import { OperationVariables } from '@apollo/client/core';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BackupValidationResult, IChapter, IMangaChapter, PaginatedList } from '@/typings.ts';
|
||||
import { BackupValidationResult } from '@/typings.ts';
|
||||
import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
|
||||
import storage from '@/util/localStorage.tsx';
|
||||
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
||||
import {
|
||||
CategoryOrderBy,
|
||||
ChapterOrderBy,
|
||||
CheckForServerUpdatesQuery,
|
||||
CheckForServerUpdatesQueryVariables,
|
||||
ClearDownloaderMutation,
|
||||
@@ -62,10 +65,16 @@ import {
|
||||
GetCategoriesQueryVariables,
|
||||
GetCategoryMangasQuery,
|
||||
GetCategoryMangasQueryVariables,
|
||||
GetChapterPagesFetchMutation,
|
||||
GetChapterPagesFetchMutationVariables,
|
||||
GetChaptersQuery,
|
||||
GetChaptersQueryVariables,
|
||||
GetExtensionsQuery,
|
||||
GetExtensionsQueryVariables,
|
||||
GetGlobalMetadatasQuery,
|
||||
GetGlobalMetadatasQueryVariables,
|
||||
GetMangaChaptersFetchMutation,
|
||||
GetMangaChaptersFetchMutationVariables,
|
||||
GetMangaFetchMutation,
|
||||
GetMangaFetchMutationVariables,
|
||||
GetMangaQuery,
|
||||
@@ -89,6 +98,7 @@ import {
|
||||
SetGlobalMetadataMutation,
|
||||
SetGlobalMetadataMutationVariables,
|
||||
SetMangaMetadataMutation,
|
||||
SortOrder,
|
||||
SourcePreferenceChangeInput,
|
||||
StartDownloaderMutation,
|
||||
StartDownloaderMutationVariables,
|
||||
@@ -153,7 +163,13 @@ import {
|
||||
STOP_DOWNLOADER,
|
||||
} from '@/lib/graphql/mutations/DownloaderMutation.ts';
|
||||
import { GET_CHAPTER, GET_CHAPTERS } from '@/lib/graphql/queries/ChapterQuery.ts';
|
||||
import { SET_CHAPTER_METADATA, UPDATE_CHAPTER, UPDATE_CHAPTERS } from '@/lib/graphql/mutations/ChapterMutation.ts';
|
||||
import {
|
||||
GET_CHAPTER_PAGES_FETCH,
|
||||
GET_MANGA_CHAPTERS_FETCH,
|
||||
SET_CHAPTER_METADATA,
|
||||
UPDATE_CHAPTER,
|
||||
UPDATE_CHAPTERS,
|
||||
} from '@/lib/graphql/mutations/ChapterMutation.ts';
|
||||
import {
|
||||
CREATE_CATEGORY,
|
||||
DELETE_CATEGORY,
|
||||
@@ -178,6 +194,7 @@ enum SWRHttpMethod {
|
||||
}
|
||||
|
||||
enum GQLMethod {
|
||||
QUERY = 'QUERY',
|
||||
USE_QUERY = 'USE_QUERY',
|
||||
USE_MUTATION = 'USE_MUTATION',
|
||||
MUTATION = 'MUTATION',
|
||||
@@ -186,8 +203,6 @@ enum GQLMethod {
|
||||
type HttpMethodType = DefaultHttpMethod | SWRHttpMethod;
|
||||
const HttpMethod = { ...SWRHttpMethod, ...DefaultHttpMethod };
|
||||
|
||||
type RequestOption = { doOnlineFetch?: boolean };
|
||||
|
||||
type CustomSWROptions<Data> = {
|
||||
skipRequest?: boolean;
|
||||
getEndpoint?: (index: number, previousData: Data | null) => string | null;
|
||||
@@ -214,6 +229,9 @@ export type AbortableSWRInfiniteResponse<Data = any, Error = any> = SWRInfiniteR
|
||||
AbortableRequest &
|
||||
SWRInfiniteResponseLoadInfo;
|
||||
|
||||
export type AbortabaleApolloQueryResponse<Data = any> = {
|
||||
response: Promise<ApolloQueryResult<Data>>;
|
||||
} & AbortableRequest;
|
||||
export type AbortableApolloUseQueryResponse<
|
||||
Data = any,
|
||||
Variables extends OperationVariables = OperationVariables,
|
||||
@@ -659,6 +677,13 @@ export class RequestManager {
|
||||
return `${this.getValidUrlFor(imageUrl, apiVersion)}${useCacheQuery}`;
|
||||
}
|
||||
|
||||
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
||||
method: GQLMethod.QUERY,
|
||||
operation: TypedDocumentNode<Data, Variables>,
|
||||
variables: Variables,
|
||||
options?: Partial<QueryOptions<Variables, Data>>,
|
||||
): AbortabaleApolloQueryResponse<Data>;
|
||||
|
||||
private doRequestNew<Data, Variables extends OperationVariables = OperationVariables>(
|
||||
method: GQLMethod.USE_QUERY,
|
||||
operation: TypedDocumentNode<Data, Variables>,
|
||||
@@ -685,15 +710,33 @@ export class RequestManager {
|
||||
operation: TypedDocumentNode<Data, Variables>,
|
||||
variables: Variables,
|
||||
options?:
|
||||
| QueryOptions<Variables, Data>
|
||||
| QueryHookOptions<Data, Variables>
|
||||
| MutationHookOptions<Data, Variables>
|
||||
| MutationOptions<Data, Variables>,
|
||||
):
|
||||
| AbortabaleApolloQueryResponse<Data>
|
||||
| AbortableApolloUseQueryResponse<Data, Variables>
|
||||
| AbortableApolloUseMutationResponse<Data, Variables>
|
||||
| AbortableApolloMutationResponse<Data> {
|
||||
const { signal, abortRequest } = this.createAbortController();
|
||||
switch (method) {
|
||||
case GQLMethod.QUERY:
|
||||
return {
|
||||
response: this.graphQLClient.client.query<Data, Variables>({
|
||||
query: operation,
|
||||
variables,
|
||||
...(options as QueryOptions<Variables, Data>),
|
||||
context: {
|
||||
...options?.context,
|
||||
fetchOptions: {
|
||||
signal,
|
||||
...options?.context?.fetchOptions,
|
||||
},
|
||||
},
|
||||
}),
|
||||
abortRequest,
|
||||
};
|
||||
case GQLMethod.USE_QUERY:
|
||||
return {
|
||||
...useQuery<Data, Variables>(operation, {
|
||||
@@ -1302,33 +1345,116 @@ export class RequestManager {
|
||||
);
|
||||
}
|
||||
|
||||
public useGetChapters(
|
||||
variables: GetChaptersQueryVariables,
|
||||
options?: QueryHookOptions<GetChaptersQuery, GetChaptersQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetChaptersQuery, GetChaptersQueryVariables> {
|
||||
return this.doRequestNew(GQLMethod.USE_QUERY, GET_CHAPTERS, variables, options);
|
||||
}
|
||||
|
||||
public useGetMangaChapters(
|
||||
mangaId: number | string,
|
||||
{ doOnlineFetch, ...swrOptions }: SWROptions<IChapter[]> & RequestOption = {},
|
||||
): AbortableSWRResponse<IChapter[]> {
|
||||
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
||||
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapters${onlineFetch}`, {
|
||||
swrOptions,
|
||||
});
|
||||
options?: QueryHookOptions<GetChaptersQuery, GetChaptersQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetChaptersQuery, GetChaptersQueryVariables> {
|
||||
return this.useGetChapters(
|
||||
{
|
||||
condition: { mangaId: Number(mangaId) },
|
||||
orderBy: ChapterOrderBy.SourceOrder,
|
||||
orderByType: SortOrder.Desc,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
public getMangaChapters(mangaId: number | string, doOnlineFetch?: boolean): AbortableAxiosResponse<IChapter[]> {
|
||||
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
|
||||
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/chapters${onlineFetch}`);
|
||||
public getMangaChaptersFetch(
|
||||
mangaId: number | string,
|
||||
options?: MutationOptions<GetMangaChaptersFetchMutation, GetMangaChaptersFetchMutationVariables>,
|
||||
): AbortableApolloMutationResponse<GetMangaChaptersFetchMutation> {
|
||||
return this.doRequestNew<GetMangaChaptersFetchMutation, GetMangaChaptersFetchMutationVariables>(
|
||||
GQLMethod.MUTATION,
|
||||
GET_MANGA_CHAPTERS_FETCH,
|
||||
{ input: { mangaId: Number(mangaId) } },
|
||||
{ refetchQueries: [GET_MANGA, GET_MANGAS, GET_CHAPTER, GET_CHAPTERS], ...options },
|
||||
);
|
||||
}
|
||||
|
||||
public useGetChapter(
|
||||
public useGetMangaChapter(
|
||||
mangaId: number | string,
|
||||
chapterIndex: number | string,
|
||||
swrOptions?: SWROptions<IChapter>,
|
||||
): AbortableSWRResponse<IChapter> {
|
||||
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapter/${chapterIndex}`, {
|
||||
swrOptions,
|
||||
});
|
||||
options?: QueryHookOptions<GetChaptersQuery, GetChaptersQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<
|
||||
Omit<GetChaptersQuery, 'chapters'> & { chapter: GetChaptersQuery['chapters']['nodes'][number] },
|
||||
GetChaptersQueryVariables
|
||||
> {
|
||||
type Response = AbortableApolloUseQueryResponse<
|
||||
Omit<GetChaptersQuery, 'chapters'> & { chapter: GetChaptersQuery['chapters']['nodes'][number] },
|
||||
GetChaptersQueryVariables
|
||||
>;
|
||||
|
||||
const chapterResponse = this.useGetChapters(
|
||||
{ condition: { mangaId: Number(mangaId), sourceOrder: Number(chapterIndex) } },
|
||||
options,
|
||||
);
|
||||
|
||||
if (!chapterResponse.data) {
|
||||
return chapterResponse as unknown as Response;
|
||||
}
|
||||
|
||||
return {
|
||||
...chapterResponse,
|
||||
data: {
|
||||
chapter: chapterResponse.data.chapters.nodes[0],
|
||||
},
|
||||
} as unknown as Response;
|
||||
}
|
||||
|
||||
public getChapter(mangaId: number | string, chapterIndex: number | string): AbortableAxiosResponse<IChapter> {
|
||||
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/chapter/${chapterIndex}`);
|
||||
public getChapter(
|
||||
mangaId: number | string,
|
||||
chapterIndex: number | string,
|
||||
): AbortabaleApolloQueryResponse<
|
||||
Omit<GetChaptersQuery, 'chapters'> & { chapter: GetChaptersQuery['chapters']['nodes'][number] }
|
||||
> {
|
||||
type ResponseData = Omit<GetChaptersQuery, 'chapters'> & {
|
||||
chapter: GetChaptersQuery['chapters']['nodes'][number];
|
||||
};
|
||||
|
||||
const chapterRequest = this.doRequestNew<GetChaptersQuery, GetChaptersQueryVariables>(
|
||||
GQLMethod.QUERY,
|
||||
GET_CHAPTERS,
|
||||
{
|
||||
condition: { mangaId: Number(mangaId), sourceOrder: Number(chapterIndex) },
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
...chapterRequest,
|
||||
response: chapterRequest.response.then((chapterResponse) => {
|
||||
if (!chapterResponse.data) {
|
||||
return chapterResponse;
|
||||
}
|
||||
|
||||
return {
|
||||
...chapterResponse,
|
||||
data: {
|
||||
chapter: chapterResponse.data.chapters.nodes[0],
|
||||
},
|
||||
};
|
||||
}) as Promise<ApolloQueryResult<ResponseData>>,
|
||||
};
|
||||
}
|
||||
|
||||
public useGetChapterPagesFetch(
|
||||
chapterId: string | number,
|
||||
options?: MutationHookOptions<GetChapterPagesFetchMutation, GetChapterPagesFetchMutationVariables>,
|
||||
): AbortableApolloUseMutationResponse<GetChapterPagesFetchMutation, GetChapterPagesFetchMutationVariables> {
|
||||
return this.doRequestNew(
|
||||
GQLMethod.USE_MUTATION,
|
||||
GET_CHAPTER_PAGES_FETCH,
|
||||
{
|
||||
input: { chapterId: Number(chapterId) },
|
||||
},
|
||||
{ refetchQueries: [GET_CHAPTER, GET_CHAPTERS], ...options },
|
||||
);
|
||||
}
|
||||
|
||||
public deleteDownloadedChapter(id: number): AbortableApolloMutationResponse<DeleteDownloadedChapterMutation> {
|
||||
@@ -1552,17 +1678,36 @@ export class RequestManager {
|
||||
}
|
||||
|
||||
public useGetRecentlyUpdatedChapters(
|
||||
initialPages?: number,
|
||||
swrOptions?: SWRInfiniteOptions<PaginatedList<IMangaChapter>>,
|
||||
): AbortableSWRInfiniteResponse<PaginatedList<IMangaChapter>> {
|
||||
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
|
||||
swrOptions: {
|
||||
getEndpoint: (page, previousData) =>
|
||||
previousData?.hasNextPage ?? true ? `update/recentChapters/${page}` : null,
|
||||
initialSize: initialPages,
|
||||
...swrOptions,
|
||||
} as typeof swrOptions,
|
||||
});
|
||||
initialPages: number = 1,
|
||||
options?: QueryHookOptions<GetChaptersQuery, GetChaptersQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetChaptersQuery, GetChaptersQueryVariables> {
|
||||
const PAGE_SIZE = 50;
|
||||
const CACHE_KEY = 'useGetRecentlyUpdatedChapters';
|
||||
|
||||
const offset = this.cache.getResponseFor<number>(CACHE_KEY, undefined) ?? 0;
|
||||
const [lastOffset] = useState(offset);
|
||||
|
||||
const result = this.useGetChapters(
|
||||
{
|
||||
filter: { inLibrary: { equalTo: true } },
|
||||
orderBy: ChapterOrderBy.FetchedAt,
|
||||
orderByType: SortOrder.Desc,
|
||||
first: initialPages * PAGE_SIZE + lastOffset,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return {
|
||||
...result,
|
||||
fetchMore: (...args: Parameters<(typeof result)['fetchMore']>) => {
|
||||
const fetchMoreOptions = args[0] ?? {};
|
||||
this.cache.cacheResponse(CACHE_KEY, undefined, fetchMoreOptions.variables?.offset);
|
||||
return result.fetchMore({
|
||||
...fetchMoreOptions,
|
||||
variables: { first: PAGE_SIZE, ...fetchMoreOptions.variables },
|
||||
});
|
||||
},
|
||||
} as typeof result;
|
||||
}
|
||||
|
||||
public startGlobalUpdate(): AbortableApolloMutationResponse<UpdateLibraryMangasMutation>;
|
||||
|
||||
@@ -6,16 +6,44 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||
import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject, Reference } from '@apollo/client';
|
||||
import { createUploadLink } from 'apollo-upload-client';
|
||||
import { BaseClient } from '@/lib/requests/client/BaseClient.ts';
|
||||
import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts';
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const typePolicies: StrictTypedTypePolicies = {
|
||||
GlobalMetaType: { keyFields: ['key'] },
|
||||
ExtensionType: { keyFields: ['apkName'] },
|
||||
AboutPayload: { keyFields: [] },
|
||||
Query: {
|
||||
fields: {
|
||||
chapters: {
|
||||
keyArgs: ['condition', 'filter', 'orderBy', 'orderByType'],
|
||||
merge(existing, incoming) {
|
||||
const merged = {
|
||||
...existing,
|
||||
...incoming,
|
||||
nodes: existing?.nodes ?? [],
|
||||
};
|
||||
const isRefetch = incoming.nodes.some(
|
||||
(incomingChapter) =>
|
||||
existing?.nodes.some(
|
||||
(existingChapter) =>
|
||||
(existingChapter as unknown as Reference).__ref ===
|
||||
(incomingChapter as unknown as Reference).__ref,
|
||||
),
|
||||
);
|
||||
if (!isRefetch) {
|
||||
merged.nodes = [...(existing?.nodes ?? []), ...incoming.nodes];
|
||||
}
|
||||
return merged;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/* eslint-enable no-underscore-dangle */
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export class GraphQLClient extends BaseClient<
|
||||
|
||||
@@ -97,7 +97,7 @@ const Manga: React.FC = () => {
|
||||
{isLoading && <LoadingPlaceholder />}
|
||||
|
||||
{manga && <MangaDetails manga={manga} />}
|
||||
<ChapterList mangaId={id} />
|
||||
{manga && <ChapterList manga={manga} isRefreshing={refreshing} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
*/
|
||||
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Box } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChapterOffset, IChapter, IReaderSettings, ReaderType, TranslationKey } from '@/typings';
|
||||
import { ChapterOffset, IReaderSettings, ReaderType, TranslationKey } from '@/typings';
|
||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||
import {
|
||||
checkAndHandleMissingStoredReaderSettings,
|
||||
@@ -27,12 +27,12 @@ import VerticalPager from '@/components/reader/pager/VerticalPager';
|
||||
import ReaderNavBar from '@/components/navbar/ReaderNavBar';
|
||||
import NavbarContext from '@/components/context/NavbarContext';
|
||||
import makeToast from '@/components/util/Toast';
|
||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ChapterType, MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
const isDupChapter = async (chapterIndex: number, currentChapter: IChapter) => {
|
||||
const nextChapter = await requestManager.getChapter(currentChapter.mangaId, chapterIndex).response;
|
||||
const isDupChapter = async (chapterIndex: number, currentChapter: ChapterType) => {
|
||||
const nextChapter = await requestManager.getChapter(currentChapter.manga.id, chapterIndex).response;
|
||||
|
||||
return nextChapter.chapterNumber === currentChapter.chapterNumber;
|
||||
return nextChapter.data.chapter.chapterNumber === currentChapter.chapterNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ const isDupChapter = async (chapterIndex: number, currentChapter: IChapter) => {
|
||||
*/
|
||||
const getOffsetChapter = async (
|
||||
chapterIndex: number,
|
||||
currentChapter: IChapter,
|
||||
currentChapter: ChapterType,
|
||||
skipDupChapters: boolean,
|
||||
offset: ChapterOffset,
|
||||
): Promise<number> => {
|
||||
@@ -82,11 +82,11 @@ const getReaderComponent = (readerType: ReaderType) => {
|
||||
const range = (n: number) => Array.from({ length: n }, (value, key) => key);
|
||||
const initialChapter = {
|
||||
pageCount: -1,
|
||||
index: -1,
|
||||
sourceOrder: -1,
|
||||
chapterCount: 0,
|
||||
lastPageRead: 0,
|
||||
name: 'Loading...',
|
||||
} as IChapter;
|
||||
} as unknown as ChapterType;
|
||||
|
||||
export default function Reader() {
|
||||
const { t } = useTranslation();
|
||||
@@ -104,17 +104,48 @@ export default function Reader() {
|
||||
genre: [],
|
||||
inLibraryAt: 0,
|
||||
lastReadAt: 0,
|
||||
chapters: { totalCount: 0 },
|
||||
}) as unknown as MangaType,
|
||||
[mangaId],
|
||||
);
|
||||
|
||||
const { data, loading: isMangaLoading } = requestManager.useGetManga(mangaId);
|
||||
const loadedChapter = useRef<ChapterType | null>(null);
|
||||
const isChapterLoaded =
|
||||
Number(mangaId) === loadedChapter.current?.manga.id &&
|
||||
Number(chapterIndex) === loadedChapter.current?.sourceOrder &&
|
||||
loadedChapter.current?.pageCount !== -1;
|
||||
const manga = (data?.manga as MangaType) ?? initialManga;
|
||||
const { data: chapter = initialChapter, isLoading: isChapterLoading } = requestManager.useGetChapter(
|
||||
mangaId,
|
||||
chapterIndex,
|
||||
{ disableCache: true, revalidateOnFocus: false },
|
||||
);
|
||||
const { data: chapterData, loading: isChapterLoading } = requestManager.useGetMangaChapter(mangaId, chapterIndex, {
|
||||
skip: isChapterLoaded,
|
||||
});
|
||||
|
||||
const getLoadedChapter = () => {
|
||||
const isAChapterLoaded = loadedChapter.current;
|
||||
|
||||
const isSameAsLoadedChapter = isAChapterLoaded && isChapterLoaded;
|
||||
if (isSameAsLoadedChapter) {
|
||||
return loadedChapter.current;
|
||||
}
|
||||
|
||||
if (chapterData?.chapter) {
|
||||
return chapterData.chapter as ChapterType;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
loadedChapter.current = getLoadedChapter();
|
||||
|
||||
const chapter = loadedChapter.current ?? initialChapter;
|
||||
const [fetchPages, { loading: areChapterPagesLoading }] = requestManager.useGetChapterPagesFetch(chapter.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isChapterLoading && chapter.pageCount === -1) {
|
||||
fetchPages();
|
||||
}
|
||||
}, [chapter.id]);
|
||||
|
||||
const isLoading = isChapterLoading || areChapterPagesLoading || chapter.pageCount === -1;
|
||||
const [wasLastPageReadSet, setWasLastPageReadSet] = useState(false);
|
||||
const [curPage, setCurPage] = useState<number>(0);
|
||||
const [pageToScrollTo, setPageToScrollTo] = useState<number | undefined>(undefined);
|
||||
@@ -136,12 +167,7 @@ export default function Reader() {
|
||||
setRetrievingNextChapter(true);
|
||||
try {
|
||||
setHistory(
|
||||
await getOffsetChapter(
|
||||
chapter.index + offset,
|
||||
chapter as IChapter,
|
||||
settings.skipDupChapters,
|
||||
offset,
|
||||
),
|
||||
await getOffsetChapter(chapter.sourceOrder + offset, chapter, settings.skipDupChapters, offset),
|
||||
);
|
||||
} catch (error) {
|
||||
const offsetToTranslationKeyMap: { [chapterOffset in ChapterOffset]: TranslationKey } = {
|
||||
@@ -158,7 +184,7 @@ export default function Reader() {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChapterLoading || !chapter) {
|
||||
if (isLoading || !chapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -167,13 +193,13 @@ export default function Reader() {
|
||||
// last page, also probably read = true, we will load the first page.
|
||||
setCurPage(0);
|
||||
} else setCurPage(chapter.lastPageRead);
|
||||
}, [chapter, isChapterLoading]);
|
||||
}, [chapter, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!manga?.title || (chapter as IChapter)?.name === t('global.label.loading')) {
|
||||
if (!manga?.title || chapter.name === t('global.label.loading')) {
|
||||
setTitle(t('reader.title'));
|
||||
} else {
|
||||
setTitle(`${manga.title}: ${(chapter as IChapter).name}`);
|
||||
setTitle(`${manga.title}: ${chapter.name}`);
|
||||
}
|
||||
}, [t, manga, chapter]);
|
||||
|
||||
@@ -193,7 +219,7 @@ export default function Reader() {
|
||||
settings={settings}
|
||||
setSettingValue={setSettingValue}
|
||||
manga={manga}
|
||||
chapter={chapter as IChapter}
|
||||
chapter={chapter}
|
||||
curPage={curPage}
|
||||
scrollToPage={setPageToScrollTo}
|
||||
openNextChapter={openNextChapter}
|
||||
@@ -212,17 +238,20 @@ export default function Reader() {
|
||||
}
|
||||
|
||||
// do not mutate the chapter, this will cause the page to jump around due to always scrolling to the last read page
|
||||
if (curPage !== -1) {
|
||||
requestManager.updateChapter(chapter.id, { lastPageRead: curPage });
|
||||
}
|
||||
const updateLastPageRead = curPage !== -1;
|
||||
const updateIsRead = curPage === chapter.pageCount - 1;
|
||||
const updateChapter = updateLastPageRead || updateIsRead;
|
||||
|
||||
if (curPage === chapter.pageCount - 1) {
|
||||
requestManager.updateChapter(chapter.id, { isRead: true });
|
||||
if (updateChapter) {
|
||||
requestManager.updateChapter(chapter.id, {
|
||||
lastPageRead: updateLastPageRead ? curPage : undefined,
|
||||
isRead: updateIsRead ? true : undefined,
|
||||
});
|
||||
}
|
||||
}, [curPage]);
|
||||
|
||||
const nextChapter = useCallback(() => {
|
||||
if (chapter.index < chapter.chapterCount) {
|
||||
if (chapter.sourceOrder < manga.chapters.totalCount) {
|
||||
requestManager.updateChapter(chapter.id, {
|
||||
lastPageRead: chapter.pageCount - 1,
|
||||
isRead: true,
|
||||
@@ -235,10 +264,10 @@ export default function Reader() {
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [chapter.index, chapter.chapterCount, chapter.pageCount, manga.id, settings.skipDupChapters]);
|
||||
}, [chapter.sourceOrder, manga.chapters.totalCount, chapter.pageCount, manga.id, settings.skipDupChapters]);
|
||||
|
||||
const prevChapter = useCallback(() => {
|
||||
if (chapter.index > 1) {
|
||||
if (chapter.sourceOrder > 1) {
|
||||
openNextChapter(ChapterOffset.PREV, (prevChapterIndex) =>
|
||||
navigate(`/manga/${manga.id}/chapter/${prevChapterIndex}`, {
|
||||
replace: true,
|
||||
@@ -246,7 +275,7 @@ export default function Reader() {
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [chapter.index, manga.id, settings.skipDupChapters]);
|
||||
}, [chapter.sourceOrder, manga.id, settings.skipDupChapters]);
|
||||
|
||||
// return spinner while chpater data is loading
|
||||
if (chapter.pageCount === -1) {
|
||||
@@ -283,6 +312,7 @@ export default function Reader() {
|
||||
>
|
||||
<PageNumber settings={settings} curPage={curPage} pageCount={chapter.pageCount} />
|
||||
<ReaderComponent
|
||||
key={chapter.id}
|
||||
pages={pages}
|
||||
pageCount={chapter.pageCount}
|
||||
setCurPage={setCurPage}
|
||||
|
||||
@@ -18,12 +18,13 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { t as translate } from 'i18next';
|
||||
import { GroupedVirtuoso } from 'react-virtuoso';
|
||||
import { IChapter, IMangaChapter, IQueue } from '@/typings';
|
||||
import { IQueue } from '@/typings';
|
||||
import requestManager from '@/lib/requests/RequestManager.ts';
|
||||
import LoadingPlaceholder from '@/components/util/LoadingPlaceholder';
|
||||
import EmptyView from '@/components/util/EmptyView';
|
||||
import DownloadStateIndicator from '@/components/molecules/DownloadStateIndicator';
|
||||
import NavbarContext from '@/components/context/NavbarContext';
|
||||
import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
const StyledGroupedVirtuoso = styled(GroupedVirtuoso)(({ theme }) => ({
|
||||
// 64px header
|
||||
@@ -81,14 +82,14 @@ function getDateString(date: Date) {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
const groupByDate = (updates: IMangaChapter[]): [date: string, items: number][] => {
|
||||
const groupByDate = (updates: ChapterType[]): [date: string, items: number][] => {
|
||||
if (!updates.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dateToItemMap = new Map<string, number>();
|
||||
updates.forEach((item) => {
|
||||
const date = getDateString(epochToDate(item.chapter.fetchedAt));
|
||||
const date = getDateString(epochToDate(Number(item.fetchedAt)));
|
||||
dateToItemMap.set(date, (dateToItemMap.get(date) ?? 0) + 1);
|
||||
});
|
||||
|
||||
@@ -106,19 +107,18 @@ const Updates: React.FC = () => {
|
||||
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
const {
|
||||
data: pages = [{ hasNextPage: false, page: [] }],
|
||||
isLoading,
|
||||
size: loadedPages,
|
||||
setSize: setPages,
|
||||
} = requestManager.useGetRecentlyUpdatedChapters();
|
||||
const { hasNextPage } = pages[pages.length - 1];
|
||||
const updateEntries = useMemo(
|
||||
() => pages.map((page) => page.page).reduce((lastPageChapters, chapters) => [...lastPageChapters, ...chapters]),
|
||||
[pages],
|
||||
);
|
||||
data: chapterUpdateData,
|
||||
loading: isLoading,
|
||||
fetchMore,
|
||||
} = requestManager.useGetRecentlyUpdatedChapters(undefined, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
const hasNextPage = !!chapterUpdateData?.chapters.pageInfo.hasNextPage;
|
||||
const endCursor = chapterUpdateData?.chapters.pageInfo.endCursor;
|
||||
const updateEntries = (chapterUpdateData?.chapters.nodes as ChapterType[]) ?? [];
|
||||
const groupedUpdates = useMemo(() => groupByDate(updateEntries), [updateEntries]);
|
||||
const groupCounts: number[] = useMemo(() => groupedUpdates.map((group) => group[1]), [groupedUpdates]);
|
||||
|
||||
const [, setWsClient] = useState<WebSocket>();
|
||||
const [{ queue }, setQueueState] = useState<IQueue>(initialQueue);
|
||||
|
||||
@@ -140,12 +140,15 @@ const Updates: React.FC = () => {
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const downloadForChapter = (chapter: IChapter) => {
|
||||
const { index, mangaId } = chapter;
|
||||
return queue.find((q) => index === q.chapterIndex && mangaId === q.mangaId);
|
||||
const downloadForChapter = (chapter: ChapterType) => {
|
||||
const {
|
||||
sourceOrder,
|
||||
manga: { id: mangaId },
|
||||
} = chapter;
|
||||
return queue.find((q) => sourceOrder === q.chapterIndex && mangaId === q.mangaId);
|
||||
};
|
||||
|
||||
const downloadChapter = (chapter: IChapter) => {
|
||||
const downloadChapter = (chapter: ChapterType) => {
|
||||
requestManager.addChapterToDownloadQueue(chapter.id);
|
||||
};
|
||||
|
||||
@@ -154,8 +157,8 @@ const Updates: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
setPages(loadedPages + 1);
|
||||
}, [hasNextPage, loadedPages]);
|
||||
fetchMore({ variables: { offset: updateEntries.length } });
|
||||
}, [hasNextPage, endCursor]);
|
||||
|
||||
if (!isLoading && updateEntries.length === 0) {
|
||||
return <EmptyView message={t('updates.error.label.no_updates_available')} />;
|
||||
@@ -179,7 +182,8 @@ const Updates: React.FC = () => {
|
||||
</StyledGroupHeader>
|
||||
)}
|
||||
itemContent={(index) => {
|
||||
const { chapter, manga } = updateEntries[index];
|
||||
const chapter = updateEntries[index];
|
||||
const { manga } = chapter;
|
||||
const download = downloadForChapter(chapter);
|
||||
|
||||
return (
|
||||
@@ -187,7 +191,7 @@ const Updates: React.FC = () => {
|
||||
<Card>
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
to={`/manga/${chapter.mangaId}/chapter/${chapter.index}`}
|
||||
to={`/manga/${chapter.manga.id}/chapter/${chapter.sourceOrder}`}
|
||||
state={location.state}
|
||||
>
|
||||
<CardContent
|
||||
@@ -208,7 +212,7 @@ const Updates: React.FC = () => {
|
||||
marginRight: 2,
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
src={requestManager.getValidImgUrlFor(manga.thumbnailUrl)}
|
||||
src={requestManager.getValidImgUrlFor(manga.thumbnailUrl ?? '')}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant="h5" component="h2">
|
||||
@@ -220,7 +224,7 @@ const Updates: React.FC = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
{download && <DownloadStateIndicator download={download} />}
|
||||
{download == null && !chapter.downloaded && (
|
||||
{download == null && !chapter.isDownloaded && (
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SvgIconTypeMap } from '@mui/material/SvgIcon/SvgIcon';
|
||||
import { ParseKeys } from 'i18next';
|
||||
import { Location } from 'react-router-dom';
|
||||
import {
|
||||
ChapterType,
|
||||
ExtensionType,
|
||||
GetSourceQuery,
|
||||
MangaType,
|
||||
@@ -165,13 +166,6 @@ export interface IMangaChapter {
|
||||
chapter: IChapter;
|
||||
}
|
||||
|
||||
export interface IPartialChapter {
|
||||
pageCount: number;
|
||||
index: number;
|
||||
chapterCount: number;
|
||||
lastPageRead: number;
|
||||
}
|
||||
|
||||
export enum IncludeInGlobalUpdate {
|
||||
EXCLUDE = 0,
|
||||
INCLUDE = 1,
|
||||
@@ -237,7 +231,7 @@ export interface IReaderProps {
|
||||
initialPage: number;
|
||||
settings: IReaderSettings;
|
||||
manga: MangaType;
|
||||
chapter: IChapter | IPartialChapter;
|
||||
chapter: ChapterType;
|
||||
nextChapter: () => void;
|
||||
prevChapter: () => void;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,20 @@
|
||||
import fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const format = (source: string, regex: RegExp, replaceValue: string): string => source.replace(regex, replaceValue);
|
||||
const format = (source: string, regex: RegExp | string, replaceValue: string): string =>
|
||||
source.replace(regex, replaceValue);
|
||||
|
||||
const generatedGraphQLFilePath = path.resolve(__dirname, '../../src/lib/graphql/generated/graphql.ts');
|
||||
const generatedGraphQLFile = fs.readFileSync(generatedGraphQLFilePath, 'utf8');
|
||||
let generatedGraphQLFilePath = path.resolve(__dirname, '../../src/lib/graphql/generated/graphql.ts');
|
||||
let generatedGraphQLFile = fs.readFileSync(generatedGraphQLFilePath, 'utf8');
|
||||
|
||||
// add logic to format the codegen generated graphql file
|
||||
|
||||
/* ******************************************* */
|
||||
/* */
|
||||
/* typescript, typescript-operations */
|
||||
/* */
|
||||
/* ******************************************* */
|
||||
|
||||
const fixCursorTyping = format(
|
||||
generatedGraphQLFile,
|
||||
/Cursor: \{ input: any; output: any; }/g,
|
||||
@@ -31,3 +38,73 @@ const fixLongStringTyping = format(
|
||||
const fixSubscriptionHookNameSuffix = format(fixLongStringTyping, /SubscriptionSubscription/g, 'Subscription');
|
||||
|
||||
fs.writeFileSync(generatedGraphQLFilePath, fixSubscriptionHookNameSuffix);
|
||||
|
||||
/* ****************************************** */
|
||||
/* */
|
||||
/* typescript-apollo-client-helpers */
|
||||
/* */
|
||||
/* ****************************************** */
|
||||
|
||||
generatedGraphQLFilePath = path.resolve(__dirname, '../../src/lib/graphql/generated/apollo-helpers.ts');
|
||||
generatedGraphQLFile = fs.readFileSync(generatedGraphQLFilePath, 'utf8');
|
||||
|
||||
const addImports = format(
|
||||
generatedGraphQLFile,
|
||||
`import { FieldPolicy, FieldReadFunction, TypePolicies, TypePolicy } from '@apollo/client/cache';`,
|
||||
`import { FieldPolicy, FieldReadFunction, TypePolicies, TypePolicy } from '@apollo/client/cache';
|
||||
import { GetChaptersQuery } from "@/lib/graphql/generated/graphql.ts";`,
|
||||
);
|
||||
|
||||
const fixTypingOfQueryTypePolicies = format(
|
||||
addImports,
|
||||
`export type QueryFieldPolicy = {
|
||||
\tabout?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcategories?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcategory?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tchapter?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tchapters?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcheckForServerUpdates?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcheckForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tdownloadStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tgetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmanga?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmeta?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmetas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\trestoreStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsettings?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsource?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsources?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tupdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tvalidateBackup?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};`,
|
||||
`export type QueryFieldPolicy = {
|
||||
\tabout?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcategories?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcategory?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tchapter?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tchapters?: FieldPolicy<GetChaptersQuery['chapters']> | FieldReadFunction<GetChaptersQuery['chapters']>,
|
||||
\tcheckForServerUpdates?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tcheckForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tdownloadStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textension?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tgetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmanga?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmangas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmeta?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tmetas?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\trestoreStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsettings?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsource?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tsources?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tupdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
\tvalidateBackup?: FieldPolicy<any> | FieldReadFunction<any>
|
||||
};`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(generatedGraphQLFilePath, fixTypingOfQueryTypePolicies);
|
||||
|
||||
Reference in New Issue
Block a user