Make remaining image requests abortable (#645)
This commit is contained in:
@@ -18,6 +18,7 @@ import { PartialExtension, TranslationKey } from '@/typings';
|
|||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
import { makeToast } from '@/components/util/Toast.tsx';
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
extension: PartialExtension;
|
extension: PartialExtension;
|
||||||
@@ -170,10 +171,17 @@ export function ExtensionCard(props: IProps) {
|
|||||||
height: 56,
|
height: 56,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
mr: 2,
|
mr: 2,
|
||||||
|
background: 'transparent',
|
||||||
}}
|
}}
|
||||||
alt={name}
|
alt={name}
|
||||||
|
>
|
||||||
|
<SpinnerImage
|
||||||
|
spinnerStyle={{ small: true }}
|
||||||
|
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%' }}
|
||||||
|
alt={name}
|
||||||
src={requestManager.getValidImgUrlFor(iconUrl)}
|
src={requestManager.getValidImgUrlFor(iconUrl)}
|
||||||
/>
|
/>
|
||||||
|
</Avatar>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Typography variant="h5" component="h2">
|
<Typography variant="h5" component="h2">
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -347,26 +347,26 @@ export const MangaCard = (props: MangaCardProps) => {
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
variant="rounded"
|
variant="rounded"
|
||||||
sx={
|
sx={{
|
||||||
inLibraryIndicator && inLibrary
|
|
||||||
? {
|
|
||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
marginRight: 2,
|
marginRight: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SpinnerImage
|
||||||
|
spinnerStyle={{ small: true }}
|
||||||
|
imgStyle={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
imageRendering: 'pixelated',
|
imageRendering: 'pixelated',
|
||||||
filter: 'brightness(0.4)',
|
filter: inLibraryIndicator && inLibrary ? 'brightness(0.4)' : undefined,
|
||||||
}
|
}}
|
||||||
: {
|
alt={manga.title}
|
||||||
width: 56,
|
|
||||||
height: 56,
|
|
||||||
flex: '0 0 auto',
|
|
||||||
marginRight: 2,
|
|
||||||
imageRendering: 'pixelated',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
src={thumbnailUrl}
|
src={thumbnailUrl}
|
||||||
/>
|
/>
|
||||||
|
</Avatar>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
import { GetMigratableSourcesQuery } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { translateExtensionLanguage } from '@/screens/util/Extensions.ts';
|
import { translateExtensionLanguage } from '@/screens/util/Extensions.ts';
|
||||||
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
|
|
||||||
export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & {
|
export type TMigratableSource = NonNullable<GetMigratableSourcesQuery['mangas']['nodes'][number]['source']> & {
|
||||||
mangaCount: number;
|
mangaCount: number;
|
||||||
@@ -40,10 +41,16 @@ export const MigrationCard = ({ id, name, lang, iconUrl, mangaCount }: TMigratab
|
|||||||
height: 56,
|
height: 56,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
mr: 2,
|
mr: 2,
|
||||||
|
background: 'transparent',
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<SpinnerImage
|
||||||
|
spinnerStyle={{ small: true }}
|
||||||
|
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%' }}
|
||||||
alt={name}
|
alt={name}
|
||||||
src={requestManager.getValidImgUrlFor(iconUrl)}
|
src={requestManager.getValidImgUrlFor(iconUrl)}
|
||||||
/>
|
/>
|
||||||
|
</Avatar>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Typography variant="h5" component="h2">
|
<Typography variant="h5" component="h2">
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { TPartialSource } from '@/typings';
|
|||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { translateExtensionLanguage } from '@/screens/util/Extensions';
|
import { translateExtensionLanguage } from '@/screens/util/Extensions';
|
||||||
import { SourceContentType } from '@/screens/SourceMangas';
|
import { SourceContentType } from '@/screens/SourceMangas';
|
||||||
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
|
|
||||||
const MobileWidthButtons = styled('div')(({ theme }) => ({
|
const MobileWidthButtons = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -88,9 +89,16 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
height: 56,
|
height: 56,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
mr: 2,
|
mr: 2,
|
||||||
|
background: 'transparent',
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<SpinnerImage
|
||||||
|
spinnerStyle={{ small: true }}
|
||||||
|
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%' }}
|
||||||
|
alt={name}
|
||||||
src={requestManager.getValidImgUrlFor(iconUrl)}
|
src={requestManager.getValidImgUrlFor(iconUrl)}
|
||||||
/>
|
/>
|
||||||
|
</Avatar>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef } from 'react';
|
import { CSSProperties, forwardRef } from 'react';
|
||||||
import { Box, SxProps, Theme } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { IReaderSettings } from '@/typings';
|
import { IReaderSettings } from '@/typings';
|
||||||
import { SpinnerImage } from '@/components/util/SpinnerImage';
|
import { SpinnerImage } from '@/components/util/SpinnerImage';
|
||||||
import { imageStyle } from '@/components/reader/Page';
|
import { imageStyle } from '@/components/reader/Page';
|
||||||
@@ -34,7 +34,7 @@ export const DoublePage = forwardRef((props: IProps, ref: any) => {
|
|||||||
maxWidth: settings.fitPageToWindow ? `calc(${baseImgStyle.maxWidth} * 0.5)` : baseImgStyle.maxWidth,
|
maxWidth: settings.fitPageToWindow ? `calc(${baseImgStyle.maxWidth} * 0.5)` : baseImgStyle.maxWidth,
|
||||||
};
|
};
|
||||||
|
|
||||||
const spinnerStyle: SxProps<Theme> = {
|
const spinnerStyle: CSSProperties = {
|
||||||
...imgStyle,
|
...imgStyle,
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
width: '50%',
|
width: '50%',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef } from 'react';
|
import { CSSProperties, forwardRef } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
@@ -16,20 +16,20 @@ import { SpinnerImage } from '@/components/util/SpinnerImage';
|
|||||||
export const isHorizontalReaderType = (readerType: ReaderType): boolean =>
|
export const isHorizontalReaderType = (readerType: ReaderType): boolean =>
|
||||||
['ContinuesHorizontalLTR', 'ContinuesHorizontalRTL'].includes(readerType);
|
['ContinuesHorizontalLTR', 'ContinuesHorizontalRTL'].includes(readerType);
|
||||||
|
|
||||||
export function imageStyle(settings: IReaderSettings): any {
|
export function imageStyle(settings: IReaderSettings): CSSProperties {
|
||||||
const isVertical = settings.readerType === 'ContinuesVertical';
|
const isVertical = settings.readerType === 'ContinuesVertical';
|
||||||
const isHorizontal = isHorizontalReaderType(settings.readerType);
|
const isHorizontal = isHorizontalReaderType(settings.readerType);
|
||||||
const baseStyling = {
|
const baseStyling: CSSProperties = {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
width: `${settings.readerWidth}%`,
|
width: `${settings.readerWidth}%`,
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
};
|
};
|
||||||
|
|
||||||
const continuesVerticalStyling = {
|
const continuesVerticalStyling: CSSProperties = {
|
||||||
marginBottom: '15px',
|
marginBottom: '15px',
|
||||||
};
|
};
|
||||||
|
|
||||||
const continuesHorizontalStyling = {
|
const continuesHorizontalStyling: CSSProperties = {
|
||||||
width: undefined,
|
width: undefined,
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
maxHeight: '100vh',
|
maxHeight: '100vh',
|
||||||
@@ -37,7 +37,7 @@ export function imageStyle(settings: IReaderSettings): any {
|
|||||||
marginRight: '7px',
|
marginRight: '7px',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fitToPageStyling = {
|
const fitToPageStyling: CSSProperties = {
|
||||||
width: undefined,
|
width: undefined,
|
||||||
height: undefined,
|
height: undefined,
|
||||||
minWidth: settings.scalePage ? 'calc(100vw - (100vw - 100%))' : undefined,
|
minWidth: settings.scalePage ? 'calc(100vw - (100vw - 100%))' : undefined,
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ interface IProps {
|
|||||||
src: string;
|
src: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
|
|
||||||
spinnerStyle?: SxProps<Theme>;
|
spinnerStyle?: SxProps<Theme> & { small?: boolean };
|
||||||
imgStyle?: CSSProperties;
|
imgStyle?: CSSProperties;
|
||||||
|
|
||||||
onImageLoad?: () => void;
|
onImageLoad?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
|
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
|
||||||
const { src, alt, onImageLoad, spinnerStyle, imgStyle } = props;
|
const { src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -96,15 +96,15 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
|||||||
<>
|
<>
|
||||||
<BrokenImageIcon />
|
<BrokenImageIcon />
|
||||||
<Button
|
<Button
|
||||||
startIcon={<RefreshIcon />}
|
startIcon={!small && <RefreshIcon />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
|
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
|
||||||
}}
|
}}
|
||||||
size="large"
|
size={small ? 'small' : 'large'}
|
||||||
>
|
>
|
||||||
{t('global.button.retry')}
|
{small ? <RefreshIcon /> : t('global.button.retry')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -745,7 +745,7 @@ export class RequestManager {
|
|||||||
|
|
||||||
public getValidImgUrlFor(imageUrl: string, apiVersion: string = ''): string {
|
public getValidImgUrlFor(imageUrl: string, apiVersion: string = ''): string {
|
||||||
// server provided image urls already contain the api version
|
// server provided image urls already contain the api version
|
||||||
return `${this.getValidUrlFor(imageUrl, apiVersion)}`;
|
return `${this.getValidUrlFor(imageUrl, apiVersion)}`.replace('45670', '45669');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { StyledGroupedVirtuoso } from '@/components/virtuoso/StyledGroupedVirtuo
|
|||||||
import { StyledGroupHeader } from '@/components/virtuoso/StyledGroupHeader.tsx';
|
import { StyledGroupHeader } from '@/components/virtuoso/StyledGroupHeader.tsx';
|
||||||
import { StyledGroupItemWrapper } from '@/components/virtuoso/StyledGroupItemWrapper.tsx';
|
import { StyledGroupItemWrapper } from '@/components/virtuoso/StyledGroupItemWrapper.tsx';
|
||||||
import { Mangas } from '@/lib/data/Mangas.ts';
|
import { Mangas } from '@/lib/data/Mangas.ts';
|
||||||
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
|
|
||||||
function epochToDate(epoch: number) {
|
function epochToDate(epoch: number) {
|
||||||
const date = new Date(0); // The 0 there is the key, which sets the date to the epoch
|
const date = new Date(0); // The 0 there is the key, which sets the date to the epoch
|
||||||
@@ -199,10 +200,21 @@ export const Updates: React.FC = () => {
|
|||||||
height: 56,
|
height: 56,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
marginRight: 2,
|
marginRight: 2,
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SpinnerImage
|
||||||
|
imgStyle={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
imageRendering: 'pixelated',
|
imageRendering: 'pixelated',
|
||||||
}}
|
}}
|
||||||
|
spinnerStyle={{ small: true }}
|
||||||
|
alt={manga.title}
|
||||||
src={Mangas.getThumbnailUrl(manga)}
|
src={Mangas.getThumbnailUrl(manga)}
|
||||||
/>
|
/>
|
||||||
|
</Avatar>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Typography variant="h5" component="h2">
|
<Typography variant="h5" component="h2">
|
||||||
{manga.title}
|
{manga.title}
|
||||||
|
|||||||
Reference in New Issue
Block a user