From e584a0364b9102c4d7cb2b6dc5d14c72579c3baa Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sat, 7 Sep 2024 11:45:24 -0500 Subject: [PATCH] Make photo querying more resilient --- src/components/cmdk/CommandKClient.tsx | 48 +++++++++++++++----------- src/photo/PhotoHeader.tsx | 7 ++-- src/photo/actions.ts | 11 +++--- src/photo/index.ts | 16 +++++++-- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/components/cmdk/CommandKClient.tsx b/src/components/cmdk/CommandKClient.tsx index 4e1e21c2..4f409c1f 100644 --- a/src/components/cmdk/CommandKClient.tsx +++ b/src/components/cmdk/CommandKClient.tsx @@ -31,7 +31,7 @@ import { useTheme } from 'next-themes'; import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi'; import { IoInvertModeSharp } from 'react-icons/io5'; import { useAppState } from '@/state/AppState'; -import { queryPhotosByTitleAction } from '@/photo/actions'; +import { searchPhotosAction } from '@/photo/actions'; import { RiToolsFill } from 'react-icons/ri'; import { BiLockAlt, BiSolidUser } from 'react-icons/bi'; import { HiDocumentText } from 'react-icons/hi'; @@ -151,27 +151,33 @@ export default function CommandKClient({ useEffect(() => { if (queryDebounced.length >= MINIMUM_QUERY_LENGTH && !isPending) { setIsLoading(true); - queryPhotosByTitleAction(queryDebounced).then(photos => { - if (isOpenRef.current) { - setQueriedSections(photos.length > 0 - ? [{ - heading: 'Photos', - accessory: , - items: photos.map(photo => ({ - label: titleForPhoto(photo), - keywords: getKeywordsForPhoto(photo), - annotation: , - accessory: , - path: pathForPhoto({ photo }), - })), - }] - : []); - } else { - // Ignore stale requests that come in after dialog is closed + searchPhotosAction(queryDebounced) + .then(photos => { + if (isOpenRef.current) { + setQueriedSections(photos.length > 0 + ? [{ + heading: 'Photos', + accessory: , + items: photos.map(photo => ({ + label: titleForPhoto(photo), + keywords: getKeywordsForPhoto(photo), + annotation: , + accessory: , + path: pathForPhoto({ photo }), + })), + }] + : []); + } else { + // Ignore stale requests that come in after dialog is closed + setQueriedSections([]); + } + setIsLoading(false); + }) + .catch(e => { + console.error(e); setQueriedSections([]); - } - setIsLoading(false); - }); + setIsLoading(false); + }); } }, [queryDebounced, isPending]); diff --git a/src/photo/PhotoHeader.tsx b/src/photo/PhotoHeader.tsx index d798eedf..f66508df 100644 --- a/src/photo/PhotoHeader.tsx +++ b/src/photo/PhotoHeader.tsx @@ -6,6 +6,7 @@ import { PhotoDateRange, PhotoSetAttributes, dateRangeForPhotos, + titleForPhoto, } from '.'; import ShareButton from '@/components/ShareButton'; import AnimateItems from '@/components/AnimateItems'; @@ -13,7 +14,6 @@ import { ReactNode } from 'react'; import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; import PhotoPrevNext from './PhotoPrevNext'; import PhotoLink from './PhotoLink'; -import { formatDate } from '@/utility/date'; import ResponsiveText from '@/components/primitives/ResponsiveText'; import { useAppState } from '@/state/AppState'; @@ -83,10 +83,7 @@ export default function PhotoHeader({ photo={selectedPhoto} className="uppercase font-bold text-ellipsis truncate" > - { - selectedPhoto.title || - formatDate(selectedPhoto.takenAt, 'tiny') - } + {titleForPhoto(selectedPhoto, true)} ); return ( diff --git a/src/photo/actions.ts b/src/photo/actions.ts index 734208c0..3d3244a6 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -35,7 +35,7 @@ import { } from '@/site/paths'; import { blurImageFromUrl, extractImageDataFromBlobPath } from './server'; import { TAG_FAVS, isTagFavs } from '@/tag'; -import { convertPhotoToPhotoDbInsert } from '.'; +import { convertPhotoToPhotoDbInsert, Photo } from '.'; import { runAuthenticatedAdminServerAction } from '@/auth'; import { AI_IMAGE_QUERIES, AiImageQuery } from './ai'; import { streamOpenAiImageQuery } from '@/services/openai'; @@ -412,6 +412,9 @@ export const getPhotosCachedAction = async (options: GetPhotosOptions) => // Public actions -export const queryPhotosByTitleAction = async (query: string) => - (await getPhotos({ query, limit: 10 })) - .filter(({ title }) => Boolean(title)); +export const searchPhotosAction = async (query: string) => + getPhotos({ query, limit: 10 }) + .catch(e => { + console.error('Could not query photos', e); + return [] as Photo[]; + }); diff --git a/src/photo/index.ts b/src/photo/index.ts index bf3feab3..e3890a20 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -5,7 +5,7 @@ import { getNextImageUrlForRequest } from '@/services/next-image'; import { FilmSimulation } from '@/simulation'; import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config'; import { ABSOLUTE_PATH_FOR_HOME_IMAGE } from '@/site/paths'; -import { formatDateFromPostgresString } from '@/utility/date'; +import { formatDate, formatDateFromPostgresString } from '@/utility/date'; import { formatAperture, formatIso, @@ -198,8 +198,18 @@ const PHOTO_ID_FORWARDING_TABLE: Record = JSON.parse( export const translatePhotoId = (id: string) => PHOTO_ID_FORWARDING_TABLE[id] || id; -export const titleForPhoto = (photo: Photo) => - photo.title || 'Untitled'; +export const titleForPhoto = ( + photo: Photo, + preferDateOverUntitled?: boolean, +) => { + if (photo.title) { + return photo.title; + } else if (preferDateOverUntitled && (photo.takenAt || photo.createdAt)) { + return formatDate(photo.takenAt || photo.createdAt, 'tiny'); + } else { + return 'Untitled'; + } +}; export const altTextForPhoto = (photo: Photo) => photo.semanticDescription || titleForPhoto(photo);