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