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:
schroda
2023-11-19 21:17:35 +01:00
committed by GitHub
parent e26907e2ce
commit d12ac27b61
10 changed files with 240 additions and 31 deletions

View File

@@ -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) {

View File

@@ -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">