Merge pull request #98 from sambecker/focal

Add focal length sets
This commit is contained in:
Sam Becker 2024-05-21 16:52:35 -05:00 committed by GitHub
commit 8992f3455b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 865 additions and 134 deletions

View File

@ -10,6 +10,10 @@ import {
isPathFilmSimulationPhoto,
isPathFilmSimulationPhotoShare,
isPathFilmSimulationShare,
isPathFocalLength,
isPathFocalLengthPhoto,
isPathFocalLengthPhotoShare,
isPathFocalLengthShare,
isPathPhoto,
isPathPhotoShare,
isPathProtected,
@ -26,6 +30,8 @@ const CAMERA_MAKE = 'fujifilm';
const CAMERA_MODEL = 'x-t1';
const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL };
const FILM_SIMULATION = 'acros';
const FOCAL_LENGTH = 90;
const FOCAL_LENGTH_STRING = `${FOCAL_LENGTH}mm`;
const SHARE = 'share';
const PATH_ROOT = '/';
@ -55,6 +61,11 @@ const PATH_FILM_SIMULATION_SHARE = `${PATH_FILM_SIMULATION}/${SHARE}`;
const PATH_FILM_SIMULATION_PHOTO = `${PATH_FILM_SIMULATION}/${PHOTO_ID}`;
const PATH_FILM_SIMULATION_PHOTO_SHARE = `${PATH_FILM_SIMULATION_PHOTO}/${SHARE}`;
const PATH_FOCAL_LENGTH = `/focal/${FOCAL_LENGTH_STRING}`;
const PATH_FOCAL_LENGTH_SHARE = `${PATH_FOCAL_LENGTH}/${SHARE}`;
const PATH_FOCAL_LENGTH_PHOTO = `${PATH_FOCAL_LENGTH}/${PHOTO_ID}`;
const PATH_FOCAL_LENGTH_PHOTO_SHARE = `${PATH_FOCAL_LENGTH_PHOTO}/${SHARE}`;
describe('Paths', () => {
it('can be protected', () => {
// Public
@ -87,6 +98,10 @@ describe('Paths', () => {
expect(isPathFilmSimulationShare(PATH_FILM_SIMULATION_SHARE)).toBe(true);
expect(isPathFilmSimulationPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(true);
expect(isPathFilmSimulationPhotoShare(PATH_FILM_SIMULATION_PHOTO_SHARE)).toBe(true);
expect(isPathFocalLength(PATH_FOCAL_LENGTH)).toBe(true);
expect(isPathFocalLengthShare(PATH_FOCAL_LENGTH_SHARE)).toBe(true);
expect(isPathFocalLengthPhoto(PATH_FOCAL_LENGTH_PHOTO)).toBe(true);
expect(isPathFocalLengthPhotoShare(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toBe(true);
// Negative
expect(isPathPhoto(PATH_TAG_PHOTO_SHARE)).toBe(false);
expect(isPathPhotoShare(PATH_TAG_PHOTO)).toBe(false);
@ -102,8 +117,13 @@ describe('Paths', () => {
expect(isPathFilmSimulationShare(PATH_TAG)).toBe(false);
expect(isPathFilmSimulationPhoto(PATH_PHOTO_SHARE)).toBe(false);
expect(isPathFilmSimulationPhotoShare(PATH_PHOTO)).toBe(false);
expect(isPathFocalLength(PATH_FILM_SIMULATION)).toBe(false);
expect(isPathFocalLengthShare(PATH_FILM_SIMULATION_SHARE)).toBe(false);
expect(isPathFocalLengthPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(false);
expect(isPathFocalLengthPhotoShare(PATH_FILM_SIMULATION_PHOTO_SHARE)).toBe(false);
});
it('can be parsed', () => {
// Core
expect(getPathComponents(PATH_ROOT)).toEqual({});
expect(getPathComponents(PATH_PHOTO)).toEqual({
photoId: PHOTO_ID,
@ -111,6 +131,7 @@ describe('Paths', () => {
expect(getPathComponents(PATH_PHOTO_SHARE)).toEqual({
photoId: PHOTO_ID,
});
// Tag
expect(getPathComponents(PATH_TAG)).toEqual({
tag: TAG,
});
@ -125,6 +146,7 @@ describe('Paths', () => {
photoId: PHOTO_ID,
tag: TAG,
});
// Camera
expect(getPathComponents(PATH_CAMERA)).toEqual({
camera: CAMERA_OBJECT,
});
@ -139,6 +161,7 @@ describe('Paths', () => {
photoId: PHOTO_ID,
camera: CAMERA_OBJECT,
});
// Film Simulation
expect(getPathComponents(PATH_FILM_SIMULATION)).toEqual({
simulation: FILM_SIMULATION,
});
@ -153,29 +176,49 @@ describe('Paths', () => {
photoId: PHOTO_ID,
simulation: FILM_SIMULATION,
});
// Focal Length
expect(getPathComponents(PATH_FOCAL_LENGTH)).toEqual({
focal: FOCAL_LENGTH,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_SHARE)).toEqual({
focal: FOCAL_LENGTH,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_PHOTO)).toEqual({
photoId: PHOTO_ID,
focal: FOCAL_LENGTH,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toEqual({
photoId: PHOTO_ID,
focal: FOCAL_LENGTH,
});
});
it('can be escaped', () => {
// Root views
// Root
expect(getEscapePath(PATH_ROOT)).toEqual(undefined);
expect(getEscapePath(PATH_GRID)).toEqual(undefined);
expect(getEscapePath(PATH_ADMIN)).toEqual(undefined);
// Photo views
// Photo
expect(getEscapePath(PATH_PHOTO)).toEqual(PATH_GRID);
expect(getEscapePath(PATH_PHOTO_SHARE)).toEqual(PATH_PHOTO);
// Tag views
// Tag
expect(getEscapePath(PATH_TAG)).toEqual(PATH_GRID);
expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG);
expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG);
expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO);
// Camera views
// Camera
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_GRID);
expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA);
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
expect(getEscapePath(PATH_CAMERA_PHOTO_SHARE)).toEqual(PATH_CAMERA_PHOTO);
// Film Simulation views
// Film Simulation
expect(getEscapePath(PATH_FILM_SIMULATION)).toEqual(PATH_GRID);
expect(getEscapePath(PATH_FILM_SIMULATION_SHARE)).toEqual(PATH_FILM_SIMULATION);
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO)).toEqual(PATH_FILM_SIMULATION);
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO_SHARE)).toEqual(PATH_FILM_SIMULATION_PHOTO);
// Focal Length
expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_GRID);
expect(getEscapePath(PATH_FOCAL_LENGTH_SHARE)).toEqual(PATH_FOCAL_LENGTH);
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH);
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toEqual(PATH_FOCAL_LENGTH_PHOTO);
});
});

View File

@ -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

View File

@ -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}
>

View File

@ -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,

View File

@ -0,0 +1,86 @@
import {
RELATED_GRID_PHOTOS_TO_SHOW,
descriptionForPhoto,
titleForPhoto,
} from '@/photo';
import { Metadata } from 'next/types';
import { redirect } from 'next/navigation';
import {
PATH_ROOT,
absolutePathForPhoto,
absolutePathForPhotoImage,
} from '@/site/paths';
import PhotoDetailPage from '@/photo/PhotoDetailPage';
import { getPhotosNearIdCached } from '@/photo/cache';
import { ReactNode, cache } from 'react';
import { getPhotosMeta } from '@/photo/db/query';
import { getFocalLengthFromString } from '@/focal';
const getPhotosNearIdCachedCached = cache((photoId: string, focal: number) =>
getPhotosNearIdCached(
photoId,
{ focal, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 },
));
interface PhotoFocalLengthProps {
params: { photoId: string, focal: string }
}
export async function generateMetadata({
params: { photoId, focal: focalString },
}: PhotoFocalLengthProps): Promise<Metadata> {
const focal = getFocalLengthFromString(focalString);
const { photo } = await getPhotosNearIdCachedCached(photoId, focal);
if (!photo) { return {}; }
const title = titleForPhoto(photo);
const description = descriptionForPhoto(photo);
const images = absolutePathForPhotoImage(photo);
const url = absolutePathForPhoto({ photo, focal });
return {
title,
description,
openGraph: {
title,
images,
description,
url,
},
twitter: {
title,
description,
images,
card: 'summary_large_image',
},
};
}
export default async function PhotoFocalLengthPage({
params: { photoId, focal: focalString },
children,
}: PhotoFocalLengthProps & { children: ReactNode }) {
const focal = getFocalLengthFromString(focalString);
const { photo, photos, photosGrid, indexNumber } =
await getPhotosNearIdCachedCached(photoId, focal);
if (!photo) { redirect(PATH_ROOT); }
const { count, dateRange } = await getPhotosMeta({ focal });
return <>
{children}
<PhotoDetailPage {...{
photo,
photos,
photosGrid,
focal,
indexNumber,
count,
dateRange,
}} />
</>;
}

View File

@ -0,0 +1,3 @@
export default function Page() {
return null;
}

View File

@ -0,0 +1,19 @@
import { getFocalLengthFromString } from '@/focal';
import { getPhotoCached } from '@/photo/cache';
import PhotoShareModal from '@/photo/PhotoShareModal';
import { PATH_ROOT } from '@/site/paths';
import { redirect } from 'next/navigation';
export default async function Share({
params: { photoId, focal: focalString },
}: {
params: { photoId: string, focal: string }
}) {
const focal = getFocalLengthFromString(focalString);
const photo = await getPhotoCached(photoId);
if (!photo) { return redirect(PATH_ROOT); }
return <PhotoShareModal {...{ photo, focal }} />;
}

View 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 },
);
}

View File

@ -0,0 +1,71 @@
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
import FocalLengthOverview from '@/focal/FocalLengthOverview';
import { getPhotosFocalLengthDataCached } 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) =>
getPhotosFocalLengthDataCached({
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 }} />
);
}

View File

@ -0,0 +1,70 @@
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
import FocalLengthOverview from '@/focal/FocalLengthOverview';
import FocalLengthShareModal from '@/focal/FocalLengthShareModal';
import { getPhotosFocalLengthDataCached } from '@/focal/data';
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
import type { Metadata } from 'next';
import { cache } from 'react';
const getPhotosFocalLengthDataCachedCached = cache((focal: number) =>
getPhotosFocalLengthDataCached({
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 getPhotosFocalLengthDataCachedCached(focal);
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 Share({
params: { focal: focalString },
}: FocalLengthProps) {
const focal = getFocalLengthFromString(focalString);
const [
photos,
{ count, dateRange },
] = await getPhotosFocalLengthDataCachedCached(focal);
return <>
<FocalLengthShareModal {...{ focal, photos, count, dateRange }} />
<FocalLengthOverview
{...{ focal, photos, count, dateRange }}
animateOnFirstLoadOnly
/>
</>;
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -147,7 +147,7 @@ export default function CommandKClient({
keywords: getKeywordsForPhoto(photo),
annotation: <PhotoDate {...{ photo }} />,
accessory: <PhotoSmall photo={photo} />,
path: pathForPhoto(photo),
path: pathForPhoto({ photo }),
})),
}]
: []);

View File

@ -0,0 +1,38 @@
import { Photo, PhotoDateRange } from '@/photo';
import { descriptionForFocalLengthPhotos } from '.';
import { pathForFocalLengthShare } 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" />}
entityDescription={descriptionForFocalLengthPhotos(
photos,
undefined,
count,
)}
photos={photos}
selectedPhoto={selectedPhoto}
sharePath={pathForFocalLengthShare(focal)}
indexNumber={indexNumber}
count={count}
dateRange={dateRange}
/>
);
}

View File

@ -0,0 +1,50 @@
import { Photo, PhotoDateRange } from '@/photo';
import {
absolutePathForFocalLengthImage,
pathForFocalLength,
} from '@/site/paths';
import OGTile from '@/components/OGTile';
import { descriptionForFocalLengthPhotos, titleForFocalLength } from '.';
export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
export default function FocalLengthOGTile({
focal,
photos,
loadingState: loadingStateExternal,
riseOnHover,
onLoad,
onFail,
retryTime,
count,
dateRange,
}: {
focal: number
photos: Photo[]
loadingState?: OGLoadingState
onLoad?: () => void
onFail?: () => void
riseOnHover?: boolean
retryTime?: number
count?: number
dateRange?: PhotoDateRange
}) {
return (
<OGTile {...{
title: titleForFocalLength(focal, photos, count),
description: descriptionForFocalLengthPhotos(
photos,
true,
count,
dateRange,
),
path: pathForFocalLength(focal),
pathImageAbsolute: absolutePathForFocalLengthImage(focal),
loadingState: loadingStateExternal,
onLoad,
onFail,
riseOnHover,
retryTime,
}}/>
);
};

View 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,
}} />
);
}

View File

@ -0,0 +1,26 @@
import { absolutePathForFocalLength, pathForFocalLength } from '@/site/paths';
import { Photo, PhotoDateRange } from '../photo';
import ShareModal from '@/components/ShareModal';
import FocalLengthOGTile from './FocalLengthOGTile';
export default function FocalLengthShareModal({
focal,
photos,
count,
dateRange,
}: {
focal: number
photos: Photo[]
count?: number
dateRange?: PhotoDateRange
}) {
return (
<ShareModal
title="Share Photos"
pathShare={absolutePathForFocalLength(focal)}
pathClose={pathForFocalLength(focal)}
>
<FocalLengthOGTile {...{ focal, photos, count, dateRange }} />
</ShareModal>
);
};

View 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
View File

@ -0,0 +1,17 @@
import {
getPhotosCached,
getPhotosMetaCached,
} from '@/photo/cache';
export const getPhotosFocalLengthDataCached = ({
focal,
limit,
}: {
focal: number,
limit?: number,
}) =>
Promise.all([
getPhotosCached({ focal, limit }),
getPhotosMetaCached({ focal }),
]);

59
src/focal/index.ts Normal file
View File

@ -0,0 +1,59 @@
import {
Photo,
PhotoDateRange,
descriptionForPhotoSet,
photoQuantityText,
} from '@/photo';
import {
absolutePathForFocalLength,
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,
) => [
`${formatFocalLength(focal)} Focal Length`,
photoQuantityText(explicitCount ?? photos.length),
].join(' ');
export const descriptionForFocalLengthPhotos = (
photos: Photo[],
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) =>
descriptionForPhotoSet(
photos,
undefined,
dateBased,
explicitCount,
explicitDateRange,
);
export const generateMetaForFocalLength = (
focal: number,
photos: Photo[],
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({
url: absolutePathForFocalLength(focal),
title: titleForFocalLength(focal, photos, explicitCount),
description: descriptionForFocalLengthPhotos(
photos,
true,
explicitCount,
explicitDateRange,
),
images: absolutePathForFocalLengthImage(focal),
});

View 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>
);
}

View File

@ -12,6 +12,7 @@ import { FilmSimulation } from '@/simulation';
import FilmSimulationHeader from '@/simulation/FilmSimulationHeader';
import { TAG_HIDDEN } from '@/tag';
import HiddenHeader from '@/tag/HiddenHeader';
import FocalLengthHeader from '@/focal/FocalLengthHeader';
export default function PhotoDetailPage({
photo,
@ -20,6 +21,7 @@ export default function PhotoDetailPage({
tag,
camera,
simulation,
focal,
indexNumber,
count,
dateRange,
@ -30,6 +32,7 @@ export default function PhotoDetailPage({
tag?: string
camera?: Camera
simulation?: FilmSimulation
focal?: number
indexNumber?: number
count?: number
dateRange?: PhotoDateRange
@ -82,6 +85,19 @@ export default function PhotoDetailPage({
dateRange={dateRange}
/>}
/>}
{focal &&
<SiteGrid
className="mt-4 mb-8"
contentMain={
<FocalLengthHeader
focal={focal}
photos={photos}
selectedPhoto={photo}
indexNumber={indexNumber}
count={count}
dateRange={dateRange}
/>}
/>}
<AnimateItems
className="md:mb-8"
animateFromAppState
@ -109,6 +125,7 @@ export default function PhotoDetailPage({
tag={tag}
camera={camera}
simulation={simulation}
focal={focal}
animateOnFirstLoadOnly
/>}
contentSide={<AnimateItems
@ -130,6 +147,7 @@ export default function PhotoDetailPage({
tag,
camera,
simulation,
focal,
}} />
</div>,
]}

View File

@ -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

View File

@ -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,
}} />}

View File

@ -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>

View File

@ -11,7 +11,11 @@ import SiteGrid from '@/components/SiteGrid';
import ImageLarge from '@/components/image/ImageLarge';
import { clsx } from 'clsx/lite';
import Link from 'next/link';
import { pathForPhoto, pathForPhotoShare } from '@/site/paths';
import {
pathForFocalLength,
pathForPhoto,
pathForPhotoShare,
} from '@/site/paths';
import PhotoTags from '@/tag/PhotoTags';
import ShareButton from '@/components/ShareButton';
import PhotoCamera from '../camera/PhotoCamera';
@ -40,6 +44,7 @@ export default function PhotoLarge({
shouldShareTag,
shouldShareCamera,
shouldShareSimulation,
shouldShareFocalLength,
shouldScrollOnShare,
onVisible,
}: {
@ -54,6 +59,7 @@ export default function PhotoLarge({
shouldShareTag?: boolean
shouldShareCamera?: boolean
shouldShareSimulation?: boolean
shouldShareFocalLength?: boolean
shouldScrollOnShare?: boolean
onVisible?: () => void
}) {
@ -76,7 +82,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',
)}
@ -153,7 +159,10 @@ export default function PhotoLarge({
<>
<ul className="text-medium">
<li>
{photo.focalLength &&
<Link href={pathForFocalLength(photo.focalLength)}>
{photo.focalLengthFormatted}
</Link>}
{photo.focalLengthIn35MmFormatFormatted &&
<>
{' '}
@ -189,12 +198,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}
/>

View File

@ -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) {

View File

@ -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 (
@ -86,6 +101,7 @@ export default function PhotoLinks({
tag={tag}
camera={camera}
simulation={simulation}
focal={focal}
scroll={false}
prefetch
>
@ -97,6 +113,7 @@ export default function PhotoLinks({
tag={tag}
camera={camera}
simulation={simulation}
focal={focal}
scroll={false}
prefetch
>

View File

@ -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',

View File

@ -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,

View File

@ -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>
);
};

View File

@ -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',

View File

@ -92,7 +92,7 @@ export const toggleFavoritePhotoAction = async (
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
revalidateAllKeysAndPaths();
if (shouldRedirect) {
redirect(pathForPhoto(photoId));
redirect(pathForPhoto({ photo: photoId }));
}
}
});

View File

@ -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');

View File

@ -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

View File

@ -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';

View File

@ -7,7 +7,7 @@ import Link from 'next/link';
import { SHOW_REPO_LINK } from '@/site/config';
import RepoLink from '../components/RepoLink';
import { usePathname } from 'next/navigation';
import { isPathAdmin, isPathSignIn, pathForAdminPhotos } from './paths';
import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './paths';
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import { signOutAndRedirectAction } from '@/auth/actions';
import Spinner from '@/components/Spinner';
@ -57,7 +57,7 @@ export default function Footer() {
</>}
</>
: <>
<Link href={pathForAdminPhotos()}>
<Link href={PATH_ADMIN_PHOTOS}>
Admin
</Link>
{SHOW_REPO_LINK &&

View File

@ -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 },

View File

@ -5,7 +5,7 @@ import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
// HARD-CODED GLOBAL CONFIGURATION
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
export const SHOULD_DEBUG_SQL = true;
export const SHOULD_DEBUG_SQL = false;
// META / DOMAINS

View File

@ -18,12 +18,14 @@ export const PREFIX_PHOTO = '/p';
export const PREFIX_TAG = '/tag';
export const PREFIX_CAMERA = '/shot-on';
export const PREFIX_FILM_SIMULATION = '/film';
export const PREFIX_FOCAL_LENGTH = '/focal';
// Dynamic paths
const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`;
const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`;
const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`;
const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
// Admin paths
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
@ -39,7 +41,6 @@ export const PATH_API_PRESIGNED_URL = `${PATH_API_STORAGE}/presigned-url`;
// Modifiers
const SHARE = 'share';
const NEXT = 'next';
const EDIT = 'edit';
export const PATHS_ADMIN = [
@ -58,24 +59,21 @@ export const PATHS_TO_CACHE = [
PATH_TAG_DYNAMIC,
PATH_CAMERA_DYNAMIC,
PATH_FILM_SIMULATION_DYNAMIC,
PATH_FOCAL_LENGTH_DYNAMIC,
...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`;
const pathWithNext = (path: string, next?: number) =>
next !== undefined ? `${path}?${NEXT}=${next}` : path;
export const pathForRoot = (next?: number) =>
pathWithNext(PATH_ROOT, next);
export const pathForGrid = (next?: number) =>
pathWithNext(PATH_GRID, next);
export const pathForAdminPhotos = (next?: number) =>
pathWithNext(PATH_ADMIN_PHOTOS, next);
export const pathForAdminUploadUrl = (url: string) =>
`${PATH_ADMIN_UPLOADS}/${encodeURIComponent(url)}`;
@ -85,20 +83,18 @@ export const pathForAdminPhotoEdit = (photo: PhotoOrPhotoId) =>
export const pathForAdminTagEdit = (tag: string) =>
`${PATH_ADMIN_TAGS}/${tag}/${EDIT}`;
export const pathForOg = (next?: number) =>
pathWithNext(PATH_OG, next);
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,
) =>
export const pathForPhoto = ({
photo,
tag,
camera,
simulation,
focal,
}: PhotoPathParams) =>
typeof photo !== 'string' && photo.hidden
? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}`
: tag
@ -107,51 +103,39 @@ export const pathForPhoto = (
? `${pathForCamera(camera)}/${getPhotoId(photo)}`
: simulation
? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}`
: focal
? `${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, next?: number) =>
pathWithNext(
`${PREFIX_TAG}/${tag}`,
next,
);
export const pathForTag = (tag: string) =>
`${PREFIX_TAG}/${tag}`;
export const pathForTagShare = (tag: string) =>
`${pathForTag(tag)}/${SHARE}`;
export const pathForCamera = ({ make, model }: Camera, next?: number) =>
pathWithNext(
`${PREFIX_CAMERA}/${parameterize(make, true)}/${parameterize(model, true)}`,
next,
);
export const pathForCamera = ({ make, model }: Camera) =>
`${PREFIX_CAMERA}/${parameterize(make, true)}/${parameterize(model, true)}`;
export const pathForCameraShare = (camera: Camera) =>
`${pathForCamera(camera)}/${SHARE}`;
export const pathForFilmSimulation =
(simulation: FilmSimulation, next?: number) =>
pathWithNext(
`${PREFIX_FILM_SIMULATION}/${simulation}`,
next,
);
export const pathForFilmSimulation = (simulation: FilmSimulation) =>
`${PREFIX_FILM_SIMULATION}/${simulation}`;
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 pathForFocalLength = (focal: number) =>
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
export const pathForFocalLengthShare = (focal: number) =>
`${pathForFocalLength(focal)}/${SHARE}`;;
export const absolutePathForPhoto = (params: PhotoPathParams) =>
`${BASE_URL}${pathForPhoto(params)}`;
export const absolutePathForTag = (tag: string) =>
`${BASE_URL}${pathForTag(tag)}`;
@ -162,8 +146,11 @@ export const absolutePathForCamera= (camera: Camera) =>
export const absolutePathForFilmSimulation = (simulation: FilmSimulation) =>
`${BASE_URL}${pathForFilmSimulation(simulation)}`;
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`;
@ -175,6 +162,10 @@ export const absolutePathForFilmSimulationImage =
(simulation: FilmSimulation) =>
`${absolutePathForFilmSimulation(simulation)}/image`;
export const absolutePathForFocalLengthImage =
(focal: number) =>
`${absolutePathForFocalLength(focal)}/image`;
// p/[photoId]
export const isPathPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_PHOTO}/[^/]+/?$`).test(pathname);
@ -232,6 +223,23 @@ export const isPathFilmSimulationPhotoShare = (pathname = '') =>
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/[^/]+/${SHARE}/?$`)
.test(pathname);
// focal/[focal]
export const isPathFocalLength = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/?$`).test(pathname);
// focal/[focal]/share
export const isPathFocalLengthShare = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/${SHARE}/?$`).test(pathname);
// focal/[focal]/[photoId]
export const isPathFocalLengthPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/[^/]+/?$`).test(pathname);
// focal/[focal]/[photoId]/share
export const isPathFocalLengthPhotoShare = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/[^/]+/${SHARE}/?$`)
.test(pathname);
export const checkPathPrefix = (pathname = '', prefix: string) =>
pathname.toLowerCase().startsWith(prefix);
@ -260,6 +268,7 @@ export const getPathComponents = (pathname = ''): {
tag?: string
camera?: Camera
simulation?: FilmSimulation
focal?: number
} => {
const photoIdFromPhoto = pathname.match(
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
@ -269,6 +278,8 @@ export const getPathComponents = (pathname = ''): {
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/((?!${SHARE})[^/]+)`))?.[1];
const photoIdFromFilmSimulation = pathname.match(
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/((?!${SHARE})[^/]+)`))?.[1];
const photoIdFromFocalLength = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[0-9]+mm/((?!${SHARE})[^/]+)`))?.[1];
const tag = pathname.match(
new RegExp(`^${PREFIX_TAG}/([^/]+)`))?.[1];
const cameraMake = pathname.match(
@ -277,41 +288,57 @@ export const getPathComponents = (pathname = ''): {
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
const simulation = pathname.match(
new RegExp(`^${PREFIX_FILM_SIMULATION}/([^/]+)`))?.[1] as FilmSimulation;
const focalString = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/([0-9]+)mm`))?.[1];
const camera = cameraMake && cameraModel
? { make: cameraMake, model: cameraModel }
: undefined;
const focal = focalString ? parseInt(focalString) : undefined;
return {
photoId: (
photoIdFromPhoto ||
photoIdFromTag ||
photoIdFromCamera ||
photoIdFromFilmSimulation
photoIdFromFilmSimulation ||
photoIdFromFocalLength
),
tag,
camera,
simulation,
focal,
};
};
export const getEscapePath = (pathname?: string) => {
const { photoId, tag, camera, simulation } = getPathComponents(pathname);
const {
photoId,
tag,
camera,
simulation,
focal,
} = getPathComponents(pathname);
if (
(photoId && isPathPhoto(pathname)) ||
(tag && isPathTag(pathname)) ||
(camera && isPathCamera(pathname)) ||
(simulation && isPathFilmSimulation(pathname))
(simulation && isPathFilmSimulation(pathname)) ||
(focal && isPathFocalLength(pathname))
) {
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({ photo: photoId, focal });
} else if (photoId && isPathPhotoShare(pathname)) {
return pathForPhoto(photoId);
return pathForPhoto({ photo: photoId });
} else if (tag && (
isPathTagPhoto(pathname) ||
isPathTagShare(pathname)
@ -327,5 +354,10 @@ export const getEscapePath = (pathname?: string) => {
isPathFilmSimulationShare(pathname)
)) {
return pathForFilmSimulation(simulation);
} else if (focal && (
isPathFocalLengthPhoto(pathname) ||
isPathFocalLengthShare(pathname)
)) {
return pathForFocalLength(focal);
}
};

View File

@ -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;