From db77448a635f00e76d450896d05372f47f3a59da Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sat, 31 Aug 2024 19:43:52 -0500 Subject: [PATCH] Combine photo nav + sets --- src/camera/CameraHeader.tsx | 5 +- src/focal/FocalLengthHeader.tsx | 5 +- src/photo/InfinitePhotoScroll.tsx | 9 +- src/photo/PhotoDetailPage.tsx | 142 +++++++----------- src/photo/PhotoGrid.tsx | 10 +- src/photo/PhotoGridContainer.tsx | 2 +- src/photo/PhotoHeader.tsx | 123 +++++++++++++++ src/photo/PhotoLink.tsx | 10 +- src/photo/PhotoMedium.tsx | 15 +- src/photo/{PhotoNav.tsx => PhotoPrevNext.tsx} | 110 ++++++-------- src/photo/PhotoSetHeader.tsx | 85 ----------- src/photo/PhotoShareModal.tsx | 10 +- src/photo/PhotoSmall.tsx | 15 +- src/photo/db/index.ts | 11 +- src/photo/index.ts | 10 ++ src/simulation/FilmSimulationHeader.tsx | 5 +- src/site/globals.css | 4 + src/site/paths.ts | 16 +- src/tag/HiddenHeader.tsx | 4 +- src/tag/TagHeader.tsx | 5 +- 20 files changed, 281 insertions(+), 315 deletions(-) create mode 100644 src/photo/PhotoHeader.tsx rename src/photo/{PhotoNav.tsx => PhotoPrevNext.tsx} (53%) delete mode 100644 src/photo/PhotoSetHeader.tsx diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx index 82675100..69326614 100644 --- a/src/camera/CameraHeader.tsx +++ b/src/camera/CameraHeader.tsx @@ -1,6 +1,6 @@ import { Photo, PhotoDateRange } from '@/photo'; import { pathForCameraShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import { Camera, cameraFromPhoto } from '.'; import PhotoCamera from './PhotoCamera'; import { descriptionForCameraPhotos } from './meta'; @@ -22,7 +22,8 @@ export default function CameraHeader({ }) { const camera = cameraFromPhoto(photos[0], cameraProp); return ( - } entityVerb="Photo" entityDescription={ diff --git a/src/focal/FocalLengthHeader.tsx b/src/focal/FocalLengthHeader.tsx index b40f459a..e30d68f3 100644 --- a/src/focal/FocalLengthHeader.tsx +++ b/src/focal/FocalLengthHeader.tsx @@ -1,7 +1,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { descriptionForFocalLengthPhotos } from '.'; import { pathForFocalLengthShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFocalLength from './PhotoFocalLength'; export default function FocalLengthHeader({ @@ -20,7 +20,8 @@ export default function FocalLengthHeader({ dateRange?: PhotoDateRange }) { return ( - } entityDescription={descriptionForFocalLengthPhotos( photos, diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index dfb85f37..b0739287 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -10,11 +10,9 @@ import { import SiteGrid from '@/components/SiteGrid'; import Spinner from '@/components/Spinner'; import { getPhotosCachedAction, getPhotosAction } from '@/photo/actions'; -import { Photo } from '.'; +import { Photo, PhotoSetAttributes } from '.'; import { clsx } from 'clsx/lite'; import { useAppState } from '@/state/AppState'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { GetPhotosOptions } from './db'; export type RevalidatePhoto = ( @@ -38,9 +36,6 @@ export default function InfinitePhotoScroll({ initialOffset: number itemsPerPage: number sortBy?: GetPhotosOptions['sortBy'] - tag?: string - camera?: Camera - simulation?: FilmSimulation cacheKey: string wrapMoreButtonInGrid?: boolean useCachedPhotos?: boolean @@ -50,7 +45,7 @@ export default function InfinitePhotoScroll({ onLastPhotoVisible: () => void revalidatePhoto?: RevalidatePhoto }) => ReactNode -}) { +} & PhotoSetAttributes) { const { swrTimestamp, isUserSignedIn } = useAppState(); const key = `${swrTimestamp}-${cacheKey}`; diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index 630f20ce..e86e5f5e 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -1,17 +1,15 @@ import AnimateItems from '@/components/AnimateItems'; -import { Photo, PhotoDateRange } from '.'; +import { Photo, PhotoDateRange, PhotoSetAttributes } from '.'; import PhotoLarge from './PhotoLarge'; import SiteGrid from '@/components/SiteGrid'; import PhotoGrid from './PhotoGrid'; -import PhotoNav from './PhotoNav'; import TagHeader from '@/tag/TagHeader'; -import { Camera } from '@/camera'; import CameraHeader from '@/camera/CameraHeader'; -import { FilmSimulation } from '@/simulation'; import FilmSimulationHeader from '@/simulation/FilmSimulationHeader'; import { TAG_HIDDEN } from '@/tag'; import HiddenHeader from '@/tag/HiddenHeader'; import FocalLengthHeader from '@/focal/FocalLengthHeader'; +import PhotoHeader from './PhotoHeader'; export default function PhotoDetailPage({ photo, @@ -30,94 +28,68 @@ export default function PhotoDetailPage({ photo: Photo photos: Photo[] photosGrid?: Photo[] - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number indexNumber?: number count?: number dateRange?: PhotoDateRange shouldShare?: boolean includeFavoriteInAdminMenu?: boolean -}) { +} & PhotoSetAttributes) { + let customHeader: JSX.Element | undefined; + + if (tag) { + customHeader = tag === TAG_HIDDEN + ? + : ; + } else if (camera) { + customHeader = ; + } else if (simulation) { + customHeader = ; + } else if (focal) { + customHeader = ; + } + return (
- {tag && - - : } + } - {camera && - } - />} - {simulation && - } - />} - {focal && - } - />} - } - />, - ]} /> void onAnimationComplete?: () => void -}) { +} & PhotoSetAttributes) { const { isUserSignedIn, selectedPhotoIds, diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx index dd29fafd..0346c4b5 100644 --- a/src/photo/PhotoGridContainer.tsx +++ b/src/photo/PhotoGridContainer.tsx @@ -38,7 +38,7 @@ export default function PhotoGridContainer({ return ( {header && photo.id === selectedPhoto.id) + : undefined; + + const renderPrevNext = () => + ; + + const renderDateRange = () => + + {start === end + ? start + : <>{end}
– {start}} +
; + + return ( + + + {entity ?? ( + (selectedPhoto?.title || SHOW_PHOTO_TITLE_FALLBACK_TEXT) + ? + : <>X of X + )} + + + {entity && <> + {selectedPhotoIndex !== undefined + // eslint-disable-next-line max-len + ? `${entityVerb ? `${entityVerb} ` : ''}${indexNumber || (selectedPhotoIndex + 1)} of ${count ?? photos.length}` + : entityDescription} + {selectedPhotoIndex === undefined && sharePath && + } + } + +
+ {selectedPhoto + ? renderPrevNext() + : renderDateRange()} +
+ , + ]} + /> + ); +} diff --git a/src/photo/PhotoLink.tsx b/src/photo/PhotoLink.tsx index 34bcca38..088ffc1d 100644 --- a/src/photo/PhotoLink.tsx +++ b/src/photo/PhotoLink.tsx @@ -1,13 +1,11 @@ 'use client'; import { ReactNode } from 'react'; -import { Photo, titleForPhoto } from '@/photo'; +import { Photo, PhotoSetAttributes, titleForPhoto } from '@/photo'; import Link from 'next/link'; import { AnimationConfig } from '../components/AnimateItems'; import { useAppState } from '@/state/AppState'; import { pathForPhoto } from '@/site/paths'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { clsx } from 'clsx/lite'; export default function PhotoLink({ @@ -23,16 +21,12 @@ export default function PhotoLink({ children, }: { photo?: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number scroll?: boolean prefetch?: boolean nextPhotoAnimation?: AnimationConfig className?: string children?: ReactNode -}) { +} & PhotoSetAttributes) { const { setNextPhotoAnimation } = useAppState(); return ( diff --git a/src/photo/PhotoMedium.tsx b/src/photo/PhotoMedium.tsx index 72fdd0a4..df1bab48 100644 --- a/src/photo/PhotoMedium.tsx +++ b/src/photo/PhotoMedium.tsx @@ -1,12 +1,15 @@ 'use client'; -import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.'; +import { + Photo, + PhotoSetAttributes, + altTextForPhoto, + doesPhotoNeedBlurCompatibility, +} from '.'; import ImageMedium from '@/components/image/ImageMedium'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; import { pathForPhoto } from '@/site/paths'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config'; import { useRef } from 'react'; import useOnVisible from '@/utility/useOnVisible'; @@ -24,16 +27,12 @@ export default function PhotoMedium({ onVisible, }: { photo: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number selected?: boolean priority?: boolean prefetch?: boolean className?: string onVisible?: () => void -}) { +} & PhotoSetAttributes) { const ref = useRef(null); useOnVisible(ref, onVisible); diff --git a/src/photo/PhotoNav.tsx b/src/photo/PhotoPrevNext.tsx similarity index 53% rename from src/photo/PhotoNav.tsx rename to src/photo/PhotoPrevNext.tsx index 25020df1..a8ef3b2d 100644 --- a/src/photo/PhotoNav.tsx +++ b/src/photo/PhotoPrevNext.tsx @@ -1,16 +1,17 @@ 'use client'; import { useEffect } from 'react'; -import { Photo, getNextPhoto, getPreviousPhoto } from '@/photo'; +import { + Photo, + PhotoSetAttributes, + getNextPhoto, + getPreviousPhoto, +} from '@/photo'; import PhotoLink from './PhotoLink'; import { useRouter } from 'next/navigation'; import { pathForPhoto } from '@/site/paths'; import { useAppState } from '@/state/AppState'; import { AnimationConfig } from '@/components/AnimateItems'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; -import { SHOW_PHOTO_TITLE_FALLBACK_TEXT } from '@/site/config'; -import { BiChevronLeft, BiChevronRight } from 'react-icons/bi'; import { clsx } from 'clsx/lite'; const LISTENER_KEYUP = 'keyup'; @@ -18,25 +19,19 @@ const LISTENER_KEYUP = 'keyup'; const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 }; const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 }; -export default function PhotoNav({ +export default function PhotoPrevNext({ photo, - photos, + photos = [], className, tag, camera, simulation, focal, - prefetch, }: { - photo: Photo - photos: Photo[] + photo?: Photo + photos?: Photo[] className?: string - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number - prefetch?: boolean -}) { +} & PhotoSetAttributes) { const router = useRouter(); const { @@ -44,8 +39,8 @@ export default function PhotoNav({ shouldRespondToKeyboardCommands, } = useAppState(); - const previousPhoto = getPreviousPhoto(photo, photos); - const nextPhoto = getNextPhoto(photo, photos); + const previousPhoto = photo ? getPreviousPhoto(photo, photos) : undefined; + const nextPhoto = photo ? getNextPhoto(photo, photos) : undefined; useEffect(() => { if (shouldRespondToKeyboardCommands) { @@ -105,54 +100,39 @@ export default function PhotoNav({ 'flex items-center', className, )}> - - - - PREV - - -
- {(photo.title || SHOW_PHOTO_TITLE_FALLBACK_TEXT) && - } +
+ + + PREV + + + / + + + NEXT + +
- - - NEXT - - -
); }; diff --git a/src/photo/PhotoSetHeader.tsx b/src/photo/PhotoSetHeader.tsx deleted file mode 100644 index 87b4da2f..00000000 --- a/src/photo/PhotoSetHeader.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { clsx } from 'clsx/lite'; -import { Photo, PhotoDateRange, dateRangeForPhotos } from '.'; -import ShareButton from '@/components/ShareButton'; -import AnimateItems from '@/components/AnimateItems'; -import { ReactNode } from 'react'; -import { HIGH_DENSITY_GRID } from '@/site/config'; -import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; - -export default function PhotoSetHeader({ - entity, - entityVerb, - entityDescription, - photos, - selectedPhoto, - sharePath, - indexNumber, - count, - dateRange, -}: { - entity: ReactNode - entityVerb?: string - entityDescription: string - photos: Photo[] - selectedPhoto?: Photo - sharePath?: string - indexNumber?: number - count?: number - dateRange?: PhotoDateRange -}) { - const { start, end } = dateRangeForPhotos(photos, dateRange); - - const selectedPhotoIndex = selectedPhoto - ? photos.findIndex(photo => photo.id === selectedPhoto.id) - : undefined; - - return ( - - - {entity} - - - {selectedPhotoIndex !== undefined - // eslint-disable-next-line max-len - ? `${entityVerb ? `${entityVerb} ` : ''}${indexNumber || (selectedPhotoIndex + 1)} of ${count ?? photos.length}` - : entityDescription} - {selectedPhotoIndex === undefined && sharePath && - } - - - {start === end - ? start - : <>{end}
– {start}} -
- ]} - /> - ); -} diff --git a/src/photo/PhotoShareModal.tsx b/src/photo/PhotoShareModal.tsx index b9a6f14c..8f577454 100644 --- a/src/photo/PhotoShareModal.tsx +++ b/src/photo/PhotoShareModal.tsx @@ -1,17 +1,11 @@ import PhotoOGTile from '@/photo/PhotoOGTile'; import { absolutePathForPhoto, pathForPhoto } from '@/site/paths'; -import { Photo } from '.'; +import { Photo, PhotoSetAttributes } from '.'; import ShareModal from '@/components/ShareModal'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; export default function PhotoShareModal(props: { photo: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -}) { +} & PhotoSetAttributes) { return ( void -}) { +} & PhotoSetAttributes) { const ref = useRef(null); useOnVisible(ref, onVisible); diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index 2bc13658..cf2b1ec7 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -1,8 +1,6 @@ -import { Camera } from '@/camera'; -import { Lens } from '@/lens'; -import { FilmSimulation } from '@/simulation'; import { PRIORITY_ORDER_ENABLED } from '@/site/config'; import { parameterize } from '@/utility/string'; +import { PhotoSetAttributes } from '..'; export const GENERATE_STATIC_PARAMS_LIMIT = 1000; export const PHOTO_DEFAULT_LIMIT = 100; @@ -12,16 +10,11 @@ export type GetPhotosOptions = { limit?: number offset?: number query?: string - tag?: string - camera?: Camera - lens?: Lens - simulation?: FilmSimulation - focal?: number takenBefore?: Date takenAfterInclusive?: Date updatedBefore?: Date hidden?: 'exclude' | 'include' | 'only' -}; +} & PhotoSetAttributes; export const areOptionsSensitive = (options: GetPhotosOptions) => options.hidden === 'include' || options.hidden === 'only'; diff --git a/src/photo/index.ts b/src/photo/index.ts index 646bd13e..bf3feab3 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -1,4 +1,6 @@ +import { Camera } from '@/camera'; import { formatFocalLength } from '@/focal'; +import { Lens } from '@/lens'; import { getNextImageUrlForRequest } from '@/services/next-image'; import { FilmSimulation } from '@/simulation'; import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config'; @@ -99,6 +101,14 @@ export interface Photo extends PhotoDb { takenAtNaiveFormatted: string } +export interface PhotoSetAttributes { + tag?: string + camera?: Camera + simulation?: FilmSimulation + focal?: number + lens?: Lens // Unimplemented as a set +} + export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => { const photoDb = camelcaseKeys( photoDbRaw as unknown as Record diff --git a/src/simulation/FilmSimulationHeader.tsx b/src/simulation/FilmSimulationHeader.tsx index b0908572..a8c95402 100644 --- a/src/simulation/FilmSimulationHeader.tsx +++ b/src/simulation/FilmSimulationHeader.tsx @@ -1,7 +1,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { FilmSimulation, descriptionForFilmSimulationPhotos } from '.'; import { pathForFilmSimulationShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; @@ -21,7 +21,8 @@ export default function FilmSimulationHeader({ dateRange?: PhotoDateRange }) { return ( - } entityVerb="Photo" entityDescription={descriptionForFilmSimulationPhotos( diff --git a/src/site/globals.css b/src/site/globals.css index c7eccf13..13d47311 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -142,6 +142,10 @@ @apply text-gray-400/80 dark:text-gray-400/50 } + .text-extra-extra-dim { + @apply + text-gray-200 dark:text-gray-800 + } .text-icon { @apply text-gray-800 dark:text-gray-200 diff --git a/src/site/paths.ts b/src/site/paths.ts index 8d15811d..e812bfa8 100644 --- a/src/site/paths.ts +++ b/src/site/paths.ts @@ -1,4 +1,4 @@ -import { Photo } from '@/photo'; +import { Photo, PhotoSetAttributes } from '@/photo'; import { BASE_URL, GRID_HOMEPAGE_ENABLED } from './config'; import { Camera } from '@/camera'; import { FilmSimulation } from '@/simulation'; @@ -75,13 +75,7 @@ export const PATHS_TO_CACHE = [ ...PATHS_ADMIN, ]; -interface PhotoPathParams { - photo: PhotoOrPhotoId - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -} +type PhotoPathParams = { photo: PhotoOrPhotoId } & PhotoSetAttributes; // Absolute paths export const ABSOLUTE_PATH_FOR_HOME_IMAGE = `${BASE_URL}/home-image`; @@ -280,11 +274,7 @@ export const isPathProtected = (pathname?: string) => export const getPathComponents = (pathname = ''): { photoId?: string - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -} => { +} & PhotoSetAttributes => { const photoIdFromPhoto = pathname.match( new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1]; const photoIdFromTag = pathname.match( diff --git a/src/tag/HiddenHeader.tsx b/src/tag/HiddenHeader.tsx index feac25fb..244013be 100644 --- a/src/tag/HiddenHeader.tsx +++ b/src/tag/HiddenHeader.tsx @@ -1,5 +1,5 @@ import { Photo, photoQuantityText } from '@/photo'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import HiddenTag from './HiddenTag'; export default function HiddenHeader({ @@ -14,7 +14,7 @@ export default function HiddenHeader({ count: number }) { return ( - } entityDescription={photoQuantityText(count, false)} diff --git a/src/tag/TagHeader.tsx b/src/tag/TagHeader.tsx index 4a9e3165..6eff23bc 100644 --- a/src/tag/TagHeader.tsx +++ b/src/tag/TagHeader.tsx @@ -2,7 +2,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import PhotoTag from './PhotoTag'; import { descriptionForTaggedPhotos, isTagFavs } from '.'; import { pathForTagShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import FavsTag from './FavsTag'; export default function TagHeader({ @@ -21,7 +21,8 @@ export default function TagHeader({ dateRange?: PhotoDateRange }) { return ( - : }