Check for duplicated library manga on add to library

This commit is contained in:
schroda
2024-05-04 14:43:41 +02:00
parent c29173681f
commit f66051d398
5 changed files with 115 additions and 26 deletions

View File

@@ -589,6 +589,16 @@
} }
}, },
"library": { "library": {
"add": {
"dialog": {
"duplicate": {
"label": {
"failure": "Could not check for duplicated manga in your library.\n\nError: {{error}}",
"info": "You have an entry in your library with the same name."
}
}
}
},
"remove": { "remove": {
"button": { "button": {
"selected": "Remove selected from the library" "selected": "Remove selected from the library"

View File

@@ -8,6 +8,7 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx'; import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/components/util/Toast.tsx'; import { makeToast } from '@/components/util/Toast.tsx';
@@ -23,6 +24,7 @@ export const useManageMangaLibraryState = (
confirmRemoval: boolean = false, confirmRemoval: boolean = false,
) => { ) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const [isInLibrary, setIsInLibrary] = useState(manga.inLibrary); const [isInLibrary, setIsInLibrary] = useState(manga.inLibrary);
@@ -79,33 +81,76 @@ export const useManageMangaLibraryState = (
}); });
const updateLibraryState = useCallback(() => { const updateLibraryState = useCallback(() => {
if (isInLibrary) { const update = async () => {
removeFromLibrary().catch( if (isInLibrary) {
defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState::removeFromLibrary'), removeFromLibrary().catch(
); defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState::removeFromLibrary'),
return; );
} return;
}
if (areSettingsLoading || categories.loading) { if (areSettingsLoading || categories.loading) {
makeToast(t('global.label.load_in_progress'), 'info'); makeToast(t('global.label.load_in_progress'), 'info');
return; return;
} }
if (categories.error) { if (categories.error) {
makeToast(t('category.error.label.request_failure'), 'error'); makeToast(t('category.error.label.request_failure'), 'error');
categories categories
.refetch() .refetch()
.catch(defaultPromiseErrorHandler('MangaDetails::handleAddToLibraryClick: refetch categories')); .catch(
return; defaultPromiseErrorHandler(
} 'useManageMangaLibraryState::updateLibraryState: refetch categories',
),
);
return;
}
const showCategorySelectDialog = showAddToLibraryCategorySelectDialog && !!userCreatedCategories.length; let duplicatedLibraryMangas:
if (!showCategorySelectDialog) { | Awaited<ReturnType<typeof Mangas.getDuplicateLibraryMangas>['response']>
addToLibrary(true, Categories.getIds(Categories.getDefaults(userCreatedCategories!))); | undefined;
return; try {
} duplicatedLibraryMangas = await Mangas.getDuplicateLibraryMangas(manga.title).response;
} catch (e: any) {
await awaitConfirmation({
title: t('global.error.label.failed_to_load_data'),
message: t('manga.action.library.add.dialog.duplicate.label.failure', {
error: e.message,
}),
actions: {
extra: { show: true, title: t('global.button.retry') },
confirm: { title: t('global.button.add') },
},
onExtra: () =>
update().catch(
defaultPromiseErrorHandler('useManageMangaLibraryState::update: retry duplicate check'),
),
});
}
openCategorySelect(true); const doDuplicatesExist = duplicatedLibraryMangas?.data.mangas.totalCount;
if (doDuplicatesExist) {
await awaitConfirmation({
title: t('global.label.are_you_sure'),
message: t('manga.action.library.add.dialog.duplicate.label.info'),
actions: {
extra: { show: true, title: t('migrate.dialog.action.button.show_entry') },
confirm: { title: t('global.button.add') },
},
onExtra: () => navigate(`/manga/${duplicatedLibraryMangas!.data.mangas.nodes[0].id}`),
});
}
const showCategorySelectDialog = showAddToLibraryCategorySelectDialog && !!userCreatedCategories.length;
if (!showCategorySelectDialog) {
addToLibrary(true, Categories.getIds(Categories.getDefaults(userCreatedCategories!)));
return;
}
openCategorySelect(true);
};
update().catch(defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState'));
}, [isInLibrary, removeFromLibrary, addToLibrary, areSettingsLoading, categories.loading]); }, [isInLibrary, removeFromLibrary, addToLibrary, areSettingsLoading, categories.loading]);
return { return {

View File

@@ -12,6 +12,7 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
type Action = { type Action = {
show?: boolean; show?: boolean;
@@ -19,6 +20,7 @@ type Action = {
}; };
type Actions = { type Actions = {
extra?: Action;
cancel?: Action; cancel?: Action;
confirm?: Action; confirm?: Action;
}; };
@@ -27,18 +29,24 @@ export const ConfirmDialog = ({
title, title,
message, message,
actions: passedActions, actions: passedActions,
onExtra,
onCancel, onCancel,
onConfirm, onConfirm,
}: { }: {
title: string; title: string;
message: string; message: string;
actions?: Actions; actions?: Actions;
onExtra?: () => void;
onCancel: () => void; onCancel: () => void;
onConfirm: () => void; onConfirm: () => void;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const actions = { const actions = {
extra: {
show: passedActions?.extra?.show ?? false,
title: passedActions?.extra?.title ?? '',
},
cancel: { cancel: {
show: passedActions?.cancel?.show ?? true, show: passedActions?.cancel?.show ?? true,
title: passedActions?.cancel?.title ?? t('global.button.cancel'), title: passedActions?.cancel?.title ?? t('global.button.cancel'),
@@ -52,10 +60,25 @@ export const ConfirmDialog = ({
return ( return (
<Dialog open> <Dialog open>
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogContent>{message}</DialogContent> <DialogContent
sx={{
whiteSpace: 'pre-line',
}}
>
{message}
</DialogContent>
<DialogActions> <DialogActions>
{actions.cancel.show && <Button onClick={onCancel}>{actions.cancel.title}</Button>} <Stack
{actions.confirm.show && <Button onClick={onConfirm}>{actions.confirm.title}</Button>} sx={{ width: '100%' }}
direction="row"
justifyContent={actions.extra.show ? 'space-between' : 'end'}
>
{actions.extra.show && <Button onClick={onExtra}>{actions.extra.title}</Button>}
<Stack direction="row">
{actions.cancel.show && <Button onClick={onCancel}>{actions.cancel.title}</Button>}
{actions.confirm.show && <Button onClick={onConfirm}>{actions.confirm.title}</Button>}
</Stack>
</Stack>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@@ -233,6 +233,13 @@ export class Mangas {
return requestManager.getValidImgUrlFor(thumbnailUrl); return requestManager.getValidImgUrlFor(thumbnailUrl);
} }
static getDuplicateLibraryMangas(title: string): ReturnType<typeof requestManager.getMangas> {
return requestManager.getMangas({
condition: { inLibrary: true },
filter: { title: { likeInsensitive: title } },
});
}
static async getChapterIdsWithState( static async getChapterIdsWithState(
mangaIds: number[], mangaIds: number[],
state: Pick<ChapterConditionInput, 'isRead' | 'isDownloaded' | 'isBookmarked'>, state: Pick<ChapterConditionInput, 'isRead' | 'isDownloaded' | 'isBookmarked'>,

View File

@@ -36,6 +36,10 @@ export const awaitConfirmation = async (
<ThemeProvider theme={getCurrentTheme()}> <ThemeProvider theme={getCurrentTheme()}>
<ConfirmDialog <ConfirmDialog
{...dialogProps} {...dialogProps}
onExtra={() => {
handleConfirmation(false);
dialogProps.onExtra?.();
}}
onCancel={() => handleConfirmation(false)} onCancel={() => handleConfirmation(false)}
onConfirm={() => handleConfirmation(true)} onConfirm={() => handleConfirmation(true)}
/> />