diff --git a/app/grid/page.tsx b/app/grid/page.tsx index e3b932bf..3866e733 100644 --- a/app/grid/page.tsx +++ b/app/grid/page.tsx @@ -25,8 +25,9 @@ export default async function GridPage() { const [ photos, photosCount, - tags, cameras, + lenses, + tags, simulations, recipes, ] = await Promise.all([ @@ -41,7 +42,15 @@ export default async function GridPage() { return ( photos.length > 0 ? : ); diff --git a/app/page.tsx b/app/page.tsx index 69045482..6568c609 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -31,8 +31,9 @@ export default async function HomePage() { const [ photos, photosCount, - tags, cameras, + lenses, + tags, simulations, recipes, ] = await Promise.all([ @@ -43,18 +44,24 @@ export default async function HomePage() { .catch(() => 0), ...(GRID_HOMEPAGE_ENABLED ? getPhotoSidebarData() - : [[], [], [], []]), + : [[], [], [], [], []]), ]); return ( photos.length > 0 ? GRID_HOMEPAGE_ENABLED ? - : + : : ); } diff --git a/src/app/CommandK.tsx b/src/app/CommandK.tsx index 341765a5..a32066b0 100644 --- a/src/app/CommandK.tsx +++ b/src/app/CommandK.tsx @@ -3,6 +3,7 @@ import { getPhotosMetaCached, getUniqueCamerasCached, getUniqueFilmSimulationsCached, + getUniqueLensesCached, getUniqueRecipesCached, getUniqueTagsCached, } from '@/photo/cache'; @@ -17,8 +18,9 @@ import { getUniqueFocalLengths } from '@/photo/db/query'; export default async function CommandK() { const [ count, - tags, cameras, + lenses, + tags, recipes, filmSimulations, focalLengths, @@ -26,8 +28,9 @@ export default async function CommandK() { getPhotosMetaCached() .then(({ count }) => count) .catch(() => 0), - getUniqueTagsCached().catch(() => []), getUniqueCamerasCached().catch(() => []), + getUniqueLensesCached().catch(() => []), + getUniqueTagsCached().catch(() => []), SHOW_RECIPES ? getUniqueRecipesCached().catch(() => []) : [], @@ -38,8 +41,9 @@ export default async function CommandK() { ]); return 0 ? [{ heading: 'Photos', - accessory: , + accessory: , items: photos.map(photo => ({ label: titleForPhoto(photo), keywords: getKeywordsForPhoto(photo), @@ -250,10 +256,30 @@ export default function CommandKClient({ CATEGORY_VISIBILITY .map(category => { switch (category) { + case 'cameras': return { + heading: 'Cameras', + accessory: , + items: cameras.map(({ camera, count }) => ({ + label: formatCameraText(camera), + annotation: formatCount(count), + annotationAria: formatCountDescriptive(count), + path: pathForCamera(camera), + })), + }; + case 'lenses': return { + heading: 'Lenses', + accessory: , + items: lenses.map(({ lens, count }) => ({ + label: formatLensText(lens, 'medium'), + annotation: formatCount(count), + annotationAria: formatCountDescriptive(count), + path: pathForLens(lens), + })), + }; case 'tags': return { heading: 'Tags', - accessory: , items: tagsIncludingHidden.map(({ tag, count }) => ({ @@ -263,19 +289,9 @@ export default function CommandKClient({ path: pathForTag(tag), })), }; - case 'cameras': return { - heading: 'Cameras', - accessory: , - items: cameras.map(({ camera, count }) => ({ - label: formatCameraText(camera), - annotation: formatCount(count), - annotationAria: formatCountDescriptive(count), - path: pathForCamera(camera), - })), - }; case 'recipes': return { heading: 'Recipes', - accessory: , @@ -288,9 +304,7 @@ export default function CommandKClient({ }; case 'films': return { heading: 'Film Simulations', - accessory: - - , + accessory: , items: simulations.map(({ simulation, count }) => ({ label: labelForFilmSimulation(simulation).medium, annotation: formatCount(count), @@ -300,9 +314,7 @@ export default function CommandKClient({ }; case 'focal-lengths': return { heading: 'Focal Lengths', - accessory: , + accessory: , items: focalLengths.map(({ focal, count }) => ({ label: formatFocalLength(focal)!, annotation: formatCount(count), @@ -313,7 +325,7 @@ export default function CommandKClient({ } }) .filter(Boolean) as CommandKSection[] - , [tagsIncludingHidden, cameras, recipes, simulations, focalLengths]); + , [tagsIncludingHidden, cameras, lenses, recipes, simulations, focalLengths]); const clientSections: CommandKSection[] = [{ heading: 'Theme', diff --git a/src/lens/index.ts b/src/lens/index.ts index e102c6ff..7aa7182f 100644 --- a/src/lens/index.ts +++ b/src/lens/index.ts @@ -41,6 +41,15 @@ export const getLensFromParams = ({ model: parameterize(model), }); +export const sortLensesWithCount = ( + a: LensWithCount, + b: LensWithCount, +) => { + const aText = formatLensText(a.lens); + const bText = formatLensText(b.lens); + return aText.localeCompare(bText); +}; + export const lensFromPhoto = ( photo: Photo | undefined, fallback?: Lens, @@ -55,15 +64,24 @@ const isLensMakeApple = (make?: string) => export const isLensApple = ({ make }: Lens) => isLensMakeApple(make); -const formatAppleLensText = (model: string) => { - if (model.includes('front')) { - return 'Front Camera'; - } else { - if (model.includes('15 Pro')) { - if (model.includes('f/2.2')) { return 'Wide Camera'; } - if (model.includes('f/1.78')) { return 'Main Camera'; } - if (model.includes('f/2.8')) { return 'Telephoto Camera'; } - } +const formatAppleLensText = ( + model: string, + includePhoneName?: boolean, +) => { + if (model.includes('15 Pro')) { + const phoneName = '15 Pro'; + if (model.includes('front')) { return includePhoneName + ? `${phoneName}: Front Camera` + : 'Front Camera'; } + if (model.includes('f/2.2')) { return includePhoneName + ? `${phoneName}: Wide Camera` + : 'Wide Camera'; } + if (model.includes('f/1.78')) { return includePhoneName + ? `${phoneName}: Main Camera` + : 'Main Camera'; } + if (model.includes('f/2.8')) { return includePhoneName + ? `${phoneName}: Telephoto Camera` + : 'Telephoto Camera'; } } return model; }; @@ -84,13 +102,16 @@ export const formatLensText = ( ); const model = isLensMakeApple(make) - ? formatAppleLensText(modelRaw) + ? formatAppleLensText(modelRaw, length === 'medium') : modelRaw; switch (length) { case 'long': - case 'medium': return `${make} ${model}`; + case 'medium': + return doesModelStartWithMake + ? model.replace(makeSimple, '').trim() + : model; case 'short': return doesModelStartWithMake ? model.replace(makeSimple, '').trim() diff --git a/src/photo/PhotoGridPage.tsx b/src/photo/PhotoGridPage.tsx index ff1966fc..4a3de748 100644 --- a/src/photo/PhotoGridPage.tsx +++ b/src/photo/PhotoGridPage.tsx @@ -11,19 +11,22 @@ import { useEffect } from 'react'; import { useAppState } from '@/state/AppState'; import clsx from 'clsx/lite'; import { Recipes } from '@/recipe'; +import { Lenses } from '@/lens'; export default function PhotoGridPage({ photos, photosCount, - tags, cameras, + lenses, + tags, simulations, recipes, }: { photos: Photo[] photosCount: number - tags: Tags cameras: Cameras + lenses: Lenses + tags: Tags simulations: FilmSimulations recipes: Recipes }) { @@ -65,6 +68,7 @@ export default function PhotoGridPage({ 0 + ? } + items={cameras + .sort(sortCamerasWithCount) + .map(({ cameraKey, camera, count }) => + )} + /> + : null; + + const lensesContent = lenses.length > 0 + ? } + items={lenses + .sort(sortLensesWithCount) + .map(({ lensKey, lens, count }) => + )} + /> + : null; + const tagsContent = tags.length > 0 ? : null; - const camerasContent = cameras.length > 0 - ? } - items={cameras - .sort(sortCamerasWithCount) - .map(({ cameraKey, camera, count }) => - )} - /> - : null; - const recipesContent = recipes.length > 0 ? } {CATEGORY_VISIBILITY.map(category => { switch (category) { - case 'cameras': return camerasContent; case 'tags': return tagsContent; + case 'cameras': return camerasContent; + case 'lenses': return lensesContent; case 'recipes': return recipesContent; case 'films': return filmsContent; } diff --git a/src/photo/data.ts b/src/photo/data.ts index 986d9e91..caa15e20 100644 --- a/src/photo/data.ts +++ b/src/photo/data.ts @@ -1,21 +1,24 @@ import { getUniqueCamerasCached, getUniqueFilmSimulationsCached, + getUniqueLensesCached, getUniqueRecipesCached, getUniqueTagsCached, } from '@/photo/cache'; import { getUniqueCameras, getUniqueFilmSimulations, + getUniqueLenses, getUniqueRecipes, getUniqueTags, } from '@/photo/db/query'; -import { SHOW_FILM_SIMULATIONS, SHOW_RECIPES } from '@/app/config'; +import { SHOW_FILM_SIMULATIONS, SHOW_LENSES, SHOW_RECIPES } from '@/app/config'; import { sortTagsObject } from '@/tag'; export const getPhotoSidebarData = () => [ - getUniqueTags().then(sortTagsObject).catch(() => []), getUniqueCameras().catch(() => []), + SHOW_LENSES ? getUniqueLenses().catch(() => []) : [], + getUniqueTags().then(sortTagsObject).catch(() => []), SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulations().catch(() => []) : [], @@ -25,8 +28,9 @@ export const getPhotoSidebarData = () => [ ] as const; export const getPhotoSidebarDataCached = () => [ - getUniqueTagsCached().then(sortTagsObject), getUniqueCamerasCached(), + SHOW_LENSES ? getUniqueLensesCached() : [], + getUniqueTagsCached().then(sortTagsObject), SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [], SHOW_RECIPES ? getUniqueRecipesCached() : [], ] as const; diff --git a/src/photo/set.ts b/src/photo/set.ts index a27518e5..57081775 100644 --- a/src/photo/set.ts +++ b/src/photo/set.ts @@ -2,7 +2,7 @@ import { Photo } from '.'; import { Camera, Cameras } from '@/camera'; import { PhotoDateRange } from '.'; import { FilmSimulation, FilmSimulations } from '@/simulation'; -import { Lens } from '@/lens'; +import { Lens, Lenses } from '@/lens'; import { Tags } from '@/tag'; import { FocalLengths } from '@/focal'; import { Recipes } from '@/recipe'; @@ -43,8 +43,9 @@ export interface PhotoSetCategory { } export interface PhotoSetCategories { - tags: Tags cameras: Cameras + lenses: Lenses + tags: Tags recipes: Recipes simulations: FilmSimulations focalLengths: FocalLengths