Build out final focal length views
This commit is contained in:
parent
7cd5ccbe15
commit
f7321bd831
86
src/app/focal/[focal]/[photoId]/layout.tsx
Normal file
86
src/app/focal/[focal]/[photoId]/layout.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
RELATED_GRID_PHOTOS_TO_SHOW,
|
||||
descriptionForPhoto,
|
||||
titleForPhoto,
|
||||
} from '@/photo';
|
||||
import { Metadata } from 'next/types';
|
||||
import { redirect } from 'next/navigation';
|
||||
import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { ReactNode, cache } from 'react';
|
||||
import { getPhotosMeta } from '@/photo/db/query';
|
||||
import { getFocalLengthFromString } from '@/focal';
|
||||
|
||||
const getPhotosNearIdCachedCached = cache((photoId: string, focal: number) =>
|
||||
getPhotosNearIdCached(
|
||||
photoId,
|
||||
{ focal, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 },
|
||||
));
|
||||
|
||||
interface PhotoFocalLengthProps {
|
||||
params: { photoId: string, focal: string }
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { photoId, focal: focalString },
|
||||
}: PhotoFocalLengthProps): Promise<Metadata> {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const { photo } = await getPhotosNearIdCachedCached(photoId, focal);
|
||||
|
||||
if (!photo) { return {}; }
|
||||
|
||||
const title = titleForPhoto(photo);
|
||||
const description = descriptionForPhoto(photo);
|
||||
const images = absolutePathForPhotoImage(photo);
|
||||
const url = absolutePathForPhoto({ photo, focal });
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
images,
|
||||
description,
|
||||
url,
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function PhotoFocalLengthPage({
|
||||
params: { photoId, focal: focalString },
|
||||
children,
|
||||
}: PhotoFocalLengthProps & { children: ReactNode }) {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const { photo, photos, photosGrid, indexNumber } =
|
||||
await getPhotosNearIdCachedCached(photoId, focal);
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const { count, dateRange } = await getPhotosMeta({ focal });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
<PhotoDetailPage {...{
|
||||
photo,
|
||||
photos,
|
||||
photosGrid,
|
||||
focal,
|
||||
indexNumber,
|
||||
count,
|
||||
dateRange,
|
||||
}} />
|
||||
</>;
|
||||
}
|
||||
3
src/app/focal/[focal]/[photoId]/page.tsx
Normal file
3
src/app/focal/[focal]/[photoId]/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return null;
|
||||
}
|
||||
19
src/app/focal/[focal]/[photoId]/share/page.tsx
Normal file
19
src/app/focal/[focal]/[photoId]/share/page.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { getFocalLengthFromString } from '@/focal';
|
||||
import { getPhotoCached } from '@/photo/cache';
|
||||
import PhotoShareModal from '@/photo/PhotoShareModal';
|
||||
import { PATH_ROOT } from '@/site/paths';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function Share({
|
||||
params: { photoId, focal: focalString },
|
||||
}: {
|
||||
params: { photoId: string, focal: string }
|
||||
}) {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return redirect(PATH_ROOT); }
|
||||
|
||||
return <PhotoShareModal {...{ photo, focal }} />;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
||||
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||
import { getPhotosFocalDataCached } from '@/focal/data';
|
||||
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
||||
import { PATH_ROOT } from '@/site/paths';
|
||||
import type { Metadata } from 'next';
|
||||
@ -8,7 +8,7 @@ import { redirect } from 'next/navigation';
|
||||
import { cache } from 'react';
|
||||
|
||||
const getPhotosFocalDataCachedCached = cache((focal: number) =>
|
||||
getPhotosFocalDataCached({
|
||||
getPhotosFocalLengthDataCached({
|
||||
focal,
|
||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
||||
}));
|
||||
|
||||
70
src/app/focal/[focal]/share/page.tsx
Normal file
70
src/app/focal/[focal]/share/page.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
||||
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||
import FocalLengthShareModal from '@/focal/FocalLengthShareModal';
|
||||
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
||||
import type { Metadata } from 'next';
|
||||
import { cache } from 'react';
|
||||
|
||||
const getPhotosFocalLengthDataCachedCached = cache((focal: number) =>
|
||||
getPhotosFocalLengthDataCached({
|
||||
focal,
|
||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
||||
}));
|
||||
|
||||
interface FocalLengthProps {
|
||||
params: { focal: string }
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { focal: focalString },
|
||||
}: FocalLengthProps): Promise<Metadata> {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ count, dateRange },
|
||||
] = await getPhotosFocalLengthDataCachedCached(focal);
|
||||
|
||||
const {
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
} = generateMetaForFocalLength(focal, photos, count, dateRange);
|
||||
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
url,
|
||||
},
|
||||
twitter: {
|
||||
images,
|
||||
description,
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
description,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Share({
|
||||
params: { focal: focalString },
|
||||
}: FocalLengthProps) {
|
||||
const focal = getFocalLengthFromString(focalString);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ count, dateRange },
|
||||
] = await getPhotosFocalLengthDataCachedCached(focal);
|
||||
|
||||
return <>
|
||||
<FocalLengthShareModal {...{ focal, photos, count, dateRange }} />
|
||||
<FocalLengthOverview
|
||||
{...{ focal, photos, count, dateRange }}
|
||||
animateOnFirstLoadOnly
|
||||
/>
|
||||
</>;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { descriptionForFocalLengthPhotos } from '.';
|
||||
import { pathForFocalLength } from '@/site/paths';
|
||||
import { pathForFocalLengthShare } from '@/site/paths';
|
||||
import PhotoSetHeader from '@/photo/PhotoSetHeader';
|
||||
import PhotoFocalLength from './PhotoFocalLength';
|
||||
|
||||
@ -22,7 +22,6 @@ export default function FocalLengthHeader({
|
||||
return (
|
||||
<PhotoSetHeader
|
||||
entity={<PhotoFocalLength focal={focal} contrast="high" />}
|
||||
entityVerb="Tagged"
|
||||
entityDescription={descriptionForFocalLengthPhotos(
|
||||
photos,
|
||||
undefined,
|
||||
@ -30,7 +29,7 @@ export default function FocalLengthHeader({
|
||||
)}
|
||||
photos={photos}
|
||||
selectedPhoto={selectedPhoto}
|
||||
sharePath={pathForFocalLength(focal)}
|
||||
sharePath={pathForFocalLengthShare(focal)}
|
||||
indexNumber={indexNumber}
|
||||
count={count}
|
||||
dateRange={dateRange}
|
||||
|
||||
50
src/focal/FocalLengthOGTile.tsx
Normal file
50
src/focal/FocalLengthOGTile.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import {
|
||||
absolutePathForFocalLengthImage,
|
||||
pathForFocalLength,
|
||||
} from '@/site/paths';
|
||||
import OGTile from '@/components/OGTile';
|
||||
import { descriptionForFocalLengthPhotos, titleForFocalLength } from '.';
|
||||
|
||||
export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
|
||||
|
||||
export default function FocalLengthOGTile({
|
||||
focal,
|
||||
photos,
|
||||
loadingState: loadingStateExternal,
|
||||
riseOnHover,
|
||||
onLoad,
|
||||
onFail,
|
||||
retryTime,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
focal: number
|
||||
photos: Photo[]
|
||||
loadingState?: OGLoadingState
|
||||
onLoad?: () => void
|
||||
onFail?: () => void
|
||||
riseOnHover?: boolean
|
||||
retryTime?: number
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
return (
|
||||
<OGTile {...{
|
||||
title: titleForFocalLength(focal, photos, count),
|
||||
description: descriptionForFocalLengthPhotos(
|
||||
photos,
|
||||
true,
|
||||
count,
|
||||
dateRange,
|
||||
),
|
||||
path: pathForFocalLength(focal),
|
||||
pathImageAbsolute: absolutePathForFocalLengthImage(focal),
|
||||
loadingState: loadingStateExternal,
|
||||
onLoad,
|
||||
onFail,
|
||||
riseOnHover,
|
||||
retryTime,
|
||||
}}/>
|
||||
);
|
||||
};
|
||||
26
src/focal/FocalLengthShareModal.tsx
Normal file
26
src/focal/FocalLengthShareModal.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { absolutePathForFocalLength, pathForFocalLength } from '@/site/paths';
|
||||
import { Photo, PhotoDateRange } from '../photo';
|
||||
import ShareModal from '@/components/ShareModal';
|
||||
import FocalLengthOGTile from './FocalLengthOGTile';
|
||||
|
||||
export default function FocalLengthShareModal({
|
||||
focal,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
focal: number
|
||||
photos: Photo[]
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
}) {
|
||||
return (
|
||||
<ShareModal
|
||||
title="Share Photos"
|
||||
pathShare={absolutePathForFocalLength(focal)}
|
||||
pathClose={pathForFocalLength(focal)}
|
||||
>
|
||||
<FocalLengthOGTile {...{ focal, photos, count, dateRange }} />
|
||||
</ShareModal>
|
||||
);
|
||||
};
|
||||
@ -3,7 +3,7 @@ import {
|
||||
getPhotosMetaCached,
|
||||
} from '@/photo/cache';
|
||||
|
||||
export const getPhotosFocalDataCached = ({
|
||||
export const getPhotosFocalLengthDataCached = ({
|
||||
focal,
|
||||
limit,
|
||||
}: {
|
||||
|
||||
@ -23,7 +23,7 @@ export const titleForFocalLength = (
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
formatFocalLength(focal),
|
||||
`${formatFocalLength(focal)} Focal Length`,
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { FilmSimulation } from '@/simulation';
|
||||
import FilmSimulationHeader from '@/simulation/FilmSimulationHeader';
|
||||
import { TAG_HIDDEN } from '@/tag';
|
||||
import HiddenHeader from '@/tag/HiddenHeader';
|
||||
import FocalLengthHeader from '@/focal/FocalLengthHeader';
|
||||
|
||||
export default function PhotoDetailPage({
|
||||
photo,
|
||||
@ -20,6 +21,7 @@ export default function PhotoDetailPage({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
indexNumber,
|
||||
count,
|
||||
dateRange,
|
||||
@ -30,6 +32,7 @@ export default function PhotoDetailPage({
|
||||
tag?: string
|
||||
camera?: Camera
|
||||
simulation?: FilmSimulation
|
||||
focal?: number
|
||||
indexNumber?: number
|
||||
count?: number
|
||||
dateRange?: PhotoDateRange
|
||||
@ -82,6 +85,19 @@ export default function PhotoDetailPage({
|
||||
dateRange={dateRange}
|
||||
/>}
|
||||
/>}
|
||||
{focal &&
|
||||
<SiteGrid
|
||||
className="mt-4 mb-8"
|
||||
contentMain={
|
||||
<FocalLengthHeader
|
||||
focal={focal}
|
||||
photos={photos}
|
||||
selectedPhoto={photo}
|
||||
indexNumber={indexNumber}
|
||||
count={count}
|
||||
dateRange={dateRange}
|
||||
/>}
|
||||
/>}
|
||||
<AnimateItems
|
||||
className="md:mb-8"
|
||||
animateFromAppState
|
||||
@ -109,6 +125,7 @@ export default function PhotoDetailPage({
|
||||
tag={tag}
|
||||
camera={camera}
|
||||
simulation={simulation}
|
||||
focal={focal}
|
||||
animateOnFirstLoadOnly
|
||||
/>}
|
||||
contentSide={<AnimateItems
|
||||
@ -130,6 +147,7 @@ export default function PhotoDetailPage({
|
||||
tag,
|
||||
camera,
|
||||
simulation,
|
||||
focal,
|
||||
}} />
|
||||
</div>,
|
||||
]}
|
||||
|
||||
@ -101,6 +101,7 @@ export default function PhotoLinks({
|
||||
tag={tag}
|
||||
camera={camera}
|
||||
simulation={simulation}
|
||||
focal={focal}
|
||||
scroll={false}
|
||||
prefetch
|
||||
>
|
||||
@ -112,6 +113,7 @@ export default function PhotoLinks({
|
||||
tag={tag}
|
||||
camera={camera}
|
||||
simulation={simulation}
|
||||
focal={focal}
|
||||
scroll={false}
|
||||
prefetch
|
||||
>
|
||||
|
||||
@ -125,11 +125,14 @@ export const pathForCameraShare = (camera: Camera) =>
|
||||
export const pathForFilmSimulation = (simulation: FilmSimulation) =>
|
||||
`${PREFIX_FILM_SIMULATION}/${simulation}`;
|
||||
|
||||
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
|
||||
`${pathForFilmSimulation(simulation)}/${SHARE}`;
|
||||
|
||||
export const pathForFocalLength = (focal: number) =>
|
||||
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
|
||||
|
||||
export const pathForFilmSimulationShare = (simulation: FilmSimulation) =>
|
||||
`${pathForFilmSimulation(simulation)}/${SHARE}`;
|
||||
export const pathForFocalLengthShare = (focal: number) =>
|
||||
`${pathForFocalLength(focal)}/${SHARE}`;;
|
||||
|
||||
export const absolutePathForPhoto = (params: PhotoPathParams) =>
|
||||
`${BASE_URL}${pathForPhoto(params)}`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user