'use client'; import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility, shouldShowCameraDataForPhoto, shouldShowExifDataForPhoto, shouldShowFilmDataForPhoto, shouldShowLensDataForPhoto, shouldShowRecipeDataForPhoto, titleForPhoto, } from '.'; import AppGrid from '@/components/AppGrid'; import ImageLarge from '@/components/image/ImageLarge'; import { clsx } from 'clsx/lite'; import Link from 'next/link'; import { pathForFocalLength, pathForPhoto } from '@/app/path'; import PhotoTags from '@/tag/PhotoTags'; import ShareButton from '@/share/ShareButton'; import DownloadButton from '@/components/DownloadButton'; import PhotoCamera from '../camera/PhotoCamera'; import { cameraFromPhoto } from '@/camera'; import PhotoFilm from '@/film/PhotoFilm'; import { sortTagsArray } from '@/tag'; import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; import PhotoLink from './PhotoLink'; import { SHOULD_PREFETCH_ALL_LINKS, ALLOW_PUBLIC_DOWNLOADS, SHOW_TAKEN_AT_TIME, MATTE_COLOR, MATTE_COLOR_DARK, } from '@/app/config'; import AdminPhotoMenu from '@/admin/AdminPhotoMenu'; import { RevalidatePhoto } from './InfinitePhotoScroll'; import { useCallback, useMemo, useRef } from 'react'; import useVisibility from '@/utility/useVisibility'; import PhotoDate from './PhotoDate'; import { useAppState } from '@/app/AppState'; import { LuExpand } from 'react-icons/lu'; import LoaderButton from '@/components/primitives/LoaderButton'; import Tooltip from '@/components/Tooltip'; import ZoomControls, { ZoomControlsRef } from '@/components/image/ZoomControls'; 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'; import MaskedScroll from '@/components/MaskedScroll'; import useCategoryCountsForPhoto from '@/category/useCategoryCountsForPhoto'; import { useAppText } from '@/i18n/state/client'; export default function PhotoLarge({ photo, className, primaryTag, priority, prefetch = SHOULD_PREFETCH_ALL_LINKS, prefetchRelatedLinks = SHOULD_PREFETCH_ALL_LINKS, recent, year, revalidatePhoto, showTitle = true, showTitleAsH1, showCamera = true, showLens = true, showFilm = true, showRecipe = true, showZoomControls: _showZoomControls = true, shouldZoomOnFKeydown = true, shouldShare = true, shouldShareRecents, shouldShareYear, shouldShareCamera, shouldShareLens, shouldShareTag, shouldShareFilm, shouldShareRecipe, shouldShareFocalLength, includeFavoriteInAdminMenu, onVisible, showAdminKeyCommands, }: { photo: Photo className?: string primaryTag?: string priority?: boolean prefetch?: boolean prefetchRelatedLinks?: boolean recent?: boolean year?: string revalidatePhoto?: RevalidatePhoto showTitle?: boolean showTitleAsH1?: boolean showCamera?: boolean showLens?: boolean showFilm?: boolean showRecipe?: boolean showZoomControls?: boolean shouldZoomOnFKeydown?: boolean shouldShare?: boolean shouldShareRecents?: boolean shouldShareYear?: boolean shouldShareCamera?: boolean shouldShareLens?: boolean shouldShareTag?: boolean shouldShareFilm?: boolean shouldShareRecipe?: boolean shouldShareFocalLength?: boolean includeFavoriteInAdminMenu?: boolean onVisible?: () => void showAdminKeyCommands?: boolean }) { const ref = useRef(null); const refZoomControls = useRef(null); const refPhotoRecipe = useRef(null); const refPhotoFilm = useRef(null); const { areZoomControlsShown, arePhotosMatted, shouldDebugRecipeOverlays, isUserSignedIn, } = useAppState(); const appText = useAppText(); const { cameraCount, lensCount, tagCounts, recipeCount, filmCount, } = useCategoryCountsForPhoto(photo); const showZoomControls = _showZoomControls && areZoomControlsShown; const selectZoomImageElement = useCallback( (container: HTMLElement | null) => Array .from(container?.getElementsByTagName('img') ?? []) // Ignore fallback blur images .filter((img) => !img.src.startsWith('data:image'))[0] , []); const refRecipe = useRef(null); const refTriggers = useMemo(() => [ refPhotoRecipe, refPhotoFilm, ], []); const { isShowingRecipeOverlay, toggleRecipeOverlay, hideRecipeOverlay, } = useRecipeOverlay({ ref: refRecipe, refTriggers, }); const tags = sortTagsArray(photo.tags, primaryTag); const camera = cameraFromPhoto(photo); const lens = lensFromPhoto(photo); const { recipeTitle } = photo; const showExifContent = shouldShowExifDataForPhoto(photo); const showCameraContent = showCamera && shouldShowCameraDataForPhoto(photo); const showLensContent = showLens && shouldShowLensDataForPhoto(photo); const showTagsContent = tags.length > 0; const showRecipeContent = showRecipe && shouldShowRecipeDataForPhoto(photo); const showFilmContent = showFilm && shouldShowFilmDataForPhoto(photo); useVisibility({ ref, onVisible }); const hasTitle = showTitle && Boolean(photo.title); const hasTitleContent = hasTitle || Boolean(photo.caption); const hasMetaContent = showCameraContent || showLensContent || showTagsContent || showRecipeContent || showFilmContent || showExifContent; const hasNonDateContent = hasTitleContent || hasMetaContent; const renderPhotoLink = ; // Restrict width for landscape photos // (portrait photos are always height restricted) const matteContentWidthForAspectRatio = photo.aspectRatio > 3 / 2 + 0.1 ? 'w-[90%]' : photo.aspectRatio >= 1 ? 'w-[80%]' : undefined; const renderLargePhoto =
{(isShowingRecipeOverlay || shouldDebugRecipeOverlays) && photo.recipeData && photo.film && }
; const renderAdminMenu = ; const largePhotoContainerClassName = clsx( arePhotosMatted && 'flex items-center justify-center aspect-3/2', // Matte theme colors defined in root layout arePhotosMatted && (MATTE_COLOR ? 'bg-(--matte-bg)' : 'bg-gray-100'), arePhotosMatted && (MATTE_COLOR_DARK ? 'dark:bg-(--matte-bg-dark)' // Only specify dark background when MATTE_COLOR is not configured : !MATTE_COLOR && 'dark:bg-gray-700/30'), ); return ( {renderLargePhoto} : {renderLargePhoto} } classNameSide="relative" sideHiddenOnMobile={false} contentSide={
{/* Meta */}
{renderAdminMenu}
{hasTitle && (showTitleAsH1 ?

{renderPhotoLink}

: renderPhotoLink)}
{photo.caption &&
{photo.caption}
} {( showCameraContent || showLensContent || showRecipeContent || showTagsContent ) &&
{(showCameraContent || showLensContent) &&
{showCameraContent && } {showLensContent && }
} {showRecipeContent && recipeTitle && } {showTagsContent && }
}
{/* EXIF Data */}
{renderAdminMenu}
{showExifContent && <>
  • {photo.focalLength && {photo.focalLengthFormatted} } {( photo.focalLengthIn35MmFormatFormatted && // eslint-disable-next-line max-len photo.focalLengthIn35MmFormatFormatted !== photo.focalLengthFormatted ) && <> {' '} {photo.focalLengthIn35MmFormatFormatted} }
  • {photo.fNumberFormatted}
  • {photo.exposureTimeFormatted}
  • {photo.isoFormatted}
  • {photo.exposureCompensationFormatted ?? '0ev'}
{showFilmContent && photo.film && } }
{showZoomControls && } onClick={() => refZoomControls.current?.open()} styleAs="link" className="text-medium translate-y-[0.25px]" hideFocusOutline />} {shouldShare && } {ALLOW_PUBLIC_DOWNLOADS && }
} /> ); };