Add focal length paths

This commit is contained in:
Sam Becker 2024-05-21 00:22:06 -05:00
parent df2d3e4781
commit 5684d423c1
5 changed files with 181 additions and 57 deletions

View File

@ -10,6 +10,10 @@ import {
isPathFilmSimulationPhoto,
isPathFilmSimulationPhotoShare,
isPathFilmSimulationShare,
isPathFocalLength,
isPathFocalLengthPhoto,
isPathFocalLengthPhotoShare,
isPathFocalLengthShare,
isPathPhoto,
isPathPhotoShare,
isPathProtected,
@ -20,13 +24,15 @@ import {
} from '@/site/paths';
import { TAG_HIDDEN } from '@/tag';
const PHOTO_ID = 'UsKSGcbt';
const TAG = 'tag-name';
const CAMERA_MAKE = 'fujifilm';
const CAMERA_MODEL = 'x-t1';
const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL };
const FILM_SIMULATION = 'acros';
const SHARE = 'share';
const PHOTO_ID = 'UsKSGcbt';
const TAG = 'tag-name';
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_NUMBER = 90;
const FOCAL_LENGTH = `${FOCAL_LENGTH_NUMBER}mm`;
const SHARE = 'share';
const PATH_ROOT = '/';
const PATH_GRID = '/grid';
@ -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}`;
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_NUMBER,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_SHARE)).toEqual({
focal: FOCAL_LENGTH_NUMBER,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_PHOTO)).toEqual({
photoId: PHOTO_ID,
focal: FOCAL_LENGTH_NUMBER,
});
expect(getPathComponents(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toEqual({
photoId: PHOTO_ID,
focal: FOCAL_LENGTH_NUMBER,
});
});
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);
});
});

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

@ -0,0 +1,50 @@
import {
Photo,
PhotoDateRange,
descriptionForPhotoSet,
photoQuantityText,
} from '@/photo';
import {
absolutePathForFocalLength,
absolutePathForFocalLengthImage,
} from '@/site/paths';
export const titleForFocalLength = (
focal: number,
photos: Photo[],
explicitCount?: number,
) => [
`${focal}mm`,
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

@ -33,7 +33,7 @@ export const sortFilmSimulationsWithCount = (
export const titleForFilmSimulation = (
simulation: FilmSimulation,
photos:Photo[],
photos: Photo[],
explicitCount?: number,
) => [
labelForFilmSimulation(simulation).large,

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

@ -6,24 +6,26 @@ import { parameterize } from '@/utility/string';
import { TAG_HIDDEN } from '@/tag';
// Core paths
export const PATH_ROOT = '/';
export const PATH_GRID = '/grid';
export const PATH_ADMIN = '/admin';
export const PATH_API = '/api';
export const PATH_SIGN_IN = '/sign-in';
export const PATH_OG = '/og';
export const PATH_ROOT = '/';
export const PATH_GRID = '/grid';
export const PATH_ADMIN = '/admin';
export const PATH_API = '/api';
export const PATH_SIGN_IN = '/sign-in';
export const PATH_OG = '/og';
// Path prefixes
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,13 @@ export const PATHS_TO_CACHE = [
PATH_TAG_DYNAMIC,
PATH_CAMERA_DYNAMIC,
PATH_FILM_SIMULATION_DYNAMIC,
PATH_FOCAL_LENGTH_DYNAMIC,
...PATHS_ADMIN,
];
// 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,9 +75,6 @@ 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) =>
@ -98,6 +85,7 @@ export const pathForPhoto = (
tag?: string,
camera?: Camera,
simulation?: FilmSimulation,
focal?: number,
) =>
typeof photo !== 'string' && photo.hidden
? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}`
@ -107,7 +95,9 @@ export const pathForPhoto = (
? `${pathForCamera(camera)}/${getPhotoId(photo)}`
: simulation
? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}`
: `${PREFIX_PHOTO}/${getPhotoId(photo)}`;
: focal
? `${pathForFocalLength(focal)}/${getPhotoId(photo)}`
: `${PREFIX_PHOTO}/${getPhotoId(photo)}`;
export const pathForPhotoShare = (
photo: PhotoOrPhotoId,
@ -117,30 +107,23 @@ export const pathForPhotoShare = (
) =>
`${pathForPhoto(photo, tag, camera, simulation)}/${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 pathForFocalLength = (focal: number) =>
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
`${pathForFilmSimulation(simulation)}/${SHARE}`;
@ -162,6 +145,9 @@ 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`;
@ -175,6 +161,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 +222,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 +267,7 @@ export const getPathComponents = (pathname = ''): {
tag?: string
camera?: Camera
simulation?: FilmSimulation
focal?: number
} => {
const photoIdFromPhoto = pathname.match(
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
@ -269,6 +277,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,31 +287,45 @@ 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)) {
@ -310,6 +334,8 @@ export const getEscapePath = (pathname?: string) => {
return pathForPhoto(photoId, undefined, camera);
} else if (photoId && isPathFilmSimulationPhotoShare(pathname)) {
return pathForPhoto(photoId, undefined, undefined, simulation);
} else if (photoId && isPathFocalLengthPhotoShare(pathname)) {
return pathForPhoto(photoId, undefined, undefined, undefined, focal);
} else if (photoId && isPathPhotoShare(pathname)) {
return pathForPhoto(photoId);
} else if (tag && (
@ -327,5 +353,10 @@ export const getEscapePath = (pathname?: string) => {
isPathFilmSimulationShare(pathname)
)) {
return pathForFilmSimulation(simulation);
} else if (focal && (
isPathFocalLengthPhoto(pathname) ||
isPathFocalLengthShare(pathname)
)) {
return pathForFocalLength(focal);
}
};