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 (
-
: }