Consolidate camera/tag pagination/date handling
This commit is contained in:
parent
49b871ab13
commit
80823c8d14
@ -10,9 +10,14 @@ import {
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotoCached, getPhotosCached } from '@/cache';
|
||||
import { getPhotos, getUniqueCameras } from '@/services/postgres';
|
||||
import { getPhotoCached } from '@/cache';
|
||||
import {
|
||||
getPhotos,
|
||||
getUniqueCameras,
|
||||
} from '@/services/postgres';
|
||||
import { cameraFromPhoto } from '@/camera';
|
||||
import { getPhotosCameraDataCached } from '@/camera/data';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface PhotoCameraProps {
|
||||
params: { photoId: string, camera: string }
|
||||
@ -69,19 +74,21 @@ export async function generateMetadata({
|
||||
export default async function PhotoCameraPage({
|
||||
params: { photoId, camera: cameraProp },
|
||||
children,
|
||||
}: PhotoCameraProps & {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
}: PhotoCameraProps & { children: ReactNode }) {
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const camera = cameraFromPhoto(photo, cameraProp);
|
||||
|
||||
const photos = await getPhotosCached({ camera });
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
] = await getPhotosCameraDataCached({ camera });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
<PhotoDetailPage {...{ photo, photos, camera }} />
|
||||
<PhotoDetailPage {...{ photo, photos, camera, count, dateRange }} />
|
||||
</>;
|
||||
}
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import { getPhotosCached, getPhotosCountCameraCached } from '@/cache';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import CameraHeader from '@/camera/CameraHeader';
|
||||
import { getMakeModelFromCameraString } from '@/camera';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import { Metadata } from 'next';
|
||||
import { generateMetaForCamera } from '@/camera/meta';
|
||||
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
|
||||
import { pathForCamera } from '@/site/paths';
|
||||
import { PaginationParams } from '@/site/pagination';
|
||||
import {
|
||||
PaginationParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
getPhotosCameraDataCached,
|
||||
getPhotosCameraDataCachedWithPagination,
|
||||
} from '@/camera/data';
|
||||
import CameraOverview from '@/camera/CameraOverview';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -26,17 +23,18 @@ export async function generateMetadata({
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ camera, limit: GRID_THUMBNAILS_TO_SHOW_MAX }),
|
||||
getPhotosCountCameraCached(camera),
|
||||
]);
|
||||
dateRange,
|
||||
] = await getPhotosCameraDataCached({
|
||||
camera,
|
||||
limit: GRID_THUMBNAILS_TO_SHOW_MAX,
|
||||
});
|
||||
|
||||
const {
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
} = generateMetaForCamera(camera, photos, count);
|
||||
} = generateMetaForCamera(camera, photos, count, dateRange);
|
||||
|
||||
return {
|
||||
title,
|
||||
@ -61,27 +59,17 @@ export default async function CameraPage({
|
||||
}: CameraProps & PaginationParams) {
|
||||
const camera = getMakeModelFromCameraString(params.camera);
|
||||
|
||||
const { offset, limit } = getPaginationForSearchParams(searchParams);
|
||||
|
||||
const [
|
||||
const {
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ camera, limit }),
|
||||
getPhotosCountCameraCached(camera),
|
||||
]);
|
||||
|
||||
const showMorePath = count > photos.length
|
||||
? pathForCamera(camera, offset + 1)
|
||||
: undefined;
|
||||
showMorePath,
|
||||
dateRange,
|
||||
} = await getPhotosCameraDataCachedWithPagination({
|
||||
camera,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
key="Camera Grid"
|
||||
contentMain={<div className="space-y-8 mt-4">
|
||||
<CameraHeader {...{ camera, photos, count }} />
|
||||
<PhotoGrid {...{ photos, camera, showMorePath }} />
|
||||
</div>}
|
||||
/>
|
||||
<CameraOverview {...{ camera, photos, count, dateRange, showMorePath }} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { getPhotosCached, getPhotosCountCameraCached } from '@/cache';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { cameraFromPhoto, getMakeModelFromCameraString } from '@/camera';
|
||||
import CameraHeader from '@/camera/CameraHeader';
|
||||
import CameraShareModal from '@/camera/CameraShareModal';
|
||||
import { generateMetaForCamera } from '@/camera/meta';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import { Metadata } from 'next';
|
||||
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
|
||||
import { pathForCamera } from '@/site/paths';
|
||||
import { PaginationParams } from '@/site/pagination';
|
||||
import {
|
||||
PaginationParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
getPhotosCameraDataCached,
|
||||
getPhotosCameraDataCachedWithPagination,
|
||||
} from '@/camera/data';
|
||||
import CameraOverview from '@/camera/CameraOverview';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -27,17 +24,18 @@ export async function generateMetadata({
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ camera, limit: GRID_THUMBNAILS_TO_SHOW_MAX }),
|
||||
getPhotosCountCameraCached(camera),
|
||||
]);
|
||||
dateRange,
|
||||
] = await getPhotosCameraDataCached({
|
||||
camera,
|
||||
limit: GRID_THUMBNAILS_TO_SHOW_MAX,
|
||||
});
|
||||
|
||||
const {
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
} = generateMetaForCamera(camera, photos, count);
|
||||
} = generateMetaForCamera(camera, photos, count, dateRange);
|
||||
|
||||
return {
|
||||
title,
|
||||
@ -62,30 +60,23 @@ export default async function Share({
|
||||
}: CameraProps & PaginationParams) {
|
||||
const cameraFromParams = getMakeModelFromCameraString(params.camera);
|
||||
|
||||
const { offset, limit } = getPaginationForSearchParams(searchParams);
|
||||
|
||||
const [
|
||||
const {
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ camera: cameraFromParams, limit }),
|
||||
getPhotosCountCameraCached(cameraFromParams),
|
||||
]);
|
||||
dateRange,
|
||||
showMorePath,
|
||||
} = await getPhotosCameraDataCachedWithPagination({
|
||||
camera: cameraFromParams,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
const camera = cameraFromPhoto(photos[0], cameraFromParams);
|
||||
|
||||
const showMorePath = count > photos.length
|
||||
? pathForCamera(camera, offset + 1)
|
||||
: undefined;
|
||||
|
||||
return <>
|
||||
<CameraShareModal {...{ camera, photos, count }} />
|
||||
<SiteGrid
|
||||
key="Camera Grid"
|
||||
contentMain={<div className="space-y-8 mt-4">
|
||||
<CameraHeader {...{ camera, photos, count }} />
|
||||
<PhotoGrid {...{ photos, camera, showMorePath, animate: false }} />
|
||||
</div>}
|
||||
<CameraShareModal {...{ camera, photos, count, dateRange }} />
|
||||
<CameraOverview
|
||||
{...{ camera, photos, count, dateRange, showMorePath }}
|
||||
animateOnFirstLoadOnly
|
||||
/>
|
||||
</>;
|
||||
}
|
||||
|
||||
@ -10,11 +10,17 @@ import {
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotoCached, getPhotosCached } from '@/cache';
|
||||
import { getPhotoCached } from '@/cache';
|
||||
import { getPhotos, getUniqueTags } from '@/services/postgres';
|
||||
import { getPhotosTagDataCached } from '@/tag/data';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface PhotoTagProps {
|
||||
params: { photoId: string, tag: string }
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const params: { params: { photoId: string, tag: string }}[] = [];
|
||||
const params: PhotoTagProps[] = [];
|
||||
|
||||
const tags = await getUniqueTags();
|
||||
tags.forEach(async tag => {
|
||||
@ -29,9 +35,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { photoId, tag },
|
||||
}: {
|
||||
params: { photoId: string, tag: string }
|
||||
}): Promise<Metadata> {
|
||||
}: PhotoTagProps): Promise<Metadata> {
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return {}; }
|
||||
@ -62,18 +66,19 @@ export async function generateMetadata({
|
||||
export default async function PhotoTagPage({
|
||||
params: { photoId, tag },
|
||||
children,
|
||||
}: {
|
||||
params: { photoId: string, tag: string }
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
}: PhotoTagProps & { children: ReactNode }) {
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const photos = await getPhotosCached({ tag });
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
] = await getPhotosTagDataCached({ tag });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
<PhotoDetailPage {...{ photo, photos, tag }} />
|
||||
<PhotoDetailPage {...{ photo, photos, tag, count, dateRange }} />
|
||||
</>;
|
||||
}
|
||||
|
||||
@ -4,8 +4,12 @@ import { getPhotos, getUniqueTags } from '@/services/postgres';
|
||||
import { PATH_ROOT } from '@/site/paths';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
interface PhotoTagProps {
|
||||
params: { photoId: string, tag: string }
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const params: { params: { photoId: string, tag: string }}[] = [];
|
||||
const params: PhotoTagProps[] = [];
|
||||
|
||||
const tags = await getUniqueTags();
|
||||
tags.forEach(async tag => {
|
||||
@ -20,9 +24,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export default async function Share({
|
||||
params: { photoId, tag },
|
||||
}: {
|
||||
params: { photoId: string, tag: string }
|
||||
}) {
|
||||
}: PhotoTagProps) {
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return redirect(PATH_ROOT); }
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import { getPhotosCached, getPhotosCountTagCached } from '@/cache';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import {
|
||||
PaginationParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
import { pathForTag } from '@/site/paths';
|
||||
import { PaginationParams } from '@/site/pagination';
|
||||
import { generateMetaForTag } from '@/tag';
|
||||
import TagHeader from '@/tag/TagHeader';
|
||||
import TagOverview from '@/tag/TagOverview';
|
||||
import {
|
||||
getPhotosTagDataCached,
|
||||
getPhotosTagDataCachedWithPagination,
|
||||
} from '@/tag/data';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -23,10 +20,10 @@ export async function generateMetadata({
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ tag, limit: GRID_THUMBNAILS_TO_SHOW_MAX }),
|
||||
getPhotosCountTagCached(tag),
|
||||
]);
|
||||
] = await getPhotosTagDataCached({
|
||||
tag,
|
||||
limit: GRID_THUMBNAILS_TO_SHOW_MAX,
|
||||
});
|
||||
|
||||
const {
|
||||
url,
|
||||
@ -56,27 +53,17 @@ export default async function TagPage({
|
||||
params: { tag },
|
||||
searchParams,
|
||||
}:TagProps & PaginationParams) {
|
||||
const { offset, limit } = getPaginationForSearchParams(searchParams);
|
||||
|
||||
const [
|
||||
const {
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ tag, limit }),
|
||||
getPhotosCountTagCached(tag),
|
||||
]);
|
||||
|
||||
const showMorePath = count > photos.length
|
||||
? pathForTag(tag, offset + 1)
|
||||
: undefined;
|
||||
showMorePath,
|
||||
dateRange,
|
||||
} = await getPhotosTagDataCachedWithPagination({
|
||||
tag,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
key="Tag Grid"
|
||||
contentMain={<div className="space-y-8 mt-4">
|
||||
<TagHeader {...{ tag, photos, count }} />
|
||||
<PhotoGrid {...{ photos, tag, showMorePath }} />
|
||||
</div>}
|
||||
/>
|
||||
<TagOverview {...{ tag, photos, count, dateRange, showMorePath }} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { getPhotosCached, getPhotosCountTagCached } from '@/cache';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { GRID_THUMBNAILS_TO_SHOW_MAX } from '@/photo';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import {
|
||||
PaginationParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
import { pathForTag } from '@/site/paths';
|
||||
import { PaginationParams } from '@/site/pagination';
|
||||
import { generateMetaForTag } from '@/tag';
|
||||
import TagHeader from '@/tag/TagHeader';
|
||||
import TagOverview from '@/tag/TagOverview';
|
||||
import TagShareModal from '@/tag/TagShareModal';
|
||||
import {
|
||||
getPhotosTagDataCached,
|
||||
getPhotosTagDataCachedWithPagination,
|
||||
} from '@/tag/data';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -24,17 +21,18 @@ export async function generateMetadata({
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ tag, limit: GRID_THUMBNAILS_TO_SHOW_MAX }),
|
||||
getPhotosCountTagCached(tag),
|
||||
]);
|
||||
dateRange,
|
||||
] = await getPhotosTagDataCached({
|
||||
tag,
|
||||
limit: GRID_THUMBNAILS_TO_SHOW_MAX,
|
||||
});
|
||||
|
||||
const {
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
} = generateMetaForTag(tag, photos, count);
|
||||
} = generateMetaForTag(tag, photos, count, dateRange);
|
||||
|
||||
return {
|
||||
title,
|
||||
@ -57,28 +55,21 @@ export default async function Share({
|
||||
params: { tag },
|
||||
searchParams,
|
||||
}: TagProps & PaginationParams) {
|
||||
const { offset, limit } = getPaginationForSearchParams(searchParams);
|
||||
|
||||
const [
|
||||
const {
|
||||
photos,
|
||||
count,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ tag, limit }),
|
||||
getPhotosCountTagCached(tag),
|
||||
]);
|
||||
|
||||
const showMorePath = count > photos.length
|
||||
? pathForTag(tag, offset + 1)
|
||||
: undefined;
|
||||
dateRange,
|
||||
showMorePath,
|
||||
} = await getPhotosTagDataCachedWithPagination({
|
||||
tag,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
return <>
|
||||
<TagShareModal {...{ tag, photos, count }} />
|
||||
<SiteGrid
|
||||
key="Tag Grid"
|
||||
contentMain={<div className="space-y-8 mt-4">
|
||||
<TagHeader {...{ tag, photos }} />
|
||||
<PhotoGrid {...{ photos, tag, showMorePath, animate: false }} />
|
||||
</div>}
|
||||
<TagOverview
|
||||
{...{ tag, photos, count, dateRange, showMorePath }}
|
||||
animateOnFirstLoadOnly
|
||||
/>
|
||||
</>;
|
||||
}
|
||||
|
||||
67
src/cache/index.ts
vendored
67
src/cache/index.ts
vendored
@ -4,11 +4,13 @@ import {
|
||||
getPhoto,
|
||||
getPhotos,
|
||||
getPhotosCount,
|
||||
getPhotosCountCamera,
|
||||
getPhotosCameraCount,
|
||||
getPhotosCountIncludingHidden,
|
||||
getPhotosCountTag,
|
||||
getPhotosTagCount,
|
||||
getUniqueCameras,
|
||||
getUniqueTags,
|
||||
getPhotosTagDateRange,
|
||||
getPhotosCameraDateRange,
|
||||
} from '@/services/postgres';
|
||||
import { parseCachedPhotosDates, parseCachedPhotoDates } from '@/photo';
|
||||
import { getBlobPhotoUrls, getBlobUploadUrls } from '@/services/blob';
|
||||
@ -16,7 +18,8 @@ import { AuthSession } from 'next-auth';
|
||||
import { Camera, createCameraKey } from '@/camera';
|
||||
|
||||
const TAG_PHOTOS = 'photos';
|
||||
const TAG_PHOTOS_COUNT = 'photos-count';
|
||||
const TAG_PHOTOS_COUNT = `${TAG_PHOTOS}-count`;
|
||||
const TAG_PHOTOS_DATE_RANGE = `${TAG_PHOTOS}-date-range`;;
|
||||
const TAG_TAGS = 'tags';
|
||||
const TAG_CAMERAS = 'cameras';
|
||||
const TAG_BLOB = 'blob';
|
||||
@ -69,6 +72,12 @@ const getPhotoTagCountTag = (tag: string) =>
|
||||
const getPhotoCameraCountTag = ({ make, model }: Camera) =>
|
||||
`${TAG_PHOTOS_COUNT}-${TAG_CAMERAS}-${createCameraKey(make, model)}`;
|
||||
|
||||
const getPhotoTagDateRangeTag = (tag: string) =>
|
||||
`${TAG_PHOTOS_DATE_RANGE}-${TAG_TAGS}-${tag}`;
|
||||
|
||||
const getPhotoCameraDateRangeTag = ({ make, model }: Camera) =>
|
||||
`${TAG_PHOTOS_DATE_RANGE}-${TAG_CAMERAS}-${createCameraKey(make, model)}`;
|
||||
|
||||
export const revalidatePhotosTag = () =>
|
||||
revalidateTag(TAG_PHOTOS);
|
||||
|
||||
@ -109,23 +118,6 @@ export const getPhotosCountCached: typeof getPhotosCount = (...args) =>
|
||||
}
|
||||
)();
|
||||
|
||||
export const getPhotosCountTagCached: typeof getPhotosCountTag = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosCountTag(...args),
|
||||
[TAG_PHOTOS, getPhotoTagCountTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoTagCountTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const getPhotosCountCameraCached: typeof getPhotosCountCamera = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosCountCamera(...args),
|
||||
[TAG_PHOTOS, getPhotoCameraCountTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoCameraCountTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
export const getPhotosCountIncludingHiddenCached: typeof getPhotosCount =
|
||||
(...args) =>
|
||||
unstable_cache(
|
||||
@ -135,6 +127,41 @@ export const getPhotosCountIncludingHiddenCached: typeof getPhotosCount =
|
||||
}
|
||||
)();
|
||||
|
||||
export const getPhotosTagCountCached: typeof getPhotosTagCount = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosTagCount(...args),
|
||||
[TAG_PHOTOS, getPhotoTagCountTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoTagCountTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const getPhotosCameraCountCached: typeof getPhotosCameraCount = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosCameraCount(...args),
|
||||
[TAG_PHOTOS, getPhotoCameraCountTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoCameraCountTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const getPhotosTagDateRangeCached: typeof getPhotosTagDateRange = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosTagDateRange(...args),
|
||||
[TAG_PHOTOS, getPhotoTagDateRangeTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoTagDateRangeTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const getPhotosCameraDateRangeCached: typeof getPhotosCameraDateRange = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosCameraDateRange(...args),
|
||||
[TAG_PHOTOS, getPhotoCameraDateRangeTag(...args)], {
|
||||
tags: [TAG_PHOTOS, getPhotoCameraDateRangeTag(...args)],
|
||||
}
|
||||
)();
|
||||
|
||||
export const getPhotoCached: typeof getPhoto = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhoto(...args),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Photo } from '@/photo';
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { pathForCameraShare } from '@/site/paths';
|
||||
import PhotoHeader from '@/photo/PhotoHeader';
|
||||
import { Camera, cameraFromPhoto } from '.';
|
||||
@ -10,22 +10,26 @@ export default function CameraHeader({
|
||||
photos,
|
||||
selectedPhoto,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
camera: Camera
|
||||
photos: Photo[]
|
||||
selectedPhoto?: Photo
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
const camera = cameraFromPhoto(photos[0], cameraProp);
|
||||
return (
|
||||
<PhotoHeader
|
||||
entity={<PhotoCamera {...{ camera }} />}
|
||||
entityVerb="Photo"
|
||||
entityDescription={descriptionForCameraPhotos(photos, undefined, count)}
|
||||
entityDescription={
|
||||
descriptionForCameraPhotos(photos, undefined, count, dateRange)}
|
||||
photos={photos}
|
||||
selectedPhoto={selectedPhoto}
|
||||
sharePath={pathForCameraShare(camera)}
|
||||
count={count}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Photo } from '@/photo';
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { absolutePathForCameraImage, pathForCamera } from '@/site/paths';
|
||||
import OGTile from '@/components/OGTile';
|
||||
import { Camera } from '.';
|
||||
@ -15,6 +15,7 @@ export default function CameraOGTile({
|
||||
onFail,
|
||||
retryTime,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
camera: Camera
|
||||
photos: Photo[]
|
||||
@ -24,11 +25,12 @@ export default function CameraOGTile({
|
||||
riseOnHover?: boolean
|
||||
retryTime?: number
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
return (
|
||||
<OGTile {...{
|
||||
title: titleForCamera(camera, photos, count),
|
||||
description: descriptionForCameraPhotos(photos, true, count),
|
||||
description: descriptionForCameraPhotos(photos, true, count, dateRange),
|
||||
path: pathForCamera(camera),
|
||||
pathImageAbsolute: absolutePathForCameraImage(camera),
|
||||
loadingState: loadingStateExternal,
|
||||
|
||||
42
src/camera/CameraOverview.tsx
Normal file
42
src/camera/CameraOverview.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
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';
|
||||
|
||||
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>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { absolutePathForCamera, pathForCamera } from '@/site/paths';
|
||||
import { Photo } from '../photo';
|
||||
import { Photo, PhotoDateRange } from '../photo';
|
||||
import ShareModal from '@/components/ShareModal';
|
||||
import CameraOGTile from './CameraOGTile';
|
||||
import { Camera } from '.';
|
||||
@ -8,10 +8,12 @@ export default function CameraShareModal({
|
||||
camera,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
camera: Camera
|
||||
photos: Photo[]
|
||||
count?: number
|
||||
count: number
|
||||
dateRange: PhotoDateRange,
|
||||
}) {
|
||||
return (
|
||||
<ShareModal
|
||||
@ -19,7 +21,7 @@ export default function CameraShareModal({
|
||||
pathShare={absolutePathForCamera(camera)}
|
||||
pathClose={pathForCamera(camera)}
|
||||
>
|
||||
<CameraOGTile {...{ camera, photos, count }} />
|
||||
<CameraOGTile {...{ camera, photos, count, dateRange }} />
|
||||
</ShareModal>
|
||||
);
|
||||
};
|
||||
|
||||
53
src/camera/data.ts
Normal file
53
src/camera/data.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {
|
||||
PaginationSearchParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
import { Camera } from '.';
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosCameraCountCached,
|
||||
getPhotosCameraDateRangeCached,
|
||||
} from '@/cache';
|
||||
import { pathForCamera } from '@/site/paths';
|
||||
|
||||
export const getPhotosCameraDataCached = ({
|
||||
camera,
|
||||
limit,
|
||||
}: {
|
||||
camera: Camera,
|
||||
limit?: number,
|
||||
}) =>
|
||||
Promise.all([
|
||||
getPhotosCached({ camera, limit }),
|
||||
getPhotosCameraCountCached(camera),
|
||||
getPhotosCameraDateRangeCached(camera),
|
||||
]);
|
||||
|
||||
export const getPhotosCameraDataCachedWithPagination = async ({
|
||||
camera,
|
||||
limit: limitProp,
|
||||
searchParams,
|
||||
}: {
|
||||
camera: Camera,
|
||||
limit?: number,
|
||||
searchParams?: PaginationSearchParams,
|
||||
}) => {
|
||||
const { offset, limit } = getPaginationForSearchParams(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,
|
||||
};
|
||||
};
|
||||
@ -1,4 +1,9 @@
|
||||
import { Photo, descriptionForPhotoSet, photoQuantityText } from '@/photo';
|
||||
import {
|
||||
Photo,
|
||||
PhotoDateRange,
|
||||
descriptionForPhotoSet,
|
||||
photoQuantityText,
|
||||
} from '@/photo';
|
||||
import { Camera, cameraFromPhoto, formatCameraText } from '.';
|
||||
import {
|
||||
absolutePathForCamera,
|
||||
@ -23,16 +28,25 @@ export const descriptionForCameraPhotos = (
|
||||
photos: Photo[],
|
||||
dateBased?: boolean,
|
||||
explicitCount?: number,
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) =>
|
||||
descriptionForPhotoSet(photos, undefined, dateBased, explicitCount);
|
||||
descriptionForPhotoSet(
|
||||
photos,
|
||||
undefined,
|
||||
dateBased,
|
||||
explicitCount,
|
||||
explicitDateRange,
|
||||
);
|
||||
|
||||
export const generateMetaForCamera = (
|
||||
camera: Camera,
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) => ({
|
||||
url: absolutePathForCamera(camera),
|
||||
title: titleForCamera(camera, photos, explicitCount),
|
||||
description: descriptionForCameraPhotos(photos, true, explicitCount),
|
||||
description:
|
||||
descriptionForCameraPhotos(photos, true, explicitCount, explicitDateRange),
|
||||
images: absolutePathForCameraImage(camera),
|
||||
});
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Variant, motion } from 'framer-motion';
|
||||
import { useAppState } from '@/state';
|
||||
|
||||
export type AnimationType = 'none' | 'scale' | 'left' | 'right';
|
||||
export type AnimationType = 'none' | 'scale' | 'left' | 'right' | 'bottom';
|
||||
|
||||
export interface AnimationConfig {
|
||||
type?: AnimationType
|
||||
@ -58,7 +58,7 @@ function AnimateItems({
|
||||
? (nextPhotoAnimationInitial.current?.duration ?? duration)
|
||||
: duration;
|
||||
|
||||
const getInitialVariant = () => {
|
||||
const getInitialVariant = (): Variant => {
|
||||
switch (typeResolved) {
|
||||
case 'left': return {
|
||||
opacity: 0,
|
||||
@ -68,6 +68,10 @@ function AnimateItems({
|
||||
opacity: 0,
|
||||
transform: `translateX(${-distanceOffset}px)`,
|
||||
};
|
||||
case 'bottom': return {
|
||||
opacity: 0,
|
||||
transform: `translateY(${distanceOffset}px)`,
|
||||
};
|
||||
default: return {
|
||||
opacity: 0,
|
||||
transform: `translateY(${distanceOffset}px) scale(${scaleOffset})`,
|
||||
@ -98,7 +102,6 @@ function AnimateItems({
|
||||
<motion.div
|
||||
key={index}
|
||||
className={classNameItem}
|
||||
// style={getInitialVariant()}
|
||||
variants={{
|
||||
hidden: getInitialVariant(),
|
||||
show: {
|
||||
|
||||
@ -47,8 +47,7 @@ export default function Nav({ showTextLinks }: { showTextLinks?: boolean }) {
|
||||
<SiteGrid
|
||||
contentMain={
|
||||
<AnimateItems
|
||||
type={!shouldAnimate ? 'none' : undefined}
|
||||
scaleOffset={1}
|
||||
type={!shouldAnimate ? 'none' : 'bottom'}
|
||||
distanceOffset={10}
|
||||
items={showNav
|
||||
? [<div
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import { Photo } from '.';
|
||||
import { Photo, PhotoDateRange } from '.';
|
||||
import PhotoLarge from './PhotoLarge';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import PhotoGrid from './PhotoGrid';
|
||||
@ -15,12 +15,16 @@ export default function PhotoDetailPage({
|
||||
photosGrid,
|
||||
tag,
|
||||
camera,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
photo: Photo
|
||||
photos: Photo[]
|
||||
photosGrid?: Photo[]
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
@ -40,10 +44,11 @@ export default function PhotoDetailPage({
|
||||
className="mt-4 mb-8"
|
||||
contentMain={
|
||||
<CameraHeader
|
||||
key={tag}
|
||||
camera={camera}
|
||||
photos={photos}
|
||||
selectedPhoto={photo}
|
||||
count={count}
|
||||
dateRange={dateRange}
|
||||
/>}
|
||||
/>}
|
||||
<AnimateItems
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { cc } from '@/utility/css';
|
||||
import { Photo, dateRangeForPhotos } from '.';
|
||||
import { Photo, PhotoDateRange, dateRangeForPhotos } from '.';
|
||||
import ShareButton from '@/components/ShareButton';
|
||||
|
||||
export default function PhotoHeader({
|
||||
@ -10,6 +10,7 @@ export default function PhotoHeader({
|
||||
selectedPhoto,
|
||||
sharePath,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
entity: JSX.Element
|
||||
entityVerb: string
|
||||
@ -18,8 +19,9 @@ export default function PhotoHeader({
|
||||
selectedPhoto?: Photo
|
||||
sharePath: string
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
const { start, end } = dateRangeForPhotos(photos);
|
||||
const { start, end } = dateRangeForPhotos(photos, dateRange);
|
||||
|
||||
const selectedPhotoIndex = selectedPhoto
|
||||
? photos.findIndex(photo => photo.id === selectedPhoto.id)
|
||||
|
||||
@ -60,7 +60,6 @@ export interface Photo extends PhotoDb {
|
||||
exposureTimeFormatted?: string
|
||||
exposureCompensationFormatted?: string
|
||||
takenAtNaiveFormatted: string
|
||||
takenAtNaiveFormattedShort: string
|
||||
}
|
||||
|
||||
export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
@ -84,8 +83,6 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
formatExposureCompensation(photoDb.exposureCompensation),
|
||||
takenAtNaiveFormatted:
|
||||
formatDateFromPostgresString(photoDb.takenAtNaive),
|
||||
takenAtNaiveFormattedShort:
|
||||
formatDateFromPostgresString(photoDb.takenAtNaive, true),
|
||||
};
|
||||
};
|
||||
|
||||
@ -163,23 +160,35 @@ const photoLabelForCount = (count: number) =>
|
||||
export const photoQuantityText = (count: number) =>
|
||||
`(${count} ${photoLabelForCount(count)})`;
|
||||
|
||||
export type PhotoDateRange = { start: string, end: string };
|
||||
|
||||
export const descriptionForPhotoSet = (
|
||||
photos:Photo[],
|
||||
descriptor?: string,
|
||||
dateBased?: boolean,
|
||||
explicitCount?: number,
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) =>
|
||||
dateBased
|
||||
? dateRangeForPhotos(photos).description.toUpperCase()
|
||||
? dateRangeForPhotos(photos, explicitDateRange).description.toUpperCase()
|
||||
: [
|
||||
explicitCount ?? photos.length,
|
||||
descriptor,
|
||||
photoLabelForCount(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
export const dateRangeForPhotos = (photos: Photo[]) => {
|
||||
const start = photos[0].takenAtNaiveFormattedShort;
|
||||
const end = photos[photos.length - 1].takenAtNaiveFormattedShort;
|
||||
export const dateRangeForPhotos = (
|
||||
photos: Photo[],
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) => {
|
||||
const start = formatDateFromPostgresString(
|
||||
explicitDateRange?.start ?? photos[0].takenAtNaive,
|
||||
true,
|
||||
);
|
||||
const end = formatDateFromPostgresString(
|
||||
explicitDateRange?.end ?? photos[photos.length - 1].takenAtNaive,
|
||||
true
|
||||
);
|
||||
const description = start === end
|
||||
? start
|
||||
: `${start}–${end}`;
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
translatePhotoId,
|
||||
parsePhotoFromDb,
|
||||
Photo,
|
||||
PhotoDateRange,
|
||||
} from '@/photo';
|
||||
import { Camera, createCameraKey } from '@/camera';
|
||||
import { parameterize } from '@/utility/string';
|
||||
@ -239,13 +240,13 @@ const sqlGetPhotosCountIncludingHidden = async () => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
|
||||
const sqlGetPhotosCountTag = async (tag: string) => sql`
|
||||
const sqlGetPhotosTagCount = async (tag: string) => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
WHERE ${tag}=ANY(tags) AND
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
|
||||
const sqlGetPhotosCountCamera = async (camera: Camera) => sql`
|
||||
const sqlGetPhotosCameraCount = async (camera: Camera) => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
WHERE
|
||||
LOWER(make)=${parameterize(camera.make)} AND
|
||||
@ -253,6 +254,22 @@ const sqlGetPhotosCountCamera = async (camera: Camera) => sql`
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
|
||||
const sqlGetPhotosTagDateRange = async (tag: string) => sql`
|
||||
SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE ${tag}=ANY(tags) AND
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => rows[0] as PhotoDateRange);
|
||||
|
||||
const sqlGetPhotosCameraDateRange = async (camera: Camera) => sql`
|
||||
SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||
FROM photos
|
||||
WHERE
|
||||
LOWER(make)=${parameterize(camera.make)} AND
|
||||
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model)} AND
|
||||
hidden IS NOT TRUE
|
||||
`.then(({ rows }) => rows[0] as PhotoDateRange);
|
||||
|
||||
const sqlGetUniqueTags = async () => sql`
|
||||
SELECT DISTINCT unnest(tags) as tag FROM photos
|
||||
WHERE hidden IS NOT TRUE
|
||||
@ -353,10 +370,15 @@ export const getPhoto = async (id: string): Promise<Photo | undefined> => {
|
||||
|
||||
export const getPhotosCount = () =>
|
||||
safelyQueryPhotos(sqlGetPhotosCount);
|
||||
export const getPhotosCountTag = (tag: string) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosCountTag(tag));
|
||||
export const getPhotosCountCamera = (camera: Camera) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosCountCamera(camera));
|
||||
export const getPhotosTagCount = (tag: string) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosTagCount(tag));
|
||||
export const getPhotosCameraCount = (camera: Camera) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosCameraCount(camera));
|
||||
|
||||
export const getPhotosTagDateRange = (tag: string) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosTagDateRange(tag));
|
||||
export const getPhotosCameraDateRange = (camera: Camera) =>
|
||||
safelyQueryPhotos(() => sqlGetPhotosCameraDateRange(camera));
|
||||
|
||||
export const getPhotosCountIncludingHidden = () =>
|
||||
safelyQueryPhotos(sqlGetPhotosCountIncludingHidden);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
type PaginationSearchParams = { next: string };
|
||||
export type PaginationSearchParams = { next: string };
|
||||
|
||||
export interface PaginationParams {
|
||||
searchParams?: PaginationSearchParams
|
||||
|
||||
41
src/tag/TagOverview.tsx
Normal file
41
src/tag/TagOverview.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import TagHeader from './TagHeader';
|
||||
|
||||
export default function TagOverview({
|
||||
tag,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
showMorePath,
|
||||
animateOnFirstLoadOnly,
|
||||
}: {
|
||||
tag: string,
|
||||
photos: Photo[],
|
||||
count: number,
|
||||
dateRange: PhotoDateRange,
|
||||
showMorePath?: string,
|
||||
animateOnFirstLoadOnly?: boolean,
|
||||
}) {
|
||||
return (
|
||||
<SiteGrid
|
||||
contentMain={<div className="space-y-8 mt-4">
|
||||
<AnimateItems
|
||||
type="bottom"
|
||||
items={[
|
||||
<TagHeader
|
||||
key="TagHeader"
|
||||
{...{ tag, photos, count, dateRange }}
|
||||
/>,
|
||||
]}
|
||||
animateOnFirstLoadOnly
|
||||
/>
|
||||
<PhotoGrid
|
||||
{...{ photos, tag, showMorePath, animateOnFirstLoadOnly }}
|
||||
/>
|
||||
</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
52
src/tag/data.ts
Normal file
52
src/tag/data.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosTagCountCached,
|
||||
getPhotosTagDateRangeCached,
|
||||
} from '@/cache';
|
||||
import {
|
||||
PaginationSearchParams,
|
||||
getPaginationForSearchParams,
|
||||
} from '@/site/pagination';
|
||||
import { pathForTag } from '@/site/paths';
|
||||
|
||||
export const getPhotosTagDataCached = ({
|
||||
tag,
|
||||
limit,
|
||||
}: {
|
||||
tag: string,
|
||||
limit?: number,
|
||||
}) =>
|
||||
Promise.all([
|
||||
getPhotosCached({ tag, limit }),
|
||||
getPhotosTagCountCached(tag),
|
||||
getPhotosTagDateRangeCached(tag),
|
||||
]);
|
||||
|
||||
export const getPhotosTagDataCachedWithPagination = async ({
|
||||
tag,
|
||||
limit: limitProp,
|
||||
searchParams,
|
||||
}: {
|
||||
tag: string,
|
||||
limit?: number,
|
||||
searchParams?: PaginationSearchParams,
|
||||
}) => {
|
||||
const { offset, limit } = getPaginationForSearchParams(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,
|
||||
};
|
||||
};
|
||||
@ -1,4 +1,9 @@
|
||||
import { Photo, descriptionForPhotoSet, photoQuantityText } from '@/photo';
|
||||
import {
|
||||
Photo,
|
||||
PhotoDateRange,
|
||||
descriptionForPhotoSet,
|
||||
photoQuantityText,
|
||||
} from '@/photo';
|
||||
import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths';
|
||||
import { capitalizeWords } from '@/utility/string';
|
||||
|
||||
@ -15,16 +20,25 @@ export const descriptionForTaggedPhotos = (
|
||||
photos: Photo[],
|
||||
dateBased?: boolean,
|
||||
explicitCount?: number,
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) =>
|
||||
descriptionForPhotoSet(photos, 'tagged', dateBased, explicitCount);
|
||||
descriptionForPhotoSet(
|
||||
photos,
|
||||
'tagged',
|
||||
dateBased,
|
||||
explicitCount,
|
||||
explicitDateRange,
|
||||
);
|
||||
|
||||
export const generateMetaForTag = (
|
||||
tag: string,
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
explicitDateRange?: PhotoDateRange,
|
||||
) => ({
|
||||
url: absolutePathForTag(tag),
|
||||
title: titleForTag(tag, photos, explicitCount),
|
||||
description: descriptionForTaggedPhotos(photos, true),
|
||||
description:
|
||||
descriptionForTaggedPhotos(photos, true, explicitCount, explicitDateRange),
|
||||
images: absolutePathForTagImage(tag),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user