Feature/download ahead while reading (#464)
* Download next chapters when marking chapter as read while reading * Add "auto delete chapter" settings * Delete chapter after finished reading * Delete chapters after marking them as read * Improve "mark previous as read" action Send only ids of chapters that are actually unread
This commit is contained in:
@@ -28,6 +28,8 @@ import { ReaderNavBar } from '@/components/navbar/ReaderNavBar';
|
||||
import { makeToast } from '@/components/util/Toast';
|
||||
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||
import { useDebounce } from '@/components/manga/hooks.ts';
|
||||
import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||
|
||||
const isDupChapter = async (chapterIndex: number, currentChapter: TChapter) => {
|
||||
const nextChapter = await requestManager.getChapter(currentChapter.manga.id, chapterIndex).response;
|
||||
@@ -121,6 +123,10 @@ export function Reader() {
|
||||
});
|
||||
const [arePagesUpdated, setArePagesUpdated] = useState(false);
|
||||
|
||||
const { data: settingsData } = requestManager.useGetServerSettings();
|
||||
const isDownloadAheadEnabled = !!settingsData?.settings.autoDownloadAheadLimit;
|
||||
const [downloadAhead] = requestManager.useDownloadAhead();
|
||||
|
||||
const getLoadedChapter = () => {
|
||||
const isAChapterLoaded = loadedChapter.current;
|
||||
|
||||
@@ -168,6 +174,34 @@ export function Reader() {
|
||||
const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
|
||||
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
|
||||
|
||||
const { settings: metadataSettings } = useMetadataServerSettings();
|
||||
|
||||
const updateChapter = (patch: UpdateChapterPatchInput) => {
|
||||
requestManager.updateChapter(chapter.id, patch).response.catch(() => {});
|
||||
|
||||
const shouldDeleteChapter =
|
||||
patch.isRead &&
|
||||
metadataSettings.deleteChaptersAutoMarkedRead &&
|
||||
chapter.isDownloaded &&
|
||||
(!chapter.isBookmarked || metadataSettings.deleteChaptersWithBookmark);
|
||||
if (shouldDeleteChapter) {
|
||||
requestManager.deleteDownloadedChapter(chapter.id).response.catch(() => {});
|
||||
}
|
||||
|
||||
const shouldDownloadAhead =
|
||||
chapter.manga.inLibrary && !chapter.isRead && patch.isRead && isDownloadAheadEnabled;
|
||||
if (shouldDownloadAhead) {
|
||||
downloadAhead({
|
||||
variables: {
|
||||
input: {
|
||||
mangaIds: [chapter.manga.id],
|
||||
latestReadChapterIds: [chapter.id],
|
||||
},
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
|
||||
setSettings({ ...settings, [key]: value });
|
||||
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
|
||||
@@ -255,31 +289,42 @@ export function Reader() {
|
||||
// do not mutate the chapter, this will cause the page to jump around due to always scrolling to the last read page
|
||||
const updateLastPageRead = curPageDebounced !== -1;
|
||||
const updateIsRead = curPageDebounced === chapter.pageCount - 1;
|
||||
const updateChapter = updateLastPageRead || updateIsRead;
|
||||
|
||||
if (updateChapter) {
|
||||
requestManager.updateChapter(chapter.id, {
|
||||
lastPageRead: updateLastPageRead ? curPageDebounced : undefined,
|
||||
isRead: updateIsRead ? true : undefined,
|
||||
});
|
||||
const shouldUpdateChapter = updateLastPageRead || updateIsRead;
|
||||
if (!shouldUpdateChapter) {
|
||||
return;
|
||||
}
|
||||
}, [curPageDebounced]);
|
||||
|
||||
updateChapter({
|
||||
lastPageRead: updateLastPageRead ? curPageDebounced : undefined,
|
||||
isRead: updateIsRead ? true : undefined,
|
||||
});
|
||||
}, [curPageDebounced, isDownloadAheadEnabled]);
|
||||
|
||||
const nextChapter = useCallback(() => {
|
||||
if (chapter.sourceOrder < manga.chapters.totalCount) {
|
||||
requestManager.updateChapter(chapter.id, {
|
||||
lastPageRead: chapter.pageCount - 1,
|
||||
isRead: true,
|
||||
});
|
||||
|
||||
openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) =>
|
||||
navigate(`/manga/${manga.id}/chapter/${nextChapterIndex}`, {
|
||||
replace: true,
|
||||
state: location.state,
|
||||
}),
|
||||
);
|
||||
const doesNextChapterExist = chapter.sourceOrder < manga.chapters.totalCount;
|
||||
if (!doesNextChapterExist) {
|
||||
return;
|
||||
}
|
||||
}, [chapter.sourceOrder, manga.chapters.totalCount, chapter.pageCount, manga.id, settings.skipDupChapters]);
|
||||
|
||||
updateChapter({
|
||||
lastPageRead: chapter.pageCount - 1,
|
||||
isRead: true,
|
||||
});
|
||||
|
||||
openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) =>
|
||||
navigate(`/manga/${manga.id}/chapter/${nextChapterIndex}`, {
|
||||
replace: true,
|
||||
state: location.state,
|
||||
}),
|
||||
);
|
||||
}, [
|
||||
chapter.sourceOrder,
|
||||
manga.chapters.totalCount,
|
||||
chapter.pageCount,
|
||||
manga.id,
|
||||
settings.skipDupChapters,
|
||||
isDownloadAheadEnabled,
|
||||
]);
|
||||
|
||||
const prevChapter = useCallback(() => {
|
||||
if (chapter.sourceOrder > 1) {
|
||||
|
||||
@@ -14,9 +14,12 @@ import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { TextSetting } from '@/components/settings/TextSetting.tsx';
|
||||
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
import { MetadataServerSettingKeys, MetadataServerSettings, ServerSettings } from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { DownloadAheadSetting } from '@/components/settings/downloads/DownloadAheadSetting.tsx';
|
||||
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
|
||||
type DownloadSettingsType = Pick<
|
||||
ServerSettings,
|
||||
@@ -49,12 +52,28 @@ export const DownloadSettings = () => {
|
||||
const { data } = requestManager.useGetServerSettings();
|
||||
const downloadSettings = data ? extractDownloadSettings(data.settings) : undefined;
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
const { metadata, settings: metadataSettings } = useMetadataServerSettings();
|
||||
|
||||
const updateSetting = <Setting extends keyof DownloadSettingsType>(
|
||||
setting: Setting,
|
||||
value: DownloadSettingsType[Setting],
|
||||
) => {
|
||||
mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
|
||||
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
|
||||
);
|
||||
};
|
||||
|
||||
const updateMetadataSetting = <Setting extends MetadataServerSettingKeys>(
|
||||
setting: Setting,
|
||||
value: MetadataServerSettings[Setting],
|
||||
) => {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestUpdateServerMetadata(convertToGqlMeta(metadata) ?? [], [[setting, value]]).catch(() =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -75,6 +94,46 @@ export const DownloadSettings = () => {
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="download-settings-auto-download">
|
||||
Delete chapters
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItem>
|
||||
<ListItemText primary="Delete chapter after manually marking it as read" />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={metadataSettings.deleteChaptersManuallyMarkedRead}
|
||||
onChange={(e) =>
|
||||
updateMetadataSetting('deleteChaptersManuallyMarkedRead', e.target.checked)
|
||||
}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Delete finished chapters while reading" />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={metadataSettings.deleteChaptersAutoMarkedRead}
|
||||
onChange={(e) => updateMetadataSetting('deleteChaptersAutoMarkedRead', e.target.checked)}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Allow deleting bookmarked chapters" />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={metadataSettings.deleteChaptersWithBookmark}
|
||||
onChange={(e) => updateMetadataSetting('deleteChaptersWithBookmark', e.target.checked)}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</List>
|
||||
<List
|
||||
subheader={
|
||||
<ListSubheader component="div" id="download-settings-auto-download">
|
||||
|
||||
Reference in New Issue
Block a user