Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/grid/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { generateOgImageMetaForPhotos } from '@/photo';
import PhotosEmptyState from '@/photo/PhotosEmptyState';
import { Metadata } from 'next/types';
import { getPhotos } from '@/photo/query';
import { cache } from 'react';
import PhotoGridPage from '@/photo/PhotoGridPage';
import { getDataForCategoriesCached } from '@/category/cache';
import { getPhotosMetaCached } from '@/photo/cache';
import { getPhotosCachedLight, getPhotosMetaCached } from '@/photo/cache';
import { USER_DEFAULT_SORT_OPTIONS } from '@/app/config';
import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed';

export const dynamic = 'force-static';
export const maxDuration = 60;

const getPhotosCached = cache(() => getPhotos(getFeedQueryOptions({
const getPhotosCached = cache(() => getPhotosCachedLight(getFeedQueryOptions({
isGrid: true,
})));

Expand Down
40 changes: 26 additions & 14 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,47 @@ import { generateOgImageMetaForPhotos } from '@/photo';
import PhotosEmptyState from '@/photo/PhotosEmptyState';
import { Metadata } from 'next/types';
import { cache } from 'react';
import { getPhotos } from '@/photo/query';
import { GRID_HOMEPAGE_ENABLED, USER_DEFAULT_SORT_OPTIONS } from '@/app/config';
import { NULL_CATEGORY_DATA } from '@/category/data';
import PhotoFullPage from '@/photo/PhotoFullPage';
import PhotoGridPage from '@/photo/PhotoGridPage';
import { getDataForCategoriesCached } from '@/category/cache';
import { getPhotosMetaCached } from '@/photo/cache';
import {
getPhotosCachedLight, getPhotosMetaCached,
getPhotosCached,
} from '@/photo/cache';
import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed';

export const dynamic = 'force-static';
export const maxDuration = 60;

const getPhotosLightCached = cache(() =>
getPhotosCachedLight(getFeedQueryOptions({ isGrid: true }))
);

const getPhotosCached = cache(() => getPhotos(getFeedQueryOptions({
isGrid: GRID_HOMEPAGE_ENABLED,
})));
const getPhotosFullCached = cache(() =>
getPhotosCached(getFeedQueryOptions({ isGrid: false }))
);

export async function generateMetadata(): Promise<Metadata> {
const photos = await getPhotosCached()
.catch(() => []);
const photos = GRID_HOMEPAGE_ENABLED
? await getPhotosLightCached().catch(() => [])
: await getPhotosFullCached().catch(() => []);
return generateOgImageMetaForPhotos(photos);
}

export default async function HomePage() {
export default async function HomePage() {
const [
photos,
photosLight,
photosFull,
photosCount,
photosCountWithExcludes,
categories,
] = await Promise.all([
getPhotosCached()
.catch(() => []),
GRID_HOMEPAGE_ENABLED ? getPhotosLightCached().catch(() => [])
: Promise.resolve([]),
!GRID_HOMEPAGE_ENABLED ? getPhotosFullCached().catch(() => [])
: Promise.resolve([]),
getPhotosMetaCached(FEED_META_QUERY_OPTIONS)
.then(({ count }) => count)
.catch(() => 0),
Expand All @@ -44,20 +54,22 @@ export default async function HomePage() {
: NULL_CATEGORY_DATA,
]);

const photos = GRID_HOMEPAGE_ENABLED ? photosLight : photosFull;

return (
photos.length > 0
? GRID_HOMEPAGE_ENABLED
? <PhotoGridPage
{...{
photos,
photos: photosLight,
photosCount,
photosCountWithExcludes,
...USER_DEFAULT_SORT_OPTIONS,
...categories,
}}
/>
: <PhotoFullPage {...{
photos,
photos: photosFull,
photosCount,
...USER_DEFAULT_SORT_OPTIONS,
}} />
Expand Down
4 changes: 2 additions & 2 deletions src/app/path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Photo } from '@/photo';
import { PhotoLight } from '@/photo';
import { PhotoSetCategory } from '@/category';
import { getBaseUrl, GRID_HOMEPAGE_ENABLED } from './config';
import { Camera } from '@/camera';
Expand Down Expand Up @@ -156,7 +156,7 @@ export const pathForAdminTagEdit = (tag: string) =>
export const pathForAdminRecipeEdit = (recipe: string) =>
`${PATH_ADMIN_RECIPES}/${recipe}/${EDIT}`;

type PhotoOrPhotoId = Photo | string;
type PhotoOrPhotoId = PhotoLight | string;

export const pathForPhoto = ({
photo,
Expand Down
6 changes: 3 additions & 3 deletions src/photo/PhotoGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Photo } from '.';
import { PhotoLight } from '.';
import { PhotoSetCategory } from '../category';
import PhotoMedium from './PhotoMedium';
import { clsx } from 'clsx/lite';
Expand All @@ -27,8 +27,8 @@ export default function PhotoGrid({
onAnimationComplete,
...categories
}: {
photos: Photo[]
selectedPhoto?: Photo
photos: PhotoLight[]
selectedPhoto?: PhotoLight
prioritizeInitialPhotos?: boolean
animate?: boolean
canStart?: boolean
Expand Down
4 changes: 2 additions & 2 deletions src/photo/PhotoGridPageClient.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Photo } from '.';
import { PhotoLight } from '.';
import { PATH_GRID_INFERRED } from '@/app/path';
import PhotoGridSidebar from './PhotoGridSidebar';
import PhotoGridContainer from './PhotoGridContainer';
Expand All @@ -19,7 +19,7 @@ export default function PhotoGridPageClient({
sortWithPriority,
...categories
}: ComponentProps<typeof PhotoGridSidebar> & {
photos: Photo[]
photos: PhotoLight[]
photosCount: number
photosCountWithExcludes: number
sortBy: SortBy
Expand Down
4 changes: 2 additions & 2 deletions src/photo/PhotoLink.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { ReactNode, ComponentProps, RefObject } from 'react';
import { Photo, titleForPhoto } from '@/photo';
import { PhotoLight, titleForPhoto } from '@/photo';
import { PhotoSetCategory } from '@/category';
import { AnimationConfig } from '../components/AnimateItems';
import { useAppState } from '@/app/AppState';
Expand All @@ -23,7 +23,7 @@ export default function PhotoLink({
...categories
}: {
ref?: RefObject<HTMLAnchorElement | null>
photo?: Photo
photo?: PhotoLight
scroll?: boolean
prefetch?: boolean
nextPhotoAnimation?: AnimationConfig
Expand Down
4 changes: 2 additions & 2 deletions src/photo/PhotoMedium.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import {
Photo,
PhotoLight,
altTextForPhoto,
doesPhotoNeedBlurCompatibility,
} from '.';
Expand All @@ -26,7 +26,7 @@ export default function PhotoMedium({
debugColor,
...categories
}: {
photo: Photo
photo: PhotoLight
selected?: boolean
priority?: boolean
prefetch?: boolean
Expand Down
18 changes: 18 additions & 0 deletions src/photo/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
getPhoto,
getPhotos,
getPhotosLight,
getUniqueCameras,
getUniqueTags,
getUniqueFilms,
Expand Down Expand Up @@ -177,6 +178,23 @@ export const revalidatePhoto = (photoId: string) => {
};

// Cache
export const getPhotosCachedLight = (
...args: Parameters<typeof getPhotosLight>
) => {
return unstable_cache(
async (...args) => {
const photos = await getPhotosLight(...args);
return photos.map(photo => ({
...photo,
takenAt: new Date(photo.takenAt),
updatedAt: new Date(photo.updatedAt),
createdAt: new Date(photo.createdAt),
}));
},
[KEY_PHOTOS, 'light', ...getPhotosCacheKeys(...args)],
{ revalidate: 3600, tags: [KEY_PHOTOS] }
)(...args);
};

export const getPhotosCached = (
...args: Parameters<typeof getPhotos>
Expand Down
52 changes: 43 additions & 9 deletions src/photo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import camelcaseKeys from 'camelcase-keys';
import { isBefore } from 'date-fns';
import type { Metadata } from 'next';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
import { PhotoUpdateStatus, generatePhotoUpdateStatus } from './update';
import { AppTextState } from '@/i18n/state';
import { PhotoColorData } from './color/client';
Expand Down Expand Up @@ -48,6 +49,21 @@ export const ACCEPTED_PHOTO_FILE_TYPES = [

export const MAX_PHOTO_UPLOAD_SIZE_IN_BYTES = 50_000_000;

export interface PhotoLight {
id: string
url: string
aspectRatio: number
blurData?: string
updatedAt: Date
takenAt: Date
takenAtNaive: string
createdAt: Date
colorData?: PhotoColorData
title?: string
semanticDescription?: string
hidden?: boolean
}

// Core EXIF data
export interface PhotoExif {
aspectRatio: number
Expand All @@ -63,7 +79,7 @@ export interface PhotoExif {
exposureCompensation?: number
latitude?: number
longitude?: number
film?: string
film?: FujifilmSimulation
recipeData?: string
takenAt?: string
takenAtNaive?: string
Expand Down Expand Up @@ -117,6 +133,24 @@ export interface Photo extends Omit<PhotoDb, 'recipeData' | 'colorData'> {
updateStatus?: PhotoUpdateStatus
}

export const parsePhotoLightFromDb = (photoDbRaw: any): PhotoLight => {
const photoDb = camelcaseKeys(photoDbRaw) as unknown as Record<string, unknown>;
return {
id: photoDb.id,
url: photoDb.url,
aspectRatio: photoDb.aspectRatio,
blurData: photoDb.blurData,
updatedAt: photoDb.updatedAt,
takenAt: photoDb.takenAt,
takenAtNaive: photoDb.takenAtNaive,
createdAt: photoDb.createdAt,
colorData: photoDb.colorData || undefined,
title: photoDb.title,
semanticDescription: photoDb.semanticDescription,
hidden: photoDb.hidden,
} as PhotoLight;
};

export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
const photoDb = camelcaseKeys(
photoDbRaw as unknown as Record<string, unknown>,
Expand Down Expand Up @@ -182,21 +216,21 @@ export const descriptionForPhoto = (
(includeSemanticDescription && photo.semanticDescription) ||
formatDate({ date: photo.takenAt }).toLocaleUpperCase();

export const getPreviousPhoto = (photo: Photo, photos: Photo[]) => {
export const getPreviousPhoto = (photo: PhotoLight, photos: PhotoLight[]) => {
const index = photos.findIndex(p => p.id === photo.id);
return index > 0
? photos[index - 1]
: undefined;
};

export const getNextPhoto = (photo: Photo, photos: Photo[]) => {
export const getNextPhoto = (photo: PhotoLight, photos: PhotoLight[]) => {
const index = photos.findIndex(p => p.id === photo.id);
return index < photos.length - 1
? photos[index + 1]
: undefined;
};

export const generateOgImageMetaForPhotos = (photos: Photo[]): Metadata => {
export const generateOgImageMetaForPhotos = (photos: PhotoLight[]): Metadata => {
if (photos.length > 0) {
return {
openGraph: {
Expand All @@ -221,7 +255,7 @@ export const translatePhotoId = (id: string) =>
PHOTO_ID_FORWARDING_TABLE[id] || id;

export const titleForPhoto = (
photo: Photo,
photo: PhotoLight,
useDateAsTitle = true,
fallback = 'Untitled',
) => {
Expand All @@ -237,7 +271,7 @@ export const titleForPhoto = (
}
};

export const altTextForPhoto = (photo: Photo) =>
export const altTextForPhoto = (photo: PhotoLight) =>
photo.semanticDescription || titleForPhoto(photo);

export const photoLabelForCount = (
Expand Down Expand Up @@ -278,7 +312,7 @@ export type PhotoDateRangeFormatted = {
};

export const descriptionForPhotoSet = (
photos:Photo[] = [],
photos: Photo[] | PhotoLight[] = [],
appText: AppTextState,
descriptor?: string,
dateBased?: boolean,
Expand All @@ -297,7 +331,7 @@ export const descriptionForPhotoSet = (
].join(' ');

const sortPhotosByDateNonDestructively = (
photos: Photo[],
photos: Photo[] | PhotoLight[],
order: 'ASC' | 'DESC' = 'DESC',
) =>
[...photos].sort((a, b) => order === 'DESC'
Expand Down Expand Up @@ -385,5 +419,5 @@ export const downloadFileNameForPhoto = (photo: Photo) =>
? `${parameterize(photo.title)}.${photo.extension}`
: photo.url.split('/').pop() || 'download';

export const doesPhotoNeedBlurCompatibility = (photo: Photo) =>
export const doesPhotoNeedBlurCompatibility = (photo: PhotoLight) =>
isBefore(photo.updatedAt, new Date('2024-05-07'));
Loading