From 5684d423c13853eb1d9abf1794b24f8fa8c6bfef Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 21 May 2024 00:22:06 -0500 Subject: [PATCH] Add focal length paths --- __tests__/path.test.ts | 67 ++++++++++++++++++----- src/focal/index.ts | 50 +++++++++++++++++ src/simulation/index.ts | 2 +- src/site/Footer.tsx | 4 +- src/site/paths.ts | 115 +++++++++++++++++++++++++--------------- 5 files changed, 181 insertions(+), 57 deletions(-) create mode 100644 src/focal/index.ts diff --git a/__tests__/path.test.ts b/__tests__/path.test.ts index 0552a40f..01cfa03f 100644 --- a/__tests__/path.test.ts +++ b/__tests__/path.test.ts @@ -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'; @@ -54,6 +60,11 @@ const PATH_FILM_SIMULATION = `/film/${FILM_SIMULATION}`; 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', () => { @@ -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); }); }); diff --git a/src/focal/index.ts b/src/focal/index.ts new file mode 100644 index 00000000..712e8a25 --- /dev/null +++ b/src/focal/index.ts @@ -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), +}); diff --git a/src/simulation/index.ts b/src/simulation/index.ts index 5a7b82fc..bb571b73 100644 --- a/src/simulation/index.ts +++ b/src/simulation/index.ts @@ -33,7 +33,7 @@ export const sortFilmSimulationsWithCount = ( export const titleForFilmSimulation = ( simulation: FilmSimulation, - photos:Photo[], + photos: Photo[], explicitCount?: number, ) => [ labelForFilmSimulation(simulation).large, diff --git a/src/site/Footer.tsx b/src/site/Footer.tsx index 3572d60d..7daa878d 100644 --- a/src/site/Footer.tsx +++ b/src/site/Footer.tsx @@ -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() { } : <> - + Admin {SHOW_REPO_LINK && diff --git a/src/site/paths.ts b/src/site/paths.ts index 9996ac6f..60a8da35 100644 --- a/src/site/paths.ts +++ b/src/site/paths.ts @@ -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); } };