diff --git a/src/app/paths.ts b/src/app/paths.ts
index 9d99ede5..4cc75ad8 100644
--- a/src/app/paths.ts
+++ b/src/app/paths.ts
@@ -5,6 +5,7 @@ import { Camera } from '@/camera';
import { FilmSimulation } from '@/simulation';
import { parameterize } from '@/utility/string';
import { TAG_HIDDEN } from '@/tag';
+import { Lens } from '@/lens';
// Core paths
export const PATH_ROOT = '/';
@@ -23,9 +24,10 @@ export const PATH_FEED_INFERRED = GRID_HOMEPAGE_ENABLED ? PATH_FEED : PATH
export const PREFIX_PHOTO = '/p';
export const PREFIX_TAG = '/tag';
export const PREFIX_CAMERA = '/shot-on';
+export const PREFIX_LENS = '/lens';
+export const PREFIX_RECIPE = '/recipe';
export const PREFIX_FILM_SIMULATION = '/film';
export const PREFIX_FOCAL_LENGTH = '/focal';
-export const PREFIX_RECIPE = '/recipe';
// Dynamic paths
const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
@@ -113,6 +115,7 @@ export const pathForPhoto = ({
photo,
tag,
camera,
+ lens,
simulation,
focal,
recipe,
@@ -123,13 +126,15 @@ export const pathForPhoto = ({
? `${pathForTag(tag)}/${getPhotoId(photo)}`
: camera
? `${pathForCamera(camera)}/${getPhotoId(photo)}`
- : simulation
- ? `${pathForFilmSimulation(simulation)}/${getPhotoId(photo)}`
- : recipe
- ? `${pathForRecipe(recipe)}/${getPhotoId(photo)}`
- : focal
- ? `${pathForFocalLength(focal)}/${getPhotoId(photo)}`
- : `${PREFIX_PHOTO}/${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)}`;
export const pathForTag = (tag: string) =>
`${PREFIX_TAG}/${tag}`;
@@ -140,6 +145,9 @@ export const pathForCamera = ({ make, model }: Camera) =>
export const pathForFilmSimulation = (simulation: FilmSimulation) =>
`${PREFIX_FILM_SIMULATION}/${simulation}`;
+export const pathForLens = ({ make, model }: Lens) =>
+ `${PREFIX_LENS}/${parameterize(make, true)}/${parameterize(model, true)}`;
+
export const pathForFocalLength = (focal: number) =>
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
diff --git a/src/camera/index.ts b/src/camera/index.ts
index d78ff763..288dae73 100644
--- a/src/camera/index.ts
+++ b/src/camera/index.ts
@@ -3,6 +3,8 @@ import { parameterize } from '@/utility/string';
const CAMERA_PLACEHOLDER: Camera = { make: 'Camera', model: 'Model' };
+const CAMERA_MAKE_APPLE = 'apple';
+
export type Camera = {
make: string
model: string
@@ -57,7 +59,7 @@ export const cameraFromPhoto = (
: fallback ?? CAMERA_PLACEHOLDER;
const isCameraMakeApple = (make?: string) =>
- make?.toLocaleLowerCase() === 'apple';
+ make?.toLocaleLowerCase() === CAMERA_MAKE_APPLE;
export const isCameraApple = ({ make }: Camera) =>
isCameraMakeApple(make);
diff --git a/src/lens/PhotoLens.tsx b/src/lens/PhotoLens.tsx
new file mode 100644
index 00000000..790fdd4c
--- /dev/null
+++ b/src/lens/PhotoLens.tsx
@@ -0,0 +1,34 @@
+import { pathForLens } from '@/app/paths';
+import { Lens, formatLensText } from '.';
+import EntityLink, {
+ EntityLinkExternalProps,
+} from '@/components/primitives/EntityLink';
+import { TbCone } from 'react-icons/tb';
+
+export default function PhotoLens({
+ lens,
+ type,
+ badged,
+ contrast,
+ prefetch,
+ countOnHover,
+ className,
+}: {
+ lens: Lens
+ hideAppleIcon?: boolean
+ countOnHover?: number
+} & EntityLinkExternalProps) {
+ return (
+ }
+ type={type}
+ className={className}
+ badged={badged}
+ contrast={contrast}
+ prefetch={prefetch}
+ hoverEntity={countOnHover}
+ />
+ );
+}
diff --git a/src/lens/index.ts b/src/lens/index.ts
index 16189f2a..eb2a48ed 100644
--- a/src/lens/index.ts
+++ b/src/lens/index.ts
@@ -3,6 +3,8 @@ import { parameterize } from '@/utility/string';
const LENS_PLACEHOLDER: Lens = { make: 'Lens', model: 'Model' };
+const LENS_MAKE_APPLE = 'apple';
+
export type Lens = {
make: string
model: string
@@ -45,3 +47,12 @@ export const lensFromPhoto = (
photo?.lensMake && photo?.lensModel
? { make: photo.lensMake, model: photo.lensModel }
: fallback ?? LENS_PLACEHOLDER;
+
+const isLensMakeApple = (make?: string) =>
+ make?.toLocaleLowerCase() === LENS_MAKE_APPLE;
+
+export const isLensApple = ({ make }: Lens) =>
+ isLensMakeApple(make);
+
+export const formatLensText = ({ make, model }: Lens, short = true) =>
+ short ? model : `${make} ${model}`;
diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx
index 8ee7f82f..987530a6 100644
--- a/src/photo/PhotoLarge.tsx
+++ b/src/photo/PhotoLarge.tsx
@@ -6,6 +6,7 @@ import {
doesPhotoNeedBlurCompatibility,
shouldShowCameraDataForPhoto,
shouldShowExifDataForPhoto,
+ shouldShowLensDataForPhoto,
titleForPhoto,
} from '.';
import SiteGrid from '@/components/SiteGrid';
@@ -45,6 +46,8 @@ import { AnimatePresence } from 'framer-motion';
import useRecipeOverlay from '../recipe/useRecipeOverlay';
import PhotoRecipeOverlay from '@/recipe/PhotoRecipeOverlay';
import PhotoRecipe from '@/recipe/PhotoRecipe';
+import PhotoLens from '@/lens/PhotoLens';
+import { lensFromPhoto } from '@/lens';
export default function PhotoLarge({
photo,
@@ -57,6 +60,7 @@ export default function PhotoLarge({
showTitle = true,
showTitleAsH1,
showCamera = true,
+ showLens = true,
showSimulation = true,
showRecipe = true,
showZoomControls: showZoomControlsProp = true,
@@ -80,6 +84,7 @@ export default function PhotoLarge({
showTitle?: boolean
showTitleAsH1?: boolean
showCamera?: boolean
+ showLens?: boolean
showSimulation?: boolean
showRecipe?: boolean
showZoomControls?: boolean
@@ -121,10 +126,11 @@ export default function PhotoLarge({
const tags = sortTags(photo.tags, primaryTag);
const camera = cameraFromPhoto(photo);
-
+ const lens = lensFromPhoto(photo);
const { recipeTitle: recipe } = photo;
const showCameraContent = showCamera && shouldShowCameraDataForPhoto(photo);
+ const showLensContent = showLens && shouldShowLensDataForPhoto(photo);
const showRecipeContent = showRecipe && recipe;
const showTagsContent = tags.length > 0;
const showExifContent = shouldShowExifDataForPhoto(photo);
@@ -284,6 +290,15 @@ export default function PhotoLarge({
contrast="medium"
prefetch={prefetchRelatedLinks}
/>}
+ {showLensContent &&
+ <>
+
+
+ >}
{showRecipeContent &&
Boolean(photo.make) &&
Boolean(photo.model);
+const photoHasLensData = (photo: Photo) =>
+ Boolean(photo.lensMake) &&
+ Boolean(photo.lensModel);
+
const photoHasExifData = (photo: Photo) =>
Boolean(photo.focalLength) ||
Boolean(photo.focalLengthIn35MmFormat) ||
@@ -309,6 +313,9 @@ const photoHasExifData = (photo: Photo) =>
export const shouldShowCameraDataForPhoto = (photo: Photo) =>
SHOW_EXIF_DATA && photoHasCameraData(photo);
+export const shouldShowLensDataForPhoto = (photo: Photo) =>
+ SHOW_EXIF_DATA && photoHasLensData(photo);
+
export const shouldShowExifDataForPhoto = (photo: Photo) =>
SHOW_EXIF_DATA && photoHasExifData(photo);
diff --git a/src/photo/set.ts b/src/photo/set.ts
index 34484b87..04977850 100644
--- a/src/photo/set.ts
+++ b/src/photo/set.ts
@@ -10,10 +10,10 @@ import { Recipes } from '@/recipe';
const CATEGORY_KEYS = [
'tags',
'cameras',
+ 'lenses',
'recipes',
'films',
'focal-lengths',
- 'lenses',
] as const;
type CategoryKey = (typeof CATEGORY_KEYS)[number];
@@ -23,6 +23,7 @@ type CategoryKeys = CategoryKey[];
export const DEFAULT_CATEGORY_KEYS: CategoryKeys = [
'tags',
'cameras',
+ 'lenses',
'recipes',
'films',
];