Check for duplicated library manga on add to library
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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'>,
|
||||
|
||||
@@ -36,6 +36,10 @@ export const awaitConfirmation = async (
|
||||
<ThemeProvider theme={getCurrentTheme()}>
|
||||
<ConfirmDialog
|
||||
{...dialogProps}
|
||||
onExtra={() => {
|
||||
handleConfirmation(false);
|
||||
dialogProps.onExtra?.();
|
||||
}}
|
||||
onCancel={() => handleConfirmation(false)}
|
||||
onConfirm={() => handleConfirmation(true)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user