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', ];