Merge pull request #95 from sambecker/image-refactor
Refactor image components
This commit is contained in:
commit
5ddb0e7111
@ -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
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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),
|
|
||||||
}} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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),
|
|
||||||
}} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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),
|
|
||||||
}} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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}
|
||||||
|
|||||||
18
src/components/image/ImageLarge.tsx
Normal file
18
src/components/image/ImageLarge.tsx
Normal 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),
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
};
|
||||||
18
src/components/image/ImageMedium.tsx
Normal file
18
src/components/image/ImageMedium.tsx
Normal 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),
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
};
|
||||||
18
src/components/image/ImageSmall.tsx
Normal file
18
src/components/image/ImageSmall.tsx
Normal 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),
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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,
|
||||||
17
src/components/image/index.ts
Normal file
17
src/components/image/index.ts
Normal 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
|
||||||
|
}
|
||||||
@ -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'] : [])}
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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] ?? ''}
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user