From 6c01c222c367b72dfffc68afcea263c8b75aff4a Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:02:20 +0200 Subject: [PATCH] Add retry button for failed downloads --- public/locales/en.json | 3 ++- src/lib/data/ChaptersWithMeta.ts | 6 +++++- src/screens/DownloadQueue.tsx | 30 +++++++++++++++++++++++++++++- src/screens/Updates.tsx | 25 ++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 03e0b0bc..3934c1ed 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -157,7 +157,8 @@ "error": { "label": { "failed_delete_all": "Could not remove all downloads from the queue", - "failed_to_remove": "Could not remove the download from the queue." + "failed_to_remove": "Could not remove the download from the queue.", + "failed_to_retry": "Could not retry failed download." } }, "label": { diff --git a/src/lib/data/ChaptersWithMeta.ts b/src/lib/data/ChaptersWithMeta.ts index e74381a8..18217dda 100644 --- a/src/lib/data/ChaptersWithMeta.ts +++ b/src/lib/data/ChaptersWithMeta.ts @@ -13,6 +13,7 @@ import { ChapterReadInfo, Chapters, } from '@/lib/data/Chapters.ts'; +import { DownloadState } from '@/lib/graphql/generated/graphql.ts'; export type ChapterWithMetaType< Chapter extends ChapterDownloadInfo & ChapterReadInfo & ChapterBookmarkInfo = ChapterDownloadInfo & @@ -50,7 +51,10 @@ export class ChaptersWithMeta { } static getDownloadable(chapters: Chapter[]): Chapter[] { - return chapters.filter(({ chapter, downloadChapter }) => !Chapters.isDownloaded(chapter) && !downloadChapter); + return chapters.filter( + ({ chapter, downloadChapter }) => + !Chapters.isDownloaded(chapter) && downloadChapter?.state !== DownloadState.Error, + ); } static getBookmarked(chapters: Chapter[]): Chapter[] { diff --git a/src/screens/DownloadQueue.tsx b/src/screens/DownloadQueue.tsx index 38623142..c68b24a5 100644 --- a/src/screens/DownloadQueue.tsx +++ b/src/screens/DownloadQueue.tsx @@ -24,6 +24,7 @@ import { useTranslation } from 'react-i18next'; import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'; import { Virtuoso } from 'react-virtuoso'; import CardContent from '@mui/material/CardContent'; +import Refresh from '@mui/icons-material/Refresh'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { StrictModeDroppable } from '@/lib/StrictModeDroppable'; import { makeToast } from '@/components/util/Toast'; @@ -33,6 +34,7 @@ import { NavBarContext } from '@/components/context/NavbarContext.tsx'; import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; import { ChapterDownloadStatus, ChapterIdInfo } from '@/lib/data/Chapters.ts'; +import { DownloadState } from '@/lib/graphql/generated/graphql.ts'; const HeightPreservingItem = ({ children, ...props }: BoxProps) => ( // the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements @@ -45,10 +47,12 @@ const DownloadChapterItem = ({ provided, item, handleDelete, + handleRetry, }: { provided: DraggableProvided; item: ChapterDownloadStatus; handleDelete: (chapter: ChapterIdInfo) => void; + handleRetry: (chapter: ChapterIdInfo) => void; }) => { const { t } = useTranslation(); @@ -80,6 +84,20 @@ const DownloadChapterItem = ({ + {item.state === DownloadState.Error && ( + + { + e.preventDefault(); + e.stopPropagation(); + handleRetry(item.chapter); + }} + size="large" + > + + + + )} { @@ -193,6 +211,14 @@ export const DownloadQueue: React.FC = () => { categoryReorder(queue, result.source.index, result.destination.index); }; + const handleRetry = async (chapter: ChapterIdInfo) => { + try { + await requestManager.addChapterToDownloadQueue(chapter.id).response; + } catch (e) { + makeToast(t('download.queue.error.label.failed_to_remove'), 'error'); + } + }; + const handleDelete = async (chapter: ChapterIdInfo) => { const isRunning = status === 'STARTED'; @@ -210,7 +236,7 @@ export const DownloadQueue: React.FC = () => { requestManager.deleteDownloadedChapter(chapter.id).response, ]); } catch (e) { - makeToast(t('download.queue.error.label.failed_to_remove'), 'error'); + makeToast(t('download.queue.error.label.failed_to_retry'), 'error'); } if (!isRunning) { @@ -248,6 +274,7 @@ export const DownloadQueue: React.FC = () => { provided={provided} item={queue[rubric.source.index]} handleDelete={handleDelete} + handleRetry={handleRetry} /> )} > @@ -271,6 +298,7 @@ export const DownloadQueue: React.FC = () => { provided={draggableProvided} item={item} handleDelete={handleDelete} + handleRetry={handleRetry} /> )} diff --git a/src/screens/Updates.tsx b/src/screens/Updates.tsx index c83665da..e55acbf6 100644 --- a/src/screens/Updates.tsx +++ b/src/screens/Updates.tsx @@ -18,11 +18,12 @@ import Typography from '@mui/material/Typography'; import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import Refresh from '@mui/icons-material/Refresh'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder'; import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx'; import { DownloadStateIndicator } from '@/components/molecules/DownloadStateIndicator'; -import { ChapterType } from '@/lib/graphql/generated/graphql.ts'; +import { ChapterType, DownloadState } from '@/lib/graphql/generated/graphql.ts'; import { NavBarContext } from '@/components/context/NavbarContext.tsx'; import { UpdateChecker } from '@/components/library/UpdateChecker.tsx'; import { StyledGroupedVirtuoso } from '@/components/virtuoso/StyledGroupedVirtuoso.tsx'; @@ -102,6 +103,14 @@ export const Updates: React.FC = () => { return queue.find((q) => sourceOrder === q.chapter.sourceOrder && mangaId === q.manga.id); }; + const handleRetry = async (chapter: ChapterIdInfo) => { + try { + await requestManager.addChapterToDownloadQueue(chapter.id).response; + } catch (e) { + makeToast(t('download.queue.error.label.failed_to_remove'), 'error'); + } + }; + const downloadChapter = (chapter: ChapterIdInfo) => { requestManager .addChapterToDownloadQueue(chapter.id) @@ -228,6 +237,20 @@ export const Updates: React.FC = () => { {download && } + {download?.state === DownloadState.Error && ( + + { + e.preventDefault(); + e.stopPropagation(); + handleRetry(download.chapter); + }} + size="large" + > + + + + )} {download == null && !chapter.isDownloaded && (