diff --git a/app/lens/[make]/[model]/[photoId]/page.tsx b/app/lens/[make]/[model]/[photoId]/page.tsx index 8d1ca67e..496ea394 100644 --- a/app/lens/[make]/[model]/[photoId]/page.tsx +++ b/app/lens/[make]/[model]/[photoId]/page.tsx @@ -16,24 +16,29 @@ import { getPhotosNearIdCached, } from '@/photo/cache'; import { cache } from 'react'; -import { getLensFromParams, lensFromPhoto, PhotoLensProps } from '@/lens'; +import { + formatLensParams, + getLensPhotoFromParams, + lensFromPhoto, + LensPhotoProps, +} from '@/lens'; const getPhotosNearIdCachedCached = cache(( photoId: string, - make: string, + make: string | undefined, model: string, ) => getPhotosNearIdCached( photoId, { - lens: getLensFromParams({ make, model }), + lens: formatLensParams({ make, model }), limit: RELATED_GRID_PHOTOS_TO_SHOW + 2, }, )); export async function generateMetadata({ params, -}: PhotoLensProps): Promise { - const { photoId, make, model } = await params; +}: LensPhotoProps): Promise { + const { photoId, make, model } = await getLensPhotoFromParams(params); const { photo } = await getPhotosNearIdCachedCached(photoId, make, model); @@ -67,8 +72,8 @@ export async function generateMetadata({ export default async function PhotoLensPage({ params, -}: PhotoLensProps) { - const { photoId, make, model } = await params; +}: LensPhotoProps) { + const { photoId, make, model } = await getLensPhotoFromParams(params); const { photo, photos, photosGrid, indexNumber } = await getPhotosNearIdCachedCached(photoId, make, model); diff --git a/app/lens/[make]/[model]/image/route.tsx b/app/lens/[make]/[model]/image/route.tsx index 7b7fd98e..3a165c06 100644 --- a/app/lens/[make]/[model]/image/route.tsx +++ b/app/lens/[make]/[model]/image/route.tsx @@ -31,7 +31,7 @@ export async function GET( _: Request, context: LensProps, ) { - const lens = getLensFromParams(await context.params); + const lens = await getLensFromParams(context.params); const [ photos, diff --git a/app/lens/[make]/[model]/page.tsx b/app/lens/[make]/[model]/page.tsx index 355ff2d5..9e49ee35 100644 --- a/app/lens/[make]/[model]/page.tsx +++ b/app/lens/[make]/[model]/page.tsx @@ -7,10 +7,10 @@ import { getUniqueLenses } from '@/photo/db/query'; import { generateMetaForLens } from '@/lens/meta'; import { getPhotosLensDataCached } from '@/lens/data'; import LensOverview from '@/lens/LensOverview'; -import { LensProps } from '@/lens'; +import { getLensFromParams, Lens, LensProps } from '@/lens'; const getPhotosLensDataCachedCached = cache(( - make: string, + make: string | undefined, model: string, ) => getPhotosLensDataCached( make, @@ -19,7 +19,7 @@ const getPhotosLensDataCachedCached = cache(( )); export let generateStaticParams: - (() => Promise<{ make: string, model: string }[]>) | undefined = undefined; + (() => Promise) | undefined = undefined; if (STATICALLY_OPTIMIZED_PHOTO_CATEGORIES && IS_PRODUCTION) { generateStaticParams = async () => { @@ -31,7 +31,7 @@ if (STATICALLY_OPTIMIZED_PHOTO_CATEGORIES && IS_PRODUCTION) { export async function generateMetadata({ params, }: LensProps): Promise { - const { make, model } = await params; + const { make, model } = await getLensFromParams(params); const [ photos, @@ -66,7 +66,7 @@ export async function generateMetadata({ export default async function LensPage({ params, }: LensProps) { - const { make, model } = await params; + const { make, model } = await getLensFromParams(params); const [ photos, diff --git a/app/shot-on/[make]/[model]/[photoId]/page.tsx b/app/shot-on/[make]/[model]/[photoId]/page.tsx index 73bf914d..a1923645 100644 --- a/app/shot-on/[make]/[model]/[photoId]/page.tsx +++ b/app/shot-on/[make]/[model]/[photoId]/page.tsx @@ -18,7 +18,7 @@ import { import { PhotoCameraProps, cameraFromPhoto, - getCameraFromParams, + formatCameraParams, } from '@/camera'; import { cache } from 'react'; @@ -29,7 +29,7 @@ const getPhotosNearIdCachedCached = cache(( ) => getPhotosNearIdCached( photoId, { - camera: getCameraFromParams({ make, model }), + camera: formatCameraParams({ make, model }), limit: RELATED_GRID_PHOTOS_TO_SHOW + 2, }, )); diff --git a/app/shot-on/[make]/[model]/image/route.tsx b/app/shot-on/[make]/[model]/image/route.tsx index daec24ea..4384a284 100644 --- a/app/shot-on/[make]/[model]/image/route.tsx +++ b/app/shot-on/[make]/[model]/image/route.tsx @@ -1,5 +1,5 @@ import { getPhotosCached } from '@/photo/cache'; -import { Camera, CameraProps, getCameraFromParams } from '@/camera'; +import { Camera, CameraProps, formatCameraParams } from '@/camera'; import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_CATEGORY, @@ -31,7 +31,7 @@ export async function GET( _: Request, context: CameraProps, ) { - const camera = getCameraFromParams(await context.params); + const camera = formatCameraParams(await context.params); const [ photos, diff --git a/app/shot-on/[make]/[model]/page.tsx b/app/shot-on/[make]/[model]/page.tsx index 0a6cccb6..573380a4 100644 --- a/app/shot-on/[make]/[model]/page.tsx +++ b/app/shot-on/[make]/[model]/page.tsx @@ -1,5 +1,5 @@ import { Metadata } from 'next/types'; -import { CameraProps } from '@/camera'; +import { Camera, CameraProps } from '@/camera'; import { generateMetaForCamera } from '@/camera/meta'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { getPhotosCameraDataCached } from '@/camera/data'; @@ -19,7 +19,7 @@ const getPhotosCameraDataCachedCached = cache(( )); export let generateStaticParams: - (() => Promise<{ make: string, model: string }[]>) | undefined = undefined; + (() => Promise) | undefined = undefined; if (STATICALLY_OPTIMIZED_PHOTO_CATEGORIES && IS_PRODUCTION) { generateStaticParams = async () => { diff --git a/src/app/paths.ts b/src/app/paths.ts index f73abe29..a20ab928 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -22,17 +22,18 @@ export const PATH_FEED_INFERRED = GRID_HOMEPAGE_ENABLED ? PATH_FEED : PATH // Path prefixes export const PREFIX_PHOTO = '/p'; -export const PREFIX_TAG = '/tag'; export const PREFIX_CAMERA = '/shot-on'; export const PREFIX_LENS = '/lens'; +export const PREFIX_TAG = '/tag'; export const PREFIX_RECIPE = '/recipe'; 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_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`; +const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`; // eslint-disable-next-line max-len const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`; const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; @@ -61,6 +62,9 @@ export const PATH_API_PRESIGNED_URL = `${PATH_API_STORAGE}/presigned-url`; // Modifiers const EDIT = 'edit'; +// Special characters +export const MISSING_FIELD = '-'; + export const PATHS_ADMIN = [ PATH_ADMIN, PATH_ADMIN_PHOTOS, @@ -79,8 +83,9 @@ export const PATHS_TO_CACHE = [ PATH_FEED, PATH_OG, PATH_PHOTO_DYNAMIC, - PATH_TAG_DYNAMIC, PATH_CAMERA_DYNAMIC, + PATH_LENS_DYNAMIC, + PATH_TAG_DYNAMIC, PATH_FILM_SIMULATION_DYNAMIC, PATH_FOCAL_LENGTH_DYNAMIC, PATH_RECIPE_DYNAMIC, @@ -119,22 +124,27 @@ export const pathForPhoto = ({ simulation, focal, recipe, -}: PhotoPathParams) => - typeof photo !== 'string' && photo.hidden - ? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}` - : tag - ? `${pathForTag(tag)}/${getPhotoId(photo)}` - : camera - ? `${pathForCamera(camera)}/${getPhotoId(photo)}` - : lens - ? `${pathForLens(lens)}/${getPhotoId(photo)}` - : simulation - ? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}` - : recipe - ? `${pathForRecipe(recipe)}/${getPhotoId(photo)}` - : focal - ? `${pathForFocalLength(focal)}/${getPhotoId(photo)}` - : `${PREFIX_PHOTO}/${getPhotoId(photo)}`; +}: PhotoPathParams) => { + let prefix = PREFIX_PHOTO; + + if (typeof photo !== 'string' && photo.hidden) { + prefix = pathForTag(TAG_HIDDEN); + } else if (camera) { + prefix = pathForCamera(camera); + } else if (lens) { + prefix = pathForLens(lens); + } else if (tag) { + prefix = pathForTag(tag); + } else if (simulation) { + prefix = pathForFilmSimulation(simulation); + } else if (recipe) { + prefix = pathForRecipe(recipe); + } else if (focal) { + prefix = pathForFocalLength(focal); + } + + return `${prefix}/${getPhotoId(photo)}`; +}; export const pathForTag = (tag: string) => `${PREFIX_TAG}/${tag}`; @@ -146,7 +156,9 @@ export const pathForFilmSimulation = (simulation: FilmSimulation) => `${PREFIX_FILM_SIMULATION}/${simulation}`; export const pathForLens = ({ make, model }: Lens) => - `${PREFIX_LENS}/${parameterize(make)}/${parameterize(model)}`; + make + ? `${PREFIX_LENS}/${parameterize(make)}/${parameterize(model)}` + : `${PREFIX_LENS}/${MISSING_FIELD}/${parameterize(model)}`; export const pathForFocalLength = (focal: number) => `${PREFIX_FOCAL_LENGTH}/${focal}mm`; diff --git a/src/camera/data.ts b/src/camera/data.ts index afd407c6..8f65b855 100644 --- a/src/camera/data.ts +++ b/src/camera/data.ts @@ -1,4 +1,4 @@ -import { cameraFromPhoto, getCameraFromParams } from '.'; +import { cameraFromPhoto, formatCameraParams } from '.'; import { getPhotosCached, getPhotosMetaCached, @@ -9,7 +9,7 @@ export const getPhotosCameraDataCached = async ( model: string, limit: number, ) => { - const camera = getCameraFromParams({ make, model }); + const camera = formatCameraParams({ make, model }); return Promise.all([ getPhotosCached({ camera, limit }), getPhotosMetaCached({ camera }), diff --git a/src/camera/index.ts b/src/camera/index.ts index 23c7474a..0bc31f41 100644 --- a/src/camera/index.ts +++ b/src/camera/index.ts @@ -29,7 +29,7 @@ export type Cameras = CameraWithCount[]; export const createCameraKey = ({ make, model }: Partial) => parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`); -export const getCameraFromParams = ({ +export const formatCameraParams = ({ make, model, }: { diff --git a/src/lens/data.ts b/src/lens/data.ts index 2242f5f6..cc76cd42 100644 --- a/src/lens/data.ts +++ b/src/lens/data.ts @@ -1,15 +1,15 @@ -import { getLensFromParams, lensFromPhoto } from '.'; +import { formatLensParams, lensFromPhoto } from '.'; import { getPhotosCached, getPhotosMetaCached, } from '@/photo/cache'; export const getPhotosLensDataCached = async ( - make: string, + make: string | undefined, model: string, limit: number, ) => { - const lens = getLensFromParams({ make, model }); + const lens = formatLensParams({ make, model }); return Promise.all([ getPhotosCached({ lens, limit }), getPhotosMetaCached({ lens }), diff --git a/src/lens/index.ts b/src/lens/index.ts index caf0b27e..4d3a6e8c 100644 --- a/src/lens/index.ts +++ b/src/lens/index.ts @@ -1,20 +1,23 @@ import { Photo } from '@/photo'; import { parameterize } from '@/utility/string'; import { formatAppleLensText, isLensMakeApple } from '../platforms/apple'; +import { MISSING_FIELD } from '@/app/paths'; const LENS_PLACEHOLDER: Lens = { make: 'Lens', model: 'Model' }; export type Lens = { - make: string + make?: string model: string }; +type LensWithPhotoId = Lens & { photoId: string }; + export interface LensProps { params: Promise } -export interface PhotoLensProps { - params: Promise +export interface LensPhotoProps { + params: Promise } export type LensWithCount = { @@ -25,18 +28,36 @@ export type LensWithCount = { export type Lenses = LensWithCount[]; +export const getLensFromParams = async ( + params: Promise, +): Promise => { + const { make, model } = await params; + return make === MISSING_FIELD + ? { model } + : { make, model }; +}; + +export const getLensPhotoFromParams = async ( + params: Promise, +): Promise => { + const { make, model, photoId } = await params; + return make === MISSING_FIELD + ? { model, photoId } + : { make, model, photoId }; +}; + // Support keys for make-only and model-only lens queries export const createLensKey = ({ make, model }: Partial) => parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`); -export const getLensFromParams = ({ +export const formatLensParams = ({ make, model, }: { - make: string, + make?: string, model: string, }): Lens => ({ - make: parameterize(make), + make: make ? parameterize(make) : undefined, model: parameterize(model), }); @@ -53,7 +74,7 @@ export const lensFromPhoto = ( photo: Photo | undefined, fallback?: Lens, ): Lens => - photo?.lensMake && photo?.lensModel + photo?.lensModel ? { make: photo.lensMake, model: photo.lensModel } : fallback ?? LENS_PLACEHOLDER; @@ -66,7 +87,7 @@ export const formatLensText = ( = 'medium', ) => { // Capture simple make without modifiers like 'Corporation' or 'Company' - const makeSimple = make.match(/^(\S+)/)?.[1]; + const makeSimple = make?.match(/^(\S+)/)?.[1]; const doesModelStartWithMake = ( makeSimple && modelRaw.toLocaleLowerCase().startsWith(makeSimple.toLocaleLowerCase()) @@ -78,7 +99,7 @@ export const formatLensText = ( switch (length) { case 'long': - return `${make} ${model}`; + return make ? `${make} ${model}` : model; case 'medium': return doesModelStartWithMake ? model.replace(makeSimple, '').trim() diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx index 48ad942d..8e4a1e8a 100644 --- a/src/photo/PhotoGrid.tsx +++ b/src/photo/PhotoGrid.tsx @@ -14,12 +14,6 @@ import { GRID_GAP_CLASSNAME } from '@/components'; export default function PhotoGrid({ photos, selectedPhoto, - tag, - camera, - lens, - simulation, - focal, - recipe, photoPriority, fast, animate = true, @@ -31,6 +25,7 @@ export default function PhotoGrid({ canSelect, onLastPhotoVisible, onAnimationComplete, + ...categories }: { photos: Photo[] selectedPhoto?: Photo @@ -95,12 +90,7 @@ export default function PhotoGrid({ )} {...{ photo, - tag, - camera, - lens, - simulation, - focal, - recipe, + ...categories, selected: photo.id === selectedPhoto?.id, priority: photoPriority, onVisible: index === photos.length - 1 diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx index d2602820..105072d6 100644 --- a/src/photo/PhotoGridContainer.tsx +++ b/src/photo/PhotoGridContainer.tsx @@ -12,16 +12,11 @@ export default function PhotoGridContainer({ cacheKey, photos, count, - tag, - camera, - lens, - simulation, - focal, - recipe, animateOnFirstLoadOnly, header, sidebar, canSelect, + ...categories }: { cacheKey: string count: number @@ -50,12 +45,7 @@ export default function PhotoGridContainer({
} diff --git a/src/photo/PhotoGridInfinite.tsx b/src/photo/PhotoGridInfinite.tsx index b69daf75..e65b5087 100644 --- a/src/photo/PhotoGridInfinite.tsx +++ b/src/photo/PhotoGridInfinite.tsx @@ -9,12 +9,9 @@ export default function PhotoGridInfinite({ cacheKey, initialOffset, canStart, - tag, - camera, - simulation, - focal, animateOnFirstLoadOnly, canSelect, + ...categories }: { cacheKey: string initialOffset: number @@ -24,18 +21,13 @@ export default function PhotoGridInfinite({ cacheKey={cacheKey} initialOffset={initialOffset} itemsPerPage={INFINITE_SCROLL_GRID_MULTIPLE} - tag={tag} - camera={camera} - simulation={simulation} + {...categories} > {({ photos, onLastPhotoVisible }) => lens_make, lens_model, COUNT(*) FROM photos WHERE hidden IS NOT TRUE - AND trim(lens_make) <> '' AND trim(lens_model) <> '' GROUP BY lens_make, lens_model ORDER BY lens ASC diff --git a/src/photo/index.ts b/src/photo/index.ts index 75618b61..7f698a0d 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -301,7 +301,6 @@ const photoHasCameraData = (photo: Photo) => Boolean(photo.model); const photoHasLensData = (photo: Photo) => - Boolean(photo.lensMake) && Boolean(photo.lensModel); const photoHasRecipeData = (photo: Photo) =>