diff --git a/src/admin/AdminPhotosTableInfinite.tsx b/src/admin/AdminPhotosTableInfinite.tsx index 0345cff6..ad7f61fc 100644 --- a/src/admin/AdminPhotosTableInfinite.tsx +++ b/src/admin/AdminPhotosTableInfinite.tsx @@ -1,5 +1,6 @@ 'use client'; +import { PATH_ADMIN_PHOTOS } from '@/site/paths'; import InfinitePhotoScroll from '../photo/InfinitePhotoScroll'; import AdminPhotosTable from './AdminPhotosTable'; @@ -12,7 +13,7 @@ export default function AdminPhotosTableInfinite({ }) { return ( ); } diff --git a/src/app/film/[simulation]/share/page.tsx b/src/app/film/[simulation]/share/page.tsx index b03b30b3..8a6d8a3b 100644 --- a/src/app/film/[simulation]/share/page.tsx +++ b/src/app/film/[simulation]/share/page.tsx @@ -2,11 +2,7 @@ import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo'; import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation'; import FilmSimulationOverview from '@/simulation/FilmSimulationOverview'; import FilmSimulationShareModal from '@/simulation/FilmSimulationShareModal'; -import { - getPhotosFilmSimulationDataCached, - getPhotosFilmSimulationDataCachedWithPagination, -} from '@/simulation/data'; -import { PaginationParams } from '@/site/pagination'; +import { getPhotosFilmSimulationDataCached } from '@/simulation/data'; import { Metadata } from 'next/types'; interface FilmSimulationProps { @@ -50,22 +46,19 @@ export async function generateMetadata({ export default async function Share({ params: { simulation }, - searchParams, -}: FilmSimulationProps & PaginationParams) { - const { +}: FilmSimulationProps) { + const [ photos, - count, - dateRange, - showMorePath, - } = await getPhotosFilmSimulationDataCachedWithPagination({ + { count, dateRange }, + ] = await getPhotosFilmSimulationDataCached({ simulation, - searchParams, + limit: GRID_THUMBNAILS_TO_SHOW_MAX, }); return <> ; diff --git a/src/app/grid/page.tsx b/src/app/grid/page.tsx index ee929b0a..a13f7e88 100644 --- a/src/app/grid/page.tsx +++ b/src/app/grid/page.tsx @@ -10,6 +10,7 @@ import { getPhotoSidebarData } from '@/photo/data'; import { getPhotos } from '@/photo/db'; import { cache } from 'react'; import PhotoGridPage from '@/photo/PhotoGridPage'; +import { PATH_GRID } from '@/site/paths'; export const dynamic = 'force-static'; @@ -38,6 +39,7 @@ export default async function GridPage() { return ( photos.length > 0 ? diff --git a/src/app/og/page.tsx b/src/app/og/page.tsx index b99fba93..5acab88d 100644 --- a/src/app/og/page.tsx +++ b/src/app/og/page.tsx @@ -1,36 +1,30 @@ -import { getPhotosCached, getPhotosCountCached } from '@/photo/cache'; -import MoreComponentsFromSearchParams from - '@/components/MoreComponentsFromSearchParams'; -import StaggeredOgPhotos from '@/photo/StaggeredOgPhotos'; import { - PaginationParams, - getPaginationFromSearchParams, -} from '@/site/pagination'; -import { pathForOg } from '@/site/paths'; - -export default async function GridPage({ searchParams }: PaginationParams) { - const { offset, limit } = getPaginationFromSearchParams(searchParams); + INFINITE_SCROLL_INITIAL_GRID, + INFINITE_SCROLL_MULTIPLE_GRID, +} from '@/photo'; +import { getPhotosCached, getPhotosCountCached } from '@/photo/cache'; +import StaggeredOgPhotos from '@/photo/StaggeredOgPhotos'; +import StaggeredOgPhotosInfinite from '@/photo/StaggeredOgPhotosInfinite'; +export default async function GridPage() { const [ photos, count, ] = await Promise.all([ - getPhotosCached({ limit }), + getPhotosCached({ limit: INFINITE_SCROLL_INITIAL_GRID }), getPhotosCountCached(), ]); - - const showMorePhotos = count > photos.length; return ( -
-
- -
- {showMorePhotos && - } -
+ <> + + {count > photos.length && +
+ +
} + ); } diff --git a/src/app/tag/[tag]/page.tsx b/src/app/tag/[tag]/page.tsx index ea436410..8ee15c8a 100644 --- a/src/app/tag/[tag]/page.tsx +++ b/src/app/tag/[tag]/page.tsx @@ -1,12 +1,8 @@ import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo'; -import { PaginationParams } from '@/site/pagination'; import { PATH_ROOT } from '@/site/paths'; import { generateMetaForTag } from '@/tag'; import TagOverview from '@/tag/TagOverview'; -import { - getPhotosTagDataCached, - getPhotosTagDataCachedWithPagination, -} from '@/tag/data'; +import { getPhotosTagDataCached } from '@/tag/data'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; @@ -55,23 +51,20 @@ export async function generateMetadata({ export default async function TagPage({ params: { tag: tagFromParams }, - searchParams, -}:TagProps & PaginationParams) { +}:TagProps) { const tag = decodeURIComponent(tagFromParams); - const { + const [ photos, - count, - showMorePath, - dateRange, - } = await getPhotosTagDataCachedWithPagination({ + { count, dateRange }, + ] = await getPhotosTagDataCached({ tag, - searchParams, + limit: GRID_THUMBNAILS_TO_SHOW_MAX, }); if (photos.length === 0) { redirect(PATH_ROOT); } return ( - + ); } diff --git a/src/app/tag/[tag]/share/page.tsx b/src/app/tag/[tag]/share/page.tsx index 5df6f74d..e50ad552 100644 --- a/src/app/tag/[tag]/share/page.tsx +++ b/src/app/tag/[tag]/share/page.tsx @@ -1,12 +1,8 @@ import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo'; -import { PaginationParams } from '@/site/pagination'; import { generateMetaForTag } from '@/tag'; import TagOverview from '@/tag/TagOverview'; import TagShareModal from '@/tag/TagShareModal'; -import { - getPhotosTagDataCached, - getPhotosTagDataCachedWithPagination, -} from '@/tag/data'; +import { getPhotosTagDataCached } from '@/tag/data'; import type { Metadata } from 'next'; interface TagProps { @@ -52,24 +48,21 @@ export async function generateMetadata({ export default async function Share({ params: { tag: tagFromParams }, - searchParams, -}: TagProps & PaginationParams) { +}: TagProps) { const tag = decodeURIComponent(tagFromParams); - - const { + + const [ photos, - count, - dateRange, - showMorePath, - } = await getPhotosTagDataCachedWithPagination({ + { count, dateRange }, + ] = await getPhotosTagDataCached({ tag, - searchParams, + limit: GRID_THUMBNAILS_TO_SHOW_MAX, }); return <> ; diff --git a/src/camera/CameraOverview.tsx b/src/camera/CameraOverview.tsx index b8144f3c..7a094871 100644 --- a/src/camera/CameraOverview.tsx +++ b/src/camera/CameraOverview.tsx @@ -1,5 +1,5 @@ import { Photo, PhotoDateRange } from '@/photo'; -import { Camera } from '.'; +import { Camera, createCameraKey } from '.'; import CameraHeader from './CameraHeader'; import PhotoGridPage from '@/photo/PhotoGridPage'; @@ -18,11 +18,17 @@ export default function CameraOverview({ }) { return ( , + header: , }} /> ); } diff --git a/src/components/OGTile.tsx b/src/components/OGTile.tsx index 46938167..e0d19a0b 100644 --- a/src/components/OGTile.tsx +++ b/src/components/OGTile.tsx @@ -1,11 +1,12 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { clsx } from 'clsx/lite'; import Link from 'next/link'; import { BiError } from 'react-icons/bi'; import Spinner from '@/components/Spinner'; import { IMAGE_OG_DIMENSION } from '../image-response'; +import useOnVisible from '@/utility/useOnVisible'; export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed'; @@ -19,6 +20,7 @@ export default function OGTile({ onLoad, onFail, retryTime, + onVisible, }: { title: string description: string @@ -29,7 +31,10 @@ export default function OGTile({ onFail?: () => void riseOnHover?: boolean retryTime?: number + onVisible?: () => void }) { + const ref = useRef(null); + const [loadingStateInternal, setLoadingStateInternal] = useState(loadingStateExternal ?? 'unloaded'); @@ -46,8 +51,11 @@ export default function OGTile({ const { width, height, aspectRatio } = IMAGE_OG_DIMENSION; + useOnVisible(ref, onVisible); + return ( void diff --git a/src/photo/PhotoGridInfinite.tsx b/src/photo/PhotoGridInfinite.tsx index 7141c69b..76b4e95a 100644 --- a/src/photo/PhotoGridInfinite.tsx +++ b/src/photo/PhotoGridInfinite.tsx @@ -6,17 +6,19 @@ import InfinitePhotoScroll from './InfinitePhotoScroll'; import PhotoGrid from './PhotoGrid'; export default function PhotoGridInfinite({ + cacheKey, initialOffset, camera, animateOnFirstLoadOnly, }: { - initialOffset: number, - camera?: Camera, - animateOnFirstLoadOnly?: boolean, + cacheKey: string + initialOffset: number + camera?: Camera + animateOnFirstLoadOnly?: boolean }) { return ( {count > photos.length && void riseOnHover?: boolean retryTime?: number + onVisible?: () => void }) { return ( ); }; diff --git a/src/photo/PhotosLargeInfinite.tsx b/src/photo/PhotosLargeInfinite.tsx index e4a90d24..0c80c0a9 100644 --- a/src/photo/PhotosLargeInfinite.tsx +++ b/src/photo/PhotosLargeInfinite.tsx @@ -1,5 +1,6 @@ 'use client'; +import { PATH_ROOT } from '@/site/paths'; import InfinitePhotoScroll from './InfinitePhotoScroll'; import PhotosLarge from './PhotosLarge'; @@ -12,7 +13,7 @@ export default function PhotosLargeInfinite({ }) { return ( ; export default function StaggeredOgPhotos({ photos, maxConcurrency = DEFAULT_MAX_CONCURRENCY, + onLastPhotoVisible, }: { photos: Photo[] maxConcurrency?: number + onLastPhotoVisible?: () => void }) { const [loadingState, setLoadingState] = useState( photos.reduce((acc, photo) => ({ @@ -52,13 +54,20 @@ export default function StaggeredOgPhotos({ recomputeLoadingState(); }, [recomputeLoadingState]); - return photos.map(photo => - recomputeLoadingState({ [photo.id]: 'loaded' })} - onFail={() => recomputeLoadingState({ [photo.id]: 'failed' })} - riseOnHover - />); + return ( +
+ {photos.map((photo, index) => + recomputeLoadingState({ [photo.id]: 'loaded' })} + onFail={() => recomputeLoadingState({ [photo.id]: 'failed' })} + onVisible={index === photos.length - 1 + ? onLastPhotoVisible + :undefined} + riseOnHover + />)} +
+ ); }; diff --git a/src/photo/StaggeredOgPhotosInfinite.tsx b/src/photo/StaggeredOgPhotosInfinite.tsx new file mode 100644 index 00000000..f568bbc3 --- /dev/null +++ b/src/photo/StaggeredOgPhotosInfinite.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { PATH_OG } from '@/site/paths'; +import InfinitePhotoScroll from './InfinitePhotoScroll'; +import StaggeredOgPhotos from './StaggeredOgPhotos'; + +export default function StaggeredOgPhotosInfinite({ + initialOffset, + itemsPerPage, +}: { + initialOffset: number + itemsPerPage: number +}) { + return ( + + {({ photos, onLastPhotoVisible }) => + } + + ); +} diff --git a/src/simulation/FilmSimulationOverview.tsx b/src/simulation/FilmSimulationOverview.tsx index bd1cbb9f..2f2da7ce 100644 --- a/src/simulation/FilmSimulationOverview.tsx +++ b/src/simulation/FilmSimulationOverview.tsx @@ -1,42 +1,33 @@ import { Photo, PhotoDateRange } from '@/photo'; -import SiteGrid from '@/components/SiteGrid'; -import AnimateItems from '@/components/AnimateItems'; -import PhotoGrid from '@/photo/PhotoGrid'; import FilmSimulationHeader from './FilmSimulationHeader'; import { FilmSimulation } from '.'; +import PhotoGridPage from '@/photo/PhotoGridPage'; export default function FilmSimulationOverview({ simulation, photos, count, dateRange, - showMorePath, animateOnFirstLoadOnly, }: { simulation: FilmSimulation, photos: Photo[], count: number, dateRange?: PhotoDateRange, - showMorePath?: string, animateOnFirstLoadOnly?: boolean, }) { return ( - - , - ]} - animateOnFirstLoadOnly - /> - - } - /> + , + animateOnFirstLoadOnly, + }} /> ); } diff --git a/src/simulation/data.ts b/src/simulation/data.ts index 99fa1946..d28b026e 100644 --- a/src/simulation/data.ts +++ b/src/simulation/data.ts @@ -2,11 +2,6 @@ import { getPhotosCached, getPhotosFilmSimulationMetaCached, } from '@/photo/cache'; -import { - PaginationSearchParams, - getPaginationFromSearchParams, -} from '@/site/pagination'; -import { pathForFilmSimulation } from '@/site/paths'; import { FilmSimulation } from '.'; export const getPhotosFilmSimulationDataCached = ({ @@ -20,32 +15,3 @@ export const getPhotosFilmSimulationDataCached = ({ getPhotosCached({ simulation, limit }), getPhotosFilmSimulationMetaCached(simulation), ]); - -export const getPhotosFilmSimulationDataCachedWithPagination = async ({ - simulation, - limit: limitProp, - searchParams, -}: { - simulation: FilmSimulation, - limit?: number, - searchParams?: PaginationSearchParams, -}) => { - const { offset, limit } = getPaginationFromSearchParams(searchParams); - - const [photos, { count, dateRange }] = - await getPhotosFilmSimulationDataCached({ - simulation, - limit: limitProp ?? limit, - }); - - const showMorePath = count > photos.length - ? pathForFilmSimulation(simulation, offset + 1) - : undefined; - - return { - photos, - count, - dateRange, - showMorePath, - }; -}; diff --git a/src/site/pagination.ts b/src/site/pagination.ts deleted file mode 100644 index 1ff9b9ae..00000000 --- a/src/site/pagination.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type PaginationSearchParams = { next: string }; - -export interface PaginationParams { - searchParams?: PaginationSearchParams -} - -export const getPaginationFromSearchParams = ( - query?: PaginationSearchParams, - limitPerOffset = 24, -) => { - const offsetInt = parseInt(query?.next ?? '0'); - const offset = (Number.isNaN(offsetInt) ? 0 : offsetInt); - return { - offset, - limit: limitPerOffset + offset * limitPerOffset, - }; -}; diff --git a/src/site/paths.ts b/src/site/paths.ts index e18af551..9996ac6f 100644 --- a/src/site/paths.ts +++ b/src/site/paths.ts @@ -252,7 +252,8 @@ export const isPathAdminConfiguration = (pathname?: string) => export const isPathProtected = (pathname?: string) => checkPathPrefix(pathname, PATH_ADMIN) || - checkPathPrefix(pathname, pathForTag(TAG_HIDDEN)); + checkPathPrefix(pathname, pathForTag(TAG_HIDDEN)) || + pathname === PATH_OG; export const getPathComponents = (pathname = ''): { photoId?: string diff --git a/src/tag/TagOverview.tsx b/src/tag/TagOverview.tsx index 505ef098..1928498e 100644 --- a/src/tag/TagOverview.tsx +++ b/src/tag/TagOverview.tsx @@ -1,41 +1,32 @@ import { Photo, PhotoDateRange } from '@/photo'; -import SiteGrid from '@/components/SiteGrid'; -import AnimateItems from '@/components/AnimateItems'; -import PhotoGrid from '@/photo/PhotoGrid'; import TagHeader from './TagHeader'; +import PhotoGridPage from '@/photo/PhotoGridPage'; export default function TagOverview({ tag, photos, count, dateRange, - showMorePath, animateOnFirstLoadOnly, }: { tag: string, photos: Photo[], count: number, dateRange?: PhotoDateRange, - showMorePath?: string, animateOnFirstLoadOnly?: boolean, }) { return ( - - , - ]} - animateOnFirstLoadOnly - /> - - } - /> + , + animateOnFirstLoadOnly, + }} /> ); } diff --git a/src/tag/data.ts b/src/tag/data.ts index 22a53fd3..c760d128 100644 --- a/src/tag/data.ts +++ b/src/tag/data.ts @@ -2,11 +2,6 @@ import { getPhotosCachedCached, getPhotosTagMetaCached, } from '@/photo/cache'; -import { - PaginationSearchParams, - getPaginationFromSearchParams, -} from '@/site/pagination'; -import { pathForTag } from '@/site/paths'; export const getPhotosTagDataCached = ({ tag, @@ -20,31 +15,3 @@ export const getPhotosTagDataCached = ({ getPhotosTagMetaCached(tag), ]); -export const getPhotosTagDataCachedWithPagination = async ({ - tag, - limit: limitProp, - searchParams, -}: { - tag: string, - limit?: number, - searchParams?: PaginationSearchParams, -}) => { - const { offset, limit } = getPaginationFromSearchParams(searchParams); - - const [photos, { count, dateRange }] = - await getPhotosTagDataCached({ - tag, - limit: limitProp ?? limit, - }); - - const showMorePath = count > photos.length - ? pathForTag(tag, offset + 1) - : undefined; - - return { - photos, - count, - dateRange, - showMorePath, - }; -};