bigbuffet-rw/packages/pl-fe/src/components/still-image.tsx

105 lines
2.9 KiB
TypeScript
Raw Normal View History

2023-02-06 10:01:03 -08:00
import clsx from 'clsx';
2022-05-07 10:46:21 -07:00
import React, { useRef } from 'react';
import { useSettings } from 'pl-fe/hooks/use-settings';
2022-05-07 10:46:21 -07:00
interface IStillImage {
2022-05-07 10:46:21 -07:00
/** Image alt text. */
alt?: string;
2022-05-07 10:46:21 -07:00
/** Extra class names for the outer <div> container. */
className?: string;
2022-05-07 10:46:21 -07:00
/** URL to the image */
src: string;
2022-05-07 10:46:21 -07:00
/** Extra CSS styles on the outer <div> element. */
style?: React.CSSProperties;
/** Whether to display the image contained vs filled in its container. */
letterboxed?: boolean;
/** Whether to show the file extension in the corner. */
showExt?: boolean;
2023-04-24 05:17:51 -07:00
/** Callback function if the image fails to load */
onError?(): void;
/** Treat as animated, no matter the extension */
isGif?: boolean;
/** Specify that the group is defined by the parent */
noGroup?: boolean;
2022-05-07 10:46:21 -07:00
}
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
const StillImage: React.FC<IStillImage> = ({
alt, className, src, style, letterboxed = false, showExt = false, onError, isGif, noGroup,
}) => {
const { autoPlayGif } = useSettings();
2022-05-07 10:46:21 -07:00
const canvas = useRef<HTMLCanvasElement>(null);
const img = useRef<HTMLImageElement>(null);
2022-05-07 10:46:21 -07:00
const hoverToPlay = (
src && !autoPlayGif && ((isGif) || src.endsWith('.gif') || src.startsWith('blob:'))
2022-05-07 10:46:21 -07:00
);
const handleImageLoad = () => {
if (hoverToPlay && canvas.current && img.current) {
canvas.current.width = img.current.naturalWidth;
2022-05-07 10:46:21 -07:00
canvas.current.height = img.current.naturalHeight;
canvas.current.getContext('2d')?.drawImage(img.current, 0, 0);
}
};
/** ClassNames shared between the `<img>` and `<canvas>` elements. */
const baseClassName = clsx('block size-full', {
'object-contain': letterboxed,
'object-cover': !letterboxed,
});
2022-05-07 10:46:21 -07:00
return (
<div
data-testid='still-image-container'
className={clsx(className, 'relative isolate overflow-hidden', { 'group': !noGroup })}
style={style}
>
<img
src={src}
alt={alt}
ref={img}
onLoad={handleImageLoad}
2023-04-24 05:17:51 -07:00
onError={onError}
2023-02-06 10:01:03 -08:00
className={clsx(baseClassName, {
'invisible group-hover:visible': hoverToPlay,
})}
/>
{hoverToPlay && (
<canvas
ref={canvas}
2023-02-06 10:01:03 -08:00
className={clsx(baseClassName, {
'absolute group-hover:invisible top-0': hoverToPlay,
})}
/>
)}
{(hoverToPlay && showExt) && (
<div className='pointer-events-none absolute bottom-2 left-2 opacity-90 group-hover:hidden'>
<ExtensionBadge ext='GIF' />
</div>
)}
2022-05-07 10:46:21 -07:00
</div>
);
};
interface IExtensionBadge {
/** File extension. */
ext: string;
}
/** Badge displaying a file extension. */
const ExtensionBadge: React.FC<IExtensionBadge> = ({ ext }) => (
<div className='inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-sm font-medium text-gray-900 dark:bg-gray-800 dark:text-gray-100'>
{ext}
</div>
);
export {
type IStillImage,
StillImage as default,
};