From 4039d3999d7d8d11d447514d60555d5268743375 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 11:05:33 -0500 Subject: [PATCH 1/8] Remove swr console.log --- src/photo/InfinitePhotoScroll.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index 0206c964..36db260e 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -41,7 +41,6 @@ export default function InfinitePhotoScroll({ , [key]); const fetcher = useCallback(([_key, size]: [string, number]) => { - console.log('Fetching', size); return getPhotosAction( initialOffset + size * itemsPerPage, itemsPerPage, From 1f0f9aa906ed52928e57775b1f7c893b697abbf5 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 11:09:33 -0500 Subject: [PATCH 2/8] Stop caching db requests on static home page --- src/app/page.tsx | 11 +++++++---- src/site/config.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 427fb627..59ea960c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,3 @@ -import { getPhotosCachedCached, getPhotosCountCached } from '@/photo/cache'; import { INFINITE_SCROLL_INITIAL_HOME, INFINITE_SCROLL_MULTIPLE_HOME, @@ -9,12 +8,16 @@ import { Metadata } from 'next/types'; import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response'; import InfinitePhotoScroll from '../photo/InfinitePhotoScroll'; import PhotosLarge from '@/photo/PhotosLarge'; +import { cache } from 'react'; +import { getPhotos, getPhotosCount } from '@/services/vercel-postgres'; export const dynamic = 'force-static'; +const getPhotosCached = cache(getPhotos); + export async function generateMetadata(): Promise { // Make homepage queries resilient to error on first time setup - const photos = await getPhotosCachedCached({ + const photos = await getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG, }) .catch(() => []); @@ -27,11 +30,11 @@ export default async function HomePage() { photos, photosCount, ] = await Promise.all([ - getPhotosCachedCached({ + getPhotosCached({ limit: INFINITE_SCROLL_INITIAL_HOME, }) .catch(() => []), - getPhotosCountCached() + getPhotosCount() .catch(() => 0), ]); diff --git a/src/site/config.ts b/src/site/config.ts index 0c10a34c..0c5d0b2d 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -5,7 +5,7 @@ import { makeUrlAbsolute, shortenUrl } from '@/utility/url'; // HARD-CODED GLOBAL CONFIGURATION export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined; -export const SHOULD_DEBUG_SQL = false; +export const SHOULD_DEBUG_SQL = true; // META / DOMAINS From 72dd044c837dff83b41b1d69a9247f6ffca79cd3 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 11:23:55 -0500 Subject: [PATCH 3/8] Hide banner for interior admin pages --- src/admin/AdminNavClient.tsx | 7 +++++-- src/site/paths.ts | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/admin/AdminNavClient.tsx b/src/admin/AdminNavClient.tsx index 69709277..953b56d1 100644 --- a/src/admin/AdminNavClient.tsx +++ b/src/admin/AdminNavClient.tsx @@ -6,6 +6,7 @@ import { PATH_ADMIN_CONFIGURATION, checkPathPrefix, isPathAdminConfiguration, + isPathTopLevelAdmin, } from '@/site/paths'; import { useAppState } from '@/state/AppState'; import { clsx } from 'clsx/lite'; @@ -39,17 +40,19 @@ export default function AdminNavClient({ .concat(adminUpdateTimes) , [mostRecentPhotoUpdateTime, adminUpdateTimes]); - const [shouldShowBanner, setShouldShowBanner] = + const [hasRecentUpdates, setHasRecentUpdates] = useState(areTimesRecent(updateTimes)); useEffect(() => { // Check every 10 seconds if update times are recent const timeout = setTimeout(() => - setShouldShowBanner(areTimesRecent(updateTimes)) + setHasRecentUpdates(areTimesRecent(updateTimes)) , 10_000); return () => clearTimeout(timeout); }, [updateTimes]); + const shouldShowBanner = hasRecentUpdates && isPathTopLevelAdmin(pathname); + return ( export const isPathAdmin = (pathname?: string) => checkPathPrefix(pathname, PATH_ADMIN); +export const isPathTopLevelAdmin = (pathname?: string) => + PATHS_ADMIN.some(path => path === pathname); + export const isPathAdminConfiguration = (pathname?: string) => checkPathPrefix(pathname, PATH_ADMIN_CONFIGURATION); From 618f0337413bc543d2b4f84d8b0ed1c291bf466f Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 12:46:48 -0500 Subject: [PATCH 4/8] Update photo updates banner --- src/admin/AdminNavClient.tsx | 2 +- src/components/InfoBlock.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/AdminNavClient.tsx b/src/admin/AdminNavClient.tsx index 953b56d1..4772002f 100644 --- a/src/admin/AdminNavClient.tsx +++ b/src/admin/AdminNavClient.tsx @@ -97,7 +97,7 @@ export default function AdminNavClient({
- Updates detected—they may take several minutes to show up + Photo updates detected—they may take several minutes to show up for visitors
} diff --git a/src/components/InfoBlock.tsx b/src/components/InfoBlock.tsx index ae780f8b..2426579e 100644 --- a/src/components/InfoBlock.tsx +++ b/src/components/InfoBlock.tsx @@ -24,7 +24,7 @@ export default function InfoBlock({ case 'blue': return [ 'text-main', 'bg-blue-50/50 border-blue-200', - 'dark:bg-blue-950/30 dark:border-blue-700/45', + 'dark:bg-blue-950/30 dark:border-blue-600/50', ]; } }; From 7ab54e48687075651a54499d977d1a37c4d42154 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 12:55:30 -0500 Subject: [PATCH 5/8] Stop caching /grid db requests --- src/app/grid/page.tsx | 9 ++++++--- src/photo/data.ts | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/app/grid/page.tsx b/src/app/grid/page.tsx index 03d93c11..8a1ffb51 100644 --- a/src/app/grid/page.tsx +++ b/src/app/grid/page.tsx @@ -1,4 +1,3 @@ -import { getPhotosCached } from '@/photo/cache'; import SiteGrid from '@/components/SiteGrid'; import { INFINITE_SCROLL_INITIAL_GRID, @@ -10,11 +9,15 @@ import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response'; import { Metadata } from 'next/types'; import PhotoGridSidebar from '@/photo/PhotoGridSidebar'; -import { getPhotoSidebarDataCached } from '@/photo/data'; +import { getPhotoSidebarData } from '@/photo/data'; import InfinitePhotoScroll from '@/photo/InfinitePhotoScroll'; +import { getPhotos } from '@/services/vercel-postgres'; +import { cache } from 'react'; export const dynamic = 'force-static'; +const getPhotosCached = cache(getPhotos); + export async function generateMetadata(): Promise { const photos = await getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG }); return generateOgImageMetaForPhotos(photos); @@ -29,7 +32,7 @@ export default async function GridPage() { simulations, ] = await Promise.all([ getPhotosCached({ limit: INFINITE_SCROLL_INITIAL_GRID }), - ...getPhotoSidebarDataCached(), + ...getPhotoSidebarData(), ]); return ( diff --git a/src/photo/data.ts b/src/photo/data.ts index 9705f779..caa294ee 100644 --- a/src/photo/data.ts +++ b/src/photo/data.ts @@ -4,14 +4,25 @@ import { getUniqueFilmSimulationsCached, getUniqueTagsCached, } from '@/photo/cache'; +import { + getPhotosCount, + getUniqueCameras, + getUniqueFilmSimulations, + getUniqueTags, +} from '@/services/vercel-postgres'; import { SHOW_FILM_SIMULATIONS } from '@/site/config'; -import { TAG_FAVS } from '@/tag'; +import { sortTagsObject } from '@/tag'; + +export const getPhotoSidebarData = () => [ + getPhotosCount(), + getUniqueTags().then(sortTagsObject), + getUniqueCameras(), + SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulations() : [], +] as const; export const getPhotoSidebarDataCached = () => [ getPhotosCountCached(), - getUniqueTagsCached().then(tags => - tags.filter(({ tag }) => tag === TAG_FAVS).concat( - tags.filter(({ tag }) => tag !== TAG_FAVS))), + getUniqueTagsCached().then(sortTagsObject), getUniqueCamerasCached(), SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [], ] as const; From 765f8367e5ca223254c9016b694de733dbed0670 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 12:59:42 -0500 Subject: [PATCH 6/8] Fix admin nav banner logic --- src/admin/AdminNavClient.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/admin/AdminNavClient.tsx b/src/admin/AdminNavClient.tsx index 4772002f..6c30609b 100644 --- a/src/admin/AdminNavClient.tsx +++ b/src/admin/AdminNavClient.tsx @@ -17,6 +17,7 @@ import { useEffect, useMemo, useState } from 'react'; import { BiCog } from 'react-icons/bi'; import { FaRegClock } from 'react-icons/fa'; +// Updates considered recent if they occurred in past 5 minutes const areTimesRecent = (dates: Date[]) => dates .some(date => differenceInMinutes(new Date(), date) < 5); @@ -44,11 +45,12 @@ export default function AdminNavClient({ useState(areTimesRecent(updateTimes)); useEffect(() => { - // Check every 10 seconds if update times are recent - const timeout = setTimeout(() => + // Check every 5 seconds if update times are recent + setHasRecentUpdates(areTimesRecent(updateTimes)); + const interval = setInterval(() => setHasRecentUpdates(areTimesRecent(updateTimes)) - , 10_000); - return () => clearTimeout(timeout); + , 5_000); + return () => clearInterval(interval); }, [updateTimes]); const shouldShowBanner = hasRecentUpdates && isPathTopLevelAdmin(pathname); From eb59e58b1c219c71c3f9061a828db4a031f6fe6e Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 13:43:01 -0500 Subject: [PATCH 7/8] Refactor infinite scroll component --- src/app/grid/page.tsx | 5 +- src/app/page.tsx | 6 +- src/photo/InfinitePhotoScroll.tsx | 67 ++++++++++++-------- src/photo/InfinitePhotoScrollGrid.tsx | 26 ++++++++ src/photo/InfinitePhotoScrollPhotosLarge.tsx | 27 ++++++++ src/photo/actions.ts | 5 +- 6 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 src/photo/InfinitePhotoScrollGrid.tsx create mode 100644 src/photo/InfinitePhotoScrollPhotosLarge.tsx diff --git a/src/app/grid/page.tsx b/src/app/grid/page.tsx index 8a1ffb51..a9db1275 100644 --- a/src/app/grid/page.tsx +++ b/src/app/grid/page.tsx @@ -10,9 +10,9 @@ import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response'; import { Metadata } from 'next/types'; import PhotoGridSidebar from '@/photo/PhotoGridSidebar'; import { getPhotoSidebarData } from '@/photo/data'; -import InfinitePhotoScroll from '@/photo/InfinitePhotoScroll'; import { getPhotos } from '@/services/vercel-postgres'; import { cache } from 'react'; +import InfinitePhotoScrollGrid from '@/photo/InfinitePhotoScrollGrid'; export const dynamic = 'force-static'; @@ -41,8 +41,7 @@ export default async function GridPage() { contentMain={
{photosCount > photos.length && - } diff --git a/src/app/page.tsx b/src/app/page.tsx index 59ea960c..a4bfc36b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,10 +6,11 @@ import { import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response'; -import InfinitePhotoScroll from '../photo/InfinitePhotoScroll'; import PhotosLarge from '@/photo/PhotosLarge'; import { cache } from 'react'; import { getPhotos, getPhotosCount } from '@/services/vercel-postgres'; +import InfinitePhotoScrollPhotosLarge from + '@/photo/InfinitePhotoScrollPhotosLarge'; export const dynamic = 'force-static'; @@ -43,8 +44,7 @@ export default async function HomePage() { ?
{photosCount > photos.length && - } diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index 36db260e..a1c728ea 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -1,17 +1,16 @@ 'use client'; import useSwrInfinite from 'swr/infinite'; -import PhotosLarge from '@/photo/PhotosLarge'; import { + ReactNode, useCallback, useMemo, useRef, } from 'react'; import SiteGrid from '@/components/SiteGrid'; import Spinner from '@/components/Spinner'; -import { getPhotosAction } from '@/photo/actions'; +import { getPhotosCachedAction, getPhotosAction } from '@/photo/actions'; import { Photo } from '.'; -import PhotoGrid from './PhotoGrid'; import { clsx } from 'clsx/lite'; import { useAppState } from '@/state/AppState'; @@ -20,19 +19,31 @@ export type RevalidatePhoto = ( revalidateRemainingPhotos?: boolean, ) => Promise; -export default function InfinitePhotoScroll({ - type = 'full-frame', - initialOffset, - itemsPerPage, -}: { - type: 'full-frame' | 'grid' +export type InfinitePhotoScrollExternalProps = { initialOffset: number itemsPerPage: number - debug?: boolean +} + +export default function InfinitePhotoScroll({ + cacheKey, + initialOffset, + itemsPerPage, + wrapMoreButtonInGrid, + useCachedPhotos = true, + children, +}: InfinitePhotoScrollExternalProps & { + cacheKey: string + wrapMoreButtonInGrid: boolean + useCachedPhotos?: boolean + children: (props: { + photos: Photo[] + onLastPhotoVisible: () => void + revalidatePhoto?: RevalidatePhoto + }) => ReactNode }) { const { swrTimestamp, isUserSignedIn } = useAppState(); - const key = `${swrTimestamp}-${type}`; + const key = `${swrTimestamp}-${cacheKey}`; const keyGenerator = useCallback( (size: number, prev: Photo[]) => prev && prev.length === 0 @@ -40,12 +51,17 @@ export default function InfinitePhotoScroll({ : [key, size] , [key]); - const fetcher = useCallback(([_key, size]: [string, number]) => { - return getPhotosAction( - initialOffset + size * itemsPerPage, - itemsPerPage, - ); - }, [initialOffset, itemsPerPage]); + const fetcher = useCallback(([_key, size]: [string, number]) => + useCachedPhotos + ? getPhotosCachedAction( + initialOffset + size * itemsPerPage, + itemsPerPage, + ) + : getPhotosAction( + initialOffset + size * itemsPerPage, + itemsPerPage, + ) + , [useCachedPhotos, initialOffset, itemsPerPage]); const { data, isLoading, isValidating, error, mutate, setSize } = useSwrInfinite( @@ -106,17 +122,12 @@ export default function InfinitePhotoScroll({ return (
- {type === 'full-frame' - ? - : } - {!isFinished && (type === 'full-frame' + {children({ + photos, + onLastPhotoVisible: advance, + revalidatePhoto, + })} + {!isFinished && (wrapMoreButtonInGrid ? : renderMoreButton())}
diff --git a/src/photo/InfinitePhotoScrollGrid.tsx b/src/photo/InfinitePhotoScrollGrid.tsx new file mode 100644 index 00000000..d3ec43dd --- /dev/null +++ b/src/photo/InfinitePhotoScrollGrid.tsx @@ -0,0 +1,26 @@ +'use client'; + +import InfinitePhotoScroll, { + InfinitePhotoScrollExternalProps, +} from './InfinitePhotoScroll'; +import PhotoGrid from './PhotoGrid'; + +export default function InfinitePhotoScrollGrid({ + initialOffset, + itemsPerPage, +}: InfinitePhotoScrollExternalProps) { + return ( + + {({ photos, onLastPhotoVisible }) => + } + + ); +} diff --git a/src/photo/InfinitePhotoScrollPhotosLarge.tsx b/src/photo/InfinitePhotoScrollPhotosLarge.tsx new file mode 100644 index 00000000..cef46974 --- /dev/null +++ b/src/photo/InfinitePhotoScrollPhotosLarge.tsx @@ -0,0 +1,27 @@ +'use client'; + +import InfinitePhotoScroll, { + InfinitePhotoScrollExternalProps, +} from './InfinitePhotoScroll'; +import PhotosLarge from './PhotosLarge'; + +export default function InfinitePhotoScrollPhotosLarge({ + initialOffset, + itemsPerPage, +}: InfinitePhotoScrollExternalProps) { + return ( + + {({ photos, onLastPhotoVisible, revalidatePhoto }) => + } + + ); +} diff --git a/src/photo/actions.ts b/src/photo/actions.ts index b0c68911..fac16382 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -195,9 +195,12 @@ export async function streamAiImageQueryAction( streamOpenAiImageQuery(imageBase64, AI_IMAGE_QUERIES[query])); } -export const getPhotosAction = async (offset: number, limit: number) => +export const getPhotosCachedAction = async (offset: number, limit: number) => getPhotosCachedCached({ offset, limit }); +export const getPhotosAction = async (offset: number, limit: number) => + getPhotos({ offset, limit }); + export const queryPhotosByTitleAction = async (query: string) => (await getPhotos({ query, limit: 10 })) .filter(({ title }) => Boolean(title)); From 6e7e46d6029bf676b7936cef4a2c6ec09ea7e699 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 17:36:20 -0500 Subject: [PATCH 8/8] Refactor infinite scroll pattern, use for admin photos --- src/admin/AdminPhotoTable.tsx | 17 ++++++++-- src/admin/AdminPhotoTableInfinite.tsx | 28 +++++++++++++++ src/app/admin/photos/page.tsx | 34 ++++++++----------- src/app/grid/page.tsx | 4 +-- src/app/page.tsx | 5 ++- src/photo/InfinitePhotoScroll.tsx | 8 +++-- ...toScrollGrid.tsx => PhotoGridInfinite.tsx} | 11 +++--- src/photo/PhotoLarge.tsx | 18 ++-------- src/photo/PhotoSmall.tsx | 18 ++-------- src/photo/PhotoTiny.tsx | 9 +++++ src/photo/PhotosLarge.tsx | 4 +-- ...hotosLarge.tsx => PhotosLargeInfinite.tsx} | 12 +++---- src/photo/actions.ts | 16 ++++++--- src/utility/useOnVisible.ts | 21 ++++++++++++ 14 files changed, 128 insertions(+), 77 deletions(-) create mode 100644 src/admin/AdminPhotoTableInfinite.tsx rename src/photo/{InfinitePhotoScrollGrid.tsx => PhotoGridInfinite.tsx} (71%) rename src/photo/{InfinitePhotoScrollPhotosLarge.tsx => PhotosLargeInfinite.tsx} (71%) create mode 100644 src/utility/useOnVisible.ts diff --git a/src/admin/AdminPhotoTable.tsx b/src/admin/AdminPhotoTable.tsx index 4bdb4d2e..4354bf8c 100644 --- a/src/admin/AdminPhotoTable.tsx +++ b/src/admin/AdminPhotoTable.tsx @@ -19,19 +19,29 @@ import { syncPhotoExifDataAction, } from '@/photo/actions'; import { useAppState } from '@/state/AppState'; +import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll'; export default function AdminPhotoTable({ photos, + onLastPhotoVisible, + revalidatePhoto, }: { photos: Photo[], + onLastPhotoVisible?: () => void + revalidatePhoto?: RevalidatePhoto }) { const { invalidateSwr } = useAppState(); return ( - {photos.map(photo => + {photos.map((photo, index) => - +
- {photo.title || 'Untitled'} + {titleForPhoto(photo)} {photo.hidden && revalidatePhoto?.(photo.id, true)} > diff --git a/src/admin/AdminPhotoTableInfinite.tsx b/src/admin/AdminPhotoTableInfinite.tsx new file mode 100644 index 00000000..ab8bf1e9 --- /dev/null +++ b/src/admin/AdminPhotoTableInfinite.tsx @@ -0,0 +1,28 @@ +'use client'; + +import InfinitePhotoScroll, { + InfinitePhotoScrollExternalProps, +} from '../photo/InfinitePhotoScroll'; +import AdminPhotoTable from './AdminPhotoTable'; + +export default function AdminPhotoTableInfinite({ + initialOffset, + itemsPerPage, +}: InfinitePhotoScrollExternalProps) { + return ( + + {({ photos, onLastPhotoVisible, revalidatePhoto }) => + } + + ); +} diff --git a/src/app/admin/photos/page.tsx b/src/app/admin/photos/page.tsx index d781ac01..f25f965c 100644 --- a/src/app/admin/photos/page.tsx +++ b/src/app/admin/photos/page.tsx @@ -1,40 +1,36 @@ import PhotoUpload from '@/photo/PhotoUpload'; import { clsx } from 'clsx/lite'; import SiteGrid from '@/components/SiteGrid'; -import { pathForAdminPhotos } from '@/site/paths'; import { getPhotosCountIncludingHiddenCached } from '@/photo/cache'; -import { - PaginationParams, - getPaginationFromSearchParams, -} from '@/site/pagination'; import StorageUrls from '@/admin/StorageUrls'; import { PRO_MODE_ENABLED } from '@/site/config'; import { getStoragePhotoUrlsNoStore } from '@/services/storage/cache'; -import MoreComponentsFromSearchParams from - '@/components/MoreComponentsFromSearchParams'; import { getPhotos } from '@/services/vercel-postgres'; import { revalidatePath } from 'next/cache'; import AdminPhotoTable from '@/admin/AdminPhotoTable'; +import AdminPhotoTableInfinite from + '@/admin/AdminPhotoTableInfinite'; const DEBUG_PHOTO_BLOBS = false; -export default async function AdminPhotosPage({ - searchParams, -}: PaginationParams) { - const { offset, limit } = getPaginationFromSearchParams(searchParams); +const INFINITE_SCROLL_INITIAL_ADMIN_PHOTOS = 25; +const INFINITE_SCROLL_MULTIPLE_ADMIN_PHOTOS = 50; +export default async function AdminPhotosPage() { const [ photos, - count, + photosCount, blobPhotoUrls, ] = await Promise.all([ - getPhotos({ includeHidden: true, sortBy: 'createdAt', limit }), + getPhotos({ + includeHidden: true, + sortBy: 'createdAt', + limit: INFINITE_SCROLL_INITIAL_ADMIN_PHOTOS, + }), getPhotosCountIncludingHiddenCached(), DEBUG_PHOTO_BLOBS ? getStoragePhotoUrlsNoStore() : [], ]); - const showMorePhotos = count > photos.length; - return ( }
- {showMorePhotos && - photos.length && + }
} diff --git a/src/app/grid/page.tsx b/src/app/grid/page.tsx index a9db1275..11cfd6bc 100644 --- a/src/app/grid/page.tsx +++ b/src/app/grid/page.tsx @@ -12,7 +12,7 @@ import PhotoGridSidebar from '@/photo/PhotoGridSidebar'; import { getPhotoSidebarData } from '@/photo/data'; import { getPhotos } from '@/services/vercel-postgres'; import { cache } from 'react'; -import InfinitePhotoScrollGrid from '@/photo/InfinitePhotoScrollGrid'; +import PhotoGridInfinite from '@/photo/PhotoGridInfinite'; export const dynamic = 'force-static'; @@ -41,7 +41,7 @@ export default async function GridPage() { contentMain={
{photosCount > photos.length && - } diff --git a/src/app/page.tsx b/src/app/page.tsx index a4bfc36b..de27623f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,8 +9,7 @@ import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response'; import PhotosLarge from '@/photo/PhotosLarge'; import { cache } from 'react'; import { getPhotos, getPhotosCount } from '@/services/vercel-postgres'; -import InfinitePhotoScrollPhotosLarge from - '@/photo/InfinitePhotoScrollPhotosLarge'; +import PhotosLargeInfinite from '@/photo/PhotosLargeInfinite'; export const dynamic = 'force-static'; @@ -44,7 +43,7 @@ export default async function HomePage() { ?
{photosCount > photos.length && - } diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index a1c728ea..64cf4df8 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -30,11 +30,13 @@ export default function InfinitePhotoScroll({ itemsPerPage, wrapMoreButtonInGrid, useCachedPhotos = true, + includeHiddenPhotos, children, }: InfinitePhotoScrollExternalProps & { cacheKey: string - wrapMoreButtonInGrid: boolean + wrapMoreButtonInGrid?: boolean useCachedPhotos?: boolean + includeHiddenPhotos?: boolean children: (props: { photos: Photo[] onLastPhotoVisible: () => void @@ -56,12 +58,14 @@ export default function InfinitePhotoScroll({ ? getPhotosCachedAction( initialOffset + size * itemsPerPage, itemsPerPage, + includeHiddenPhotos, ) : getPhotosAction( initialOffset + size * itemsPerPage, itemsPerPage, + includeHiddenPhotos, ) - , [useCachedPhotos, initialOffset, itemsPerPage]); + , [useCachedPhotos, initialOffset, itemsPerPage, includeHiddenPhotos]); const { data, isLoading, isValidating, error, mutate, setSize } = useSwrInfinite( diff --git a/src/photo/InfinitePhotoScrollGrid.tsx b/src/photo/PhotoGridInfinite.tsx similarity index 71% rename from src/photo/InfinitePhotoScrollGrid.tsx rename to src/photo/PhotoGridInfinite.tsx index d3ec43dd..ffde37c6 100644 --- a/src/photo/InfinitePhotoScrollGrid.tsx +++ b/src/photo/PhotoGridInfinite.tsx @@ -5,7 +5,7 @@ import InfinitePhotoScroll, { } from './InfinitePhotoScroll'; import PhotoGrid from './PhotoGrid'; -export default function InfinitePhotoScrollGrid({ +export default function PhotoGridInfinite({ initialOffset, itemsPerPage, }: InfinitePhotoScrollExternalProps) { @@ -14,13 +14,12 @@ export default function InfinitePhotoScrollGrid({ cacheKey="Grid" initialOffset={initialOffset} itemsPerPage={itemsPerPage} - wrapMoreButtonInGrid={false} > {({ photos, onLastPhotoVisible }) => - } + } ); } diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index fff7c8b6..fdf755e5 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -22,7 +22,8 @@ import PhotoLink from './PhotoLink'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config'; import AdminPhotoMenuClient from '@/admin/AdminPhotoMenuClient'; import { RevalidatePhoto } from './InfinitePhotoScroll'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; +import useOnVisible from '@/utility/useOnVisible'; export default function PhotoLarge({ photo, @@ -63,20 +64,7 @@ export default function PhotoLarge({ const showTagsContent = tags.length > 0; const showExifContent = shouldShowExifDataForPhoto(photo); - useEffect(() => { - if (onVisible && ref.current) { - const observer = new IntersectionObserver(e => { - if (e[0].isIntersecting) { - onVisible(); - } - }, { - root: null, - threshold: 0, - }); - observer.observe(ref.current); - return () => observer.disconnect(); - } - }, [onVisible]); + useOnVisible(ref, onVisible); return ( (null); - useEffect(() => { - if (onVisible && ref.current) { - const observer = new IntersectionObserver(e => { - if (e[0].isIntersecting) { - onVisible(); - } - }, { - root: null, - threshold: 0, - }); - observer.observe(ref.current); - return () => observer.disconnect(); - } - }, [onVisible]); + useOnVisible(ref, onVisible); return ( void }) { + const ref = useRef(null); + + useOnVisible(ref, onVisible); + return ( void + revalidatePhoto?: RevalidatePhoto }) { return ( {({ photos, onLastPhotoVisible, revalidatePhoto }) => - } + } ); } diff --git a/src/photo/actions.ts b/src/photo/actions.ts index fac16382..030b4e22 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -195,11 +195,19 @@ export async function streamAiImageQueryAction( streamOpenAiImageQuery(imageBase64, AI_IMAGE_QUERIES[query])); } -export const getPhotosCachedAction = async (offset: number, limit: number) => - getPhotosCachedCached({ offset, limit }); +export const getPhotosCachedAction = async ( + offset: number, + limit: number, + includeHidden?: boolean, +) => + getPhotosCachedCached({ offset, includeHidden, limit }); -export const getPhotosAction = async (offset: number, limit: number) => - getPhotos({ offset, limit }); +export const getPhotosAction = async ( + offset: number, + limit: number, + includeHidden?: boolean, +) => + getPhotos({ offset, includeHidden, limit }); export const queryPhotosByTitleAction = async (query: string) => (await getPhotos({ query, limit: 10 })) diff --git a/src/utility/useOnVisible.ts b/src/utility/useOnVisible.ts new file mode 100644 index 00000000..97a35406 --- /dev/null +++ b/src/utility/useOnVisible.ts @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; + +export default function useOnVisible( + ref: React.RefObject, + onVisible?: () => void +) { + useEffect(() => { + if (onVisible && ref.current) { + const observer = new IntersectionObserver(e => { + if (e[0].isIntersecting) { + onVisible(); + } + }, { + root: null, + threshold: 0, + }); + observer.observe(ref.current); + return () => observer.disconnect(); + } + }, [ref, onVisible]); +}