Generalize infinite scroll, add to camera views

This commit is contained in:
Sam Becker 2024-05-18 13:53:53 -05:00
parent 6feb3346a4
commit af7424315d
11 changed files with 145 additions and 154 deletions

View File

@ -1,14 +1,15 @@
'use client';
import InfinitePhotoScroll, {
InfinitePhotoScrollExternalProps,
} from '../photo/InfinitePhotoScroll';
import InfinitePhotoScroll from '../photo/InfinitePhotoScroll';
import AdminPhotosTable from './AdminPhotosTable';
export default function AdminPhotosTableInfinite({
initialOffset,
itemsPerPage,
}: InfinitePhotoScrollExternalProps) {
}: {
initialOffset: number
itemsPerPage: number
}) {
return (
<InfinitePhotoScroll
cacheKey="AdminPhotoTable"

View File

@ -1,10 +1,7 @@
import SiteGrid from '@/components/SiteGrid';
import {
INFINITE_SCROLL_INITIAL_GRID,
INFINITE_SCROLL_MULTIPLE_GRID,
generateOgImageMetaForPhotos,
} from '@/photo';
import PhotoGrid from '@/photo/PhotoGrid';
import PhotosEmptyState from '@/photo/PhotosEmptyState';
import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response';
import { Metadata } from 'next/types';
@ -12,7 +9,7 @@ import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
import { getPhotoSidebarData } from '@/photo/data';
import { getPhotos } from '@/photo/db';
import { cache } from 'react';
import PhotoGridInfinite from '@/photo/PhotoGridInfinite';
import PhotoGridPage from '@/photo/PhotoGridPage';
export const dynamic = 'force-static';
@ -40,16 +37,10 @@ export default async function GridPage() {
return (
photos.length > 0
? <SiteGrid
contentMain={<div className="space-y-0.5 sm:space-y-1">
<PhotoGrid {...{ photos }} />
{photosCount > photos.length &&
<PhotoGridInfinite
initialOffset={INFINITE_SCROLL_INITIAL_GRID}
itemsPerPage={INFINITE_SCROLL_MULTIPLE_GRID}
/>}
</div>}
contentSide={<div className="sticky top-4 space-y-4 mt-[-4px]">
? <PhotoGridPage
photos={photos}
count={photosCount}
sidebar={<div className="sticky top-4 space-y-4 mt-[-4px]">
<PhotoGridSidebar {...{
tags,
cameras,
@ -57,7 +48,6 @@ export default async function GridPage() {
photosCount,
}} />
</div>}
sideHiddenOnMobile
/>
: <PhotosEmptyState />
);

View File

@ -1,12 +1,11 @@
import { Metadata } from 'next/types';
import { CameraProps, getCameraFromParams } from '@/camera';
import { generateMetaForCamera } from '@/camera/meta';
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
import { PaginationParams } from '@/site/pagination';
import {
getPhotosCameraDataCached,
getPhotosCameraDataCachedWithPagination,
} from '@/camera/data';
GRID_THUMBNAILS_TO_SHOW_MAX,
INFINITE_SCROLL_INITIAL_GRID,
} from '@/photo';
import { getPhotosCameraDataCached } from '@/camera/data';
import CameraOverview from '@/camera/CameraOverview';
export async function generateMetadata({
@ -46,23 +45,18 @@ export async function generateMetadata({
};
}
export default async function CameraPage({
params,
searchParams,
}: CameraProps & PaginationParams) {
export default async function CameraPage({ params }: CameraProps) {
const camera = getCameraFromParams(params);
const {
const [
photos,
count,
showMorePath,
dateRange,
} = await getPhotosCameraDataCachedWithPagination({
{ count, dateRange },
] = await getPhotosCameraDataCached({
camera,
searchParams,
limit: INFINITE_SCROLL_INITIAL_GRID,
});
return (
<CameraOverview {...{ camera, photos, count, dateRange, showMorePath }} />
<CameraOverview {...{ camera, photos, count, dateRange }} />
);
}

View File

@ -6,12 +6,11 @@ import {
import CameraShareModal from '@/camera/CameraShareModal';
import { generateMetaForCamera } from '@/camera/meta';
import { Metadata } from 'next/types';
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
import { PaginationParams } from '@/site/pagination';
import {
getPhotosCameraDataCached,
getPhotosCameraDataCachedWithPagination,
} from '@/camera/data';
GRID_THUMBNAILS_TO_SHOW_MAX,
INFINITE_SCROLL_INITIAL_GRID,
} from '@/photo';
import { getPhotosCameraDataCached } from '@/camera/data';
import CameraOverview from '@/camera/CameraOverview';
export async function generateMetadata({
@ -51,20 +50,15 @@ export async function generateMetadata({
};
}
export default async function Share({
params,
searchParams,
}: CameraProps & PaginationParams) {
export default async function Share({ params }: CameraProps) {
const cameraFromParams = getCameraFromParams(params);
const {
const [
photos,
count,
dateRange,
showMorePath,
} = await getPhotosCameraDataCachedWithPagination({
{ count, dateRange },
] = await getPhotosCameraDataCached({
camera: cameraFromParams,
searchParams,
limit: INFINITE_SCROLL_INITIAL_GRID,
});
const camera = cameraFromPhoto(photos[0], cameraFromParams);
@ -72,7 +66,7 @@ export default async function Share({
return <>
<CameraShareModal {...{ camera, photos, count, dateRange }} />
<CameraOverview
{...{ camera, photos, count, dateRange, showMorePath }}
{...{ camera, photos, count, dateRange }}
animateOnFirstLoadOnly
/>
</>;

View File

@ -1,42 +1,28 @@
import { Photo, PhotoDateRange } from '@/photo';
import { Camera } from '.';
import SiteGrid from '@/components/SiteGrid';
import AnimateItems from '@/components/AnimateItems';
import CameraHeader from './CameraHeader';
import PhotoGrid from '@/photo/PhotoGrid';
import PhotoGridPage from '@/photo/PhotoGridPage';
export default function CameraOverview({
camera,
photos,
count,
dateRange,
showMorePath,
animateOnFirstLoadOnly,
}: {
camera: Camera,
photos: Photo[],
count: number,
dateRange?: PhotoDateRange,
showMorePath?: string,
animateOnFirstLoadOnly?: boolean,
}) {
return (
<SiteGrid
contentMain={<div className="space-y-8 mt-4">
<AnimateItems
type="bottom"
items={[
<CameraHeader
key="CameraHeader"
{...{ camera, photos, count, dateRange }}
/>,
]}
animateOnFirstLoadOnly
/>
<PhotoGrid
{...{ photos, camera, showMorePath, animateOnFirstLoadOnly }}
/>
</div>}
/>
<PhotoGridPage {...{
photos,
count,
camera,
animateOnFirstLoadOnly,
header: <CameraHeader {...{ camera, photos, count, dateRange }} />,
}} />
);
}

View File

@ -1,13 +1,8 @@
import {
PaginationSearchParams,
getPaginationFromSearchParams,
} from '@/site/pagination';
import { Camera } from '.';
import {
getPhotosCached,
getPhotosCameraMetaCached,
} from '@/photo/cache';
import { pathForCamera } from '@/site/paths';
export const getPhotosCameraDataCached = ({
camera,
@ -20,32 +15,3 @@ export const getPhotosCameraDataCached = ({
getPhotosCached({ camera, limit }),
getPhotosCameraMetaCached(camera),
]);
export const getPhotosCameraDataCachedWithPagination = async ({
camera,
limit: limitProp,
searchParams,
}: {
camera: Camera,
limit?: number,
searchParams?: PaginationSearchParams,
}) => {
const { offset, limit } = getPaginationFromSearchParams(searchParams);
const [photos, { count, dateRange }] =
await getPhotosCameraDataCached({
camera,
limit: limitProp ?? limit,
});
const showMorePath = count > photos.length
? pathForCamera(camera, offset + 1)
: undefined;
return {
photos,
count,
dateRange,
showMorePath,
};
};

View File

@ -13,26 +13,26 @@ import { getPhotosCachedAction, getPhotosAction } from '@/photo/actions';
import { Photo } from '.';
import { clsx } from 'clsx/lite';
import { useAppState } from '@/state/AppState';
import { Camera } from '@/camera';
export type RevalidatePhoto = (
photoId: string,
revalidateRemainingPhotos?: boolean,
) => Promise<any>;
export type InfinitePhotoScrollExternalProps = {
initialOffset: number
itemsPerPage: number
}
export default function InfinitePhotoScroll({
cacheKey,
initialOffset,
itemsPerPage,
camera,
wrapMoreButtonInGrid,
useCachedPhotos = true,
includeHiddenPhotos,
children,
}: InfinitePhotoScrollExternalProps & {
}: {
initialOffset: number
itemsPerPage: number
camera?: Camera
cacheKey: string
wrapMoreButtonInGrid?: boolean
useCachedPhotos?: boolean
@ -55,17 +55,25 @@ export default function InfinitePhotoScroll({
const fetcher = useCallback(([_key, size]: [string, number]) =>
useCachedPhotos
? getPhotosCachedAction(
initialOffset + size * itemsPerPage,
itemsPerPage,
includeHiddenPhotos ? 'include' : 'exclude',
)
: getPhotosAction(
initialOffset + size * itemsPerPage,
itemsPerPage,
includeHiddenPhotos ? 'include' : 'exclude',
)
, [useCachedPhotos, initialOffset, itemsPerPage, includeHiddenPhotos]);
? getPhotosCachedAction({
offset: initialOffset + size * itemsPerPage,
limit: itemsPerPage,
hidden: includeHiddenPhotos ? 'include' : 'exclude',
camera,
})
: getPhotosAction({
offset: initialOffset + size * itemsPerPage,
limit: itemsPerPage,
hidden: includeHiddenPhotos ? 'include' : 'exclude',
camera,
})
, [
useCachedPhotos,
initialOffset,
itemsPerPage,
includeHiddenPhotos,
camera,
]);
const { data, isLoading, isValidating, error, mutate, setSize } =
useSwrInfinite<Photo[]>(

View File

@ -1,25 +1,33 @@
'use client';
import InfinitePhotoScroll, {
InfinitePhotoScrollExternalProps,
} from './InfinitePhotoScroll';
import { Camera } from '@/camera';
import { INFINITE_SCROLL_MULTIPLE_GRID } from '.';
import InfinitePhotoScroll from './InfinitePhotoScroll';
import PhotoGrid from './PhotoGrid';
export default function PhotoGridInfinite({
initialOffset,
itemsPerPage,
}: InfinitePhotoScrollExternalProps) {
camera,
animateOnFirstLoadOnly,
}: {
initialOffset: number,
camera?: Camera,
animateOnFirstLoadOnly?: boolean,
}) {
return (
<InfinitePhotoScroll
cacheKey="Grid"
initialOffset={initialOffset}
itemsPerPage={itemsPerPage}
itemsPerPage={INFINITE_SCROLL_MULTIPLE_GRID}
camera={camera}
>
{({ photos, onLastPhotoVisible }) =>
<PhotoGrid
photos={photos}
onLastPhotoVisible={onLastPhotoVisible}
/>}
<PhotoGrid {...{
photos,
camera,
onLastPhotoVisible,
animateOnFirstLoadOnly,
}} />}
</InfinitePhotoScroll>
);
}

View File

@ -0,0 +1,49 @@
import SiteGrid from '@/components/SiteGrid';
import { Photo } from '.';
import PhotoGrid from './PhotoGrid';
import PhotoGridInfinite from './PhotoGridInfinite';
import { Camera } from '@/camera';
import { clsx } from 'clsx/lite';
import AnimateItems from '@/components/AnimateItems';
export default function PhotoGridPage({
photos,
count,
camera,
animateOnFirstLoadOnly,
header,
sidebar,
}: {
photos: Photo[]
count: number
camera?: Camera
animateOnFirstLoadOnly?: boolean
header?: JSX.Element
sidebar?: JSX.Element
}) {
return (
<SiteGrid
contentMain={<div className={clsx(
header && 'space-y-8 mt-4',
)}>
{header &&
<AnimateItems
type="bottom"
items={[header]}
animateOnFirstLoadOnly
/>}
<div className="space-y-0.5 sm:space-y-1">
<PhotoGrid {...{ photos, camera, animateOnFirstLoadOnly }} />
{count > photos.length &&
<PhotoGridInfinite {...{
initialOffset: photos.length,
camera,
animateOnFirstLoadOnly,
}} />}
</div>
</div>}
contentSide={sidebar}
sideHiddenOnMobile
/>
);
}

View File

@ -1,14 +1,15 @@
'use client';
import InfinitePhotoScroll, {
InfinitePhotoScrollExternalProps,
} from './InfinitePhotoScroll';
import InfinitePhotoScroll from './InfinitePhotoScroll';
import PhotosLarge from './PhotosLarge';
export default function PhotosLargeInfinite({
initialOffset,
itemsPerPage,
}: InfinitePhotoScrollExternalProps) {
}: {
initialOffset: number
itemsPerPage: number
}) {
return (
<InfinitePhotoScroll
cacheKey="PhotosLarge"

View File

@ -211,23 +211,17 @@ export const getPhotosTagHiddenMetaCachedAction = async () =>
// Public/Private actions
export const getPhotosAction = async (
offset: number,
limit: number,
hidden?: GetPhotosOptions['hidden'],
) => (hidden === 'include' || hidden === 'only')
? safelyRunAdminServerAction(() =>
getPhotos({ offset, hidden, limit }))
: getPhotos({ offset, hidden, limit });
export const getPhotosAction = async (options: GetPhotosOptions) =>
(options.hidden === 'include' || options.hidden === 'only')
? safelyRunAdminServerAction(() =>
getPhotos(options))
: getPhotos(options);
export const getPhotosCachedAction = async (
offset: number,
limit: number,
hidden?: GetPhotosOptions['hidden'],
) => (hidden === 'include' || hidden === 'only')
? safelyRunAdminServerAction(() =>
getPhotosCachedCached({ offset, hidden, limit }))
: getPhotosCachedCached({ offset, hidden, limit });
export const getPhotosCachedAction = async (options: GetPhotosOptions) =>
(options.hidden === 'include' || options.hidden === 'only')
? safelyRunAdminServerAction(() =>
getPhotosCachedCached(options))
: getPhotosCachedCached(options);
// Public actions