Merge pull request #95 from sambecker/image-refactor

Refactor image components
This commit is contained in:
Sam Becker 2024-05-13 20:37:49 -05:00 committed by GitHub
commit 5ddb0e7111
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 161 additions and 185 deletions

View File

@ -3,7 +3,7 @@
import { Photo, deleteConfirmationTextForPhoto, titleForPhoto } from '@/photo'; import { Photo, deleteConfirmationTextForPhoto, titleForPhoto } from '@/photo';
import AdminTable from './AdminTable'; import AdminTable from './AdminTable';
import { Fragment } from 'react'; import { Fragment } from 'react';
import PhotoTiny from '@/photo/PhotoTiny'; import PhotoSmall from '@/photo/PhotoSmall';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { pathForAdminPhotoEdit, pathForPhoto } from '@/site/paths'; import { pathForAdminPhotoEdit, pathForPhoto } from '@/site/paths';
import Link from 'next/link'; import Link from 'next/link';
@ -36,7 +36,7 @@ export default function AdminPhotosTable({
<AdminTable> <AdminTable>
{photos.map((photo, index) => {photos.map((photo, index) =>
<Fragment key={photo.id}> <Fragment key={photo.id}>
<PhotoTiny <PhotoSmall
photo={photo} photo={photo}
onVisible={index === photos.length - 1 onVisible={index === photos.length - 1
? onLastPhotoVisible ? onLastPhotoVisible

View File

@ -1,7 +1,6 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import AdminTable from './AdminTable'; import AdminTable from './AdminTable';
import Link from 'next/link'; import Link from 'next/link';
import ImageTiny from '@/components/ImageTiny';
import { StorageListResponse, fileNameForStorageUrl } from '@/services/storage'; import { StorageListResponse, fileNameForStorageUrl } from '@/services/storage';
import FormWithConfirm from '@/components/FormWithConfirm'; import FormWithConfirm from '@/components/FormWithConfirm';
import { deleteBlobPhotoAction } from '@/photo/actions'; import { deleteBlobPhotoAction } from '@/photo/actions';
@ -10,6 +9,7 @@ import { clsx } from 'clsx/lite';
import { pathForAdminUploadUrl } from '@/site/paths'; import { pathForAdminUploadUrl } from '@/site/paths';
import AddButton from './AddButton'; import AddButton from './AddButton';
import { formatDate } from 'date-fns'; import { formatDate } from 'date-fns';
import ImageSmall from '@/components/image/ImageSmall';
export default function AdminUploadsTable({ export default function AdminUploadsTable({
title, title,
@ -25,7 +25,7 @@ export default function AdminUploadsTable({
const uploadFileName = fileNameForStorageUrl(url); const uploadFileName = fileNameForStorageUrl(url);
return <Fragment key={url}> return <Fragment key={url}>
<Link href={addUploadPath} prefetch={false}> <Link href={addUploadPath} prefetch={false}>
<ImageTiny <ImageSmall
alt={`Upload: ${uploadFileName}`} alt={`Upload: ${uploadFileName}`}
src={url} src={url}
aspectRatio={3.0 / 2.0} aspectRatio={3.0 / 2.0}

View File

@ -36,7 +36,7 @@ import { signOutAndRedirectAction } from '@/auth/actions';
import { TbPhoto } from 'react-icons/tb'; import { TbPhoto } from 'react-icons/tb';
import { getKeywordsForPhoto, titleForPhoto } from '@/photo'; import { getKeywordsForPhoto, titleForPhoto } from '@/photo';
import PhotoDate from '@/photo/PhotoDate'; import PhotoDate from '@/photo/PhotoDate';
import PhotoTiny from '@/photo/PhotoTiny'; import PhotoSmall from '@/photo/PhotoSmall';
import { FaCheck } from 'react-icons/fa6'; import { FaCheck } from 'react-icons/fa6';
import { TagsWithMeta, addHiddenToTags } from '@/tag'; import { TagsWithMeta, addHiddenToTags } from '@/tag';
import { FaTag } from 'react-icons/fa'; import { FaTag } from 'react-icons/fa';
@ -79,12 +79,12 @@ export default function CommandKClient({
hiddenPhotosCount, hiddenPhotosCount,
arePhotosMatted, arePhotosMatted,
shouldShowBaselineGrid, shouldShowBaselineGrid,
shouldDebugBlur, shouldDebugImageFallbacks,
setIsCommandKOpen: setIsOpen, setIsCommandKOpen: setIsOpen,
setShouldRespondToKeyboardCommands, setShouldRespondToKeyboardCommands,
setShouldShowBaselineGrid, setShouldShowBaselineGrid,
setArePhotosMatted, setArePhotosMatted,
setShouldDebugBlur, setShouldDebugImageFallbacks,
} = useAppState(); } = useAppState();
const isOpenRef = useRef(isOpen); const isOpenRef = useRef(isOpen);
@ -146,7 +146,7 @@ export default function CommandKClient({
label: titleForPhoto(photo), label: titleForPhoto(photo),
keywords: getKeywordsForPhoto(photo), keywords: getKeywordsForPhoto(photo),
annotation: <PhotoDate {...{ photo }} />, annotation: <PhotoDate {...{ photo }} />,
accessory: <PhotoTiny photo={photo} />, accessory: <PhotoSmall photo={photo} />,
path: pathForPhoto(photo), path: pathForPhoto(photo),
})), })),
}] }]
@ -228,9 +228,11 @@ export default function CommandKClient({
action: () => setArePhotosMatted?.(prev => !prev), action: () => setArePhotosMatted?.(prev => !prev),
annotation: arePhotosMatted ? <FaCheck size={12} /> : undefined, annotation: arePhotosMatted ? <FaCheck size={12} /> : undefined,
}, { }, {
label: 'Toggle Blur Debug', label: 'Toggle Image Fallbacks',
action: () => setShouldDebugBlur?.(prev => !prev), action: () => setShouldDebugImageFallbacks?.(prev => !prev),
annotation: shouldDebugBlur ? <FaCheck size={12} /> : undefined, annotation: shouldDebugImageFallbacks
? <FaCheck size={12} />
: undefined,
}, { }, {
label: 'Toggle Baseline Grid', label: 'Toggle Baseline Grid',
action: () => setShouldShowBaselineGrid?.(prev => !prev), action: () => setShouldShowBaselineGrid?.(prev => !prev),

View File

@ -1,36 +0,0 @@
import { IMAGE_LARGE_WIDTH } from '@/site';
import ImageBlurFallback from './ImageBlurFallback';
export default function ImageLarge({
className,
imgClassName,
src,
alt,
aspectRatio,
blurData,
blurCompatibilityMode,
priority,
}: {
className?: string
imgClassName?: string
src: string
alt: string
aspectRatio: number
blurData?: string
blurCompatibilityMode?: boolean
priority?: boolean
}) {
return (
<ImageBlurFallback {...{
className,
imgClassName,
src,
alt,
blurDataURL: blurData,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
priority,
width: IMAGE_LARGE_WIDTH,
height: Math.round(IMAGE_LARGE_WIDTH / aspectRatio),
}} />
);
};

View File

@ -1,33 +0,0 @@
import { IMAGE_SMALL_WIDTH } from '@/site';
import ImageBlurFallback from './ImageBlurFallback';
export default function ImageSmall({
className,
src,
alt,
aspectRatio,
blurData,
blurCompatibilityMode,
priority,
}: {
className?: string
src: string
alt: string
aspectRatio: number
blurData?: string
blurCompatibilityMode?: boolean
priority?: boolean
}) {
return (
<ImageBlurFallback {...{
className,
src,
alt,
blurDataURL: blurData,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
priority,
width: IMAGE_SMALL_WIDTH,
height: Math.round(IMAGE_SMALL_WIDTH / aspectRatio),
}} />
);
};

View File

@ -1,30 +0,0 @@
import { IMAGE_TINY_WIDTH } from '@/site';
import ImageBlurFallback from './ImageBlurFallback';
export default function ImageTiny({
className,
src,
alt,
aspectRatio,
blurData,
blurCompatibilityMode,
}: {
className?: string
src: string
alt: string
aspectRatio: number
blurData?: string
blurCompatibilityMode?: boolean
}) {
return (
<ImageBlurFallback {...{
className,
src,
alt,
blurDataURL: blurData,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
width: IMAGE_TINY_WIDTH,
height: Math.round(IMAGE_TINY_WIDTH / aspectRatio),
}} />
);
};

View File

@ -24,7 +24,7 @@ export default function ShareButton({
'-mx-0.5 translate-x-0.5', '-mx-0.5 translate-x-0.5',
'sm:mx-0 sm:translate-x-0', 'sm:mx-0 sm:translate-x-0',
)} )}
icon={<TbPhotoShare size={17} />} icon={<TbPhotoShare size={16} />}
spinnerColor="dim" spinnerColor="dim"
prefetch={prefetch} prefetch={prefetch}
shouldScroll={shouldScroll} shouldScroll={shouldScroll}

View File

@ -0,0 +1,18 @@
import { IMAGE_WIDTH_LARGE, ImageProps } from '.';
import ImageWithFallback from './ImageWithFallback';
export default function ImageLarge(props: ImageProps) {
const {
aspectRatio,
blurCompatibilityMode,
...rest
} = props;
return (
<ImageWithFallback {...{
...rest,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
width: IMAGE_WIDTH_LARGE,
height: Math.round(IMAGE_WIDTH_LARGE / aspectRatio),
}} />
);
};

View File

@ -0,0 +1,18 @@
import { IMAGE_WIDTH_MEDIUM, ImageProps } from '.';
import ImageWithFallback from './ImageWithFallback';
export default function ImageMedium(props: ImageProps) {
const {
aspectRatio,
blurCompatibilityMode,
...rest
} = props;
return (
<ImageWithFallback {...{
...rest,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
width: IMAGE_WIDTH_MEDIUM,
height: Math.round(IMAGE_WIDTH_MEDIUM / aspectRatio),
}} />
);
};

View File

@ -0,0 +1,18 @@
import { IMAGE_WIDTH_SMALL, ImageProps } from '.';
import ImageWithFallback from './ImageWithFallback';
export default function ImageSmall(props: ImageProps) {
const {
aspectRatio,
blurCompatibilityMode,
...rest
} = props;
return (
<ImageWithFallback {...{
...rest,
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
width: IMAGE_WIDTH_SMALL,
height: Math.round(IMAGE_WIDTH_SMALL / aspectRatio),
}} />
);
};

View File

@ -7,7 +7,7 @@ import { clsx} from 'clsx/lite';
import Image, { ImageProps } from 'next/image'; import Image, { ImageProps } from 'next/image';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
export default function ImageBlurFallback(props: ImageProps & { export default function ImageWithFallback(props: ImageProps & {
blurCompatibilityLevel?: 'none' | 'low' | 'high' blurCompatibilityLevel?: 'none' | 'low' | 'high'
imgClassName?: string imgClassName?: string
}) { }) {
@ -20,7 +20,7 @@ export default function ImageBlurFallback(props: ImageProps & {
...rest ...rest
} = props; } = props;
const { shouldDebugBlur } = useAppState(); const { shouldDebugImageFallbacks } = useAppState();
const [wasCached, setWasCached] = useState(true); const [wasCached, setWasCached] = useState(true);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -29,7 +29,7 @@ export default function ImageBlurFallback(props: ImageProps & {
const onLoad = useCallback(() => setIsLoading(false), []); const onLoad = useCallback(() => setIsLoading(false), []);
const onError = useCallback(() => setDidError(true), []); const onError = useCallback(() => setDidError(true), []);
const [hideBlurPlaceholder, setHideBlurPlaceholder] = useState(false); const [hideFallback, setHideFallback] = useState(false);
const imgRef = useRef<HTMLImageElement>(null); const imgRef = useRef<HTMLImageElement>(null);
@ -44,15 +44,15 @@ export default function ImageBlurFallback(props: ImageProps & {
useEffect(() => { useEffect(() => {
if (!isLoading && !didError) { if (!isLoading && !didError) {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setHideBlurPlaceholder(true); setHideFallback(true);
}, 1000); }, 1000);
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
} }
}, [isLoading, didError]); }, [isLoading, didError]);
const showPlaceholder = const showFallback =
!wasCached && !wasCached &&
!hideBlurPlaceholder; !hideFallback;
const getBlurClass = () => { const getBlurClass = () => {
switch (blurCompatibilityLevel) { switch (blurCompatibilityLevel) {
@ -71,16 +71,18 @@ export default function ImageBlurFallback(props: ImageProps & {
'flex relative', 'flex relative',
)} )}
> >
{(showPlaceholder || shouldDebugBlur) && {(showFallback || shouldDebugImageFallbacks) &&
<div className={clsx( <div className={clsx(
'@container', '@container',
'absolute inset-0', 'absolute inset-0',
'overflow-hidden', 'overflow-hidden',
'transition-opacity duration-300 ease-in', 'transition-opacity duration-300 ease-in',
!(BLUR_ENABLED && props.blurDataURL) && 'bg-main', !(BLUR_ENABLED && blurDataURL) && 'bg-main',
(isLoading || shouldDebugBlur) ? 'opacity-100' : 'opacity-0', (isLoading || shouldDebugImageFallbacks)
? 'opacity-100'
: 'opacity-0',
)}> )}>
{(BLUR_ENABLED && props.blurDataURL) {(BLUR_ENABLED && blurDataURL)
? <img {...{ ? <img {...{
...rest, ...rest,
src: blurDataURL, src: blurDataURL,

View File

@ -0,0 +1,17 @@
// Height determined by intrinsic photo aspect ratio
export const IMAGE_WIDTH_SMALL = 50;
// Height determined by intrinsic photo aspect ratio
export const IMAGE_WIDTH_MEDIUM = 300;
// Height determined by intrinsic photo aspect ratio
export const IMAGE_WIDTH_LARGE = 1000;
export interface ImageProps {
aspectRatio: number
blurCompatibilityMode?: boolean
className?: string
imgClassName?: string
src: string
alt: string
blurDataURL?: string
priority?: boolean
}

View File

@ -1,5 +1,5 @@
import { Photo } from '.'; import { Photo } from '.';
import PhotoSmall from './PhotoSmall'; import PhotoMedium from './PhotoMedium';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import AnimateItems from '@/components/AnimateItems'; import AnimateItems from '@/components/AnimateItems';
import { Camera } from '@/camera'; import { Camera } from '@/camera';
@ -57,7 +57,7 @@ export default function PhotoGrid({
<div <div
key={photo.id} key={photo.id}
className={GRID_ASPECT_RATIO !== 0 className={GRID_ASPECT_RATIO !== 0
? 'aspect-square overflow-hidden' ? 'flex relative overflow-hidden'
: undefined} : undefined}
style={{ style={{
...GRID_ASPECT_RATIO !== 0 && { ...GRID_ASPECT_RATIO !== 0 && {
@ -65,17 +65,20 @@ export default function PhotoGrid({
}, },
}} }}
> >
<PhotoSmall {...{ <PhotoMedium
photo, className="flex w-full h-full"
tag, {...{
camera, photo,
simulation, tag,
selected: photo.id === selectedPhoto?.id, camera,
priority: photoPriority, simulation,
onVisible: index === photos.length - 1 selected: photo.id === selectedPhoto?.id,
? onLastPhotoVisible priority: photoPriority,
: undefined, onVisible: index === photos.length - 1
}} /> ? onLastPhotoVisible
: undefined,
}}
/>
</div>).concat(additionalTile ?? [])} </div>).concat(additionalTile ?? [])}
itemKeys={photos.map(photo => photo.id) itemKeys={photos.map(photo => photo.id)
.concat(additionalTile ? ['more'] : [])} .concat(additionalTile ? ['more'] : [])}

View File

@ -8,7 +8,7 @@ import {
shouldShowExifDataForPhoto, shouldShowExifDataForPhoto,
} from '.'; } from '.';
import SiteGrid from '@/components/SiteGrid'; import SiteGrid from '@/components/SiteGrid';
import ImageLarge from '@/components/ImageLarge'; import ImageLarge from '@/components/image/ImageLarge';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import Link from 'next/link'; import Link from 'next/link';
import { pathForPhoto, pathForPhotoShare } from '@/site/paths'; import { pathForPhoto, pathForPhotoShare } from '@/site/paths';
@ -96,7 +96,7 @@ export default function PhotoLarge({
alt={altTextForPhoto(photo)} alt={altTextForPhoto(photo)}
src={photo.url} src={photo.url}
aspectRatio={photo.aspectRatio} aspectRatio={photo.aspectRatio}
blurData={photo.blurData} blurDataURL={photo.blurData}
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)} blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
priority={priority} priority={priority}
/> />

View File

@ -1,25 +1,35 @@
'use client';
import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.'; import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.';
import ImageTiny from '@/components/ImageTiny'; import ImageMedium from '@/components/image/ImageMedium';
import Link from 'next/link'; 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 { Camera } from '@/camera';
import { FilmSimulation } from '@/simulation';
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';
export default function PhotoTiny({ export default function PhotoMedium({
photo, photo,
tag, tag,
camera,
simulation,
selected, selected,
className, priority,
prefetch = SHOULD_PREFETCH_ALL_LINKS, prefetch = SHOULD_PREFETCH_ALL_LINKS,
className,
onVisible, onVisible,
}: { }: {
photo: Photo photo: Photo
tag?: string tag?: string
camera?: Camera
simulation?: FilmSimulation
selected?: boolean selected?: boolean
className?: string priority?: boolean
prefetch?: boolean prefetch?: boolean
className?: string
onVisible?: () => void onVisible?: () => void
}) { }) {
const ref = useRef<HTMLAnchorElement>(null); const ref = useRef<HTMLAnchorElement>(null);
@ -29,23 +39,23 @@ export default function PhotoTiny({
return ( return (
<Link <Link
ref={ref} ref={ref}
href={pathForPhoto(photo, tag)} href={pathForPhoto(photo, tag, camera, simulation)}
className={clsx( className={clsx(
className,
'active:brightness-75', 'active:brightness-75',
selected && 'brightness-50', selected && 'brightness-50',
'min-w-[50px]', className,
'rounded-[0.15rem] overflow-hidden',
'border border-gray-200 dark:border-gray-800',
)} )}
prefetch={prefetch} prefetch={prefetch}
> >
<ImageTiny <ImageMedium
src={photo.url} src={photo.url}
aspectRatio={photo.aspectRatio} aspectRatio={photo.aspectRatio}
blurData={photo.blurData} blurDataURL={photo.blurData}
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)} blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
className="flex object-cover w-full h-full"
imgClassName="object-cover w-full h-full"
alt={altTextForPhoto(photo)} alt={altTextForPhoto(photo)}
priority={priority}
/> />
</Link> </Link>
); );

View File

@ -1,12 +1,8 @@
'use client';
import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.'; import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.';
import ImageSmall from '@/components/ImageSmall'; import ImageSmall from '@/components/image/ImageSmall';
import Link from 'next/link'; 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 { Camera } from '@/camera';
import { FilmSimulation } from '@/simulation';
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';
@ -14,19 +10,15 @@ import useOnVisible from '@/utility/useOnVisible';
export default function PhotoSmall({ export default function PhotoSmall({
photo, photo,
tag, tag,
camera,
simulation,
selected, selected,
priority, className,
prefetch = SHOULD_PREFETCH_ALL_LINKS, prefetch = SHOULD_PREFETCH_ALL_LINKS,
onVisible, onVisible,
}: { }: {
photo: Photo photo: Photo
tag?: string tag?: string
camera?: Camera
simulation?: FilmSimulation
selected?: boolean selected?: boolean
priority?: boolean className?: string
prefetch?: boolean prefetch?: boolean
onVisible?: () => void onVisible?: () => void
}) { }) {
@ -37,22 +29,23 @@ export default function PhotoSmall({
return ( return (
<Link <Link
ref={ref} ref={ref}
href={pathForPhoto(photo, tag, camera, simulation)} href={pathForPhoto(photo, tag)}
className={clsx( className={clsx(
'flex w-full h-full', className,
'active:brightness-75', 'active:brightness-75',
selected && 'brightness-50', selected && 'brightness-50',
'min-w-[50px]',
'rounded-[0.15rem] overflow-hidden',
'border border-gray-200 dark:border-gray-800',
)} )}
prefetch={prefetch} prefetch={prefetch}
> >
<ImageSmall <ImageSmall
src={photo.url} src={photo.url}
aspectRatio={photo.aspectRatio} aspectRatio={photo.aspectRatio}
blurData={photo.blurData} blurDataURL={photo.blurData}
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)} blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
className="w-full"
alt={altTextForPhoto(photo)} alt={altTextForPhoto(photo)}
priority={priority}
/> />
</Link> </Link>
); );

View File

@ -18,7 +18,7 @@ import { clsx } from 'clsx/lite';
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths'; import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths';
import { toastSuccess, toastWarning } from '@/toast'; import { toastSuccess, toastWarning } from '@/toast';
import { getDimensionsFromSize } from '@/utility/size'; import { getDimensionsFromSize } from '@/utility/size';
import ImageBlurFallback from '@/components/ImageBlurFallback'; import ImageWithFallback from '@/components/image/ImageWithFallback';
import { TagsWithMeta, sortTagsObjectWithoutFavs } from '@/tag'; import { TagsWithMeta, sortTagsObjectWithoutFavs } from '@/tag';
import { formatCount, formatCountDescriptive } from '@/utility/string'; import { formatCount, formatCountDescriptive } from '@/utility/string';
import { AiContent } from '../ai/useAiImageQueries'; import { AiContent } from '../ai/useAiImageQueries';
@ -59,7 +59,7 @@ export default function PhotoForm({
const [formErrors, setFormErrors] = const [formErrors, setFormErrors] =
useState(getFormErrors(initialPhotoForm)); useState(getFormErrors(initialPhotoForm));
const { invalidateSwr, shouldDebugBlur } = useAppState(); const { invalidateSwr, shouldDebugImageFallbacks } = useAppState();
const changedFormKeys = useMemo(() => const changedFormKeys = useMemo(() =>
getChangedFormFields(initialPhotoForm, formData), getChangedFormFields(initialPhotoForm, formData),
@ -199,7 +199,7 @@ export default function PhotoForm({
shouldConfirm={Boolean(formData.semanticDescription)} shouldConfirm={Boolean(formData.semanticDescription)}
/>; />;
case 'blurData': case 'blurData':
return shouldDebugBlur && type === 'edit' && formData.url return shouldDebugImageFallbacks && type === 'edit' && formData.url
? <UpdateBlurDataButton ? <UpdateBlurDataButton
photoUrl={getNextImageUrlForManipulation(formData.url)} photoUrl={getNextImageUrlForManipulation(formData.url)}
onUpdatedBlurData={blurData => onUpdatedBlurData={blurData =>
@ -219,7 +219,7 @@ export default function PhotoForm({
key === 'blurData' && key === 'blurData' &&
type === 'create' && type === 'create' &&
!BLUR_ENABLED && !BLUR_ENABLED &&
!shouldDebugBlur !shouldDebugImageFallbacks
) { ) {
return true; return true;
} else { } else {
@ -234,7 +234,7 @@ export default function PhotoForm({
<div className="space-y-8 max-w-[38rem] relative"> <div className="space-y-8 max-w-[38rem] relative">
<div className="flex gap-2"> <div className="flex gap-2">
<div className="relative"> <div className="relative">
<ImageBlurFallback <ImageWithFallback
alt="Upload" alt="Upload"
src={url} src={url}
className={clsx( className={clsx(
@ -307,9 +307,11 @@ export default function PhotoForm({
<FieldSetWithStatus <FieldSetWithStatus
key={key} key={key}
id={key} id={key}
label={label + (key === 'blurData' && shouldDebugBlur label={label + (
? ` (${(formData[key] ?? '').length} chars.)` key === 'blurData' && shouldDebugImageFallbacks
: '')} ? ` (${(formData[key] ?? '').length} chars.)`
: ''
)}
note={note} note={note}
error={formErrors[key]} error={formErrors[key]}
value={formData[key] ?? ''} value={formData[key] ?? ''}

View File

@ -1,8 +0,0 @@
// Height determined by intrinsic photo aspect ratio
export const IMAGE_TINY_WIDTH = 50;
// Height determined by intrinsic photo aspect ratio
export const IMAGE_SMALL_WIDTH = 300;
// Height determined by intrinsic photo aspect ratio
export const IMAGE_LARGE_WIDTH = 1000;

View File

@ -25,8 +25,8 @@ export interface AppStateContext {
// DEBUG // DEBUG
arePhotosMatted?: boolean arePhotosMatted?: boolean
setArePhotosMatted?: Dispatch<SetStateAction<boolean>> setArePhotosMatted?: Dispatch<SetStateAction<boolean>>
shouldDebugBlur?: boolean shouldDebugImageFallbacks?: boolean
setShouldDebugBlur?: Dispatch<SetStateAction<boolean>> setShouldDebugImageFallbacks?: Dispatch<SetStateAction<boolean>>
shouldShowBaselineGrid?: boolean shouldShowBaselineGrid?: boolean
setShouldShowBaselineGrid?: Dispatch<SetStateAction<boolean>> setShouldShowBaselineGrid?: Dispatch<SetStateAction<boolean>>
} }

View File

@ -37,7 +37,7 @@ export default function AppStateProvider({
// DEBUG // DEBUG
const [arePhotosMatted, setArePhotosMatted] = const [arePhotosMatted, setArePhotosMatted] =
useState(MATTE_PHOTOS); useState(MATTE_PHOTOS);
const [shouldDebugBlur, setShouldDebugBlur] = const [shouldDebugImageFallbacks, setShouldDebugImageFallbacks] =
useState(false); useState(false);
const [shouldShowBaselineGrid, setShouldShowBaselineGrid] = const [shouldShowBaselineGrid, setShouldShowBaselineGrid] =
useState(false); useState(false);
@ -96,10 +96,10 @@ export default function AppStateProvider({
// DEBUG // DEBUG
arePhotosMatted, arePhotosMatted,
setArePhotosMatted, setArePhotosMatted,
setShouldDebugBlur, shouldDebugImageFallbacks,
setShouldShowBaselineGrid, setShouldDebugImageFallbacks,
shouldShowBaselineGrid, shouldShowBaselineGrid,
shouldDebugBlur, setShouldShowBaselineGrid,
}} }}
> >
{children} {children}