|
- import * as React from 'react';
- import {augmentImageFile, getMimeTypeDescription} from 'packages/web-kitchensink-reactnext/src/utils/blob';
- import {formatFileSize, formatNumeral} from 'packages/web-kitchensink-reactnext/src/utils/numeral';
- import clsx from 'clsx';
- import {useFileMetadata, useFileUrl, useImageControls} from 'src/index';
- import {KeyValueTable} from 'categories/information/react';
- import {useClientSide} from 'packages/react-utils';
- import type {CommonPreviewComponentProps} from '../../../../../categories/blob/react/src/components/FileSelectBox';
- import {Swatch} from 'categories/color/react';
-
- export type ImageFilePreviewDerivedElement = HTMLImageElement;
-
- export interface ImageFilePreviewProps<F extends Partial<File> = Partial<File>>
- extends Omit<React.HTMLProps<ImageFilePreviewDerivedElement>, 'src' | 'alt'>, CommonPreviewComponentProps<F> {}
-
- export const ImageFilePreview = React.forwardRef<ImageFilePreviewDerivedElement, ImageFilePreviewProps>(({
- file,
- className,
- style,
- disabled = false,
- enhanced: enhancedProp = false,
- ...etcProps
- }, forwardedRef) => {
- const { fileWithUrl, loading: urlLoading } = useFileUrl({ file });
- const { fileWithMetadata, loading: metadataLoading, error } = useFileMetadata({
- file: fileWithUrl as File,
- augmentFunction: augmentImageFile,
- });
-
- const {
- fullScreen,
- handleAction,
- imageRef,
- filenameRef,
- } = useImageControls({
- forwardedRef,
- });
-
- const { clientSide } = useClientSide({ clientSide: enhancedProp });
-
- if (!(fileWithMetadata)) {
- return null;
- }
-
- const cannotDisplayPicture = Boolean(
- typeof fileWithMetadata.url !== 'string'
- && error
- );
-
- return (
- <div
- className={clsx(
- 'flex flex-col sm:grid sm:grid-cols-3 gap-8 w-full',
- className,
- )}
- style={style}
- >
- <div className="h-full relative">
- <div className="sm:absolute top-0 left-0 w-full sm:h-full z-[3]">
- {typeof fileWithMetadata.url === 'string' && (
- <img
- {...etcProps}
- ref={imageRef}
- className={clsx(
- 'block h-full max-w-full object-center bg-[#000000]',
- {
- 'object-contain fixed w-full top-0 left-0 z-[3]': fullScreen,
- 'object-cover w-full': !fullScreen,
- },
- )}
- src={fileWithMetadata.url}
- alt=""
- data-testid="preview"
- />
- )}
- {cannotDisplayPicture && (
- <div className="w-full h-full flex items-center justify-center text-center px-4 bg-[#000000] select-none">
- {error!.message}
- </div>
- )}
- </div>
- </div>
- <div
- className="col-span-2 flex-shrink-0 m-0 flex flex-col gap-4 justify-between"
- >
- <KeyValueTable
- hiddenKeys
- data-testid="infoBox"
- properties={[
- Boolean(fileWithMetadata.name) && {
- key: 'Name',
- className: 'font-bold',
- valueProps: {
- ref: filenameRef,
- title: fileWithMetadata.name,
- children: fileWithMetadata.name,
- },
- },
- (clientSide && Boolean(getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) || '(Loading)') || Boolean(fileWithMetadata.type)) && {
- key: 'Type',
- valueProps: {
- className: clsx(
- !getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) && 'opacity-50'
- ),
- children: getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) || '(Loading)',
- },
- },
- (clientSide && Boolean(formatFileSize(fileWithMetadata.size) || '(Loading)') || Boolean(fileWithMetadata.size)) && {
- key: 'Size',
- valueProps: {
- className: clsx(
- !formatFileSize(fileWithMetadata.size) && 'opacity-50'
- ),
- title: `${formatNumeral(fileWithMetadata.size ?? 0)} byte(s)`,
- children: formatFileSize(fileWithMetadata.size) || '(Loading)',
- },
- },
- typeof fileWithMetadata.metadata?.width === 'number'
- && typeof fileWithMetadata.metadata?.height === 'number'
- && {
- key: 'Pixel Dimensions',
- valueProps: {
- children: `${formatNumeral(fileWithMetadata.metadata.width)} × ${formatNumeral(fileWithMetadata.metadata.height)} pixel(s)`,
- },
- },
- Array.isArray(fileWithMetadata.metadata?.palette)
- && {
- key: 'Palette',
- valueProps: {
- className: '-ml-4 pl-4 pt-4 ',
- children: (
- <div className="flex flex-wrap gap-3">
- {fileWithMetadata.metadata?.palette.map((rgb, i) => (
- <React.Fragment
- key={rgb.join(' ')}
- >
- {i > 0 && ' '}
- <Swatch
- color={rgb}
- mode="rgb"
- />
- </React.Fragment>
- ))}
- </div>
- ),
- },
- },
- ]}
- />
- {clientSide && (
- <form
- onSubmit={handleAction}
- className="flex gap-4 justify-end"
- >
- <fieldset
- disabled={disabled || cannotDisplayPicture}
- className="contents"
- >
- <legend className="sr-only">
- Controls
- </legend>
- <button
- type="submit"
- name="action"
- value="toggleFullScreen"
- className={clsx(
- 'h-12 flex text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none',
- 'focus:outline-0',
- 'disabled:opacity-50 disabled:cursor-not-allowed',
- {
- 'fixed top-0 left-0 w-full h-full opacity-0 z-[3]': fullScreen,
- }
- )}
- >
- <span
- className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
- >
- Preview
- </span>
- </button>
- {' '}
- <button
- type="submit"
- name="action"
- value="download"
- className={clsx(
- 'h-12 flex text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none',
- 'focus:outline-0',
- 'disabled:opacity-50 disabled:cursor-not-allowed',
- )}
- >
- <span
- className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
- >
- Download
- </span>
- </button>
- </fieldset>
- </form>
- )}
- </div>
- </div>
- );
- });
-
- ImageFilePreview.displayName = 'ImageFilePreview';
|