From 1d20cb58b2ac303d246339a1b606951557818ace Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 3 Mar 2025 19:43:08 -0600 Subject: [PATCH] Refactor recipe schema and pages --- app/admin/recipe/[photoId]/page.tsx | 28 ------- app/admin/recipe/page.tsx | 33 -------- app/film/[simulation]/page.tsx | 6 ++ app/recipe/[recipe]/[photoId]/page.tsx | 90 ++++++++++++++++++++++ app/recipe/[recipe]/image/route.tsx | 57 ++++++++++++++ app/recipe/[recipe]/page.tsx | 25 +++--- src/app/paths.ts | 13 +--- src/image-response/RecipeImageResponse.tsx | 51 ++++++++++++ src/photo/PhotoDetailPage.tsx | 13 ++++ src/photo/PhotoGridSidebar.tsx | 60 ++++++--------- src/photo/PhotoHeader.tsx | 2 + src/photo/PhotoLarge.tsx | 47 ++++++++--- src/photo/PhotoPrevNext.tsx | 8 +- src/photo/db/index.ts | 5 ++ src/photo/db/migration.ts | 27 ++++++- src/photo/db/query.ts | 28 ++++++- src/photo/form/index.ts | 22 ++++-- src/photo/index.ts | 13 ++-- src/recipe/PhotoRecipe.tsx | 10 ++- src/recipe/PhotoRecipeGrid.tsx | 2 +- src/recipe/PhotoRecipeOverlay.tsx | 52 ------------- src/recipe/RecipeHeader.tsx | 14 ++-- src/recipe/RecipeModal.tsx | 5 +- src/recipe/data.ts | 16 ++++ src/recipe/index.ts | 22 ++---- src/recipe/useRecipeState.ts | 16 +--- src/tag/PhotoTags.tsx | 13 +--- 27 files changed, 423 insertions(+), 255 deletions(-) delete mode 100644 app/admin/recipe/[photoId]/page.tsx delete mode 100644 app/admin/recipe/page.tsx create mode 100644 app/recipe/[recipe]/[photoId]/page.tsx create mode 100644 app/recipe/[recipe]/image/route.tsx create mode 100644 src/image-response/RecipeImageResponse.tsx delete mode 100644 src/recipe/PhotoRecipeOverlay.tsx create mode 100644 src/recipe/data.ts diff --git a/app/admin/recipe/[photoId]/page.tsx b/app/admin/recipe/[photoId]/page.tsx deleted file mode 100644 index f8129146..00000000 --- a/app/admin/recipe/[photoId]/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import SiteGrid from '@/components/SiteGrid'; -import { getPhoto, getPhotos } from '@/photo/db/query'; -import PhotoRecipeOverlay from '@/recipe/PhotoRecipeOverlay'; - -export default async function AdminRecipePage({ - params, -}: { - params: Promise<{ photoId: string }> -}) { - const { photoId } = await params; - const photo = await getPhoto(photoId); - const photosHidden = await getPhotos({ hidden: 'only' }); - const { filmSimulation } = photo!; - const { fujifilmRecipe } = photosHidden[0]; - - return ( - - :
- Can't find photo/recipe -
} - /> - ); -} diff --git a/app/admin/recipe/page.tsx b/app/admin/recipe/page.tsx deleted file mode 100644 index 67f01aa4..00000000 --- a/app/admin/recipe/page.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { getPhoto, getPhotos } from '@/photo/db/query'; -import PhotoRecipeOverlay from '@/recipe/PhotoRecipeOverlay'; - -export default async function AdminRecipePage() { - const [ - photos, - photo1, - photo2, - photo3, - photo4, - photosHidden, - ] = await Promise.all([ - getPhotos({ tag: 'favs' }), - getPhoto('4zT6dgPr'), - getPhoto('9MopluBn'), - getPhoto('ifv8zq45'), - getPhoto('2BO2YoW6'), - getPhotos({ hidden: 'only', limit: 1 }), - ]); - const { fujifilmRecipe } = photosHidden[0]; - return ( - - ); -} diff --git a/app/film/[simulation]/page.tsx b/app/film/[simulation]/page.tsx index a941adb7..ec6e74b6 100644 --- a/app/film/[simulation]/page.tsx +++ b/app/film/[simulation]/page.tsx @@ -7,6 +7,8 @@ import { getPhotosFilmSimulationDataCached } from '@/simulation/data'; import { STATICALLY_OPTIMIZED_PHOTO_CATEGORIES } from '@/app/config'; import { Metadata } from 'next/types'; import { cache } from 'react'; +import { PATH_ROOT } from '@/app/paths'; +import { redirect } from 'next/navigation'; const getPhotosFilmSimulationDataCachedCached = cache(getPhotosFilmSimulationDataCached); @@ -38,6 +40,8 @@ export async function generateMetadata({ limit: INFINITE_SCROLL_GRID_INITIAL, }); + if (photos.length === 0) { return {}; } + const { url, title, @@ -75,6 +79,8 @@ export default async function FilmSimulationPage({ limit: INFINITE_SCROLL_GRID_INITIAL, }); + if (photos.length === 0) { redirect(PATH_ROOT); } + return ( + getPhotosNearIdCached( + photoId, + { recipe, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 }, + )); + +interface PhotoRecipeProps { + params: Promise<{ photoId: string, recipe: string }> +} + +export async function generateMetadata({ + params, +}: PhotoRecipeProps): Promise { + const { photoId, recipe: recipeFromParams } = await params; + + const recipe = decodeURIComponent(recipeFromParams); + + const { photo } = await getPhotosNearIdCachedCached(photoId, recipe); + + if (!photo) { return {}; } + + const title = titleForPhoto(photo); + const description = descriptionForPhoto(photo); + const images = absolutePathForPhotoImage(photo); + const url = absolutePathForPhoto({ photo, recipe }); + + return { + title, + description, + openGraph: { + title, + images, + description, + url, + }, + twitter: { + title, + description, + images, + card: 'summary_large_image', + }, + }; +} + +export default async function PhotoTagPage({ + params, +}: PhotoRecipeProps) { + const { photoId, recipe: recipeFromParams } = await params; + + const recipe = decodeURIComponent(recipeFromParams); + + const { photo, photos, photosGrid, indexNumber } = + await getPhotosNearIdCachedCached(photoId, recipe); + + if (!photo) { redirect(PATH_ROOT); } + + const { count, dateRange } = await getPhotosMeta({ recipe }); + + return ( + + ); +} diff --git a/app/recipe/[recipe]/image/route.tsx b/app/recipe/[recipe]/image/route.tsx new file mode 100644 index 00000000..56e999e5 --- /dev/null +++ b/app/recipe/[recipe]/image/route.tsx @@ -0,0 +1,57 @@ +import { getPhotosCached } from '@/photo/cache'; +import { + IMAGE_OG_DIMENSION_SMALL, + MAX_PHOTOS_TO_SHOW_PER_TAG, +} from '@/image-response'; +import { getIBMPlexMonoMedium } from '@/app/font'; +import { ImageResponse } from 'next/og'; +import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; +import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; +import { getUniqueRecipes } from '@/photo/db/query'; +import { + STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES, + IS_PRODUCTION, +} from '@/app/config'; +import RecipeImageResponse from '@/image-response/RecipeImageResponse'; + +export let generateStaticParams: + (() => Promise<{ recipe: string }[]>) | undefined = undefined; + +if (STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES && IS_PRODUCTION) { + generateStaticParams = async () => { + const recipes = await getUniqueRecipes(); + return recipes + .slice(0, GENERATE_STATIC_PARAMS_LIMIT) + .map(({ recipe }) => ({ recipe })); + }; +} + +export async function GET( + _: Request, + context: { params: Promise<{ recipe: string }> }, +) { + const { recipe } = await context.params; + + const [ + photos, + { fontFamily, fonts }, + headers, + ] = await Promise.all([ + getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_TAG, recipe }), + getIBMPlexMonoMedium(), + getImageResponseCacheControlHeaders(), + ]); + + const { width, height } = IMAGE_OG_DIMENSION_SMALL; + + return new ImageResponse( + , + { width, height, fonts, headers }, + ); +} diff --git a/app/recipe/[recipe]/page.tsx b/app/recipe/[recipe]/page.tsx index 57283086..830e4779 100644 --- a/app/recipe/[recipe]/page.tsx +++ b/app/recipe/[recipe]/page.tsx @@ -1,27 +1,24 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueTags } from '@/photo/db/query'; +import { getUniqueRecipes } from '@/photo/db/query'; import { IS_PRODUCTION } from '@/app/config'; import { STATICALLY_OPTIMIZED_PHOTO_CATEGORIES } from '@/app/config'; import { PATH_ROOT } from '@/app/paths'; -import { getPhotosTagDataCached } from '@/tag/data'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { cache } from 'react'; -import { convertRecipeToTag, generateMetaForRecipe } from '@/recipe'; +import { generateMetaForRecipe } from '@/recipe'; import RecipeOverview from '@/recipe/RecipeOverview'; +import { getPhotosRecipeDataCached } from '@/recipe/data'; -const getPhotosTagDataCachedCached = cache((tag: string) => - getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL})); +const getPhotosRecipeDataCachedCached = cache(getPhotosRecipeDataCached); export let generateStaticParams: (() => Promise<{ recipe: string }[]>) | undefined = undefined; if (STATICALLY_OPTIMIZED_PHOTO_CATEGORIES && IS_PRODUCTION) { generateStaticParams = async () => { - const tags = await getUniqueTags(); - return tags - .filter(({ tag }) => tag.startsWith('recipe')) - .map(({ tag }) => ({ recipe: tag.replace(/^recipe-?/i, '')})); + const recipes = await getUniqueRecipes(); + return recipes.map(({ recipe }) => ({ recipe })); }; } @@ -39,7 +36,10 @@ export async function generateMetadata({ const [ photos, { count, dateRange }, - ] = await getPhotosTagDataCachedCached(convertRecipeToTag(recipe)); + ] = await getPhotosRecipeDataCachedCached({ + recipe, + limit: INFINITE_SCROLL_GRID_INITIAL, + }); if (photos.length === 0) { return {}; } @@ -77,7 +77,10 @@ export default async function RecipePage({ const [ photos, { count, dateRange }, - ] = await getPhotosTagDataCachedCached(convertRecipeToTag(recipe)); + ] = await getPhotosRecipeDataCachedCached({ + recipe, + limit: INFINITE_SCROLL_GRID_INITIAL, + }); if (photos.length === 0) { redirect(PATH_ROOT); } diff --git a/src/app/paths.ts b/src/app/paths.ts index 31b60343..f1bb086a 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -35,10 +35,6 @@ const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]` const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; const PATH_RECIPE_DYNAMIC = `${PREFIX_RECIPE}/[recipe]`; -// Search params -export const SEARCH_PARAM_SHOW = 'show'; -export const SEARCH_PARAM_SHOW_RECIPE = 'recipe'; - // Admin paths export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`; export const PATH_ADMIN_OUTDATED = `${PATH_ADMIN}/outdated`; @@ -114,9 +110,8 @@ export const pathForPhoto = ({ simulation, focal, recipe, - showRecipe, -}: PhotoPathParams) => { - const path = typeof photo !== 'string' && photo.hidden +}: PhotoPathParams) => + typeof photo !== 'string' && photo.hidden ? `${pathForTag(TAG_HIDDEN)}/${getPhotoId(photo)}` : tag ? `${pathForTag(tag)}/${getPhotoId(photo)}` @@ -129,10 +124,6 @@ export const pathForPhoto = ({ : recipe ? `${pathForRecipe(recipe)}/${getPhotoId(photo)}` : `${PREFIX_PHOTO}/${getPhotoId(photo)}`; - return showRecipe - ? `${path}?${SEARCH_PARAM_SHOW}=${SEARCH_PARAM_SHOW_RECIPE}` - : path; -}; export const pathForTag = (tag: string) => `${PREFIX_TAG}/${tag}`; diff --git a/src/image-response/RecipeImageResponse.tsx b/src/image-response/RecipeImageResponse.tsx new file mode 100644 index 00000000..afd07fc6 --- /dev/null +++ b/src/image-response/RecipeImageResponse.tsx @@ -0,0 +1,51 @@ +import type { Photo } from '../photo'; +import ImageCaption from './components/ImageCaption'; +import ImagePhotoGrid from './components/ImagePhotoGrid'; +import ImageContainer from './components/ImageContainer'; +import type { NextImageSize } from '@/platforms/next-image'; +import { formatTag } from '@/tag'; +import { TbChecklist } from 'react-icons/tb'; + +export default function RecipeImageResponse({ + recipe, + photos, + width, + height, + fontFamily, +}: { + recipe: string, + photos: Photo[] + width: NextImageSize + height: number + fontFamily: string +}) { + return ( + + + , + }}> + {formatTag(recipe).toLocaleUpperCase()} + + + ); +} diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index 2a0ea013..619ffae8 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -11,6 +11,7 @@ import HiddenHeader from '@/tag/HiddenHeader'; import FocalLengthHeader from '@/focal/FocalLengthHeader'; import PhotoHeader from './PhotoHeader'; import { JSX } from 'react'; +import RecipeHeader from '@/recipe/RecipeHeader'; export default function PhotoDetailPage({ photo, @@ -19,6 +20,7 @@ export default function PhotoDetailPage({ tag, camera, simulation, + recipe, focal, indexNumber, count, @@ -72,6 +74,14 @@ export default function PhotoDetailPage({ count={count} dateRange={dateRange} />; + } else if (recipe) { + customHeader = ; } else if (focal) { customHeader = } /> , ]} diff --git a/src/photo/PhotoGridSidebar.tsx b/src/photo/PhotoGridSidebar.tsx index 5af0a37c..b8ca861e 100644 --- a/src/photo/PhotoGridSidebar.tsx +++ b/src/photo/PhotoGridSidebar.tsx @@ -21,8 +21,6 @@ import { safelyParseFormattedHtml, } from '@/utility/html'; import { clsx } from 'clsx/lite'; -import { convertTagToRecipe, isTagRecipe } from '@/recipe'; -import PhotoRecipe from '@/recipe/PhotoRecipe'; export default function PhotoGridSidebar({ tags, @@ -53,47 +51,35 @@ export default function PhotoGridSidebar({ className="text-icon translate-y-[1px]" />} items={tagsIncludingHidden.map(({ tag, count }) => { - if (isTagRecipe(tag)) { - return ; - } else { - switch (tag) { - case TAG_FAVS: - return ; - case TAG_HIDDEN: - return ; - default: - return ; - } + case TAG_HIDDEN: + return ; + default: + return ; } })} /> diff --git a/src/photo/PhotoHeader.tsx b/src/photo/PhotoHeader.tsx index c06ae8a8..36662d10 100644 --- a/src/photo/PhotoHeader.tsx +++ b/src/photo/PhotoHeader.tsx @@ -22,6 +22,7 @@ export default function PhotoHeader({ camera, simulation, focal, + recipe, photos, selectedPhoto, entity, @@ -68,6 +69,7 @@ export default function PhotoHeader({ camera, simulation, focal, + recipe, }} />; const renderDateRange = () => diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index f5450b54..880f3b11 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -30,7 +30,7 @@ import { } from '@/app/config'; import AdminPhotoMenuClient from '@/admin/AdminPhotoMenuClient'; import { RevalidatePhoto } from './InfinitePhotoScroll'; -import { useRef } from 'react'; +import { useMemo, useRef } from 'react'; import useVisible from '@/utility/useVisible'; import PhotoDate from './PhotoDate'; import { useAppState } from '@/state/AppState'; @@ -42,7 +42,8 @@ import { TbChecklist } from 'react-icons/tb'; import { IoCloseSharp } from 'react-icons/io5'; import { AnimatePresence } from 'framer-motion'; import useRecipeState from '../recipe/useRecipeState'; -import PhotoRecipeGrid from '@/recipe/PhotoRecipeGrid'; +import PhotoRecipeOverlay from '@/recipe/PhotoRecipeGrid'; +import PhotoRecipe from '@/recipe/PhotoRecipe'; export default function PhotoLarge({ photo, @@ -56,12 +57,14 @@ export default function PhotoLarge({ showTitleAsH1, showCamera = true, showSimulation = true, + showRecipe = true, showZoomControls: showZoomControlsProp = true, shouldZoomOnFKeydown = true, shouldShare = true, shouldShareTag, shouldShareCamera, shouldShareSimulation, + shouldShareRecipe, shouldShareFocalLength, includeFavoriteInAdminMenu, onVisible, @@ -77,12 +80,14 @@ export default function PhotoLarge({ showTitleAsH1?: boolean showCamera?: boolean showSimulation?: boolean + showRecipe?: boolean showZoomControls?: boolean shouldZoomOnFKeydown?: boolean shouldShare?: boolean shouldShareTag?: boolean shouldShareCamera?: boolean shouldShareSimulation?: boolean + shouldShareRecipe?: boolean shouldShareFocalLength?: boolean includeFavoriteInAdminMenu?: boolean onVisible?: () => void @@ -101,21 +106,26 @@ export default function PhotoLarge({ const showZoomControls = showZoomControlsProp && areZoomControlsShown; const refRecipe = useRef(null); - const refRecipeTrigger = useRef(null); + const refRecipeTitle = useRef(null); + const refRecipeButton = useRef(null); + const refTriggers = useMemo(() => [refRecipeTitle, refRecipeButton], []); const { shouldShowRecipe, toggleRecipe, hideRecipe, } = useRecipeState({ ref: refRecipe, - refTrigger: refRecipeTrigger, + refTriggers, }); const tags = sortTags(photo.tags, primaryTag); const camera = cameraFromPhoto(photo); + + const { recipeTitle: recipe } = photo; const showCameraContent = showCamera && shouldShowCameraDataForPhoto(photo); + const showRecipeContent = showRecipe && recipe; const showTagsContent = tags.length > 0; const showExifContent = shouldShowExifDataForPhoto(photo); @@ -132,6 +142,7 @@ export default function PhotoLarge({ const hasMetaContent = showCameraContent || showTagsContent || + showRecipeContent || showExifContent; const hasNonDateContent = @@ -185,11 +196,11 @@ export default function PhotoLarge({ )}> {(shouldShowRecipe || shouldDebugRecipeOverlays) && - photo.fujifilmRecipe && + photo.recipeData && photo.filmSimulation && - {photo.caption} } - {(showCameraContent || showTagsContent) && + {(showCameraContent || showRecipeContent || showTagsContent) &&
{showCameraContent && } + {showRecipeContent && + } {showTagsContent && {showExifContent && <> @@ -310,7 +330,7 @@ export default function PhotoLarge({ {( (showSimulation && photo.filmSimulation) || - (SHOW_RECIPES && photo.fujifilmRecipe) + (SHOW_RECIPES && showRecipe && photo.recipeData) ) &&
{showSimulation && photo.filmSimulation && @@ -318,9 +338,9 @@ export default function PhotoLarge({ simulation={photo.filmSimulation} prefetch={prefetchRelatedLinks} />} - {SHOW_RECIPES && photo.fujifilmRecipe && + {SHOW_RECIPES && photo.recipeData && }
); diff --git a/src/recipe/PhotoRecipeGrid.tsx b/src/recipe/PhotoRecipeGrid.tsx index 5d4e677b..83a95125 100644 --- a/src/recipe/PhotoRecipeGrid.tsx +++ b/src/recipe/PhotoRecipeGrid.tsx @@ -10,7 +10,7 @@ import { RecipeProps } from '.'; const addSign = (value = 0) => value < 0 ? value : `+${value}`; -export default function PhotoRecipeGrid({ +export default function PhotoRecipeOverlay({ ref, recipe: { dynamicRange, diff --git a/src/recipe/PhotoRecipeOverlay.tsx b/src/recipe/PhotoRecipeOverlay.tsx deleted file mode 100644 index ec02723a..00000000 --- a/src/recipe/PhotoRecipeOverlay.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; -import clsx from 'clsx/lite'; -import ImageLarge from '@/components/image/ImageLarge'; -import PhotoRecipeGrid from './PhotoRecipeGrid'; -import { Photo } from '../photo'; -import { useEffect, useState } from 'react'; -export default function PhotoRecipeOverlay({ - photos, - recipe, - className, -}: { - photos: Photo[] - recipe: FujifilmRecipe - className?: string -}) { - const [photoIndex, setPhotoIndex] = useState(0); - - const photo = photos[photoIndex]; - - useEffect(() => { - const interval = setInterval(() => { - setPhotoIndex((photoIndex + 1) % photos.length); - }, 500); - return () => clearInterval(interval); - }, [photoIndex, photos]); - - return ( -
- -
- -
-
- ); -} diff --git a/src/recipe/RecipeHeader.tsx b/src/recipe/RecipeHeader.tsx index 0d4dd68b..a90356a8 100644 --- a/src/recipe/RecipeHeader.tsx +++ b/src/recipe/RecipeHeader.tsx @@ -1,10 +1,10 @@ 'use client'; import { Photo, PhotoDateRange } from '@/photo'; -import { descriptionForTaggedPhotos } from '../tag'; import PhotoHeader from '@/photo/PhotoHeader'; import PhotoRecipe from './PhotoRecipe'; import { useAppState } from '@/state/AppState'; +import { descriptionForRecipePhotos } from '.'; export default function RecipeHeader({ recipe, photos, @@ -22,27 +22,27 @@ export default function RecipeHeader({ }) { const { setRecipeModalProps } = useAppState(); - const photo = photos.find(({ filmSimulation, fujifilmRecipe }) => - fujifilmRecipe && filmSimulation); + const photo = photos.find(({ filmSimulation, recipeData }) => + recipeData && filmSimulation); return ( ( - photo?.fujifilmRecipe && + photo?.recipeData && photo?.filmSimulation ) ? setRecipeModalProps?.({ simulation: photo.filmSimulation, - recipe: photo.fujifilmRecipe, + recipe: photo.recipeData, iso: photo.isoFormatted, exposure: photo.exposureTimeFormatted, }) : undefined} />} - entityDescription={descriptionForTaggedPhotos(photos, undefined, count)} + entityDescription={descriptionForRecipePhotos(photos, undefined, count)} photos={photos} selectedPhoto={selectedPhoto} indexNumber={indexNumber} diff --git a/src/recipe/RecipeModal.tsx b/src/recipe/RecipeModal.tsx index 5c6576a3..5883b66e 100644 --- a/src/recipe/RecipeModal.tsx +++ b/src/recipe/RecipeModal.tsx @@ -2,7 +2,7 @@ import Modal from '@/components/Modal'; import { useAppState } from '@/state/AppState'; -import PhotoRecipeGrid from './PhotoRecipeGrid'; +import PhotoRecipeOverlay from './PhotoRecipeGrid'; export default function ShareModals() { const { @@ -12,10 +12,11 @@ export default function ShareModals() { if (recipeModalProps) { return setRecipeModalProps?.(undefined)} container={false} > - setRecipeModalProps?.(undefined), }}/> diff --git a/src/recipe/data.ts b/src/recipe/data.ts new file mode 100644 index 00000000..4ffc302a --- /dev/null +++ b/src/recipe/data.ts @@ -0,0 +1,16 @@ +import { + getPhotosCached, + getPhotosMetaCached, +} from '@/photo/cache'; + +export const getPhotosRecipeDataCached = ({ + recipe, + limit, +}: { + recipe: string, + limit?: number, +}) => + Promise.all([ + getPhotosCached({ recipe, limit }), + getPhotosMetaCached({ recipe }), + ]); diff --git a/src/recipe/index.ts b/src/recipe/index.ts index 9499a3b5..19ce2ca4 100644 --- a/src/recipe/index.ts +++ b/src/recipe/index.ts @@ -1,13 +1,16 @@ import { absolutePathForRecipe, absolutePathForRecipeImage } from '@/app/paths'; import { descriptionForPhotoSet, Photo, photoQuantityText } from '@/photo'; import { PhotoDateRange } from '@/photo'; -import { Tags } from '../tag'; -import { parameterize } from '@/utility/string'; import { capitalizeWords } from '@/utility/string'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FilmSimulation } from '@/simulation'; -const KEY_RECIPE = 'recipe'; +export type RecipeWithCount = { + recipe: string + count: number +} + +export type Recipes = RecipeWithCount[] export interface RecipeProps { recipe: FujifilmRecipe @@ -16,19 +19,6 @@ export interface RecipeProps { exposure?: string } -export const isTagRecipe = (tag: string) => - (new RegExp(`^${KEY_RECIPE}-?`).test(tag)); - -export const convertTagsToRecipes = (tags: Tags) => - tags.filter(({ tag }) => isTagRecipe(tag)) - .map(({ tag }) => convertTagToRecipe(tag)); - -export const convertRecipeToTag = (recipe: string) => - `${KEY_RECIPE}-${parameterize(recipe)}`; - -export const convertTagToRecipe = (tag: string) => - tag.replace(new RegExp(`^${KEY_RECIPE}-?`), ''); - export const formatRecipe = (recipe?: string) => capitalizeWords(recipe?.replaceAll('-', ' ')); diff --git a/src/recipe/useRecipeState.ts b/src/recipe/useRecipeState.ts index 2420a9de..0ac10b25 100644 --- a/src/recipe/useRecipeState.ts +++ b/src/recipe/useRecipeState.ts @@ -1,20 +1,18 @@ import { getPathComponents, pathForPhoto, - SEARCH_PARAM_SHOW_RECIPE, } from '@/app/paths'; import { usePathname } from 'next/navigation'; -import { SEARCH_PARAM_SHOW } from '@/app/paths'; import { RefObject, useCallback, useEffect, useState } from 'react'; import { isElementEntirelyInViewport } from '@/utility/dom'; import useClickInsideOutside from '@/utility/useClickInsideOutside'; export default function useRecipeState({ ref, - refTrigger, + refTriggers = [], }: { ref?: RefObject, - refTrigger?: RefObject, + refTriggers?: RefObject[], }) { const pathname = usePathname(); @@ -23,13 +21,7 @@ export default function useRecipeState({ ...pathComponents } = getPathComponents(pathname); - const searchParamShow = typeof document !== 'undefined' - ? (new URLSearchParams(document.location.search)).get(SEARCH_PARAM_SHOW) - : undefined; - - const showRecipeInitially = searchParamShow === SEARCH_PARAM_SHOW_RECIPE; - - const [shouldShowRecipe, setShouldShowRecipe] = useState(showRecipeInitially); + const [shouldShowRecipe, setShouldShowRecipe] = useState(false); const setVisibility = useCallback((shouldShow: boolean) => { if (shouldShow) { @@ -69,7 +61,7 @@ export default function useRecipeState({ [setVisibility, shouldShowRecipe]); useClickInsideOutside({ - htmlElements: [ref, refTrigger], + htmlElements: [ref, ...refTriggers], onClickOutside: hideRecipe, }); diff --git a/src/tag/PhotoTags.tsx b/src/tag/PhotoTags.tsx index 9b0a3217..802f76ee 100644 --- a/src/tag/PhotoTags.tsx +++ b/src/tag/PhotoTags.tsx @@ -3,8 +3,6 @@ import { isTagFavs } from '.'; import FavsTag from './FavsTag'; import { EntityLinkExternalProps } from '@/components/primitives/EntityLink'; import { Fragment } from 'react'; -import { convertTagToRecipe, isTagRecipe } from '@/recipe'; -import PhotoRecipe from '@/recipe/PhotoRecipe'; export default function PhotoTags({ tags, @@ -17,14 +15,9 @@ export default function PhotoTags({
{tags.map(tag => - {isTagRecipe(tag) - ? console.log('clicked'), - }} /> - : isTagFavs(tag) - ? - : } + {isTagFavs(tag) + ? + : } )}
);