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": {
"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": {
"button": {
"selected": "Remove selected from the library"

View File

@@ -8,6 +8,7 @@
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useCategorySelect } from '@/components/navbar/action/useCategorySelect.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/components/util/Toast.tsx';
@@ -23,6 +24,7 @@ export const useManageMangaLibraryState = (
confirmRemoval: boolean = false,
) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [isInLibrary, setIsInLibrary] = useState(manga.inLibrary);
@@ -79,6 +81,7 @@ export const useManageMangaLibraryState = (
});
const updateLibraryState = useCallback(() => {
const update = async () => {
if (isInLibrary) {
removeFromLibrary().catch(
defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState::removeFromLibrary'),
@@ -95,10 +98,49 @@ export const useManageMangaLibraryState = (
makeToast(t('category.error.label.request_failure'), 'error');
categories
.refetch()
.catch(defaultPromiseErrorHandler('MangaDetails::handleAddToLibraryClick: refetch categories'));
.catch(
defaultPromiseErrorHandler(
'useManageMangaLibraryState::updateLibraryState: refetch categories',
),
);
return;
}
let duplicatedLibraryMangas:
| Awaited<ReturnType<typeof Mangas.getDuplicateLibraryMangas>['response']>
| undefined;
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'),
),
});
}
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!)));
@@ -106,6 +148,9 @@ export const useManageMangaLibraryState = (
}
openCategorySelect(true);
};
update().catch(defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState'));
}, [isInLibrary, removeFromLibrary, addToLibrary, areSettingsLoading, categories.loading]);
return {

View File

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

View File

@@ -233,6 +233,13 @@ export class Mangas {
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(
mangaIds: number[],
state: Pick<ChapterConditionInput, 'isRead' | 'isDownloaded' | 'isBookmarked'>,

View File

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