Merge pull request #167 from sambecker/thumbnail-loader
Show loading status in more places
This commit is contained in:
commit
ebbf01c698
@ -14,6 +14,7 @@ const eslintConfig = [
|
|||||||
rules: {
|
rules: {
|
||||||
'@next/next/no-img-element': 'off',
|
'@next/next/no-img-element': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
'no-unused-expressions': ['warn'],
|
'no-unused-expressions': ['warn'],
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn', {
|
'warn', {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
||||||
import type { NextConfig } from 'next';
|
import type { NextConfig } from 'next';
|
||||||
import { RemotePattern } from 'next/dist/shared/lib/image-config';
|
import { RemotePattern } from 'next/dist/shared/lib/image-config';
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,15 @@ const FLICKER_THRESHOLD = 400;
|
|||||||
// Clear loading status after long duration
|
// Clear loading status after long duration
|
||||||
const MAX_LOADING_DURATION = 15_000;
|
const MAX_LOADING_DURATION = 15_000;
|
||||||
|
|
||||||
export type LinkWithStatusProps = ComponentProps<typeof Link> & {
|
export type LinkWithStatusProps = Omit<
|
||||||
|
ComponentProps<typeof Link>, 'children'
|
||||||
|
> & {
|
||||||
loadingElement?: ReactNode
|
loadingElement?: ReactNode
|
||||||
loadingClassName?: string
|
loadingClassName?: string
|
||||||
contentClassName?: string
|
contentClassName?: string
|
||||||
|
children: ReactNode | ((props: {
|
||||||
|
isLoading: boolean
|
||||||
|
}) => ReactNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LinkWithStatus({
|
export default function LinkWithStatus({
|
||||||
@ -44,6 +49,8 @@ export default function LinkWithStatus({
|
|||||||
const stopLoadingTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
const stopLoadingTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
const maxLoadingTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
const maxLoadingTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
|
|
||||||
|
const isControlled = typeof children === 'function';
|
||||||
|
|
||||||
const clearTimeouts = useCallback(() => {
|
const clearTimeouts = useCallback(() => {
|
||||||
[startLoadingTimeout, stopLoadingTimeout, maxLoadingTimeout]
|
[startLoadingTimeout, stopLoadingTimeout, maxLoadingTimeout]
|
||||||
.forEach(timeout => {
|
.forEach(timeout => {
|
||||||
@ -114,11 +121,13 @@ export default function LinkWithStatus({
|
|||||||
contentClassName,
|
contentClassName,
|
||||||
loadingElement
|
loadingElement
|
||||||
? isLoading ? 'opacity-0' : 'opacity-100'
|
? isLoading ? 'opacity-0' : 'opacity-100'
|
||||||
: loadingClassName
|
: (loadingClassName || isControlled)
|
||||||
? 'opacity-100'
|
? 'opacity-100'
|
||||||
: isLoading ? 'opacity-50' : 'opacity-100',
|
: isLoading ? 'opacity-50' : 'opacity-100',
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{typeof children === 'function'
|
||||||
|
? children({ isLoading })
|
||||||
|
: children}
|
||||||
</span>
|
</span>
|
||||||
{isLoading && loadingElement && <span className={clsx(
|
{isLoading && loadingElement && <span className={clsx(
|
||||||
'absolute inset-0',
|
'absolute inset-0',
|
||||||
|
|||||||
@ -7,12 +7,13 @@ import {
|
|||||||
doesPhotoNeedBlurCompatibility,
|
doesPhotoNeedBlurCompatibility,
|
||||||
} from '.';
|
} from '.';
|
||||||
import ImageMedium from '@/components/image/ImageMedium';
|
import ImageMedium from '@/components/image/ImageMedium';
|
||||||
import Link from 'next/link';
|
|
||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
import { pathForPhoto } from '@/site/paths';
|
import { pathForPhoto } from '@/site/paths';
|
||||||
import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
|
import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import useOnVisible from '@/utility/useOnVisible';
|
import useOnVisible from '@/utility/useOnVisible';
|
||||||
|
import LinkWithStatus from '@/components/LinkWithStatus';
|
||||||
|
import Spinner from '@/components/Spinner';
|
||||||
|
|
||||||
export default function PhotoMedium({
|
export default function PhotoMedium({
|
||||||
photo,
|
photo,
|
||||||
@ -38,7 +39,7 @@ export default function PhotoMedium({
|
|||||||
useOnVisible(ref, onVisible);
|
useOnVisible(ref, onVisible);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<LinkWithStatus
|
||||||
ref={ref}
|
ref={ref}
|
||||||
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@ -48,16 +49,28 @@ export default function PhotoMedium({
|
|||||||
)}
|
)}
|
||||||
prefetch={prefetch}
|
prefetch={prefetch}
|
||||||
>
|
>
|
||||||
<ImageMedium
|
{({ isLoading }) =>
|
||||||
src={photo.url}
|
<div>
|
||||||
aspectRatio={photo.aspectRatio}
|
{isLoading &&
|
||||||
blurDataURL={photo.blurData}
|
<div className={clsx(
|
||||||
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
|
'absolute inset-0 flex items-center justify-center',
|
||||||
className="flex object-cover w-full h-full"
|
'text-white bg-black/25 backdrop-blur-sm',
|
||||||
imgClassName="object-cover w-full h-full"
|
'animate-fade-in',
|
||||||
alt={altTextForPhoto(photo)}
|
'z-10',
|
||||||
priority={priority}
|
)}>
|
||||||
/>
|
<Spinner size={20} color="text" />
|
||||||
</Link>
|
</div>}
|
||||||
|
<ImageMedium
|
||||||
|
src={photo.url}
|
||||||
|
aspectRatio={photo.aspectRatio}
|
||||||
|
blurDataURL={photo.blurData}
|
||||||
|
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
|
||||||
|
className="flex object-cover w-full h-full "
|
||||||
|
imgClassName="object-cover w-full h-full"
|
||||||
|
alt={altTextForPhoto(photo)}
|
||||||
|
priority={priority}
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
</LinkWithStatus>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,12 +28,18 @@ module.exports = {
|
|||||||
animation: {
|
animation: {
|
||||||
'rotate-pulse':
|
'rotate-pulse':
|
||||||
'rotate-pulse 0.75s linear infinite normal both running',
|
'rotate-pulse 0.75s linear infinite normal both running',
|
||||||
|
'fade-in':
|
||||||
|
'fade-in 0.5s linear',
|
||||||
'hover-drift':
|
'hover-drift':
|
||||||
'hover-drift 8s linear infinite',
|
'hover-drift 8s linear infinite',
|
||||||
'hover-wobble':
|
'hover-wobble':
|
||||||
'hover-wobble 6s linear infinite normal both running',
|
'hover-wobble 6s linear infinite normal both running',
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
|
'fade-in': {
|
||||||
|
'0%': { opacity: '0' },
|
||||||
|
'100%': { opacity: '1' },
|
||||||
|
},
|
||||||
'rotate-pulse': {
|
'rotate-pulse': {
|
||||||
'0%': { transform: 'rotate(0deg) scale(1)' },
|
'0%': { transform: 'rotate(0deg) scale(1)' },
|
||||||
'50%': { transform: 'rotate(180deg) scale(0.8)' },
|
'50%': { transform: 'rotate(180deg) scale(0.8)' },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user