diff --git a/public/locales/en.json b/public/locales/en.json index 320113b0..8a5a32f9 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -461,6 +461,7 @@ "library": { "action": { "label": { + "stop_update": "Stop global update", "update_category": "Update category", "update_library": "Update library" } @@ -470,7 +471,8 @@ "add_to_library": "Could not add manga to library!", "empty": "Your library is empty", "no_matches": "No manga matches this filter", - "remove_from_library": "Could not remove manga from library!" + "remove_from_library": "Could not remove manga from library!", + "stop_global_update": "Could not stop global update" } }, "info": { diff --git a/src/components/library/UpdateChecker.tsx b/src/components/library/UpdateChecker.tsx index 258866f3..c7c6e84f 100644 --- a/src/components/library/UpdateChecker.tsx +++ b/src/components/library/UpdateChecker.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import IconButton from '@mui/material/IconButton'; import RefreshIcon from '@mui/icons-material/Refresh'; import { useTranslation } from 'react-i18next'; @@ -14,6 +14,8 @@ import Tooltip from '@mui/material/Tooltip'; import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; +import ClearIcon from '@mui/icons-material/Clear'; +import Stack from '@mui/material/Stack'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/components/util/Toast'; import { UpdaterSubscription } from '@/lib/graphql/generated/graphql.ts'; @@ -21,6 +23,7 @@ import { Progress } from '@/components/util/Progress'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; import { dateTimeFormatter } from '@/util/date.ts'; import { TCategory } from '@/typings.ts'; +import { MediaQuery } from '@/lib/ui/MediaQuery.tsx'; const calcProgress = (status: UpdaterSubscription['updateStatusChanged'] | undefined) => { if (!status) { @@ -45,6 +48,9 @@ export function UpdateChecker({ handleFinishedUpdate?: () => void; }) { const { t } = useTranslation(); + const isTouchDevice = MediaQuery.useIsTouchDevice(); + + const [isHovered, setIsHovered] = useState(false); const { data: lastUpdateTimestampData, refetch: reFetchLastTimestamp } = requestManager.useGetLastGlobalUpdateTimestamp(); @@ -52,7 +58,7 @@ export function UpdateChecker({ const { data: updaterData } = requestManager.useGetGlobalUpdateSummary(); const status = updaterData?.updateStatus; - const loading = !!status?.isRunning; + const isRunning = !!status?.isRunning; const progress = useMemo( () => calcProgress(status), [ @@ -79,7 +85,7 @@ export function UpdateChecker({ reFetchLastTimestamp().catch(defaultPromiseErrorHandler('UpdateChecker::reFetchLastTimestamp')); }, [status?.isRunning]); - const onClick = async (category?: TCategory['id']) => { + const startUpdate = async (category?: TCategory['id']) => { try { lastRunningState = true; await requestManager.startGlobalUpdate(category !== undefined ? [category] : undefined).response; @@ -90,42 +96,75 @@ export function UpdateChecker({ } }; + const stopUpdate = async () => { + try { + await requestManager.resetGlobalUpdate(); + } catch (e) { + makeToast(t('library.error.label.stop_global_update'), 'error'); + } + }; + + const onClick = async (category?: TCategory['id']) => { + if (isRunning) { + stopUpdate(); + } else { + startUpdate(category); + } + }; + return ( - - - {(popupState) => ( - <> + + {(popupState) => ( + <> + onClick() })} - disabled={loading} + sx={{ position: 'relative' }} + {...(categoryId !== undefined && !isRunning + ? bindTrigger(popupState) + : { onClick: () => onClick() })} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} > - {loading ? : } + {!isRunning ? ( + + ) : ( + <> + + + + + + )} - - { - popupState.close(); - onClick(); - }} - > - {t('library.action.label.update_library')} - - { - popupState.close(); - onClick(categoryId); - }} - > - {t('library.action.label.update_category')} - - - - )} - - + + + { + popupState.close(); + onClick(); + }} + > + {t('library.action.label.update_library')} + + { + popupState.close(); + onClick(categoryId); + }} + > + {t('library.action.label.update_category')} + + + + )} + ); } diff --git a/src/components/util/Progress.tsx b/src/components/util/Progress.tsx index 05bc8894..e2d1d880 100644 --- a/src/components/util/Progress.tsx +++ b/src/components/util/Progress.tsx @@ -10,11 +10,13 @@ import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; import Typography from '@mui/material/Typography'; -export const Progress = ({ progress }: { progress: number }) => ( +export const Progress = ({ progress, showText = true }: { progress: number; showText?: boolean }) => ( - - {`${Math.round(progress)}%`} - + {showText && ( + + {`${Math.round(progress)}%`} + + )} );