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:
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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<IProps> = ({ manga, isRefreshing }) => {
|
||||
onClick={() =>
|
||||
Chapters.download(
|
||||
ChaptersWithMeta.getIds(ChaptersWithMeta.getNonDownloaded(chaptersWithMeta)),
|
||||
)
|
||||
).catch(defaultPromiseErrorHandler('ChapterList::download'))
|
||||
}
|
||||
>
|
||||
<DownloadIcon />
|
||||
|
||||
@@ -229,11 +229,7 @@ export const MangaDetails: React.FC<IProps> = ({ 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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<void> {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -1584,13 +1584,17 @@ export class RequestManager {
|
||||
|
||||
public updateMangas(
|
||||
ids: number[],
|
||||
patch: UpdateMangaPatchInput,
|
||||
patch: { updateMangas: UpdateMangaPatchInput; updateMangasCategories?: UpdateMangaCategoriesPatchInput },
|
||||
options?: MutationOptions<UpdateMangasMutation, UpdateMangasMutationVariables>,
|
||||
): AbortableApolloMutationResponse<UpdateMangasMutation> {
|
||||
const result = this.doRequest<UpdateMangasMutation, UpdateMangasMutationVariables>(
|
||||
GQLMethod.MUTATION,
|
||||
UPDATE_MANGAS,
|
||||
{ input: { ids, patch } },
|
||||
{
|
||||
input: { ids, patch: patch.updateMangas },
|
||||
updateCategoryInput: { ids, patch: patch.updateMangasCategories ?? {} },
|
||||
updateCategories: !!patch.updateMangasCategories,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
|
||||
@@ -97,6 +97,19 @@ export function LibrarySettings() {
|
||||
onChange={(e) => setSettingValue('showAddToLibraryCategorySelectDialog', e.target.checked)}
|
||||
/>
|
||||
</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>
|
||||
<GlobalUpdateSettings />
|
||||
<List
|
||||
|
||||
@@ -228,6 +228,7 @@ export type MetadataServerSettings = {
|
||||
// library
|
||||
showAddToLibraryCategorySelectDialog: boolean;
|
||||
ignoreFilters: boolean;
|
||||
removeMangaFromCategories: boolean;
|
||||
};
|
||||
|
||||
export interface ISearchSettings {
|
||||
|
||||
@@ -20,6 +20,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
|
||||
// library
|
||||
showAddToLibraryCategorySelectDialog: true,
|
||||
ignoreFilters: false,
|
||||
removeMangaFromCategories: false,
|
||||
});
|
||||
|
||||
const getMetadataServerSettingsWithDefaultFallback = (
|
||||
|
||||
Reference in New Issue
Block a user