From f66051d39824ab0a7992d4426ac7538d34b180bb Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 4 May 2024 14:43:41 +0200 Subject: [PATCH] Check for duplicated library manga on add to library --- public/locales/en.json | 10 ++ .../manga/useManageMangaLibraryState.tsx | 91 ++++++++++++++----- src/components/molecules/ConfirmDialog.tsx | 29 +++++- src/lib/data/Mangas.ts | 7 ++ src/lib/ui/AwaitableDialog.tsx | 4 + 5 files changed, 115 insertions(+), 26 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index dde40666..d4ac0b36 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -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" diff --git a/src/components/manga/useManageMangaLibraryState.tsx b/src/components/manga/useManageMangaLibraryState.tsx index 5b361448..d3fee46c 100644 --- a/src/components/manga/useManageMangaLibraryState.tsx +++ b/src/components/manga/useManageMangaLibraryState.tsx @@ -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,33 +81,76 @@ export const useManageMangaLibraryState = ( }); const updateLibraryState = useCallback(() => { - if (isInLibrary) { - removeFromLibrary().catch( - defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState::removeFromLibrary'), - ); - return; - } + const update = async () => { + if (isInLibrary) { + removeFromLibrary().catch( + defaultPromiseErrorHandler('useManageMangaLibraryState::updateLibraryState::removeFromLibrary'), + ); + return; + } - if (areSettingsLoading || categories.loading) { - makeToast(t('global.label.load_in_progress'), 'info'); - return; - } + if (areSettingsLoading || categories.loading) { + makeToast(t('global.label.load_in_progress'), 'info'); + return; + } - if (categories.error) { - makeToast(t('category.error.label.request_failure'), 'error'); - categories - .refetch() - .catch(defaultPromiseErrorHandler('MangaDetails::handleAddToLibraryClick: refetch categories')); - return; - } + if (categories.error) { + makeToast(t('category.error.label.request_failure'), 'error'); + categories + .refetch() + .catch( + defaultPromiseErrorHandler( + 'useManageMangaLibraryState::updateLibraryState: refetch categories', + ), + ); + return; + } - const showCategorySelectDialog = showAddToLibraryCategorySelectDialog && !!userCreatedCategories.length; - if (!showCategorySelectDialog) { - addToLibrary(true, Categories.getIds(Categories.getDefaults(userCreatedCategories!))); - return; - } + let duplicatedLibraryMangas: + | Awaited['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'), + ), + }); + } - 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]); return { diff --git a/src/components/molecules/ConfirmDialog.tsx b/src/components/molecules/ConfirmDialog.tsx index b0a1cb7a..140db2d7 100644 --- a/src/components/molecules/ConfirmDialog.tsx +++ b/src/components/molecules/ConfirmDialog.tsx @@ -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 ( {title} - {message} + + {message} + - {actions.cancel.show && } - {actions.confirm.show && } + + {actions.extra.show && } + + {actions.cancel.show && } + {actions.confirm.show && } + + ); diff --git a/src/lib/data/Mangas.ts b/src/lib/data/Mangas.ts index 766ae33d..1e47b9b8 100644 --- a/src/lib/data/Mangas.ts +++ b/src/lib/data/Mangas.ts @@ -233,6 +233,13 @@ export class Mangas { return requestManager.getValidImgUrlFor(thumbnailUrl); } + static getDuplicateLibraryMangas(title: string): ReturnType { + return requestManager.getMangas({ + condition: { inLibrary: true }, + filter: { title: { likeInsensitive: title } }, + }); + } + static async getChapterIdsWithState( mangaIds: number[], state: Pick, diff --git a/src/lib/ui/AwaitableDialog.tsx b/src/lib/ui/AwaitableDialog.tsx index 8be3eadd..a86e43e2 100644 --- a/src/lib/ui/AwaitableDialog.tsx +++ b/src/lib/ui/AwaitableDialog.tsx @@ -36,6 +36,10 @@ export const awaitConfirmation = async ( { + handleConfirmation(false); + dialogProps.onExtra?.(); + }} onCancel={() => handleConfirmation(false)} onConfirm={() => handleConfirmation(true)} />