Account for lenses without makes

This commit is contained in:
Sam Becker 2025-03-17 10:01:00 -05:00
parent 7e207ed95b
commit 500b9b4561
17 changed files with 102 additions and 97 deletions

View File

@ -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<Metadata> {
const { photoId, make, model } = await params;
}: LensPhotoProps): Promise<Metadata> {
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);

View File

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

View File

@ -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<Lens[]>) | 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<Metadata> {
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,

View File

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

View File

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

View File

@ -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<Camera[]>) | undefined = undefined;
if (STATICALLY_OPTIMIZED_PHOTO_CATEGORIES && IS_PRODUCTION) {
generateStaticParams = async () => {

View File

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

View File

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

View File

@ -29,7 +29,7 @@ export type Cameras = CameraWithCount[];
export const createCameraKey = ({ make, model }: Partial<Camera>) =>
parameterize(`${make ?? 'ANY'}-${model ?? 'ANY'}`);
export const getCameraFromParams = ({
export const formatCameraParams = ({
make,
model,
}: {

View File

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

View File

@ -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<Lens>
}
export interface PhotoLensProps {
params: Promise<Lens & { photoId: string }>
export interface LensPhotoProps {
params: Promise<LensWithPhotoId>
}
export type LensWithCount = {
@ -25,18 +28,36 @@ export type LensWithCount = {
export type Lenses = LensWithCount[];
export const getLensFromParams = async (
params: Promise<Lens>,
): Promise<Lens> => {
const { make, model } = await params;
return make === MISSING_FIELD
? { model }
: { make, model };
};
export const getLensPhotoFromParams = async (
params: Promise<LensWithPhotoId>,
): Promise<LensWithPhotoId> => {
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<Lens>) =>
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()

View File

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

View File

@ -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({
<div className={GRID_SPACE_CLASSNAME}>
<PhotoGrid {...{
photos,
tag,
camera,
lens,
simulation,
focal,
recipe,
...categories,
animateOnFirstLoadOnly,
onAnimationComplete,
canSelect,
@ -64,13 +54,8 @@ export default function PhotoGridContainer({
<PhotoGridInfinite {...{
cacheKey,
initialOffset: photos.length,
...categories,
canStart: shouldAnimateDynamicItems,
tag,
camera,
lens,
simulation,
focal,
recipe,
animateOnFirstLoadOnly,
canSelect,
}} />}

View File

@ -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 }) =>
<PhotoGrid {...{
photos,
...categories,
canStart,
tag,
camera,
simulation,
focal,
onLastPhotoVisible,
animateOnFirstLoadOnly,
canSelect,

View File

@ -100,6 +100,8 @@ export const getWheresFromOptions = (
}
if (lens?.model) {
wheres.push(`${parameterizeForDb('lens_model')}=$${valuesIndex++}`);
// Ensure unique queries for lenses missing makes
if (!lens.make) { wheres.push('lens_make IS NULL'); }
wheresValues.push(parameterize(lens.model));
}
if (tag) {

View File

@ -340,7 +340,6 @@ export const getUniqueLenses = async () =>
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

View File

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