Add core focal length views
This commit is contained in:
parent
393ec17f84
commit
7cd5ccbe15
@ -25,7 +25,7 @@ export default function AdminPhotoMenuClient({
|
||||
const isFav = isPhotoFav(photo);
|
||||
const path = usePathname();
|
||||
const shouldRedirectFav = isPathFavs(path) && isFav;
|
||||
const shouldRedirectDelete = pathForPhoto(photo.id) === path;
|
||||
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path;
|
||||
|
||||
return (
|
||||
isUserSignedIn
|
||||
|
||||
@ -45,7 +45,7 @@ export default function AdminPhotosTable({
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<Link
|
||||
key={photo.id}
|
||||
href={pathForPhoto(photo)}
|
||||
href={pathForPhoto({ photo })}
|
||||
className="lg:w-[50%] flex items-center gap-2"
|
||||
prefetch={false}
|
||||
>
|
||||
|
||||
@ -41,7 +41,7 @@ export async function generateMetadata({
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const images = absolutePathForPhotoImage(photo);
|
||||
const url = absolutePathForPhoto(photo, simulation);
|
||||
const url = absolutePathForPhoto({ photo, simulation });
|
||||
|
||||
return {
|
||||
title,
|
||||
|
||||
41
src/app/focal/[focal]/image/route.tsx
Normal file
41
src/app/focal/[focal]/image/route.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import {
|
||||
IMAGE_OG_DIMENSION_SMALL,
|
||||
MAX_PHOTOS_TO_SHOW_PER_TAG,
|
||||
} from '@/image-response';
|
||||
import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
|
||||
import FocalLengthImageResponse from
|
||||
'@/image-response/FocalLengthImageResponse';
|
||||
import { getFocalLengthFromString } from '@/focal';
|
||||
|
||||
export async function GET(
|
||||
_: Request,
|
||||
context: { params: { focal: string } },
|
||||
) {
|
||||
const focal = getFocalLengthFromString(context.params.focal);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ fontFamily, fonts },
|
||||
headers,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_TAG, focal }),
|
||||
getIBMPlexMonoMedium(),
|
||||
getImageResponseCacheControlHeaders(),
|
||||
]);
|
||||
|
||||
const { width, height } = IMAGE_OG_DIMENSION_SMALL;
|
||||
|
||||
return new ImageResponse(
|
||||
<FocalLengthImageResponse {...{
|
||||
focal,
|
||||
photos,
|
||||
width,
|
||||
height,
|
||||
fontFamily,
|
||||
}}/>,
|
||||
{ width, height, fonts, headers },
|
||||
);
|
||||
}
|
||||
71
src/app/focal/[focal]/page.tsx
Normal file
71
src/app/focal/[focal]/page.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
||||
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||
import { getPhotosFocalDataCached } from '@/focal/data';
|
||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
||||
import { PATH_ROOT } from '@/site/paths';
|
||||
import type { Metadata } from 'next';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { cache } from 'react';
|
||||
|
||||
const getPhotosFocalDataCachedCached = cache((focal: number) =>
|
||||
getPhotosFocalDataCached({
|
||||
focal,
|
||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
||||
}));
|
||||
|
||||
interface FocalLengthProps {
|
||||
params: { focal: string }
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { focal: focalString },
|
||||
}: FocalLengthProps): Promise<Metadata> {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ count, dateRange },
|
||||
] = await getPhotosFocalDataCachedCached(focal);
|
||||
|
||||
if (photos.length === 0) { return {}; }
|
||||
|
||||
const {
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
} = generateMetaForFocalLength(focal, photos, count, dateRange);
|
||||
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
url,
|
||||
},
|
||||
twitter: {
|
||||
images,
|
||||
description,
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
description,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function TagPage({
|
||||
params: { focal: focalString },
|
||||
}:FocalLengthProps) {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ count, dateRange },
|
||||
] = await getPhotosFocalDataCachedCached(focal);
|
||||
|
||||
if (photos.length === 0) { redirect(PATH_ROOT); }
|
||||
|
||||
return (
|
||||
<FocalLengthOverview {...{ focal, photos, count, dateRange }} />
|
||||
);
|
||||
}
|
||||
@ -44,7 +44,7 @@ export async function generateMetadata({
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const images = absolutePathForPhotoImage(photo);
|
||||
const url = absolutePathForPhoto(photo);
|
||||
const url = absolutePathForPhoto({ photo });
|
||||
|
||||
return {
|
||||
title,
|
||||
|
||||
@ -44,11 +44,10 @@ export async function generateMetadata({
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const images = absolutePathForPhotoImage(photo);
|
||||
const url = absolutePathForPhoto(
|
||||
const url = absolutePathForPhoto({
|
||||
photo,
|
||||
undefined,
|
||||
cameraFromPhoto(photo, { make, model }),
|
||||
);
|
||||
camera: cameraFromPhoto(photo, { make, model }),
|
||||
});
|
||||
|
||||
return {
|
||||
title,
|
||||
|
||||
@ -35,7 +35,7 @@ export async function generateMetadata({
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const images = absolutePathForPhotoImage(photo);
|
||||
const url = absolutePathForPhoto(photo, tag);
|
||||
const url = absolutePathForPhoto({ photo, tag });
|
||||
|
||||
return {
|
||||
title,
|
||||
|
||||
@ -33,7 +33,7 @@ export async function generateMetadata({
|
||||
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const url = absolutePathForPhoto(photo, TAG_HIDDEN);
|
||||
const url = absolutePathForPhoto({ photo, tag: TAG_HIDDEN });
|
||||
|
||||
return {
|
||||
title,
|
||||
|
||||
@ -147,7 +147,7 @@ export default function CommandKClient({
|
||||
keywords: getKeywordsForPhoto(photo),
|
||||
annotation: <PhotoDate {...{ photo }} />,
|
||||
accessory: <PhotoSmall photo={photo} />,
|
||||
path: pathForPhoto(photo),
|
||||
path: pathForPhoto({ photo }),
|
||||
})),
|
||||
}]
|
||||
: []);
|
||||
|
||||
39
src/focal/FocalLengthHeader.tsx
Normal file
39
src/focal/FocalLengthHeader.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { descriptionForFocalLengthPhotos } from '.';
|
||||
import { pathForFocalLength } from '@/site/paths';
|
||||
import PhotoSetHeader from '@/photo/PhotoSetHeader';
|
||||
import PhotoFocalLength from './PhotoFocalLength';
|
||||
|
||||
export default function FocalLengthHeader({
|
||||
focal,
|
||||
photos,
|
||||
selectedPhoto,
|
||||
indexNumber,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
focal: number
|
||||
photos: Photo[]
|
||||
selectedPhoto?: Photo
|
||||
indexNumber?: number
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
return (
|
||||
<PhotoSetHeader
|
||||
entity={<PhotoFocalLength focal={focal} contrast="high" />}
|
||||
entityVerb="Tagged"
|
||||
entityDescription={descriptionForFocalLengthPhotos(
|
||||
photos,
|
||||
undefined,
|
||||
count,
|
||||
)}
|
||||
photos={photos}
|
||||
selectedPhoto={selectedPhoto}
|
||||
sharePath={pathForFocalLength(focal)}
|
||||
indexNumber={indexNumber}
|
||||
count={count}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
33
src/focal/FocalLengthOverview.tsx
Normal file
33
src/focal/FocalLengthOverview.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
||||
import FocalLengthHeader from './FocalLengthHeader';
|
||||
|
||||
export default function FocalLengthOverview({
|
||||
focal,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
animateOnFirstLoadOnly,
|
||||
}: {
|
||||
focal: number,
|
||||
photos: Photo[],
|
||||
count: number,
|
||||
dateRange?: PhotoDateRange,
|
||||
animateOnFirstLoadOnly?: boolean,
|
||||
}) {
|
||||
return (
|
||||
<PhotoGridPage {...{
|
||||
cacheKey: `focal-${focal}`,
|
||||
photos,
|
||||
count,
|
||||
focal,
|
||||
header: <FocalLengthHeader {...{
|
||||
focal,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
}} />,
|
||||
animateOnFirstLoadOnly,
|
||||
}} />
|
||||
);
|
||||
}
|
||||
31
src/focal/PhotoFocalLength.tsx
Normal file
31
src/focal/PhotoFocalLength.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { pathForFocalLength } from '@/site/paths';
|
||||
import EntityLink, {
|
||||
EntityLinkExternalProps,
|
||||
} from '@/components/primitives/EntityLink';
|
||||
import { TbCone } from 'react-icons/tb';
|
||||
import { formatFocalLength } from '.';
|
||||
|
||||
export default function PhotoFocalLength({
|
||||
focal,
|
||||
type,
|
||||
badged,
|
||||
contrast,
|
||||
prefetch,
|
||||
countOnHover,
|
||||
}: {
|
||||
focal: number
|
||||
countOnHover?: number
|
||||
} & EntityLinkExternalProps) {
|
||||
return (
|
||||
<EntityLink
|
||||
label={formatFocalLength(focal)}
|
||||
href={pathForFocalLength(focal)}
|
||||
icon={<TbCone className="rotate-[270deg]" />}
|
||||
type={type}
|
||||
badged={badged}
|
||||
contrast={contrast}
|
||||
prefetch={prefetch}
|
||||
hoverEntity={countOnHover}
|
||||
/>
|
||||
);
|
||||
}
|
||||
17
src/focal/data.ts
Normal file
17
src/focal/data.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosMetaCached,
|
||||
} from '@/photo/cache';
|
||||
|
||||
export const getPhotosFocalDataCached = ({
|
||||
focal,
|
||||
limit,
|
||||
}: {
|
||||
focal: number,
|
||||
limit?: number,
|
||||
}) =>
|
||||
Promise.all([
|
||||
getPhotosCached({ focal, limit }),
|
||||
getPhotosMetaCached({ focal }),
|
||||
]);
|
||||
|
||||
@ -9,12 +9,21 @@ import {
|
||||
absolutePathForFocalLengthImage,
|
||||
} from '@/site/paths';
|
||||
|
||||
export const getFocalLengthFromString = (focalString?: string) => {
|
||||
const focal = focalString?.match(/^([0-9]+)mm/)?.[1];
|
||||
return focal ? parseInt(focal, 10) : 0;
|
||||
};
|
||||
|
||||
export const formatFocalLength = (focal?: number) => focal ?
|
||||
`${focal}mm`
|
||||
: undefined;
|
||||
|
||||
export const titleForFocalLength = (
|
||||
focal: number,
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
`${focal}mm`,
|
||||
formatFocalLength(focal),
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
|
||||
46
src/image-response/FocalLengthImageResponse.tsx
Normal file
46
src/image-response/FocalLengthImageResponse.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import type { Photo } from '../photo';
|
||||
import ImageCaption from './components/ImageCaption';
|
||||
import ImagePhotoGrid from './components/ImagePhotoGrid';
|
||||
import ImageContainer from './components/ImageContainer';
|
||||
import type { NextImageSize } from '@/services/next-image';
|
||||
import { TbCone } from 'react-icons/tb';
|
||||
import { formatFocalLength } from '@/focal';
|
||||
|
||||
export default function FocalLengthImageResponse({
|
||||
focal,
|
||||
photos,
|
||||
width,
|
||||
height,
|
||||
fontFamily,
|
||||
}: {
|
||||
focal: number,
|
||||
photos: Photo[]
|
||||
width: NextImageSize
|
||||
height: number
|
||||
fontFamily: string
|
||||
}) {
|
||||
return (
|
||||
<ImageContainer {...{
|
||||
width,
|
||||
height,
|
||||
...photos.length === 0 && { background: 'black' },
|
||||
}}>
|
||||
<ImagePhotoGrid
|
||||
{...{
|
||||
photos,
|
||||
width,
|
||||
height,
|
||||
}}
|
||||
/>
|
||||
<ImageCaption {...{ width, height, fontFamily }}>
|
||||
<TbCone
|
||||
size={height * .08}
|
||||
style={{
|
||||
transform: `translateY(${height * .01}px) rotate(270deg)`,
|
||||
}}
|
||||
/>
|
||||
<span>{formatFocalLength(focal)}</span>
|
||||
</ImageCaption>
|
||||
</ImageContainer>
|
||||
);
|
||||
}
|
||||
@ -12,6 +12,7 @@ export default function PhotoGrid({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
photoPriority,
|
||||
fast,
|
||||
animate = true,
|
||||
@ -28,6 +29,7 @@ export default function PhotoGrid({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
photoPriority?: boolean
|
||||
fast?: boolean
|
||||
animate?: boolean
|
||||
@ -77,6 +79,7 @@ export default function PhotoGrid({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
selected: photo.id === selectedPhoto?.id,
|
||||
priority: photoPriority,
|
||||
onVisible: index === photos.length - 1
|
||||
|
||||
@ -13,6 +13,7 @@ export default function PhotoGridInfinite({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
animateOnFirstLoadOnly,
|
||||
}: {
|
||||
cacheKey: string
|
||||
@ -21,6 +22,7 @@ export default function PhotoGridInfinite({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
animateOnFirstLoadOnly?: boolean
|
||||
}) {
|
||||
return (
|
||||
@ -39,6 +41,7 @@ export default function PhotoGridInfinite({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
onLastPhotoVisible,
|
||||
animateOnFirstLoadOnly,
|
||||
}} />}
|
||||
|
||||
@ -17,6 +17,7 @@ export default function PhotoGridPage({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
animateOnFirstLoadOnly,
|
||||
header,
|
||||
sidebar,
|
||||
@ -27,6 +28,7 @@ export default function PhotoGridPage({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
animateOnFirstLoadOnly?: boolean
|
||||
header?: JSX.Element
|
||||
sidebar?: JSX.Element
|
||||
@ -58,6 +60,7 @@ export default function PhotoGridPage({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
animateOnFirstLoadOnly,
|
||||
onAnimationComplete,
|
||||
}} />
|
||||
@ -69,6 +72,7 @@ export default function PhotoGridPage({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
animateOnFirstLoadOnly,
|
||||
}} />}
|
||||
</div>
|
||||
|
||||
@ -40,6 +40,7 @@ export default function PhotoLarge({
|
||||
shouldShareTag,
|
||||
shouldShareCamera,
|
||||
shouldShareSimulation,
|
||||
shouldShareFocalLength,
|
||||
shouldScrollOnShare,
|
||||
onVisible,
|
||||
}: {
|
||||
@ -54,6 +55,7 @@ export default function PhotoLarge({
|
||||
shouldShareTag?: boolean
|
||||
shouldShareCamera?: boolean
|
||||
shouldShareSimulation?: boolean
|
||||
shouldShareFocalLength?: boolean
|
||||
shouldScrollOnShare?: boolean
|
||||
onVisible?: () => void
|
||||
}) {
|
||||
@ -76,7 +78,7 @@ export default function PhotoLarge({
|
||||
containerRef={ref}
|
||||
contentMain={
|
||||
<Link
|
||||
href={pathForPhoto(photo)}
|
||||
href={pathForPhoto({ photo })}
|
||||
className={clsx(arePhotosMatted &&
|
||||
'flex items-center aspect-[3/2] bg-gray-100',
|
||||
)}
|
||||
@ -189,12 +191,14 @@ export default function PhotoLarge({
|
||||
'md:translate-x-[-2.5px]',
|
||||
'translate-y-[1.5px] md:translate-y-0',
|
||||
)}
|
||||
path={pathForPhotoShare(
|
||||
path={pathForPhotoShare({
|
||||
photo,
|
||||
shouldShareTag ? primaryTag : undefined,
|
||||
shouldShareCamera ? camera : undefined,
|
||||
shouldShareSimulation ? photo.filmSimulation : undefined,
|
||||
)}
|
||||
tag: shouldShareTag ? primaryTag : undefined,
|
||||
camera: shouldShareCamera ? camera : undefined,
|
||||
// eslint-disable-next-line max-len
|
||||
simulation: shouldShareSimulation ? photo.filmSimulation : undefined,
|
||||
focal: shouldShareFocalLength ? photo.focalLength : undefined,
|
||||
})}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
shouldScroll={shouldScrollOnShare}
|
||||
/>
|
||||
|
||||
@ -15,6 +15,7 @@ export default function PhotoLink({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
scroll,
|
||||
prefetch,
|
||||
nextPhotoAnimation,
|
||||
@ -25,6 +26,7 @@ export default function PhotoLink({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
scroll?: boolean
|
||||
prefetch?: boolean
|
||||
nextPhotoAnimation?: AnimationConfig
|
||||
@ -36,7 +38,7 @@ export default function PhotoLink({
|
||||
return (
|
||||
photo
|
||||
? <Link
|
||||
href={pathForPhoto(photo, tag, camera, simulation)}
|
||||
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||
prefetch={prefetch}
|
||||
onClick={() => {
|
||||
if (nextPhotoAnimation) {
|
||||
|
||||
@ -21,12 +21,14 @@ export default function PhotoLinks({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
}: {
|
||||
photo: Photo
|
||||
photos: Photo[]
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
@ -47,7 +49,13 @@ export default function PhotoLinks({
|
||||
if (previousPhoto) {
|
||||
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
||||
router.push(
|
||||
pathForPhoto(previousPhoto, tag, camera, simulation),
|
||||
pathForPhoto({
|
||||
photo: previousPhoto,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
}),
|
||||
{ scroll: false },
|
||||
);
|
||||
}
|
||||
@ -57,7 +65,13 @@ export default function PhotoLinks({
|
||||
if (nextPhoto) {
|
||||
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
||||
router.push(
|
||||
pathForPhoto(nextPhoto, tag, camera, simulation),
|
||||
pathForPhoto({
|
||||
photo: nextPhoto,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
}),
|
||||
{ scroll: false },
|
||||
);
|
||||
}
|
||||
@ -76,6 +90,7 @@ export default function PhotoLinks({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -16,6 +16,7 @@ export default function PhotoMedium({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
selected,
|
||||
priority,
|
||||
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
||||
@ -26,6 +27,7 @@ export default function PhotoMedium({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
selected?: boolean
|
||||
priority?: boolean
|
||||
prefetch?: boolean
|
||||
@ -39,7 +41,7 @@ export default function PhotoMedium({
|
||||
return (
|
||||
<Link
|
||||
ref={ref}
|
||||
href={pathForPhoto(photo, tag, camera, simulation)}
|
||||
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||
className={clsx(
|
||||
'active:brightness-75',
|
||||
selected && 'brightness-50',
|
||||
|
||||
@ -29,7 +29,7 @@ export default function PhotoOGTile({
|
||||
<OGTile {...{
|
||||
title: titleForPhoto(photo),
|
||||
description: descriptionForPhoto(photo),
|
||||
path: pathForPhoto(photo),
|
||||
path: pathForPhoto({ photo }),
|
||||
pathImageAbsolute: absolutePathForPhotoImage(photo),
|
||||
loadingState: loadingStateExternal,
|
||||
onLoad,
|
||||
|
||||
@ -5,24 +5,20 @@ import ShareModal from '@/components/ShareModal';
|
||||
import { Camera } from '@/camera';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
|
||||
export default function PhotoShareModal({
|
||||
photo,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
}: {
|
||||
export default function PhotoShareModal(props: {
|
||||
photo: Photo
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
}) {
|
||||
return (
|
||||
<ShareModal
|
||||
title="Share Photo"
|
||||
pathShare={absolutePathForPhoto(photo, tag, camera, simulation)}
|
||||
pathClose={pathForPhoto(photo, tag, camera, simulation)}
|
||||
pathShare={absolutePathForPhoto(props)}
|
||||
pathClose={pathForPhoto(props)}
|
||||
>
|
||||
<PhotoOGTile photo={photo} />
|
||||
<PhotoOGTile photo={props.photo} />
|
||||
</ShareModal>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,10 +6,15 @@ import { pathForPhoto } from '@/site/paths';
|
||||
import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
|
||||
import { useRef } from 'react';
|
||||
import useOnVisible from '@/utility/useOnVisible';
|
||||
import { Camera } from '@/camera';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
|
||||
export default function PhotoSmall({
|
||||
photo,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
selected,
|
||||
className,
|
||||
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
||||
@ -17,6 +22,9 @@ export default function PhotoSmall({
|
||||
}: {
|
||||
photo: Photo
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
selected?: boolean
|
||||
className?: string
|
||||
prefetch?: boolean
|
||||
@ -29,7 +37,7 @@ export default function PhotoSmall({
|
||||
return (
|
||||
<Link
|
||||
ref={ref}
|
||||
href={pathForPhoto(photo, tag)}
|
||||
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||
className={clsx(
|
||||
className,
|
||||
'active:brightness-75',
|
||||
|
||||
@ -92,7 +92,7 @@ export const toggleFavoritePhotoAction = async (
|
||||
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
|
||||
revalidateAllKeysAndPaths();
|
||||
if (shouldRedirect) {
|
||||
redirect(pathForPhoto(photoId));
|
||||
redirect(pathForPhoto({ photo: photoId }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -113,7 +113,7 @@ export const revalidatePhoto = (photoId: string) => {
|
||||
revalidateCamerasKey();
|
||||
revalidateFilmSimulationsKey();
|
||||
// Paths
|
||||
revalidatePath(pathForPhoto(photoId), 'layout');
|
||||
revalidatePath(pathForPhoto({ photo: photoId }), 'layout');
|
||||
revalidatePath(PATH_ROOT, 'layout');
|
||||
revalidatePath(PATH_GRID, 'layout');
|
||||
revalidatePath(PREFIX_TAG, 'layout');
|
||||
|
||||
@ -7,16 +7,17 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
||||
export const PHOTO_DEFAULT_LIMIT = 100;
|
||||
|
||||
export type GetPhotosOptions = {
|
||||
sortBy?: 'createdAt' | 'takenAt' | 'priority';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
query?: string;
|
||||
tag?: string;
|
||||
camera?: Camera;
|
||||
simulation?: FilmSimulation;
|
||||
takenBefore?: Date;
|
||||
takenAfterInclusive?: Date;
|
||||
hidden?: 'exclude' | 'include' | 'only';
|
||||
sortBy?: 'createdAt' | 'takenAt' | 'priority'
|
||||
limit?: number
|
||||
offset?: number
|
||||
query?: string
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
takenBefore?: Date
|
||||
takenAfterInclusive?: Date
|
||||
hidden?: 'exclude' | 'include' | 'only'
|
||||
};
|
||||
|
||||
export const getWheresFromOptions = (
|
||||
@ -31,6 +32,7 @@ export const getWheresFromOptions = (
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
} = options;
|
||||
|
||||
const wheres = [] as string[];
|
||||
@ -73,6 +75,10 @@ export const getWheresFromOptions = (
|
||||
wheres.push(`film_simulation=$${valuesIndex++}`);
|
||||
wheresValues.push(simulation);
|
||||
}
|
||||
if (focal) {
|
||||
wheres.push(`focal_length=$${valuesIndex++}`);
|
||||
wheresValues.push(focal);
|
||||
}
|
||||
|
||||
return {
|
||||
wheres: wheres.length > 0
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { formatFocalLength } from '@/focal';
|
||||
import { getNextImageUrlForRequest } from '@/services/next-image';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config';
|
||||
@ -8,7 +9,6 @@ import {
|
||||
formatIso,
|
||||
formatExposureCompensation,
|
||||
formatExposureTime,
|
||||
formatFocalLength,
|
||||
} from '@/utility/exif';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
import { isBefore } from 'date-fns';
|
||||
|
||||
@ -30,7 +30,7 @@ interface PublicApiPhoto {
|
||||
export const formatPhotoForApi = (photo: Photo): PublicApiPhoto => ({
|
||||
id: photo.id,
|
||||
title: photo.title,
|
||||
url: absolutePathForPhoto(photo),
|
||||
url: absolutePathForPhoto({ photo }),
|
||||
...photo.make && { make: photo.make },
|
||||
...photo.model && { model: photo.model },
|
||||
...photo.tags.length > 0 && { tags: photo.tags },
|
||||
|
||||
@ -63,6 +63,14 @@ export const PATHS_TO_CACHE = [
|
||||
...PATHS_ADMIN,
|
||||
];
|
||||
|
||||
interface PhotoPathParams {
|
||||
photo: PhotoOrPhotoId
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
}
|
||||
|
||||
// Absolute paths
|
||||
export const ABSOLUTE_PATH_FOR_HOME_IMAGE = `${BASE_URL}/home-image`;
|
||||
|
||||
@ -80,13 +88,13 @@ type PhotoOrPhotoId = Photo | string;
|
||||
const getPhotoId = (photoOrPhotoId: PhotoOrPhotoId) =>
|
||||
typeof photoOrPhotoId === 'string' ? photoOrPhotoId : photoOrPhotoId.id;
|
||||
|
||||
export const pathForPhoto = (
|
||||
photo: PhotoOrPhotoId,
|
||||
tag?: string,
|
||||
camera?: Camera,
|
||||
simulation?: FilmSimulation,
|
||||
focal?: number,
|
||||
) =>
|
||||
export const pathForPhoto = ({
|
||||
photo,
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
}: PhotoPathParams) =>
|
||||
typeof photo !== 'string' && photo.hidden
|
||||
? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}`
|
||||
: tag
|
||||
@ -99,13 +107,8 @@ export const pathForPhoto = (
|
||||
? `${pathForFocalLength(focal)}/${getPhotoId(photo)}`
|
||||
: `${PREFIX_PHOTO}/${getPhotoId(photo)}`;
|
||||
|
||||
export const pathForPhotoShare = (
|
||||
photo: PhotoOrPhotoId,
|
||||
tag?: string,
|
||||
camera?: Camera,
|
||||
simulation?: FilmSimulation,
|
||||
) =>
|
||||
`${pathForPhoto(photo, tag, camera, simulation)}/${SHARE}`;
|
||||
export const pathForPhotoShare = (params: PhotoPathParams) =>
|
||||
`${pathForPhoto(params)}/${SHARE}`;
|
||||
|
||||
export const pathForTag = (tag: string) =>
|
||||
`${PREFIX_TAG}/${tag}`;
|
||||
@ -128,13 +131,8 @@ export const pathForFocalLength = (focal: number) =>
|
||||
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
|
||||
`${pathForFilmSimulation(simulation)}/${SHARE}`;
|
||||
|
||||
export const absolutePathForPhoto = (
|
||||
photo: PhotoOrPhotoId,
|
||||
tag?: string,
|
||||
camera?: Camera,
|
||||
simulation?: FilmSimulation
|
||||
) =>
|
||||
`${BASE_URL}${pathForPhoto(photo, tag, camera, simulation)}`;
|
||||
export const absolutePathForPhoto = (params: PhotoPathParams) =>
|
||||
`${BASE_URL}${pathForPhoto(params)}`;
|
||||
|
||||
export const absolutePathForTag = (tag: string) =>
|
||||
`${BASE_URL}${pathForTag(tag)}`;
|
||||
@ -149,7 +147,7 @@ export const absolutePathForFocalLength = (focal: number) =>
|
||||
`${BASE_URL}${pathForFocalLength(focal)}`;
|
||||
|
||||
export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) =>
|
||||
`${absolutePathForPhoto(photo)}/image`;
|
||||
`${absolutePathForPhoto({ photo })}/image`;
|
||||
|
||||
export const absolutePathForTagImage = (tag: string) =>
|
||||
`${absolutePathForTag(tag)}/image`;
|
||||
@ -329,15 +327,15 @@ export const getEscapePath = (pathname?: string) => {
|
||||
) {
|
||||
return PATH_GRID;
|
||||
} else if (photoId && isPathTagPhotoShare(pathname)) {
|
||||
return pathForPhoto(photoId, tag);
|
||||
return pathForPhoto({ photo: photoId, tag });
|
||||
} else if (photoId && isPathCameraPhotoShare(pathname)) {
|
||||
return pathForPhoto(photoId, undefined, camera);
|
||||
return pathForPhoto({ photo: photoId, camera });
|
||||
} else if (photoId && isPathFilmSimulationPhotoShare(pathname)) {
|
||||
return pathForPhoto(photoId, undefined, undefined, simulation);
|
||||
return pathForPhoto({ photo: photoId, simulation });
|
||||
} else if (photoId && isPathFocalLengthPhotoShare(pathname)) {
|
||||
return pathForPhoto(photoId, undefined, undefined, undefined, focal);
|
||||
return pathForPhoto({ photo: photoId, focal });
|
||||
} else if (photoId && isPathPhotoShare(pathname)) {
|
||||
return pathForPhoto(photoId);
|
||||
return pathForPhoto({ photo: photoId });
|
||||
} else if (tag && (
|
||||
isPathTagPhoto(pathname) ||
|
||||
isPathTagShare(pathname)
|
||||
|
||||
@ -31,9 +31,6 @@ export const getAspectRatioFromExif = (data: ExifData): number => {
|
||||
}
|
||||
};
|
||||
|
||||
export const formatFocalLength = (focalLength?: number) =>
|
||||
focalLength ? `${focalLength}mm` : undefined;
|
||||
|
||||
export const formatAperture = (aperture?: number) =>
|
||||
aperture ? `ƒ/${aperture}` : undefined;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user