From 199b62341fb8c54ca17924b77ae56e21d61c2c33 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 15 Jan 2024 00:32:39 +0100 Subject: [PATCH] Feature/add retry button for failed image requests (#550) * Improve img loading placeholder Prevent showing a loading placeholder in case the image is already cached. * Add failed img load retry button --- src/components/MangaCard.tsx | 2 +- src/components/reader/Page.tsx | 2 - .../reader/pager/DoublePagedPager.tsx | 6 +- .../reader/pager/HorizontalPager.tsx | 7 +- src/components/reader/pager/PagedPager.tsx | 5 +- src/components/util/SpinnerImage.tsx | 96 ++++++++++++------- 6 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/components/MangaCard.tsx b/src/components/MangaCard.tsx index 459dab1f..6c4e91e1 100644 --- a/src/components/MangaCard.tsx +++ b/src/components/MangaCard.tsx @@ -113,7 +113,7 @@ export const MangaCard = (props: IProps) => { handleSelection?.(id, !selected); }} to={mangaLinkTo} - style={gridLayout === GridLayout.Comfortable ? { textDecoration: 'none' } : {}} + style={{ textDecoration: 'none' }} > { ...imgStyle, height: '100vh', width: '70vw', - padding: '50px calc(50% - 20px)', backgroundColor: '#525252', - marginBottom: 10, }} imgStyle={imgStyle} /> diff --git a/src/components/reader/pager/DoublePagedPager.tsx b/src/components/reader/pager/DoublePagedPager.tsx index 5436e9a2..dc640a7c 100644 --- a/src/components/reader/pager/DoublePagedPager.tsx +++ b/src/components/reader/pager/DoublePagedPager.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useEffect, useRef, useState } from 'react'; +import { MouseEvent, useEffect, useRef, useState } from 'react'; import { Box } from '@mui/material'; import { IReaderProps } from '@/typings'; import { Page } from '@/components/reader/Page'; @@ -134,11 +134,9 @@ export function DoublePagedPager(props: IReaderProps) { useEffect(() => { document.addEventListener('keydown', keyboardControl); - selfRef.current?.addEventListener('click', clickControl); return () => { document.removeEventListener('keydown', keyboardControl); - selfRef.current?.removeEventListener('click', clickControl); }; }, [selfRef, curPage, settings.readerType, prevChapter, nextChapter, pagesLoadState, pagesToSpreadState]); @@ -157,7 +155,7 @@ export function DoublePagedPager(props: IReaderProps) { }, [settings.offsetFirstPage]); return ( - + {pages.map((page) => ( = window.innerWidth * 0.85) { goRight(); } else if (e.clientX <= window.innerWidth * 0.15) { @@ -129,11 +129,9 @@ export function HorizontalPager(props: IReaderProps) { if (settings.loadNextOnEnding) { document.addEventListener('scroll', handleLoadNextonEnding); } - selfRef.current?.addEventListener('mousedown', clickControl); return () => { document.removeEventListener('scroll', handleLoadNextonEnding); - selfRef.current?.removeEventListener('mousedown', clickControl); }; }, [selfRef, curPage, prevChapter, nextChapter]); @@ -174,6 +172,7 @@ export function HorizontalPager(props: IReaderProps) { overflowX: 'visible', userSelect: 'none', }} + onClick={clickControl} > {pages.map((page) => ( { document.addEventListener('keydown', keyboardControl); - selfRef.current?.addEventListener('click', clickControl); return () => { document.removeEventListener('keydown', keyboardControl); - selfRef.current?.removeEventListener('click', clickControl); }; }, [selfRef, curPage, settings.readerType, prevChapter, nextChapter]); @@ -107,6 +105,7 @@ export function PagedPager(props: IReaderProps) { width: '100%', height: '100vh', }} + onClick={clickControl} > {}} src={pages[curPage].src} settings={settings} /> diff --git a/src/components/util/SpinnerImage.tsx b/src/components/util/SpinnerImage.tsx index 5269c388..6d2dc055 100644 --- a/src/components/util/SpinnerImage.tsx +++ b/src/components/util/SpinnerImage.tsx @@ -6,10 +6,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import React, { useEffect, useState, CSSProperties } from 'react'; +import React, { useState, CSSProperties, useEffect } from 'react'; import CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/material/Box'; -import { Theme, SxProps } from '@mui/material'; +import { Theme, SxProps, Stack, Button } from '@mui/material'; + +import BrokenImageIcon from '@mui/icons-material/BrokenImage'; +import RefreshIcon from '@mui/icons-material/Refresh'; interface IProps { src: string; @@ -25,41 +28,66 @@ interface IProps { export function SpinnerImage(props: IProps) { const { src, alt, onImageLoad, imgRef, spinnerStyle, imgStyle } = props; - const [imageSrc, setImagsrc] = useState(''); + + const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0); + const [isLoading, setIsLoading] = useState(undefined); + const [hasError, setHasError] = useState(false); + + const updateImageState = (loading: boolean, error: boolean = false) => { + setIsLoading(loading); + setHasError(error); + + if (!loading && !error) { + onImageLoad?.(); + } + }; useEffect(() => { - const img = new Image(); - img.src = src; + // only activate the loading state in case the image has not been cached yet. + // otherwise, the loading placeholder will always be visible before the actual image is shown, which looks like flickering + const timeout = setTimeout(() => setIsLoading((prevState) => (prevState === undefined ? true : prevState)), 1); + return () => clearTimeout(timeout); + }, []); - img.onload = () => { - setImagsrc(src); - onImageLoad?.(); - }; - - img.onerror = () => { - // Setting to an actual image so CSS styling works consistently - setImagsrc('/notFound.svg'); - }; - - return () => { - img.onload = null; - img.onerror = null; - }; - }, [src]); - - if (imageSrc.length === 0) { - return ( - - - - ); - } - - if (imageSrc === 'Not Found') { - return ; - } - - return {alt}; + return ( + <> + {(isLoading || hasError) && ( + + + {isLoading && } + {hasError && ( + <> + + + + )} + + + )} + {alt} updateImageState(false)} + onError={() => updateImageState(false, true)} + draggable={false} + /> + + ); } SpinnerImage.defaultProps = {