commit
8992f3455b
@ -10,6 +10,10 @@ import {
|
|||||||
isPathFilmSimulationPhoto,
|
isPathFilmSimulationPhoto,
|
||||||
isPathFilmSimulationPhotoShare,
|
isPathFilmSimulationPhotoShare,
|
||||||
isPathFilmSimulationShare,
|
isPathFilmSimulationShare,
|
||||||
|
isPathFocalLength,
|
||||||
|
isPathFocalLengthPhoto,
|
||||||
|
isPathFocalLengthPhotoShare,
|
||||||
|
isPathFocalLengthShare,
|
||||||
isPathPhoto,
|
isPathPhoto,
|
||||||
isPathPhotoShare,
|
isPathPhotoShare,
|
||||||
isPathProtected,
|
isPathProtected,
|
||||||
@ -20,13 +24,15 @@ import {
|
|||||||
} from '@/site/paths';
|
} from '@/site/paths';
|
||||||
import { TAG_HIDDEN } from '@/tag';
|
import { TAG_HIDDEN } from '@/tag';
|
||||||
|
|
||||||
const PHOTO_ID = 'UsKSGcbt';
|
const PHOTO_ID = 'UsKSGcbt';
|
||||||
const TAG = 'tag-name';
|
const TAG = 'tag-name';
|
||||||
const CAMERA_MAKE = 'fujifilm';
|
const CAMERA_MAKE = 'fujifilm';
|
||||||
const CAMERA_MODEL = 'x-t1';
|
const CAMERA_MODEL = 'x-t1';
|
||||||
const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL };
|
const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL };
|
||||||
const FILM_SIMULATION = 'acros';
|
const FILM_SIMULATION = 'acros';
|
||||||
const SHARE = 'share';
|
const FOCAL_LENGTH = 90;
|
||||||
|
const FOCAL_LENGTH_STRING = `${FOCAL_LENGTH}mm`;
|
||||||
|
const SHARE = 'share';
|
||||||
|
|
||||||
const PATH_ROOT = '/';
|
const PATH_ROOT = '/';
|
||||||
const PATH_GRID = '/grid';
|
const PATH_GRID = '/grid';
|
||||||
@ -54,6 +60,11 @@ const PATH_FILM_SIMULATION = `/film/${FILM_SIMULATION}`;
|
|||||||
const PATH_FILM_SIMULATION_SHARE = `${PATH_FILM_SIMULATION}/${SHARE}`;
|
const PATH_FILM_SIMULATION_SHARE = `${PATH_FILM_SIMULATION}/${SHARE}`;
|
||||||
const PATH_FILM_SIMULATION_PHOTO = `${PATH_FILM_SIMULATION}/${PHOTO_ID}`;
|
const PATH_FILM_SIMULATION_PHOTO = `${PATH_FILM_SIMULATION}/${PHOTO_ID}`;
|
||||||
const PATH_FILM_SIMULATION_PHOTO_SHARE = `${PATH_FILM_SIMULATION_PHOTO}/${SHARE}`;
|
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', () => {
|
describe('Paths', () => {
|
||||||
it('can be protected', () => {
|
it('can be protected', () => {
|
||||||
@ -87,6 +98,10 @@ describe('Paths', () => {
|
|||||||
expect(isPathFilmSimulationShare(PATH_FILM_SIMULATION_SHARE)).toBe(true);
|
expect(isPathFilmSimulationShare(PATH_FILM_SIMULATION_SHARE)).toBe(true);
|
||||||
expect(isPathFilmSimulationPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(true);
|
expect(isPathFilmSimulationPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(true);
|
||||||
expect(isPathFilmSimulationPhotoShare(PATH_FILM_SIMULATION_PHOTO_SHARE)).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
|
// Negative
|
||||||
expect(isPathPhoto(PATH_TAG_PHOTO_SHARE)).toBe(false);
|
expect(isPathPhoto(PATH_TAG_PHOTO_SHARE)).toBe(false);
|
||||||
expect(isPathPhotoShare(PATH_TAG_PHOTO)).toBe(false);
|
expect(isPathPhotoShare(PATH_TAG_PHOTO)).toBe(false);
|
||||||
@ -102,8 +117,13 @@ describe('Paths', () => {
|
|||||||
expect(isPathFilmSimulationShare(PATH_TAG)).toBe(false);
|
expect(isPathFilmSimulationShare(PATH_TAG)).toBe(false);
|
||||||
expect(isPathFilmSimulationPhoto(PATH_PHOTO_SHARE)).toBe(false);
|
expect(isPathFilmSimulationPhoto(PATH_PHOTO_SHARE)).toBe(false);
|
||||||
expect(isPathFilmSimulationPhotoShare(PATH_PHOTO)).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', () => {
|
it('can be parsed', () => {
|
||||||
|
// Core
|
||||||
expect(getPathComponents(PATH_ROOT)).toEqual({});
|
expect(getPathComponents(PATH_ROOT)).toEqual({});
|
||||||
expect(getPathComponents(PATH_PHOTO)).toEqual({
|
expect(getPathComponents(PATH_PHOTO)).toEqual({
|
||||||
photoId: PHOTO_ID,
|
photoId: PHOTO_ID,
|
||||||
@ -111,6 +131,7 @@ describe('Paths', () => {
|
|||||||
expect(getPathComponents(PATH_PHOTO_SHARE)).toEqual({
|
expect(getPathComponents(PATH_PHOTO_SHARE)).toEqual({
|
||||||
photoId: PHOTO_ID,
|
photoId: PHOTO_ID,
|
||||||
});
|
});
|
||||||
|
// Tag
|
||||||
expect(getPathComponents(PATH_TAG)).toEqual({
|
expect(getPathComponents(PATH_TAG)).toEqual({
|
||||||
tag: TAG,
|
tag: TAG,
|
||||||
});
|
});
|
||||||
@ -125,6 +146,7 @@ describe('Paths', () => {
|
|||||||
photoId: PHOTO_ID,
|
photoId: PHOTO_ID,
|
||||||
tag: TAG,
|
tag: TAG,
|
||||||
});
|
});
|
||||||
|
// Camera
|
||||||
expect(getPathComponents(PATH_CAMERA)).toEqual({
|
expect(getPathComponents(PATH_CAMERA)).toEqual({
|
||||||
camera: CAMERA_OBJECT,
|
camera: CAMERA_OBJECT,
|
||||||
});
|
});
|
||||||
@ -139,6 +161,7 @@ describe('Paths', () => {
|
|||||||
photoId: PHOTO_ID,
|
photoId: PHOTO_ID,
|
||||||
camera: CAMERA_OBJECT,
|
camera: CAMERA_OBJECT,
|
||||||
});
|
});
|
||||||
|
// Film Simulation
|
||||||
expect(getPathComponents(PATH_FILM_SIMULATION)).toEqual({
|
expect(getPathComponents(PATH_FILM_SIMULATION)).toEqual({
|
||||||
simulation: FILM_SIMULATION,
|
simulation: FILM_SIMULATION,
|
||||||
});
|
});
|
||||||
@ -153,29 +176,49 @@ describe('Paths', () => {
|
|||||||
photoId: PHOTO_ID,
|
photoId: PHOTO_ID,
|
||||||
simulation: FILM_SIMULATION,
|
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', () => {
|
it('can be escaped', () => {
|
||||||
// Root views
|
// Root
|
||||||
expect(getEscapePath(PATH_ROOT)).toEqual(undefined);
|
expect(getEscapePath(PATH_ROOT)).toEqual(undefined);
|
||||||
expect(getEscapePath(PATH_GRID)).toEqual(undefined);
|
expect(getEscapePath(PATH_GRID)).toEqual(undefined);
|
||||||
expect(getEscapePath(PATH_ADMIN)).toEqual(undefined);
|
expect(getEscapePath(PATH_ADMIN)).toEqual(undefined);
|
||||||
// Photo views
|
// Photo
|
||||||
expect(getEscapePath(PATH_PHOTO)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_PHOTO)).toEqual(PATH_GRID);
|
||||||
expect(getEscapePath(PATH_PHOTO_SHARE)).toEqual(PATH_PHOTO);
|
expect(getEscapePath(PATH_PHOTO_SHARE)).toEqual(PATH_PHOTO);
|
||||||
// Tag views
|
// Tag
|
||||||
expect(getEscapePath(PATH_TAG)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_TAG)).toEqual(PATH_GRID);
|
||||||
expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG);
|
expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG);
|
||||||
expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG);
|
expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG);
|
||||||
expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO);
|
expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO);
|
||||||
// Camera views
|
// Camera
|
||||||
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_GRID);
|
||||||
expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA);
|
expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA);
|
||||||
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
|
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
|
||||||
expect(getEscapePath(PATH_CAMERA_PHOTO_SHARE)).toEqual(PATH_CAMERA_PHOTO);
|
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)).toEqual(PATH_GRID);
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION_SHARE)).toEqual(PATH_FILM_SIMULATION);
|
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)).toEqual(PATH_FILM_SIMULATION);
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO_SHARE)).toEqual(PATH_FILM_SIMULATION_PHOTO);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export default function AdminPhotoMenuClient({
|
|||||||
const isFav = isPhotoFav(photo);
|
const isFav = isPhotoFav(photo);
|
||||||
const path = usePathname();
|
const path = usePathname();
|
||||||
const shouldRedirectFav = isPathFavs(path) && isFav;
|
const shouldRedirectFav = isPathFavs(path) && isFav;
|
||||||
const shouldRedirectDelete = pathForPhoto(photo.id) === path;
|
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isUserSignedIn
|
isUserSignedIn
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default function AdminPhotosTable({
|
|||||||
<div className="flex flex-col lg:flex-row">
|
<div className="flex flex-col lg:flex-row">
|
||||||
<Link
|
<Link
|
||||||
key={photo.id}
|
key={photo.id}
|
||||||
href={pathForPhoto(photo)}
|
href={pathForPhoto({ photo })}
|
||||||
className="lg:w-[50%] flex items-center gap-2"
|
className="lg:w-[50%] flex items-center gap-2"
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export async function generateMetadata({
|
|||||||
const title = titleForPhoto(photo);
|
const title = titleForPhoto(photo);
|
||||||
const description = descriptionForPhoto(photo);
|
const description = descriptionForPhoto(photo);
|
||||||
const images = absolutePathForPhotoImage(photo);
|
const images = absolutePathForPhotoImage(photo);
|
||||||
const url = absolutePathForPhoto(photo, simulation);
|
const url = absolutePathForPhoto({ photo, simulation });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
86
src/app/focal/[focal]/[photoId]/layout.tsx
Normal file
86
src/app/focal/[focal]/[photoId]/layout.tsx
Normal 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,
|
||||||
|
}} />
|
||||||
|
</>;
|
||||||
|
}
|
||||||
3
src/app/focal/[focal]/[photoId]/page.tsx
Normal file
3
src/app/focal/[focal]/[photoId]/page.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function Page() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
19
src/app/focal/[focal]/[photoId]/share/page.tsx
Normal file
19
src/app/focal/[focal]/[photoId]/share/page.tsx
Normal 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 }} />;
|
||||||
|
}
|
||||||
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 { 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 }} />
|
||||||
|
);
|
||||||
|
}
|
||||||
70
src/app/focal/[focal]/share/page.tsx
Normal file
70
src/app/focal/[focal]/share/page.tsx
Normal 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
|
||||||
|
/>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ export async function generateMetadata({
|
|||||||
const title = titleForPhoto(photo);
|
const title = titleForPhoto(photo);
|
||||||
const description = descriptionForPhoto(photo);
|
const description = descriptionForPhoto(photo);
|
||||||
const images = absolutePathForPhotoImage(photo);
|
const images = absolutePathForPhotoImage(photo);
|
||||||
const url = absolutePathForPhoto(photo);
|
const url = absolutePathForPhoto({ photo });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@ -44,11 +44,10 @@ export async function generateMetadata({
|
|||||||
const title = titleForPhoto(photo);
|
const title = titleForPhoto(photo);
|
||||||
const description = descriptionForPhoto(photo);
|
const description = descriptionForPhoto(photo);
|
||||||
const images = absolutePathForPhotoImage(photo);
|
const images = absolutePathForPhotoImage(photo);
|
||||||
const url = absolutePathForPhoto(
|
const url = absolutePathForPhoto({
|
||||||
photo,
|
photo,
|
||||||
undefined,
|
camera: cameraFromPhoto(photo, { make, model }),
|
||||||
cameraFromPhoto(photo, { make, model }),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export async function generateMetadata({
|
|||||||
const title = titleForPhoto(photo);
|
const title = titleForPhoto(photo);
|
||||||
const description = descriptionForPhoto(photo);
|
const description = descriptionForPhoto(photo);
|
||||||
const images = absolutePathForPhotoImage(photo);
|
const images = absolutePathForPhotoImage(photo);
|
||||||
const url = absolutePathForPhoto(photo, tag);
|
const url = absolutePathForPhoto({ photo, tag });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export async function generateMetadata({
|
|||||||
|
|
||||||
const title = titleForPhoto(photo);
|
const title = titleForPhoto(photo);
|
||||||
const description = descriptionForPhoto(photo);
|
const description = descriptionForPhoto(photo);
|
||||||
const url = absolutePathForPhoto(photo, TAG_HIDDEN);
|
const url = absolutePathForPhoto({ photo, tag: TAG_HIDDEN });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@ -147,7 +147,7 @@ export default function CommandKClient({
|
|||||||
keywords: getKeywordsForPhoto(photo),
|
keywords: getKeywordsForPhoto(photo),
|
||||||
annotation: <PhotoDate {...{ photo }} />,
|
annotation: <PhotoDate {...{ photo }} />,
|
||||||
accessory: <PhotoSmall photo={photo} />,
|
accessory: <PhotoSmall photo={photo} />,
|
||||||
path: pathForPhoto(photo),
|
path: pathForPhoto({ photo }),
|
||||||
})),
|
})),
|
||||||
}]
|
}]
|
||||||
: []);
|
: []);
|
||||||
|
|||||||
38
src/focal/FocalLengthHeader.tsx
Normal file
38
src/focal/FocalLengthHeader.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/focal/FocalLengthOGTile.tsx
Normal file
50
src/focal/FocalLengthOGTile.tsx
Normal 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,
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
};
|
||||||
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,
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
}
|
||||||
26
src/focal/FocalLengthShareModal.tsx
Normal file
26
src/focal/FocalLengthShareModal.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
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 getPhotosFocalLengthDataCached = ({
|
||||||
|
focal,
|
||||||
|
limit,
|
||||||
|
}: {
|
||||||
|
focal: number,
|
||||||
|
limit?: number,
|
||||||
|
}) =>
|
||||||
|
Promise.all([
|
||||||
|
getPhotosCached({ focal, limit }),
|
||||||
|
getPhotosMetaCached({ focal }),
|
||||||
|
]);
|
||||||
|
|
||||||
59
src/focal/index.ts
Normal file
59
src/focal/index.ts
Normal 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),
|
||||||
|
});
|
||||||
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 @@ import { FilmSimulation } from '@/simulation';
|
|||||||
import FilmSimulationHeader from '@/simulation/FilmSimulationHeader';
|
import FilmSimulationHeader from '@/simulation/FilmSimulationHeader';
|
||||||
import { TAG_HIDDEN } from '@/tag';
|
import { TAG_HIDDEN } from '@/tag';
|
||||||
import HiddenHeader from '@/tag/HiddenHeader';
|
import HiddenHeader from '@/tag/HiddenHeader';
|
||||||
|
import FocalLengthHeader from '@/focal/FocalLengthHeader';
|
||||||
|
|
||||||
export default function PhotoDetailPage({
|
export default function PhotoDetailPage({
|
||||||
photo,
|
photo,
|
||||||
@ -20,6 +21,7 @@ export default function PhotoDetailPage({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
indexNumber,
|
indexNumber,
|
||||||
count,
|
count,
|
||||||
dateRange,
|
dateRange,
|
||||||
@ -30,6 +32,7 @@ export default function PhotoDetailPage({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
indexNumber?: number
|
indexNumber?: number
|
||||||
count?: number
|
count?: number
|
||||||
dateRange?: PhotoDateRange
|
dateRange?: PhotoDateRange
|
||||||
@ -82,6 +85,19 @@ export default function PhotoDetailPage({
|
|||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
/>}
|
/>}
|
||||||
/>}
|
/>}
|
||||||
|
{focal &&
|
||||||
|
<SiteGrid
|
||||||
|
className="mt-4 mb-8"
|
||||||
|
contentMain={
|
||||||
|
<FocalLengthHeader
|
||||||
|
focal={focal}
|
||||||
|
photos={photos}
|
||||||
|
selectedPhoto={photo}
|
||||||
|
indexNumber={indexNumber}
|
||||||
|
count={count}
|
||||||
|
dateRange={dateRange}
|
||||||
|
/>}
|
||||||
|
/>}
|
||||||
<AnimateItems
|
<AnimateItems
|
||||||
className="md:mb-8"
|
className="md:mb-8"
|
||||||
animateFromAppState
|
animateFromAppState
|
||||||
@ -109,6 +125,7 @@ export default function PhotoDetailPage({
|
|||||||
tag={tag}
|
tag={tag}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
simulation={simulation}
|
simulation={simulation}
|
||||||
|
focal={focal}
|
||||||
animateOnFirstLoadOnly
|
animateOnFirstLoadOnly
|
||||||
/>}
|
/>}
|
||||||
contentSide={<AnimateItems
|
contentSide={<AnimateItems
|
||||||
@ -130,6 +147,7 @@ export default function PhotoDetailPage({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
}} />
|
}} />
|
||||||
</div>,
|
</div>,
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export default function PhotoGrid({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
photoPriority,
|
photoPriority,
|
||||||
fast,
|
fast,
|
||||||
animate = true,
|
animate = true,
|
||||||
@ -28,6 +29,7 @@ export default function PhotoGrid({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
photoPriority?: boolean
|
photoPriority?: boolean
|
||||||
fast?: boolean
|
fast?: boolean
|
||||||
animate?: boolean
|
animate?: boolean
|
||||||
@ -77,6 +79,7 @@ export default function PhotoGrid({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
selected: photo.id === selectedPhoto?.id,
|
selected: photo.id === selectedPhoto?.id,
|
||||||
priority: photoPriority,
|
priority: photoPriority,
|
||||||
onVisible: index === photos.length - 1
|
onVisible: index === photos.length - 1
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export default function PhotoGridInfinite({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
animateOnFirstLoadOnly,
|
animateOnFirstLoadOnly,
|
||||||
}: {
|
}: {
|
||||||
cacheKey: string
|
cacheKey: string
|
||||||
@ -21,6 +22,7 @@ export default function PhotoGridInfinite({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
animateOnFirstLoadOnly?: boolean
|
animateOnFirstLoadOnly?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@ -39,6 +41,7 @@ export default function PhotoGridInfinite({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
onLastPhotoVisible,
|
onLastPhotoVisible,
|
||||||
animateOnFirstLoadOnly,
|
animateOnFirstLoadOnly,
|
||||||
}} />}
|
}} />}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export default function PhotoGridPage({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
animateOnFirstLoadOnly,
|
animateOnFirstLoadOnly,
|
||||||
header,
|
header,
|
||||||
sidebar,
|
sidebar,
|
||||||
@ -27,6 +28,7 @@ export default function PhotoGridPage({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
animateOnFirstLoadOnly?: boolean
|
animateOnFirstLoadOnly?: boolean
|
||||||
header?: JSX.Element
|
header?: JSX.Element
|
||||||
sidebar?: JSX.Element
|
sidebar?: JSX.Element
|
||||||
@ -58,6 +60,7 @@ export default function PhotoGridPage({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
animateOnFirstLoadOnly,
|
animateOnFirstLoadOnly,
|
||||||
onAnimationComplete,
|
onAnimationComplete,
|
||||||
}} />
|
}} />
|
||||||
@ -69,6 +72,7 @@ export default function PhotoGridPage({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
animateOnFirstLoadOnly,
|
animateOnFirstLoadOnly,
|
||||||
}} />}
|
}} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,7 +11,11 @@ import SiteGrid from '@/components/SiteGrid';
|
|||||||
import ImageLarge from '@/components/image/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 {
|
||||||
|
pathForFocalLength,
|
||||||
|
pathForPhoto,
|
||||||
|
pathForPhotoShare,
|
||||||
|
} from '@/site/paths';
|
||||||
import PhotoTags from '@/tag/PhotoTags';
|
import PhotoTags from '@/tag/PhotoTags';
|
||||||
import ShareButton from '@/components/ShareButton';
|
import ShareButton from '@/components/ShareButton';
|
||||||
import PhotoCamera from '../camera/PhotoCamera';
|
import PhotoCamera from '../camera/PhotoCamera';
|
||||||
@ -40,6 +44,7 @@ export default function PhotoLarge({
|
|||||||
shouldShareTag,
|
shouldShareTag,
|
||||||
shouldShareCamera,
|
shouldShareCamera,
|
||||||
shouldShareSimulation,
|
shouldShareSimulation,
|
||||||
|
shouldShareFocalLength,
|
||||||
shouldScrollOnShare,
|
shouldScrollOnShare,
|
||||||
onVisible,
|
onVisible,
|
||||||
}: {
|
}: {
|
||||||
@ -54,6 +59,7 @@ export default function PhotoLarge({
|
|||||||
shouldShareTag?: boolean
|
shouldShareTag?: boolean
|
||||||
shouldShareCamera?: boolean
|
shouldShareCamera?: boolean
|
||||||
shouldShareSimulation?: boolean
|
shouldShareSimulation?: boolean
|
||||||
|
shouldShareFocalLength?: boolean
|
||||||
shouldScrollOnShare?: boolean
|
shouldScrollOnShare?: boolean
|
||||||
onVisible?: () => void
|
onVisible?: () => void
|
||||||
}) {
|
}) {
|
||||||
@ -76,7 +82,7 @@ export default function PhotoLarge({
|
|||||||
containerRef={ref}
|
containerRef={ref}
|
||||||
contentMain={
|
contentMain={
|
||||||
<Link
|
<Link
|
||||||
href={pathForPhoto(photo)}
|
href={pathForPhoto({ photo })}
|
||||||
className={clsx(arePhotosMatted &&
|
className={clsx(arePhotosMatted &&
|
||||||
'flex items-center aspect-[3/2] bg-gray-100',
|
'flex items-center aspect-[3/2] bg-gray-100',
|
||||||
)}
|
)}
|
||||||
@ -153,7 +159,10 @@ export default function PhotoLarge({
|
|||||||
<>
|
<>
|
||||||
<ul className="text-medium">
|
<ul className="text-medium">
|
||||||
<li>
|
<li>
|
||||||
{photo.focalLengthFormatted}
|
{photo.focalLength &&
|
||||||
|
<Link href={pathForFocalLength(photo.focalLength)}>
|
||||||
|
{photo.focalLengthFormatted}
|
||||||
|
</Link>}
|
||||||
{photo.focalLengthIn35MmFormatFormatted &&
|
{photo.focalLengthIn35MmFormatFormatted &&
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
@ -189,12 +198,14 @@ export default function PhotoLarge({
|
|||||||
'md:translate-x-[-2.5px]',
|
'md:translate-x-[-2.5px]',
|
||||||
'translate-y-[1.5px] md:translate-y-0',
|
'translate-y-[1.5px] md:translate-y-0',
|
||||||
)}
|
)}
|
||||||
path={pathForPhotoShare(
|
path={pathForPhotoShare({
|
||||||
photo,
|
photo,
|
||||||
shouldShareTag ? primaryTag : undefined,
|
tag: shouldShareTag ? primaryTag : undefined,
|
||||||
shouldShareCamera ? camera : undefined,
|
camera: shouldShareCamera ? camera : undefined,
|
||||||
shouldShareSimulation ? photo.filmSimulation : undefined,
|
// eslint-disable-next-line max-len
|
||||||
)}
|
simulation: shouldShareSimulation ? photo.filmSimulation : undefined,
|
||||||
|
focal: shouldShareFocalLength ? photo.focalLength : undefined,
|
||||||
|
})}
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
shouldScroll={shouldScrollOnShare}
|
shouldScroll={shouldScrollOnShare}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export default function PhotoLink({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
scroll,
|
scroll,
|
||||||
prefetch,
|
prefetch,
|
||||||
nextPhotoAnimation,
|
nextPhotoAnimation,
|
||||||
@ -25,6 +26,7 @@ export default function PhotoLink({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
scroll?: boolean
|
scroll?: boolean
|
||||||
prefetch?: boolean
|
prefetch?: boolean
|
||||||
nextPhotoAnimation?: AnimationConfig
|
nextPhotoAnimation?: AnimationConfig
|
||||||
@ -36,7 +38,7 @@ export default function PhotoLink({
|
|||||||
return (
|
return (
|
||||||
photo
|
photo
|
||||||
? <Link
|
? <Link
|
||||||
href={pathForPhoto(photo, tag, camera, simulation)}
|
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||||
prefetch={prefetch}
|
prefetch={prefetch}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (nextPhotoAnimation) {
|
if (nextPhotoAnimation) {
|
||||||
|
|||||||
@ -21,12 +21,14 @@ export default function PhotoLinks({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
}: {
|
}: {
|
||||||
photo: Photo
|
photo: Photo
|
||||||
photos: Photo[]
|
photos: Photo[]
|
||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -47,7 +49,13 @@ export default function PhotoLinks({
|
|||||||
if (previousPhoto) {
|
if (previousPhoto) {
|
||||||
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
setNextPhotoAnimation?.(ANIMATION_RIGHT);
|
||||||
router.push(
|
router.push(
|
||||||
pathForPhoto(previousPhoto, tag, camera, simulation),
|
pathForPhoto({
|
||||||
|
photo: previousPhoto,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
}),
|
||||||
{ scroll: false },
|
{ scroll: false },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -57,7 +65,13 @@ export default function PhotoLinks({
|
|||||||
if (nextPhoto) {
|
if (nextPhoto) {
|
||||||
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
setNextPhotoAnimation?.(ANIMATION_LEFT);
|
||||||
router.push(
|
router.push(
|
||||||
pathForPhoto(nextPhoto, tag, camera, simulation),
|
pathForPhoto({
|
||||||
|
photo: nextPhoto,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
}),
|
||||||
{ scroll: false },
|
{ scroll: false },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -76,6 +90,7 @@ export default function PhotoLinks({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -86,6 +101,7 @@ export default function PhotoLinks({
|
|||||||
tag={tag}
|
tag={tag}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
simulation={simulation}
|
simulation={simulation}
|
||||||
|
focal={focal}
|
||||||
scroll={false}
|
scroll={false}
|
||||||
prefetch
|
prefetch
|
||||||
>
|
>
|
||||||
@ -97,6 +113,7 @@ export default function PhotoLinks({
|
|||||||
tag={tag}
|
tag={tag}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
simulation={simulation}
|
simulation={simulation}
|
||||||
|
focal={focal}
|
||||||
scroll={false}
|
scroll={false}
|
||||||
prefetch
|
prefetch
|
||||||
>
|
>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export default function PhotoMedium({
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
selected,
|
selected,
|
||||||
priority,
|
priority,
|
||||||
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
||||||
@ -26,6 +27,7 @@ export default function PhotoMedium({
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
priority?: boolean
|
priority?: boolean
|
||||||
prefetch?: boolean
|
prefetch?: boolean
|
||||||
@ -39,7 +41,7 @@ export default function PhotoMedium({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
ref={ref}
|
ref={ref}
|
||||||
href={pathForPhoto(photo, tag, camera, simulation)}
|
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'active:brightness-75',
|
'active:brightness-75',
|
||||||
selected && 'brightness-50',
|
selected && 'brightness-50',
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export default function PhotoOGTile({
|
|||||||
<OGTile {...{
|
<OGTile {...{
|
||||||
title: titleForPhoto(photo),
|
title: titleForPhoto(photo),
|
||||||
description: descriptionForPhoto(photo),
|
description: descriptionForPhoto(photo),
|
||||||
path: pathForPhoto(photo),
|
path: pathForPhoto({ photo }),
|
||||||
pathImageAbsolute: absolutePathForPhotoImage(photo),
|
pathImageAbsolute: absolutePathForPhotoImage(photo),
|
||||||
loadingState: loadingStateExternal,
|
loadingState: loadingStateExternal,
|
||||||
onLoad,
|
onLoad,
|
||||||
|
|||||||
@ -5,24 +5,20 @@ import ShareModal from '@/components/ShareModal';
|
|||||||
import { Camera } from '@/camera';
|
import { Camera } from '@/camera';
|
||||||
import { FilmSimulation } from '@/simulation';
|
import { FilmSimulation } from '@/simulation';
|
||||||
|
|
||||||
export default function PhotoShareModal({
|
export default function PhotoShareModal(props: {
|
||||||
photo,
|
|
||||||
tag,
|
|
||||||
camera,
|
|
||||||
simulation,
|
|
||||||
}: {
|
|
||||||
photo: Photo
|
photo: Photo
|
||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ShareModal
|
<ShareModal
|
||||||
title="Share Photo"
|
title="Share Photo"
|
||||||
pathShare={absolutePathForPhoto(photo, tag, camera, simulation)}
|
pathShare={absolutePathForPhoto(props)}
|
||||||
pathClose={pathForPhoto(photo, tag, camera, simulation)}
|
pathClose={pathForPhoto(props)}
|
||||||
>
|
>
|
||||||
<PhotoOGTile photo={photo} />
|
<PhotoOGTile photo={props.photo} />
|
||||||
</ShareModal>
|
</ShareModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,10 +6,15 @@ import { pathForPhoto } from '@/site/paths';
|
|||||||
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';
|
||||||
|
import { Camera } from '@/camera';
|
||||||
|
import { FilmSimulation } from '@/simulation';
|
||||||
|
|
||||||
export default function PhotoSmall({
|
export default function PhotoSmall({
|
||||||
photo,
|
photo,
|
||||||
tag,
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
selected,
|
selected,
|
||||||
className,
|
className,
|
||||||
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
||||||
@ -17,6 +22,9 @@ export default function PhotoSmall({
|
|||||||
}: {
|
}: {
|
||||||
photo: Photo
|
photo: Photo
|
||||||
tag?: string
|
tag?: string
|
||||||
|
camera?: Camera
|
||||||
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
prefetch?: boolean
|
prefetch?: boolean
|
||||||
@ -29,7 +37,7 @@ export default function PhotoSmall({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
ref={ref}
|
ref={ref}
|
||||||
href={pathForPhoto(photo, tag)}
|
href={pathForPhoto({ photo, tag, camera, simulation, focal })}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
className,
|
||||||
'active:brightness-75',
|
'active:brightness-75',
|
||||||
|
|||||||
@ -92,7 +92,7 @@ export const toggleFavoritePhotoAction = async (
|
|||||||
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
|
await updatePhoto(convertPhotoToPhotoDbInsert(photo));
|
||||||
revalidateAllKeysAndPaths();
|
revalidateAllKeysAndPaths();
|
||||||
if (shouldRedirect) {
|
if (shouldRedirect) {
|
||||||
redirect(pathForPhoto(photoId));
|
redirect(pathForPhoto({ photo: photoId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export const revalidatePhoto = (photoId: string) => {
|
|||||||
revalidateCamerasKey();
|
revalidateCamerasKey();
|
||||||
revalidateFilmSimulationsKey();
|
revalidateFilmSimulationsKey();
|
||||||
// Paths
|
// Paths
|
||||||
revalidatePath(pathForPhoto(photoId), 'layout');
|
revalidatePath(pathForPhoto({ photo: photoId }), 'layout');
|
||||||
revalidatePath(PATH_ROOT, 'layout');
|
revalidatePath(PATH_ROOT, 'layout');
|
||||||
revalidatePath(PATH_GRID, 'layout');
|
revalidatePath(PATH_GRID, 'layout');
|
||||||
revalidatePath(PREFIX_TAG, 'layout');
|
revalidatePath(PREFIX_TAG, 'layout');
|
||||||
|
|||||||
@ -7,16 +7,17 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
|||||||
export const PHOTO_DEFAULT_LIMIT = 100;
|
export const PHOTO_DEFAULT_LIMIT = 100;
|
||||||
|
|
||||||
export type GetPhotosOptions = {
|
export type GetPhotosOptions = {
|
||||||
sortBy?: 'createdAt' | 'takenAt' | 'priority';
|
sortBy?: 'createdAt' | 'takenAt' | 'priority'
|
||||||
limit?: number;
|
limit?: number
|
||||||
offset?: number;
|
offset?: number
|
||||||
query?: string;
|
query?: string
|
||||||
tag?: string;
|
tag?: string
|
||||||
camera?: Camera;
|
camera?: Camera
|
||||||
simulation?: FilmSimulation;
|
simulation?: FilmSimulation
|
||||||
takenBefore?: Date;
|
focal?: number
|
||||||
takenAfterInclusive?: Date;
|
takenBefore?: Date
|
||||||
hidden?: 'exclude' | 'include' | 'only';
|
takenAfterInclusive?: Date
|
||||||
|
hidden?: 'exclude' | 'include' | 'only'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWheresFromOptions = (
|
export const getWheresFromOptions = (
|
||||||
@ -31,6 +32,7 @@ export const getWheresFromOptions = (
|
|||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const wheres = [] as string[];
|
const wheres = [] as string[];
|
||||||
@ -73,6 +75,10 @@ export const getWheresFromOptions = (
|
|||||||
wheres.push(`film_simulation=$${valuesIndex++}`);
|
wheres.push(`film_simulation=$${valuesIndex++}`);
|
||||||
wheresValues.push(simulation);
|
wheresValues.push(simulation);
|
||||||
}
|
}
|
||||||
|
if (focal) {
|
||||||
|
wheres.push(`focal_length=$${valuesIndex++}`);
|
||||||
|
wheresValues.push(focal);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wheres: wheres.length > 0
|
wheres: wheres.length > 0
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { formatFocalLength } from '@/focal';
|
||||||
import { getNextImageUrlForRequest } from '@/services/next-image';
|
import { getNextImageUrlForRequest } from '@/services/next-image';
|
||||||
import { FilmSimulation } from '@/simulation';
|
import { FilmSimulation } from '@/simulation';
|
||||||
import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config';
|
import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config';
|
||||||
@ -8,7 +9,6 @@ import {
|
|||||||
formatIso,
|
formatIso,
|
||||||
formatExposureCompensation,
|
formatExposureCompensation,
|
||||||
formatExposureTime,
|
formatExposureTime,
|
||||||
formatFocalLength,
|
|
||||||
} from '@/utility/exif';
|
} from '@/utility/exif';
|
||||||
import camelcaseKeys from 'camelcase-keys';
|
import camelcaseKeys from 'camelcase-keys';
|
||||||
import { isBefore } from 'date-fns';
|
import { isBefore } from 'date-fns';
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const sortFilmSimulationsWithCount = (
|
|||||||
|
|
||||||
export const titleForFilmSimulation = (
|
export const titleForFilmSimulation = (
|
||||||
simulation: FilmSimulation,
|
simulation: FilmSimulation,
|
||||||
photos:Photo[],
|
photos: Photo[],
|
||||||
explicitCount?: number,
|
explicitCount?: number,
|
||||||
) => [
|
) => [
|
||||||
labelForFilmSimulation(simulation).large,
|
labelForFilmSimulation(simulation).large,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import Link from 'next/link';
|
|||||||
import { SHOW_REPO_LINK } from '@/site/config';
|
import { SHOW_REPO_LINK } from '@/site/config';
|
||||||
import RepoLink from '../components/RepoLink';
|
import RepoLink from '../components/RepoLink';
|
||||||
import { usePathname } from 'next/navigation';
|
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 SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
import { signOutAndRedirectAction } from '@/auth/actions';
|
import { signOutAndRedirectAction } from '@/auth/actions';
|
||||||
import Spinner from '@/components/Spinner';
|
import Spinner from '@/components/Spinner';
|
||||||
@ -57,7 +57,7 @@ export default function Footer() {
|
|||||||
</>}
|
</>}
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<Link href={pathForAdminPhotos()}>
|
<Link href={PATH_ADMIN_PHOTOS}>
|
||||||
Admin
|
Admin
|
||||||
</Link>
|
</Link>
|
||||||
{SHOW_REPO_LINK &&
|
{SHOW_REPO_LINK &&
|
||||||
|
|||||||
@ -30,7 +30,7 @@ interface PublicApiPhoto {
|
|||||||
export const formatPhotoForApi = (photo: Photo): PublicApiPhoto => ({
|
export const formatPhotoForApi = (photo: Photo): PublicApiPhoto => ({
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
title: photo.title,
|
title: photo.title,
|
||||||
url: absolutePathForPhoto(photo),
|
url: absolutePathForPhoto({ photo }),
|
||||||
...photo.make && { make: photo.make },
|
...photo.make && { make: photo.make },
|
||||||
...photo.model && { model: photo.model },
|
...photo.model && { model: photo.model },
|
||||||
...photo.tags.length > 0 && { tags: photo.tags },
|
...photo.tags.length > 0 && { tags: photo.tags },
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
|
|||||||
// HARD-CODED GLOBAL CONFIGURATION
|
// HARD-CODED GLOBAL CONFIGURATION
|
||||||
|
|
||||||
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
|
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
|
||||||
export const SHOULD_DEBUG_SQL = true;
|
export const SHOULD_DEBUG_SQL = false;
|
||||||
|
|
||||||
// META / DOMAINS
|
// META / DOMAINS
|
||||||
|
|
||||||
|
|||||||
@ -6,24 +6,26 @@ import { parameterize } from '@/utility/string';
|
|||||||
import { TAG_HIDDEN } from '@/tag';
|
import { TAG_HIDDEN } from '@/tag';
|
||||||
|
|
||||||
// Core paths
|
// Core paths
|
||||||
export const PATH_ROOT = '/';
|
export const PATH_ROOT = '/';
|
||||||
export const PATH_GRID = '/grid';
|
export const PATH_GRID = '/grid';
|
||||||
export const PATH_ADMIN = '/admin';
|
export const PATH_ADMIN = '/admin';
|
||||||
export const PATH_API = '/api';
|
export const PATH_API = '/api';
|
||||||
export const PATH_SIGN_IN = '/sign-in';
|
export const PATH_SIGN_IN = '/sign-in';
|
||||||
export const PATH_OG = '/og';
|
export const PATH_OG = '/og';
|
||||||
|
|
||||||
// Path prefixes
|
// Path prefixes
|
||||||
export const PREFIX_PHOTO = '/p';
|
export const PREFIX_PHOTO = '/p';
|
||||||
export const PREFIX_TAG = '/tag';
|
export const PREFIX_TAG = '/tag';
|
||||||
export const PREFIX_CAMERA = '/shot-on';
|
export const PREFIX_CAMERA = '/shot-on';
|
||||||
export const PREFIX_FILM_SIMULATION = '/film';
|
export const PREFIX_FILM_SIMULATION = '/film';
|
||||||
|
export const PREFIX_FOCAL_LENGTH = '/focal';
|
||||||
|
|
||||||
// Dynamic paths
|
// Dynamic paths
|
||||||
const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
|
const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
|
||||||
const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`;
|
const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`;
|
||||||
const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`;
|
const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`;
|
||||||
const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`;
|
const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`;
|
||||||
|
const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
|
||||||
|
|
||||||
// Admin paths
|
// Admin paths
|
||||||
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
|
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
|
||||||
@ -39,7 +41,6 @@ export const PATH_API_PRESIGNED_URL = `${PATH_API_STORAGE}/presigned-url`;
|
|||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
const SHARE = 'share';
|
const SHARE = 'share';
|
||||||
const NEXT = 'next';
|
|
||||||
const EDIT = 'edit';
|
const EDIT = 'edit';
|
||||||
|
|
||||||
export const PATHS_ADMIN = [
|
export const PATHS_ADMIN = [
|
||||||
@ -58,24 +59,21 @@ export const PATHS_TO_CACHE = [
|
|||||||
PATH_TAG_DYNAMIC,
|
PATH_TAG_DYNAMIC,
|
||||||
PATH_CAMERA_DYNAMIC,
|
PATH_CAMERA_DYNAMIC,
|
||||||
PATH_FILM_SIMULATION_DYNAMIC,
|
PATH_FILM_SIMULATION_DYNAMIC,
|
||||||
|
PATH_FOCAL_LENGTH_DYNAMIC,
|
||||||
...PATHS_ADMIN,
|
...PATHS_ADMIN,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface PhotoPathParams {
|
||||||
|
photo: PhotoOrPhotoId
|
||||||
|
tag?: string
|
||||||
|
camera?: Camera
|
||||||
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
|
}
|
||||||
|
|
||||||
// Absolute paths
|
// Absolute paths
|
||||||
export const ABSOLUTE_PATH_FOR_HOME_IMAGE = `${BASE_URL}/home-image`;
|
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) =>
|
export const pathForAdminUploadUrl = (url: string) =>
|
||||||
`${PATH_ADMIN_UPLOADS}/${encodeURIComponent(url)}`;
|
`${PATH_ADMIN_UPLOADS}/${encodeURIComponent(url)}`;
|
||||||
|
|
||||||
@ -85,20 +83,18 @@ export const pathForAdminPhotoEdit = (photo: PhotoOrPhotoId) =>
|
|||||||
export const pathForAdminTagEdit = (tag: string) =>
|
export const pathForAdminTagEdit = (tag: string) =>
|
||||||
`${PATH_ADMIN_TAGS}/${tag}/${EDIT}`;
|
`${PATH_ADMIN_TAGS}/${tag}/${EDIT}`;
|
||||||
|
|
||||||
export const pathForOg = (next?: number) =>
|
|
||||||
pathWithNext(PATH_OG, next);
|
|
||||||
|
|
||||||
type PhotoOrPhotoId = Photo | string;
|
type PhotoOrPhotoId = Photo | string;
|
||||||
|
|
||||||
const getPhotoId = (photoOrPhotoId: PhotoOrPhotoId) =>
|
const getPhotoId = (photoOrPhotoId: PhotoOrPhotoId) =>
|
||||||
typeof photoOrPhotoId === 'string' ? photoOrPhotoId : photoOrPhotoId.id;
|
typeof photoOrPhotoId === 'string' ? photoOrPhotoId : photoOrPhotoId.id;
|
||||||
|
|
||||||
export const pathForPhoto = (
|
export const pathForPhoto = ({
|
||||||
photo: PhotoOrPhotoId,
|
photo,
|
||||||
tag?: string,
|
tag,
|
||||||
camera?: Camera,
|
camera,
|
||||||
simulation?: FilmSimulation,
|
simulation,
|
||||||
) =>
|
focal,
|
||||||
|
}: PhotoPathParams) =>
|
||||||
typeof photo !== 'string' && photo.hidden
|
typeof photo !== 'string' && photo.hidden
|
||||||
? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}`
|
? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}`
|
||||||
: tag
|
: tag
|
||||||
@ -107,51 +103,39 @@ export const pathForPhoto = (
|
|||||||
? `${pathForCamera(camera)}/${getPhotoId(photo)}`
|
? `${pathForCamera(camera)}/${getPhotoId(photo)}`
|
||||||
: simulation
|
: simulation
|
||||||
? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}`
|
? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}`
|
||||||
: `${PREFIX_PHOTO}/${getPhotoId(photo)}`;
|
: focal
|
||||||
|
? `${pathForFocalLength(focal)}/${getPhotoId(photo)}`
|
||||||
|
: `${PREFIX_PHOTO}/${getPhotoId(photo)}`;
|
||||||
|
|
||||||
export const pathForPhotoShare = (
|
export const pathForPhotoShare = (params: PhotoPathParams) =>
|
||||||
photo: PhotoOrPhotoId,
|
`${pathForPhoto(params)}/${SHARE}`;
|
||||||
tag?: string,
|
|
||||||
camera?: Camera,
|
|
||||||
simulation?: FilmSimulation,
|
|
||||||
) =>
|
|
||||||
`${pathForPhoto(photo, tag, camera, simulation)}/${SHARE}`;
|
|
||||||
|
|
||||||
export const pathForTag = (tag: string, next?: number) =>
|
export const pathForTag = (tag: string) =>
|
||||||
pathWithNext(
|
`${PREFIX_TAG}/${tag}`;
|
||||||
`${PREFIX_TAG}/${tag}`,
|
|
||||||
next,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const pathForTagShare = (tag: string) =>
|
export const pathForTagShare = (tag: string) =>
|
||||||
`${pathForTag(tag)}/${SHARE}`;
|
`${pathForTag(tag)}/${SHARE}`;
|
||||||
|
|
||||||
export const pathForCamera = ({ make, model }: Camera, next?: number) =>
|
export const pathForCamera = ({ make, model }: Camera) =>
|
||||||
pathWithNext(
|
`${PREFIX_CAMERA}/${parameterize(make, true)}/${parameterize(model, true)}`;
|
||||||
`${PREFIX_CAMERA}/${parameterize(make, true)}/${parameterize(model, true)}`,
|
|
||||||
next,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const pathForCameraShare = (camera: Camera) =>
|
export const pathForCameraShare = (camera: Camera) =>
|
||||||
`${pathForCamera(camera)}/${SHARE}`;
|
`${pathForCamera(camera)}/${SHARE}`;
|
||||||
|
|
||||||
export const pathForFilmSimulation =
|
export const pathForFilmSimulation = (simulation: FilmSimulation) =>
|
||||||
(simulation: FilmSimulation, next?: number) =>
|
`${PREFIX_FILM_SIMULATION}/${simulation}`;
|
||||||
pathWithNext(
|
|
||||||
`${PREFIX_FILM_SIMULATION}/${simulation}`,
|
|
||||||
next,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
|
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
|
||||||
`${pathForFilmSimulation(simulation)}/${SHARE}`;
|
`${pathForFilmSimulation(simulation)}/${SHARE}`;
|
||||||
|
|
||||||
export const absolutePathForPhoto = (
|
export const pathForFocalLength = (focal: number) =>
|
||||||
photo: PhotoOrPhotoId,
|
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
|
||||||
tag?: string,
|
|
||||||
camera?: Camera,
|
export const pathForFocalLengthShare = (focal: number) =>
|
||||||
simulation?: FilmSimulation
|
`${pathForFocalLength(focal)}/${SHARE}`;;
|
||||||
) =>
|
|
||||||
`${BASE_URL}${pathForPhoto(photo, tag, camera, simulation)}`;
|
export const absolutePathForPhoto = (params: PhotoPathParams) =>
|
||||||
|
`${BASE_URL}${pathForPhoto(params)}`;
|
||||||
|
|
||||||
export const absolutePathForTag = (tag: string) =>
|
export const absolutePathForTag = (tag: string) =>
|
||||||
`${BASE_URL}${pathForTag(tag)}`;
|
`${BASE_URL}${pathForTag(tag)}`;
|
||||||
@ -162,8 +146,11 @@ export const absolutePathForCamera= (camera: Camera) =>
|
|||||||
export const absolutePathForFilmSimulation = (simulation: FilmSimulation) =>
|
export const absolutePathForFilmSimulation = (simulation: FilmSimulation) =>
|
||||||
`${BASE_URL}${pathForFilmSimulation(simulation)}`;
|
`${BASE_URL}${pathForFilmSimulation(simulation)}`;
|
||||||
|
|
||||||
|
export const absolutePathForFocalLength = (focal: number) =>
|
||||||
|
`${BASE_URL}${pathForFocalLength(focal)}`;
|
||||||
|
|
||||||
export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) =>
|
export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) =>
|
||||||
`${absolutePathForPhoto(photo)}/image`;
|
`${absolutePathForPhoto({ photo })}/image`;
|
||||||
|
|
||||||
export const absolutePathForTagImage = (tag: string) =>
|
export const absolutePathForTagImage = (tag: string) =>
|
||||||
`${absolutePathForTag(tag)}/image`;
|
`${absolutePathForTag(tag)}/image`;
|
||||||
@ -175,6 +162,10 @@ export const absolutePathForFilmSimulationImage =
|
|||||||
(simulation: FilmSimulation) =>
|
(simulation: FilmSimulation) =>
|
||||||
`${absolutePathForFilmSimulation(simulation)}/image`;
|
`${absolutePathForFilmSimulation(simulation)}/image`;
|
||||||
|
|
||||||
|
export const absolutePathForFocalLengthImage =
|
||||||
|
(focal: number) =>
|
||||||
|
`${absolutePathForFocalLength(focal)}/image`;
|
||||||
|
|
||||||
// p/[photoId]
|
// p/[photoId]
|
||||||
export const isPathPhoto = (pathname = '') =>
|
export const isPathPhoto = (pathname = '') =>
|
||||||
new RegExp(`^${PREFIX_PHOTO}/[^/]+/?$`).test(pathname);
|
new RegExp(`^${PREFIX_PHOTO}/[^/]+/?$`).test(pathname);
|
||||||
@ -232,6 +223,23 @@ export const isPathFilmSimulationPhotoShare = (pathname = '') =>
|
|||||||
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/[^/]+/${SHARE}/?$`)
|
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/[^/]+/${SHARE}/?$`)
|
||||||
.test(pathname);
|
.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) =>
|
export const checkPathPrefix = (pathname = '', prefix: string) =>
|
||||||
pathname.toLowerCase().startsWith(prefix);
|
pathname.toLowerCase().startsWith(prefix);
|
||||||
|
|
||||||
@ -260,6 +268,7 @@ export const getPathComponents = (pathname = ''): {
|
|||||||
tag?: string
|
tag?: string
|
||||||
camera?: Camera
|
camera?: Camera
|
||||||
simulation?: FilmSimulation
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
} => {
|
} => {
|
||||||
const photoIdFromPhoto = pathname.match(
|
const photoIdFromPhoto = pathname.match(
|
||||||
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
|
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
|
||||||
@ -269,6 +278,8 @@ export const getPathComponents = (pathname = ''): {
|
|||||||
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/((?!${SHARE})[^/]+)`))?.[1];
|
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/((?!${SHARE})[^/]+)`))?.[1];
|
||||||
const photoIdFromFilmSimulation = pathname.match(
|
const photoIdFromFilmSimulation = pathname.match(
|
||||||
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/((?!${SHARE})[^/]+)`))?.[1];
|
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(
|
const tag = pathname.match(
|
||||||
new RegExp(`^${PREFIX_TAG}/([^/]+)`))?.[1];
|
new RegExp(`^${PREFIX_TAG}/([^/]+)`))?.[1];
|
||||||
const cameraMake = pathname.match(
|
const cameraMake = pathname.match(
|
||||||
@ -277,41 +288,57 @@ export const getPathComponents = (pathname = ''): {
|
|||||||
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
|
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
|
||||||
const simulation = pathname.match(
|
const simulation = pathname.match(
|
||||||
new RegExp(`^${PREFIX_FILM_SIMULATION}/([^/]+)`))?.[1] as FilmSimulation;
|
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
|
const camera = cameraMake && cameraModel
|
||||||
? { make: cameraMake, model: cameraModel }
|
? { make: cameraMake, model: cameraModel }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const focal = focalString ? parseInt(focalString) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
photoId: (
|
photoId: (
|
||||||
photoIdFromPhoto ||
|
photoIdFromPhoto ||
|
||||||
photoIdFromTag ||
|
photoIdFromTag ||
|
||||||
photoIdFromCamera ||
|
photoIdFromCamera ||
|
||||||
photoIdFromFilmSimulation
|
photoIdFromFilmSimulation ||
|
||||||
|
photoIdFromFocalLength
|
||||||
),
|
),
|
||||||
tag,
|
tag,
|
||||||
camera,
|
camera,
|
||||||
simulation,
|
simulation,
|
||||||
|
focal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEscapePath = (pathname?: string) => {
|
export const getEscapePath = (pathname?: string) => {
|
||||||
const { photoId, tag, camera, simulation } = getPathComponents(pathname);
|
const {
|
||||||
|
photoId,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
} = getPathComponents(pathname);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(photoId && isPathPhoto(pathname)) ||
|
(photoId && isPathPhoto(pathname)) ||
|
||||||
(tag && isPathTag(pathname)) ||
|
(tag && isPathTag(pathname)) ||
|
||||||
(camera && isPathCamera(pathname)) ||
|
(camera && isPathCamera(pathname)) ||
|
||||||
(simulation && isPathFilmSimulation(pathname))
|
(simulation && isPathFilmSimulation(pathname)) ||
|
||||||
|
(focal && isPathFocalLength(pathname))
|
||||||
) {
|
) {
|
||||||
return PATH_GRID;
|
return PATH_GRID;
|
||||||
} else if (photoId && isPathTagPhotoShare(pathname)) {
|
} else if (photoId && isPathTagPhotoShare(pathname)) {
|
||||||
return pathForPhoto(photoId, tag);
|
return pathForPhoto({ photo: photoId, tag });
|
||||||
} else if (photoId && isPathCameraPhotoShare(pathname)) {
|
} else if (photoId && isPathCameraPhotoShare(pathname)) {
|
||||||
return pathForPhoto(photoId, undefined, camera);
|
return pathForPhoto({ photo: photoId, camera });
|
||||||
} else if (photoId && isPathFilmSimulationPhotoShare(pathname)) {
|
} 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)) {
|
} else if (photoId && isPathPhotoShare(pathname)) {
|
||||||
return pathForPhoto(photoId);
|
return pathForPhoto({ photo: photoId });
|
||||||
} else if (tag && (
|
} else if (tag && (
|
||||||
isPathTagPhoto(pathname) ||
|
isPathTagPhoto(pathname) ||
|
||||||
isPathTagShare(pathname)
|
isPathTagShare(pathname)
|
||||||
@ -327,5 +354,10 @@ export const getEscapePath = (pathname?: string) => {
|
|||||||
isPathFilmSimulationShare(pathname)
|
isPathFilmSimulationShare(pathname)
|
||||||
)) {
|
)) {
|
||||||
return pathForFilmSimulation(simulation);
|
return pathForFilmSimulation(simulation);
|
||||||
|
} else if (focal && (
|
||||||
|
isPathFocalLengthPhoto(pathname) ||
|
||||||
|
isPathFocalLengthShare(pathname)
|
||||||
|
)) {
|
||||||
|
return pathForFocalLength(focal);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -31,9 +31,6 @@ export const getAspectRatioFromExif = (data: ExifData): number => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatFocalLength = (focalLength?: number) =>
|
|
||||||
focalLength ? `${focalLength}mm` : undefined;
|
|
||||||
|
|
||||||
export const formatAperture = (aperture?: number) =>
|
export const formatAperture = (aperture?: number) =>
|
||||||
aperture ? `ƒ/${aperture}` : undefined;
|
aperture ? `ƒ/${aperture}` : undefined;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user