Check for duplicated library manga on add to library
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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'>,
|
||||||
|
|||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user