diff --git a/src/app/admin/photos/page.tsx b/src/app/admin/photos/page.tsx index 1efe0170..b59a38f8 100644 --- a/src/app/admin/photos/page.tsx +++ b/src/app/admin/photos/page.tsx @@ -72,13 +72,7 @@ export default async function AdminPhotosPage({ {photos.map(photo => - +
void }[] } export default function CommandKClient({ - isLoading, onQueryChange, sections = [], }: { - isLoading?: boolean - onQueryChange?: (query: string) => void + onQueryChange?: (query: string) => Promise sections?: CommandKSection[] }) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(''); const [queryDebounced] = useDebounce(query, 1000); + const [isLoading, setIsLoading] = useState(false); + const [queriedSections, setQueriedSections] = useState([]); + const { setTheme } = useTheme(); const router = useRouter(); @@ -50,10 +53,20 @@ export default function CommandKClient({ useEffect(() => { if (queryDebounced) { - onQueryChange?.(queryDebounced); + setIsLoading(true); + onQueryChange?.(queryDebounced).then(querySections => { + setQueriedSections(querySections); + setIsLoading(false); + }); } }, [queryDebounced, onQueryChange]); + useEffect(() => { + if (query === '') { + setQueriedSections([]); + } + }, [query]); + const sectionTheme: CommandKSection = { heading: 'Theme', items: [{ @@ -73,19 +86,23 @@ export default function CommandKClient({ open={open} onOpenChange={setOpen} label="Global Command Menu" + loop > setOpen(false)} fast > -
+
setQuery(e.currentTarget.value)} className={clsx( 'w-full', - 'placeholder:text-gray-400', + 'focus:ring-0', + '!border-gray-200 dark:!border-gray-800', + 'focus:border-gray-200 focus:dark:border-gray-800', + 'placeholder:text-gray-400/80', 'placeholder:dark:text-gray-700', )} style={{ paddingRight: '2rem' }} @@ -97,8 +114,14 @@ export default function CommandKClient({ }
- No results found. - {sections + + {queriedSections + .concat(sections) .concat(sectionTheme) .filter(({ items }) => items.length > 0) .map(({ heading, items }) => @@ -108,21 +131,23 @@ export default function CommandKClient({ className={clsx( 'uppercase', 'select-none', - '[&>*:first-child]:py-1.5', + '[&>*:first-child]:py-1', '[&>*:first-child]:font-medium', '[&>*:first-child]:text-dim', '[&>*:first-child]:text-xs', '[&>*:first-child]:tracking-wider', )} > - {items.map(({ label, path, action }) => + {items.map(({ accessory, label, path, action }) => { action?.(); @@ -132,7 +157,10 @@ export default function CommandKClient({ } }} > - {label} +
+ {accessory} + {label} +
)} )}
diff --git a/src/photo/PhotoTiny.tsx b/src/photo/PhotoTiny.tsx index 8a7177f9..20e0a9f1 100644 --- a/src/photo/PhotoTiny.tsx +++ b/src/photo/PhotoTiny.tsx @@ -23,6 +23,8 @@ export default function PhotoTiny({ 'active:brightness-75', selected && 'brightness-50', 'min-w-[50px]', + 'rounded-sm overflow-hidden', + 'border border-gray-200 dark:border-gray-800', )} > { sortBy = PRIORITY_ORDER_ENABLED ? 'priority' : 'takenAt', limit = PHOTO_DEFAULT_LIMIT, offset = 0, + title, tag, camera, simulation, @@ -334,6 +336,10 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { wheres.push(`taken_at <= $${valueIndex++}`); values.push(takenAfterInclusive.toISOString()); } + if (title) { + wheres.push(`LOWER(title) LIKE $${valueIndex++}`); + values.push(`%${title.toLowerCase()}%`); + } if (tag) { wheres.push(`$${valueIndex++}=ANY(tags)`); values.push(tag); diff --git a/src/site/CommandK.tsx b/src/site/CommandK.tsx index 8ada4952..83307b1f 100644 --- a/src/site/CommandK.tsx +++ b/src/site/CommandK.tsx @@ -11,10 +11,14 @@ import { PATH_ADMIN_UPLOADS, pathForCamera, pathForFilmSimulation, + pathForPhoto, pathForTag, } from './paths'; import { formatCameraText } from '@/camera'; import { authCached } from '@/auth/cache'; +import { getPhotos } from '@/services/vercel-postgres'; +import { titleForPhoto } from '@/photo'; +import PhotoTiny from '@/photo/PhotoTiny'; export default async function CommandK() { const [ @@ -85,5 +89,20 @@ export default async function CommandK() { SECTION_FILM, SECTION_PAGES, ]} + onQueryChange={async (query) => { + 'use server'; + const photos = (await getPhotos({ title: query })) + .filter(({ title }) => Boolean(title)); + return photos.length > 0 + ? [{ + heading: 'Photos', + items: photos.map(photo => ({ + accessory: , + label: titleForPhoto(photo), + path: pathForPhoto(photo), + })), + }] + : []; + }} />; }