From 8bdf035ca727b236aa2cb875a00ca89ae7704f18 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 2 Mar 2024 15:28:58 +0100 Subject: [PATCH] Feature/add option to remove manga from categories when removing from library (#633) * Optionally remove manga categories when removing them from library * Use "Mangas::removeFromLibrary" * Handle uncatched promises --- .../chapter/ChapterActionMenuItems.tsx | 3 ++- src/components/chapter/ChapterList.tsx | 3 ++- src/components/manga/MangaDetails.tsx | 6 +----- .../navbar/action/CategorySelect.tsx | 3 ++- src/i18n/locale/en.json | 8 ++++++++ src/lib/data/Mangas.ts | 8 +++++++- src/lib/graphql/generated/graphql.ts | 4 +++- src/lib/graphql/mutations/MangaMutation.ts | 20 ++++++++++++++++++- src/lib/requests/RequestManager.ts | 8 ++++++-- src/screens/settings/LibrarySettings.tsx | 13 ++++++++++++ src/typings.ts | 1 + src/util/metadataServerSettings.ts | 1 + 12 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/components/chapter/ChapterActionMenuItems.tsx b/src/components/chapter/ChapterActionMenuItems.tsx index 48ea9e83..2f71f3ba 100644 --- a/src/components/chapter/ChapterActionMenuItems.tsx +++ b/src/components/chapter/ChapterActionMenuItems.tsx @@ -30,6 +30,7 @@ import { MenuItem } from '@/components/menu/MenuItem.tsx'; import { IChapterWithMeta } from '@/components/chapter/ChapterList.tsx'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts'; +import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; type BaseProps = { onClose: () => void }; @@ -116,7 +117,7 @@ export const ChapterActionMenuItems = ({ Chapters.performAction(actualAction, chapter ? [chapter.id] : ChaptersWithMeta.getIds(chaptersWithMeta), { chapters: getChapters(), wasManuallyMarkedAsRead: true, - }); + }).catch(defaultPromiseErrorHandler('ChapterActionMenuItems::performAction')); onClose(); }; diff --git a/src/components/chapter/ChapterList.tsx b/src/components/chapter/ChapterList.tsx index 9ec6acdb..fd3676c9 100644 --- a/src/components/chapter/ChapterList.tsx +++ b/src/components/chapter/ChapterList.tsx @@ -29,6 +29,7 @@ import { SelectableCollectionSelectAll } from '@/components/collection/Selectabl import { Chapters } from '@/lib/data/Chapters.ts'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; import { ChapterActionMenuItems } from '@/components/chapter/ChapterActionMenuItems.tsx'; +import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; const ChapterListHeader = styled(Stack)(({ theme }) => ({ margin: 8, @@ -167,7 +168,7 @@ export const ChapterList: React.FC = ({ manga, isRefreshing }) => { onClick={() => Chapters.download( ChaptersWithMeta.getIds(ChaptersWithMeta.getNonDownloaded(chaptersWithMeta)), - ) + ).catch(defaultPromiseErrorHandler('ChapterList::download')) } > diff --git a/src/components/manga/MangaDetails.tsx b/src/components/manga/MangaDetails.tsx index 11f89575..59774163 100644 --- a/src/components/manga/MangaDetails.tsx +++ b/src/components/manga/MangaDetails.tsx @@ -229,11 +229,7 @@ export const MangaDetails: React.FC = ({ manga }) => { }; const removeFromLibrary = () => { - Promise.all([requestManager.updateManga(manga.id, { updateManga: { inLibrary: false } }).response]) - .then(() => makeToast(t('library.info.label.removed_from_library'), 'success')) - .catch(() => { - makeToast(t('library.error.label.remove_from_library'), 'error'); - }); + Mangas.removeFromLibrary([manga.id]).catch(defaultPromiseErrorHandler('MangaDetails::removeFromLibrary')); }; return ( diff --git a/src/components/navbar/action/CategorySelect.tsx b/src/components/navbar/action/CategorySelect.tsx index 8cbbc643..76bdb665 100644 --- a/src/components/navbar/action/CategorySelect.tsx +++ b/src/components/navbar/action/CategorySelect.tsx @@ -25,6 +25,7 @@ import { CheckboxInput } from '@/components/atoms/CheckboxInput.tsx'; import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts'; import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts'; import { makeToast } from '@/components/util/Toast.tsx'; +import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; type BaseProps = { open: boolean; @@ -155,7 +156,7 @@ export function CategorySelect(props: Props) { addToCategories, removeFromCategories, }, - }); + }).catch(defaultPromiseErrorHandler('CategorySelect::handleOk')); }; return ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 956a7411..517c595c 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -492,6 +492,14 @@ } } }, + "remove_from_library": { + "remove_from_categories": { + "label": { + "description": "Remove manga from categories when removing them from the library", + "title": "Forget manga categories" + } + } + }, "search": { "ignore_filters": { "label": { diff --git a/src/lib/data/Mangas.ts b/src/lib/data/Mangas.ts index 9433f6a1..424a7b05 100644 --- a/src/lib/data/Mangas.ts +++ b/src/lib/data/Mangas.ts @@ -18,6 +18,7 @@ import { } from '@/lib/graphql/generated/graphql.ts'; import { Chapters } from '@/lib/data/Chapters.ts'; import { makeToast } from '@/components/util/Toast.tsx'; +import { getMetadataServerSettings } from '@/util/metadataServerSettings.ts'; export type MangaAction = | 'download' @@ -208,10 +209,15 @@ export class Mangas { } static async removeFromLibrary(mangaIds: number[]): Promise { + const { removeMangaFromCategories } = await getMetadataServerSettings(); return Mangas.executeAction( 'remove_from_library', mangaIds.length, - () => requestManager.updateMangas(mangaIds, { inLibrary: false }).response, + () => + requestManager.updateMangas(mangaIds, { + updateMangas: { inLibrary: false }, + updateMangasCategories: removeMangaFromCategories ? { clearCategories: true } : undefined, + }).response, ); } diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index 2872422c..25262be7 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -2842,10 +2842,12 @@ export type UpdateMangaMutation = { __typename?: 'Mutation', updateManga: { __ty export type UpdateMangasMutationVariables = Exact<{ input: UpdateMangasInput; + updateCategoryInput: UpdateMangasCategoriesInput; + updateCategories: Scalars['Boolean']['input']; }>; -export type UpdateMangasMutation = { __typename?: 'Mutation', updateMangas: { __typename?: 'UpdateMangasPayload', clientMutationId?: string | null, mangas: Array<{ __typename?: 'MangaType', id: number, inLibrary: boolean, inLibraryAt: any, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> } }> } }; +export type UpdateMangasMutation = { __typename?: 'Mutation', updateMangas: { __typename?: 'UpdateMangasPayload', clientMutationId?: string | null, mangas: Array<{ __typename?: 'MangaType', id: number, inLibrary: boolean, inLibraryAt: any, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> } }> }, updateMangasCategories?: { __typename?: 'UpdateMangasCategoriesPayload', mangas: Array<{ __typename?: 'MangaType', id: number, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> } }> } }; export type UpdateMangaCategoriesMutationVariables = Exact<{ input: UpdateMangaCategoriesInput; diff --git a/src/lib/graphql/mutations/MangaMutation.ts b/src/lib/graphql/mutations/MangaMutation.ts index 0fb6daeb..7942b053 100644 --- a/src/lib/graphql/mutations/MangaMutation.ts +++ b/src/lib/graphql/mutations/MangaMutation.ts @@ -130,7 +130,11 @@ export const UPDATE_MANGA = gql` `; export const UPDATE_MANGAS = gql` - mutation UPDATE_MANGAS($input: UpdateMangasInput!) { + mutation UPDATE_MANGAS( + $input: UpdateMangasInput! + $updateCategoryInput: UpdateMangasCategoriesInput! + $updateCategories: Boolean! + ) { updateMangas(input: $input) { clientMutationId mangas { @@ -148,6 +152,20 @@ export const UPDATE_MANGAS = gql` } } } + updateMangasCategories(input: $updateCategoryInput) @include(if: $updateCategories) { + mangas { + id + categories { + nodes { + id + mangas { + totalCount + } + } + totalCount + } + } + } } `; diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index f1f7588f..38ec0931 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -1584,13 +1584,17 @@ export class RequestManager { public updateMangas( ids: number[], - patch: UpdateMangaPatchInput, + patch: { updateMangas: UpdateMangaPatchInput; updateMangasCategories?: UpdateMangaCategoriesPatchInput }, options?: MutationOptions, ): AbortableApolloMutationResponse { const result = this.doRequest( GQLMethod.MUTATION, UPDATE_MANGAS, - { input: { ids, patch } }, + { + input: { ids, patch: patch.updateMangas }, + updateCategoryInput: { ids, patch: patch.updateMangasCategories ?? {} }, + updateCategories: !!patch.updateMangasCategories, + }, options, ); diff --git a/src/screens/settings/LibrarySettings.tsx b/src/screens/settings/LibrarySettings.tsx index 7eaef979..10e12c13 100644 --- a/src/screens/settings/LibrarySettings.tsx +++ b/src/screens/settings/LibrarySettings.tsx @@ -97,6 +97,19 @@ export function LibrarySettings() { onChange={(e) => setSettingValue('showAddToLibraryCategorySelectDialog', e.target.checked)} /> + + + setSettingValue('removeMangaFromCategories', e.target.checked)} + /> + ({ // library showAddToLibraryCategorySelectDialog: true, ignoreFilters: false, + removeMangaFromCategories: false, }); const getMetadataServerSettingsWithDefaultFallback = (