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
This commit is contained in:
schroda
2024-03-02 15:28:58 +01:00
committed by GitHub
parent c5a5c4b8af
commit 8bdf035ca7
12 changed files with 65 additions and 13 deletions

View File

@@ -30,6 +30,7 @@ import { MenuItem } from '@/components/menu/MenuItem.tsx';
import { IChapterWithMeta } from '@/components/chapter/ChapterList.tsx'; import { IChapterWithMeta } from '@/components/chapter/ChapterList.tsx';
import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts';
import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts'; import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
type BaseProps = { onClose: () => void }; type BaseProps = { onClose: () => void };
@@ -116,7 +117,7 @@ export const ChapterActionMenuItems = ({
Chapters.performAction(actualAction, chapter ? [chapter.id] : ChaptersWithMeta.getIds(chaptersWithMeta), { Chapters.performAction(actualAction, chapter ? [chapter.id] : ChaptersWithMeta.getIds(chaptersWithMeta), {
chapters: getChapters(), chapters: getChapters(),
wasManuallyMarkedAsRead: true, wasManuallyMarkedAsRead: true,
}); }).catch(defaultPromiseErrorHandler('ChapterActionMenuItems::performAction'));
onClose(); onClose();
}; };

View File

@@ -29,6 +29,7 @@ import { SelectableCollectionSelectAll } from '@/components/collection/Selectabl
import { Chapters } from '@/lib/data/Chapters.ts'; import { Chapters } from '@/lib/data/Chapters.ts';
import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts';
import { ChapterActionMenuItems } from '@/components/chapter/ChapterActionMenuItems.tsx'; import { ChapterActionMenuItems } from '@/components/chapter/ChapterActionMenuItems.tsx';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
const ChapterListHeader = styled(Stack)(({ theme }) => ({ const ChapterListHeader = styled(Stack)(({ theme }) => ({
margin: 8, margin: 8,
@@ -167,7 +168,7 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
onClick={() => onClick={() =>
Chapters.download( Chapters.download(
ChaptersWithMeta.getIds(ChaptersWithMeta.getNonDownloaded(chaptersWithMeta)), ChaptersWithMeta.getIds(ChaptersWithMeta.getNonDownloaded(chaptersWithMeta)),
) ).catch(defaultPromiseErrorHandler('ChapterList::download'))
} }
> >
<DownloadIcon /> <DownloadIcon />

View File

@@ -229,11 +229,7 @@ export const MangaDetails: React.FC<IProps> = ({ manga }) => {
}; };
const removeFromLibrary = () => { const removeFromLibrary = () => {
Promise.all([requestManager.updateManga(manga.id, { updateManga: { inLibrary: false } }).response]) Mangas.removeFromLibrary([manga.id]).catch(defaultPromiseErrorHandler('MangaDetails::removeFromLibrary'));
.then(() => makeToast(t('library.info.label.removed_from_library'), 'success'))
.catch(() => {
makeToast(t('library.error.label.remove_from_library'), 'error');
});
}; };
return ( return (

View File

@@ -25,6 +25,7 @@ import { CheckboxInput } from '@/components/atoms/CheckboxInput.tsx';
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts'; import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts'; import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts';
import { makeToast } from '@/components/util/Toast.tsx'; import { makeToast } from '@/components/util/Toast.tsx';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
type BaseProps = { type BaseProps = {
open: boolean; open: boolean;
@@ -155,7 +156,7 @@ export function CategorySelect(props: Props) {
addToCategories, addToCategories,
removeFromCategories, removeFromCategories,
}, },
}); }).catch(defaultPromiseErrorHandler('CategorySelect::handleOk'));
}; };
return ( return (

View File

@@ -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": { "search": {
"ignore_filters": { "ignore_filters": {
"label": { "label": {

View File

@@ -18,6 +18,7 @@ import {
} from '@/lib/graphql/generated/graphql.ts'; } from '@/lib/graphql/generated/graphql.ts';
import { Chapters } from '@/lib/data/Chapters.ts'; import { Chapters } from '@/lib/data/Chapters.ts';
import { makeToast } from '@/components/util/Toast.tsx'; import { makeToast } from '@/components/util/Toast.tsx';
import { getMetadataServerSettings } from '@/util/metadataServerSettings.ts';
export type MangaAction = export type MangaAction =
| 'download' | 'download'
@@ -208,10 +209,15 @@ export class Mangas {
} }
static async removeFromLibrary(mangaIds: number[]): Promise<void> { static async removeFromLibrary(mangaIds: number[]): Promise<void> {
const { removeMangaFromCategories } = await getMetadataServerSettings();
return Mangas.executeAction( return Mangas.executeAction(
'remove_from_library', 'remove_from_library',
mangaIds.length, mangaIds.length,
() => requestManager.updateMangas(mangaIds, { inLibrary: false }).response, () =>
requestManager.updateMangas(mangaIds, {
updateMangas: { inLibrary: false },
updateMangasCategories: removeMangaFromCategories ? { clearCategories: true } : undefined,
}).response,
); );
} }

View File

@@ -2842,10 +2842,12 @@ export type UpdateMangaMutation = { __typename?: 'Mutation', updateManga: { __ty
export type UpdateMangasMutationVariables = Exact<{ export type UpdateMangasMutationVariables = Exact<{
input: UpdateMangasInput; 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<{ export type UpdateMangaCategoriesMutationVariables = Exact<{
input: UpdateMangaCategoriesInput; input: UpdateMangaCategoriesInput;

View File

@@ -130,7 +130,11 @@ export const UPDATE_MANGA = gql`
`; `;
export const UPDATE_MANGAS = gql` export const UPDATE_MANGAS = gql`
mutation UPDATE_MANGAS($input: UpdateMangasInput!) { mutation UPDATE_MANGAS(
$input: UpdateMangasInput!
$updateCategoryInput: UpdateMangasCategoriesInput!
$updateCategories: Boolean!
) {
updateMangas(input: $input) { updateMangas(input: $input) {
clientMutationId clientMutationId
mangas { mangas {
@@ -148,6 +152,20 @@ export const UPDATE_MANGAS = gql`
} }
} }
} }
updateMangasCategories(input: $updateCategoryInput) @include(if: $updateCategories) {
mangas {
id
categories {
nodes {
id
mangas {
totalCount
}
}
totalCount
}
}
}
} }
`; `;

View File

@@ -1584,13 +1584,17 @@ export class RequestManager {
public updateMangas( public updateMangas(
ids: number[], ids: number[],
patch: UpdateMangaPatchInput, patch: { updateMangas: UpdateMangaPatchInput; updateMangasCategories?: UpdateMangaCategoriesPatchInput },
options?: MutationOptions<UpdateMangasMutation, UpdateMangasMutationVariables>, options?: MutationOptions<UpdateMangasMutation, UpdateMangasMutationVariables>,
): AbortableApolloMutationResponse<UpdateMangasMutation> { ): AbortableApolloMutationResponse<UpdateMangasMutation> {
const result = this.doRequest<UpdateMangasMutation, UpdateMangasMutationVariables>( const result = this.doRequest<UpdateMangasMutation, UpdateMangasMutationVariables>(
GQLMethod.MUTATION, GQLMethod.MUTATION,
UPDATE_MANGAS, UPDATE_MANGAS,
{ input: { ids, patch } }, {
input: { ids, patch: patch.updateMangas },
updateCategoryInput: { ids, patch: patch.updateMangasCategories ?? {} },
updateCategories: !!patch.updateMangasCategories,
},
options, options,
); );

View File

@@ -97,6 +97,19 @@ export function LibrarySettings() {
onChange={(e) => setSettingValue('showAddToLibraryCategorySelectDialog', e.target.checked)} onChange={(e) => setSettingValue('showAddToLibraryCategorySelectDialog', e.target.checked)}
/> />
</ListItem> </ListItem>
<ListItem>
<ListItemText
primary={t('library.settings.general.remove_from_library.remove_from_categories.label.title')}
secondary={t(
'library.settings.general.remove_from_library.remove_from_categories.label.description',
)}
/>
<Switch
edge="end"
checked={settings.removeMangaFromCategories}
onChange={(e) => setSettingValue('removeMangaFromCategories', e.target.checked)}
/>
</ListItem>
</List> </List>
<GlobalUpdateSettings /> <GlobalUpdateSettings />
<List <List

View File

@@ -228,6 +228,7 @@ export type MetadataServerSettings = {
// library // library
showAddToLibraryCategorySelectDialog: boolean; showAddToLibraryCategorySelectDialog: boolean;
ignoreFilters: boolean; ignoreFilters: boolean;
removeMangaFromCategories: boolean;
}; };
export interface ISearchSettings { export interface ISearchSettings {

View File

@@ -20,6 +20,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
// library // library
showAddToLibraryCategorySelectDialog: true, showAddToLibraryCategorySelectDialog: true,
ignoreFilters: false, ignoreFilters: false,
removeMangaFromCategories: false,
}); });
const getMetadataServerSettingsWithDefaultFallback = ( const getMetadataServerSettingsWithDefaultFallback = (