From ad0f0726517197000e2fcd90d81254a384897e0b Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Tue, 5 Dec 2023 22:20:13 +0100 Subject: [PATCH] Feature/automatic chapter deletion more options (#484) * Add missing translations keys * [Codegen] Update to latest server schema changes * [VersionMapping] Require server version "r1431" for preview * Rename "deleteChaptersAutoMarkedRead" metadata setting * Add more options to auto delete chapters while reading Makes it possible to delete the last to fifth to last read chapter instead of only being able to delete the last read chapter --- src/components/manga/ChapterCard.tsx | 2 +- .../DeleteChaptersWhileReadingSetting.tsx | 78 +++++++++++++++++++ src/i18n/locale/en.json | 22 ++++++ src/lib/graphql/Fragments.ts | 1 - src/lib/graphql/generated/apollo-helpers.ts | 33 +++++++- src/lib/graphql/generated/graphql.ts | 53 ++++++++++--- src/lib/graphql/mutations/ChapterMutation.ts | 3 +- src/lib/graphql/queries/ServerInfoQuery.ts | 8 +- src/lib/requests/RequestManager.ts | 10 ++- src/screens/Reader.tsx | 30 +++++-- src/screens/settings/DownloadSettings.tsx | 23 +++--- src/typings.ts | 2 +- src/util/metadata.ts | 3 + src/util/metadataServerSettings.ts | 2 +- versionToServerVersionMapping.json | 2 +- 15 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 src/components/settings/downloads/DeleteChaptersWhileReadingSetting.tsx diff --git a/src/components/manga/ChapterCard.tsx b/src/components/manga/ChapterCard.tsx index 3847f690..a4a403b7 100644 --- a/src/components/manga/ChapterCard.tsx +++ b/src/components/manga/ChapterCard.tsx @@ -102,7 +102,7 @@ export const ChapterCard: React.FC = (props: IProps) => { requestManager.updateChapter(chapter.id, { [key]: value, lastPageRead: key === 'isRead' ? 0 : undefined, - deleteChapter: !!chapterIdsToDelete.length, + chapterIdToDelete: chapterIdsToDelete[0], }); }; diff --git a/src/components/settings/downloads/DeleteChaptersWhileReadingSetting.tsx b/src/components/settings/downloads/DeleteChaptersWhileReadingSetting.tsx new file mode 100644 index 00000000..740bb342 --- /dev/null +++ b/src/components/settings/downloads/DeleteChaptersWhileReadingSetting.tsx @@ -0,0 +1,78 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { useTranslation } from 'react-i18next'; +import { + SelectSetting, + SelectSettingValue, + SelectSettingValueDisplayInfo, +} from '@/components/settings/SelectSetting.tsx'; + +const CHAPTERS_TO_DELETE = [0, 1, 2, 3, 4, 5] as const; +const CHAPTERS_TO_DELETE_TO_TRANSLATION_KEY: { + [flavor in (typeof CHAPTERS_TO_DELETE)[number]]: SelectSettingValueDisplayInfo; +} = { + 0: { + text: 'global.label.disabled', + }, + 1: { + text: 'download.settings.delete_chapters.while_reading.option.label.first', + }, + 2: { + text: 'download.settings.delete_chapters.while_reading.option.label.second', + }, + 3: { + text: 'download.settings.delete_chapters.while_reading.option.label.third', + }, + 4: { + text: 'download.settings.delete_chapters.while_reading.option.label.fourth', + }, + 5: { + text: 'download.settings.delete_chapters.while_reading.option.label.fifth', + }, +}; +const CHAPTERS_TO_DELETE_SELECT_VALUES: SelectSettingValue<(typeof CHAPTERS_TO_DELETE)[number]>[] = + CHAPTERS_TO_DELETE.map((chapterToDelete) => [ + chapterToDelete, + CHAPTERS_TO_DELETE_TO_TRANSLATION_KEY[chapterToDelete], + ]); + +const getNormalizedChapterToDelete = (chapterToDelete?: number | boolean) => { + if (!chapterToDelete) { + return undefined; + } + + const isMigrationVersion0 = typeof chapterToDelete === 'boolean'; + if (isMigrationVersion0) { + return Number(chapterToDelete); + } + + return chapterToDelete; +}; + +export const DeleteChaptersWhileReadingSetting = ({ + chapterToDelete, + handleChange, +}: { + chapterToDelete?: number; + handleChange: (chapterToDelete: number) => void; +}) => { + const { t } = useTranslation(); + + const normalizedChapterToDelete = getNormalizedChapterToDelete(chapterToDelete); + + return ( + + ); +}; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 65b94b62..a95c788f 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -157,6 +157,27 @@ }, "title": "Auto-download" }, + "delete_chapters": { + "label": { + "allow_deletion_of_bookmarked": "Allow deleting bookmarked chapters", + "manually_marked_as_read": "Delete chapter after manually marking it as read" + }, + "title": "Delete chapters", + "while_reading": { + "label": { + "title": "Delete finished chapters while reading" + }, + "option": { + "label": { + "fifth": "Fifth to last read chapter", + "first": "Last read chapter", + "fourth": "Fourth to last read chapter", + "second": "Second to last read chapter", + "third": "Third to last read chapter" + } + } + } + }, "download_ahead": { "label": { "description": "How many chapters should get downloaded when marking a chapter as read while reading.", @@ -286,6 +307,7 @@ "label": { "browse": "Browse", "client": "Client", + "disabled": "Disabled", "discord": "Discord", "display": "Display", "filter": "Filter", diff --git a/src/lib/graphql/Fragments.ts b/src/lib/graphql/Fragments.ts index 79d959b1..9bc436aa 100644 --- a/src/lib/graphql/Fragments.ts +++ b/src/lib/graphql/Fragments.ts @@ -416,7 +416,6 @@ export const WEBUI_UPDATE_INFO = gql` fragment WEBUI_UPDATE_INFO on WebUIUpdateInfo { channel tag - updateAvailable } `; diff --git a/src/lib/graphql/generated/apollo-helpers.ts b/src/lib/graphql/generated/apollo-helpers.ts index 6cd7aa9d..a2b028c6 100644 --- a/src/lib/graphql/generated/apollo-helpers.ts +++ b/src/lib/graphql/generated/apollo-helpers.ts @@ -10,6 +10,11 @@ export type AboutServerPayloadFieldPolicy = { revision?: FieldPolicy | FieldReadFunction, version?: FieldPolicy | FieldReadFunction }; +export type AboutWebUIKeySpecifier = ('channel' | 'tag' | AboutWebUIKeySpecifier)[]; +export type AboutWebUIFieldPolicy = { + channel?: FieldPolicy | FieldReadFunction, + tag?: FieldPolicy | FieldReadFunction +}; export type BackupRestoreStatusKeySpecifier = ('mangaProgress' | 'state' | 'totalManga' | BackupRestoreStatusKeySpecifier)[]; export type BackupRestoreStatusFieldPolicy = { mangaProgress?: FieldPolicy | FieldReadFunction, @@ -105,6 +110,13 @@ export type CheckForServerUpdatesPayloadFieldPolicy = { tag?: FieldPolicy | FieldReadFunction, url?: FieldPolicy | FieldReadFunction }; +export type ClearCachedImagesPayloadKeySpecifier = ('cachedPages' | 'cachedThumbnails' | 'clientMutationId' | 'downloadedThumbnails' | ClearCachedImagesPayloadKeySpecifier)[]; +export type ClearCachedImagesPayloadFieldPolicy = { + cachedPages?: FieldPolicy | FieldReadFunction, + cachedThumbnails?: FieldPolicy | FieldReadFunction, + clientMutationId?: FieldPolicy | FieldReadFunction, + downloadedThumbnails?: FieldPolicy | FieldReadFunction +}; export type ClearDownloaderPayloadKeySpecifier = ('clientMutationId' | 'downloadStatus' | ClearDownloaderPayloadKeySpecifier)[]; export type ClearDownloaderPayloadFieldPolicy = { clientMutationId?: FieldPolicy | FieldReadFunction, @@ -390,8 +402,9 @@ export type MultiSelectListPreferenceFieldPolicy = { title?: FieldPolicy | FieldReadFunction, visible?: FieldPolicy | FieldReadFunction }; -export type MutationKeySpecifier = ('clearDownloader' | 'createBackup' | 'createCategory' | 'deleteCategory' | 'deleteCategoryMeta' | 'deleteChapterMeta' | 'deleteDownloadedChapter' | 'deleteDownloadedChapters' | 'deleteGlobalMeta' | 'deleteMangaMeta' | 'dequeueChapterDownload' | 'dequeueChapterDownloads' | 'downloadAhead' | 'enqueueChapterDownload' | 'enqueueChapterDownloads' | 'fetchChapterPages' | 'fetchChapters' | 'fetchExtensions' | 'fetchManga' | 'fetchSourceManga' | 'installExternalExtension' | 'reorderChapterDownload' | 'resetSettings' | 'restoreBackup' | 'setCategoryMeta' | 'setChapterMeta' | 'setGlobalMeta' | 'setMangaMeta' | 'setSettings' | 'startDownloader' | 'stopDownloader' | 'updateCategories' | 'updateCategory' | 'updateCategoryManga' | 'updateCategoryOrder' | 'updateChapter' | 'updateChapters' | 'updateExtension' | 'updateExtensions' | 'updateLibraryManga' | 'updateManga' | 'updateMangaCategories' | 'updateMangas' | 'updateMangasCategories' | 'updateSourcePreference' | 'updateStop' | 'updateWebUI' | MutationKeySpecifier)[]; +export type MutationKeySpecifier = ('clearCachedImages' | 'clearDownloader' | 'createBackup' | 'createCategory' | 'deleteCategory' | 'deleteCategoryMeta' | 'deleteChapterMeta' | 'deleteDownloadedChapter' | 'deleteDownloadedChapters' | 'deleteGlobalMeta' | 'deleteMangaMeta' | 'dequeueChapterDownload' | 'dequeueChapterDownloads' | 'downloadAhead' | 'enqueueChapterDownload' | 'enqueueChapterDownloads' | 'fetchChapterPages' | 'fetchChapters' | 'fetchExtensions' | 'fetchManga' | 'fetchSourceManga' | 'installExternalExtension' | 'reorderChapterDownload' | 'resetSettings' | 'restoreBackup' | 'setCategoryMeta' | 'setChapterMeta' | 'setGlobalMeta' | 'setMangaMeta' | 'setSettings' | 'startDownloader' | 'stopDownloader' | 'updateCategories' | 'updateCategory' | 'updateCategoryManga' | 'updateCategoryOrder' | 'updateChapter' | 'updateChapters' | 'updateExtension' | 'updateExtensions' | 'updateLibraryManga' | 'updateManga' | 'updateMangaCategories' | 'updateMangas' | 'updateMangasCategories' | 'updateSourcePreference' | 'updateStop' | 'updateWebUI' | MutationKeySpecifier)[]; export type MutationFieldPolicy = { + clearCachedImages?: FieldPolicy | FieldReadFunction, clearDownloader?: FieldPolicy | FieldReadFunction, createBackup?: FieldPolicy | FieldReadFunction, createCategory?: FieldPolicy | FieldReadFunction, @@ -814,6 +827,12 @@ export type ValidateBackupSourceFieldPolicy = { id?: FieldPolicy | FieldReadFunction, name?: FieldPolicy | FieldReadFunction }; +export type WebUIUpdateCheckKeySpecifier = ('channel' | 'tag' | 'updateAvailable' | WebUIUpdateCheckKeySpecifier)[]; +export type WebUIUpdateCheckFieldPolicy = { + channel?: FieldPolicy | FieldReadFunction, + tag?: FieldPolicy | FieldReadFunction, + updateAvailable?: FieldPolicy | FieldReadFunction +}; export type WebUIUpdateInfoKeySpecifier = ('channel' | 'tag' | 'updateAvailable' | WebUIUpdateInfoKeySpecifier)[]; export type WebUIUpdateInfoFieldPolicy = { channel?: FieldPolicy | FieldReadFunction, @@ -836,6 +855,10 @@ export type StrictTypedTypePolicies = { keyFields?: false | AboutServerPayloadKeySpecifier | (() => undefined | AboutServerPayloadKeySpecifier), fields?: AboutServerPayloadFieldPolicy, }, + AboutWebUI?: Omit & { + keyFields?: false | AboutWebUIKeySpecifier | (() => undefined | AboutWebUIKeySpecifier), + fields?: AboutWebUIFieldPolicy, + }, BackupRestoreStatus?: Omit & { keyFields?: false | BackupRestoreStatusKeySpecifier | (() => undefined | BackupRestoreStatusKeySpecifier), fields?: BackupRestoreStatusFieldPolicy, @@ -884,6 +907,10 @@ export type StrictTypedTypePolicies = { keyFields?: false | CheckForServerUpdatesPayloadKeySpecifier | (() => undefined | CheckForServerUpdatesPayloadKeySpecifier), fields?: CheckForServerUpdatesPayloadFieldPolicy, }, + ClearCachedImagesPayload?: Omit & { + keyFields?: false | ClearCachedImagesPayloadKeySpecifier | (() => undefined | ClearCachedImagesPayloadKeySpecifier), + fields?: ClearCachedImagesPayloadFieldPolicy, + }, ClearDownloaderPayload?: Omit & { keyFields?: false | ClearDownloaderPayloadKeySpecifier | (() => undefined | ClearDownloaderPayloadKeySpecifier), fields?: ClearDownloaderPayloadFieldPolicy, @@ -1248,6 +1275,10 @@ export type StrictTypedTypePolicies = { keyFields?: false | ValidateBackupSourceKeySpecifier | (() => undefined | ValidateBackupSourceKeySpecifier), fields?: ValidateBackupSourceFieldPolicy, }, + WebUIUpdateCheck?: Omit & { + keyFields?: false | WebUIUpdateCheckKeySpecifier | (() => undefined | WebUIUpdateCheckKeySpecifier), + fields?: WebUIUpdateCheckFieldPolicy, + }, WebUIUpdateInfo?: Omit & { keyFields?: false | WebUIUpdateInfoKeySpecifier | (() => undefined | WebUIUpdateInfoKeySpecifier), fields?: WebUIUpdateInfoFieldPolicy, diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index 27a5cd9a..8d137f30 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -28,6 +28,12 @@ export type AboutServerPayload = { version: Scalars['String']['output']; }; +export type AboutWebUi = { + __typename?: 'AboutWebUI'; + channel: Scalars['String']['output']; + tag: Scalars['String']['output']; +}; + export enum BackupRestoreState { Failure = 'FAILURE', Idle = 'IDLE', @@ -244,6 +250,21 @@ export type CheckForServerUpdatesPayload = { url: Scalars['String']['output']; }; +export type ClearCachedImagesInput = { + cachedPages?: InputMaybe; + cachedThumbnails?: InputMaybe; + clientMutationId?: InputMaybe; + downloadedThumbnails?: InputMaybe; +}; + +export type ClearCachedImagesPayload = { + __typename?: 'ClearCachedImagesPayload'; + cachedPages?: Maybe; + cachedThumbnails?: Maybe; + clientMutationId?: Maybe; + downloadedThumbnails?: Maybe; +}; + export type ClearDownloaderInput = { clientMutationId?: InputMaybe; }; @@ -907,6 +928,7 @@ export type MultiSelectListPreference = { export type Mutation = { __typename?: 'Mutation'; + clearCachedImages: ClearCachedImagesPayload; clearDownloader: ClearDownloaderPayload; createBackup: CreateBackupPayload; createCategory: CreateCategoryPayload; @@ -957,6 +979,11 @@ export type Mutation = { }; +export type MutationClearCachedImagesArgs = { + input: ClearCachedImagesInput; +}; + + export type MutationClearDownloaderArgs = { input: ClearDownloaderInput; }; @@ -1294,13 +1321,13 @@ export type Preference = CheckBoxPreference | EditTextPreference | ListPreferenc export type Query = { __typename?: 'Query'; aboutServer: AboutServerPayload; - aboutWebUI: WebUiUpdateInfo; + aboutWebUI: AboutWebUi; categories: CategoryNodeList; category: CategoryType; chapter: ChapterType; chapters: ChapterNodeList; checkForServerUpdates: Array; - checkForWebUIUpdate: WebUiUpdateInfo; + checkForWebUIUpdate: WebUiUpdateCheck; downloadStatus: DownloadStatus; extension: ExtensionType; extensions: ExtensionNodeList; @@ -2055,6 +2082,13 @@ export enum WebUiInterface { Electron = 'ELECTRON' } +export type WebUiUpdateCheck = { + __typename?: 'WebUIUpdateCheck'; + channel: Scalars['String']['output']; + tag: Scalars['String']['output']; + updateAvailable: Scalars['Boolean']['output']; +}; + export type WebUiUpdateInfo = { __typename?: 'WebUIUpdateInfo'; channel: Scalars['String']['output']; @@ -2109,9 +2143,9 @@ export type PartialUpdaterStatusFragment = { __typename?: 'UpdateStatus', isRunn export type FullUpdaterStatusFragment = { __typename?: 'UpdateStatus', isRunning: boolean, completeJobs: { __typename?: 'UpdateStatusType', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', unreadCount: number, downloadCount: number, artist?: string | null, author?: string | null, chaptersLastFetchedAt?: any | null, description?: string | null, genre: Array, id: number, inLibrary: boolean, inLibraryAt: any, initialized: boolean, lastFetchedAt?: any | null, realUrl?: string | null, status: MangaStatus, thumbnailUrl?: string | null, title: string, url: string, 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 }> } }, failedJobs: { __typename?: 'UpdateStatusType', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null }> } }, pendingJobs: { __typename?: 'UpdateStatusType', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null }> } }, runningJobs: { __typename?: 'UpdateStatusType', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null }> } }, skippedJobs: { __typename?: 'UpdateStatusType', mangas: { __typename?: 'MangaNodeList', totalCount: number, nodes: Array<{ __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null }> } }, updatingCategories: { __typename?: 'UpdateStatusCategoryType', categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, name: string, includeInUpdate: IncludeInUpdate }> } }, skippedCategories: { __typename?: 'UpdateStatusCategoryType', categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, name: string, includeInUpdate: IncludeInUpdate }> } } }; -export type WebuiUpdateInfoFragment = { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean }; +export type WebuiUpdateInfoFragment = { __typename?: 'WebUIUpdateInfo', channel: string, tag: string }; -export type WebuiUpdateStatusFragment = { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } }; +export type WebuiUpdateStatusFragment = { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string } }; export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string }; @@ -2218,6 +2252,7 @@ export type UpdateChapterMutationVariables = Exact<{ getRead: Scalars['Boolean']['input']; getLastPageRead: Scalars['Boolean']['input']; id: Scalars['Int']['input']; + chapterIdToDelete: Scalars['Int']['input']; deleteChapter: Scalars['Boolean']['input']; mangaId: Scalars['Int']['input']; downloadAhead: Scalars['Boolean']['input']; @@ -2414,7 +2449,7 @@ export type UpdateWebuiMutationVariables = Exact<{ }>; -export type UpdateWebuiMutation = { __typename?: 'Mutation', updateWebUI: { __typename?: 'WebUIUpdatePayload', clientMutationId?: string | null, updateStatus: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } } } }; +export type UpdateWebuiMutation = { __typename?: 'Mutation', updateWebUI: { __typename?: 'WebUIUpdatePayload', clientMutationId?: string | null, updateStatus: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string } } } }; export type ResetServerSettingsMutationVariables = Exact<{ input: ResetSettingsInput; @@ -2604,7 +2639,7 @@ export type GetMangasQuery = { __typename?: 'Query', mangas: { __typename?: 'Man export type GetAboutQueryVariables = Exact<{ [key: string]: never; }>; -export type GetAboutQuery = { __typename?: 'Query', aboutServer: { __typename?: 'AboutServerPayload', buildTime: any, buildType: string, discord: string, github: string, name: string, revision: string, version: string }, aboutWebUI: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } }; +export type GetAboutQuery = { __typename?: 'Query', aboutServer: { __typename?: 'AboutServerPayload', buildTime: any, buildType: string, discord: string, github: string, name: string, revision: string, version: string }, aboutWebUI: { __typename?: 'AboutWebUI', channel: string, tag: string } }; export type CheckForServerUpdatesQueryVariables = Exact<{ [key: string]: never; }>; @@ -2614,12 +2649,12 @@ export type CheckForServerUpdatesQuery = { __typename?: 'Query', checkForServerU export type CheckForWebuiUpdateQueryVariables = Exact<{ [key: string]: never; }>; -export type CheckForWebuiUpdateQuery = { __typename?: 'Query', checkForWebUIUpdate: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } }; +export type CheckForWebuiUpdateQuery = { __typename?: 'Query', checkForWebUIUpdate: { __typename?: 'WebUIUpdateCheck', channel: string, tag: string, updateAvailable: boolean } }; export type GetWebuiUpdateStatusQueryVariables = Exact<{ [key: string]: never; }>; -export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateStatus: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } } }; +export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateStatus: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string } } }; export type GetServerSettingsQueryVariables = Exact<{ [key: string]: never; }>; @@ -2656,7 +2691,7 @@ export type DownloadStatusSubscription = { __typename?: 'Subscription', download export type WebuiUpdateSubscriptionVariables = Exact<{ [key: string]: never; }>; -export type WebuiUpdateSubscription = { __typename?: 'Subscription', webUIUpdateStatusChange: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } } }; +export type WebuiUpdateSubscription = { __typename?: 'Subscription', webUIUpdateStatusChange: { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string } } }; export type UpdaterSubscriptionVariables = Exact<{ [key: string]: never; }>; diff --git a/src/lib/graphql/mutations/ChapterMutation.ts b/src/lib/graphql/mutations/ChapterMutation.ts index 06c59314..e4a9fb77 100644 --- a/src/lib/graphql/mutations/ChapterMutation.ts +++ b/src/lib/graphql/mutations/ChapterMutation.ts @@ -88,6 +88,7 @@ export const UPDATE_CHAPTER = gql` $getRead: Boolean! $getLastPageRead: Boolean! $id: Int! + $chapterIdToDelete: Int! $deleteChapter: Boolean! $mangaId: Int! $downloadAhead: Boolean! @@ -109,7 +110,7 @@ export const UPDATE_CHAPTER = gql` } } } - deleteDownloadedChapter(input: { id: $id }) @include(if: $deleteChapter) { + deleteDownloadedChapter(input: { id: $chapterIdToDelete }) @include(if: $deleteChapter) { clientMutationId chapters { id diff --git a/src/lib/graphql/queries/ServerInfoQuery.ts b/src/lib/graphql/queries/ServerInfoQuery.ts index 1e84a0d2..b1d8b9d5 100644 --- a/src/lib/graphql/queries/ServerInfoQuery.ts +++ b/src/lib/graphql/queries/ServerInfoQuery.ts @@ -7,7 +7,7 @@ */ import gql from 'graphql-tag'; -import { WEBUI_UPDATE_INFO, WEBUI_UPDATE_STATUS } from '@/lib/graphql/Fragments'; +import { WEBUI_UPDATE_STATUS } from '@/lib/graphql/Fragments'; export const GET_ABOUT = gql` query GET_ABOUT { @@ -23,7 +23,6 @@ export const GET_ABOUT = gql` aboutWebUI { channel tag - updateAvailable } } `; @@ -39,10 +38,11 @@ export const CHECK_FOR_SERVER_UPDATES = gql` `; export const CHECK_FOR_WEBUI_UPDATE = gql` - ${WEBUI_UPDATE_INFO} query CHECK_FOR_WEBUI_UPDATE { checkForWebUIUpdate { - ...WEBUI_UPDATE_INFO + channel + tag + updateAvailable } } `; diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index 86efec84..9ebb9bcc 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -1498,10 +1498,13 @@ export class RequestManager { public updateChapter( id: number, - patch: UpdateChapterPatchInput & { deleteChapter?: boolean; downloadAheadMangaId?: number }, + patch: UpdateChapterPatchInput & { + chapterIdToDelete?: number; + downloadAheadMangaId?: number; + }, options?: MutationOptions, ): AbortableApolloMutationResponse { - const { deleteChapter, downloadAheadMangaId = -1, ...updatePatch } = patch; + const { chapterIdToDelete = -1, downloadAheadMangaId = -1, ...updatePatch } = patch; return this.doRequest( GQLMethod.MUTATION, @@ -1512,7 +1515,8 @@ export class RequestManager { getBookmarked: patch.isBookmarked != null, getRead: patch.isRead != null, getLastPageRead: patch.lastPageRead != null, - deleteChapter: !!deleteChapter, + chapterIdToDelete, + deleteChapter: chapterIdToDelete >= 0, mangaId: downloadAheadMangaId, downloadAhead: downloadAheadMangaId !== -1, }, diff --git a/src/screens/Reader.tsx b/src/screens/Reader.tsx index d6d801b4..77eea13e 100644 --- a/src/screens/Reader.tsx +++ b/src/screens/Reader.tsx @@ -169,6 +169,8 @@ export function Reader() { const [pageToScrollTo, setPageToScrollTo] = useState(undefined); const { setOverride, setTitle } = useContext(NavBarContext); const [retrievingNextChapter, setRetrievingNextChapter] = useState(false); + const { data: mangaChaptersData } = requestManager.useGetMangaChapters(mangaId, { nextFetchPolicy: 'standby' }); + const mangaChapters = mangaChaptersData?.chapters.nodes; const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings(); const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings)); @@ -176,11 +178,27 @@ export function Reader() { const { settings: metadataSettings } = useMetadataServerSettings(); const updateChapter = (patch: UpdateChapterPatchInput) => { - const shouldDeleteChapter = - !!patch.isRead && - metadataSettings.deleteChaptersAutoMarkedRead && - chapter.isDownloaded && - (!chapter.isBookmarked || metadataSettings.deleteChaptersWithBookmark); + const isAutoDeletionEnabled = !!patch.isRead && !!metadataSettings.deleteChaptersWhileReading; + + const getChapterIdToDelete = () => { + if (!isAutoDeletionEnabled || !mangaChapters) { + return -1; + } + + const chapterToDeleteSourceOrder = Number(chapterIndex) - (metadataSettings.deleteChaptersWhileReading - 1); + const chapterToDelete = mangaChapters.find( + (mangaChapter) => mangaChapter.sourceOrder === chapterToDeleteSourceOrder, + ); + + const shouldDeleteChapter = + chapterToDelete?.isDownloaded && + (!chapterToDelete?.isBookmarked || metadataSettings.deleteChaptersWithBookmark); + if (!shouldDeleteChapter) { + return -1; + } + + return chapterToDelete.id; + }; const shouldDownloadAhead = chapter.manga.inLibrary && !chapter.isRead && !!patch.isRead && isDownloadAheadEnabled; @@ -188,7 +206,7 @@ export function Reader() { requestManager .updateChapter(chapter.id, { ...patch, - deleteChapter: shouldDeleteChapter, + chapterIdToDelete: getChapterIdToDelete(), downloadAheadMangaId: shouldDownloadAhead ? chapter.manga.id : undefined, }) .response.catch(() => {}); diff --git a/src/screens/settings/DownloadSettings.tsx b/src/screens/settings/DownloadSettings.tsx index 14105c04..0d03be8e 100644 --- a/src/screens/settings/DownloadSettings.tsx +++ b/src/screens/settings/DownloadSettings.tsx @@ -20,6 +20,7 @@ import { DownloadAheadSetting } from '@/components/settings/downloads/DownloadAh import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts'; import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts'; import { makeToast } from '@/components/util/Toast.tsx'; +import { DeleteChaptersWhileReadingSetting } from '@/components/settings/downloads/DeleteChaptersWhileReadingSetting.tsx'; type DownloadSettingsType = Pick< ServerSettings, @@ -97,12 +98,12 @@ export const DownloadSettings = () => { - Delete chapters + {t('download.settings.delete_chapters.title')} } > - + { /> + + updateMetadataSetting('deleteChaptersWhileReading', chapterToDelete) + } + /> - - - updateMetadataSetting('deleteChaptersAutoMarkedRead', e.target.checked)} - /> - - - - + { diff --git a/src/util/metadataServerSettings.ts b/src/util/metadataServerSettings.ts index a22d8b2f..8cf80621 100644 --- a/src/util/metadataServerSettings.ts +++ b/src/util/metadataServerSettings.ts @@ -12,7 +12,7 @@ import { convertFromGqlMeta, getMetadataFrom } from '@/util/metadata'; export const getDefaultSettings = (): MetadataServerSettings => ({ deleteChaptersManuallyMarkedRead: false, - deleteChaptersAutoMarkedRead: false, + deleteChaptersWhileReading: 0, deleteChaptersWithBookmark: false, }); diff --git a/versionToServerVersionMapping.json b/versionToServerVersionMapping.json index acad1a08..44e05c95 100644 --- a/versionToServerVersionMapping.json +++ b/versionToServerVersionMapping.json @@ -1,6 +1,6 @@ [ { "uiVersion": "PREVIEW", - "serverVersion": "r1427" + "serverVersion": "r1431" } ]