Extract photo query logic to hook
This commit is contained in:
parent
0465b51427
commit
7baedd0700
@ -2,7 +2,7 @@ import AdminComponentPageClient from '@/admin/AdminComponentPageClient';
|
||||
import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache';
|
||||
|
||||
export default async function ComponentsPage() {
|
||||
const photos = await getPhotosCached();
|
||||
const photos = await getPhotosCached({ limit: 50 });
|
||||
const photosCount = await getPhotosMetaCached()
|
||||
.then(({ count }) => count);
|
||||
|
||||
|
||||
@ -37,14 +37,12 @@ import {
|
||||
} from '../app/path';
|
||||
import Modal from '../components/Modal';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import Spinner from '../components/Spinner';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { BiDesktop, BiLockAlt, BiMoon, BiSun } from 'react-icons/bi';
|
||||
import { IoClose, IoInvertModeSharp } from 'react-icons/io5';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { searchPhotosAction } from '@/photo/actions';
|
||||
import { RiToolsFill } from 'react-icons/ri';
|
||||
import { signOutAction } from '@/auth/actions';
|
||||
import { getKeywordsForPhoto, titleForPhoto } from '@/photo';
|
||||
@ -98,12 +96,12 @@ import { getSortStateFromPath } from '@/photo/sort/path';
|
||||
import IconSort from '@/components/icons/IconSort';
|
||||
import { useSelectPhotosState } from '@/admin/select/SelectPhotosState';
|
||||
import IconAlbum from '@/components/icons/IconAlbum';
|
||||
import usePhotoQuery from '@/photo/usePhotoQuery';
|
||||
|
||||
const DIALOG_TITLE = 'Global Command-K Menu';
|
||||
const DIALOG_DESCRIPTION = 'For searching photos, views, and settings';
|
||||
|
||||
const LISTENER_KEYDOWN = 'keydown';
|
||||
const MINIMUM_QUERY_LENGTH = 2;
|
||||
|
||||
const MAX_HEIGHT = '20rem';
|
||||
|
||||
@ -246,19 +244,13 @@ export default function CommandKClient({
|
||||
}
|
||||
}, [isWaiting, setIsOpen]);
|
||||
|
||||
// Raw query values
|
||||
const [queryLiveRaw, setQueryLiveRaw] = useState('');
|
||||
const [queryDebouncedRaw] =
|
||||
useDebounce(queryLiveRaw, 500, { trailing: true });
|
||||
|
||||
// Parameterized query values
|
||||
const queryLive = useMemo(() =>
|
||||
queryLiveRaw.trim().toLocaleLowerCase(), [queryLiveRaw]);
|
||||
const queryDebounced = useMemo(() =>
|
||||
queryDebouncedRaw.trim().toLocaleLowerCase(), [queryDebouncedRaw]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [queriedSections, setQueriedSections] = useState<CommandKSection[]>([]);
|
||||
const [query, setQuery] = useState('');
|
||||
const {
|
||||
queryFormatted,
|
||||
photos,
|
||||
isLoading,
|
||||
reset,
|
||||
} = usePhotoQuery(query, !isPending);
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
@ -283,14 +275,9 @@ export default function CommandKClient({
|
||||
return () => document.removeEventListener(LISTENER_KEYDOWN, down);
|
||||
}, [setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryDebounced.length >= MINIMUM_QUERY_LENGTH && !isPending) {
|
||||
setIsLoading(true);
|
||||
searchPhotosAction(queryDebounced)
|
||||
.then(photos => {
|
||||
if (isOpenRef.current) {
|
||||
setQueriedSections(photos.length > 0
|
||||
? [{
|
||||
const queriedSections = useMemo<CommandKSection[]>(() => {
|
||||
if (isOpenRef.current && photos.length > 0) {
|
||||
return [{
|
||||
heading: 'Photos',
|
||||
accessory: <IconPhoto size={14} />,
|
||||
items: photos.map(photo => ({
|
||||
@ -300,38 +287,20 @@ export default function CommandKClient({
|
||||
accessory: <PhotoSmall photo={photo} />,
|
||||
path: pathForPhoto({ photo }),
|
||||
})),
|
||||
}]
|
||||
: []);
|
||||
}];
|
||||
} else {
|
||||
// Ignore stale requests that come in after dialog is closed
|
||||
setQueriedSections([]);
|
||||
return [];
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
setQueriedSections([]);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [queryDebounced, isPending, appText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryLive === '') {
|
||||
setQueriedSections([]);
|
||||
setIsLoading(false);
|
||||
} else if (queryLive.length >= MINIMUM_QUERY_LENGTH) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
}, [queryLive]);
|
||||
},
|
||||
[photos],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setQueryLiveRaw('');
|
||||
setQueriedSections([]);
|
||||
setIsLoading(false);
|
||||
setQuery('');
|
||||
reset();
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isOpen, reset]);
|
||||
|
||||
const recent = recents[0];
|
||||
const recentsStatus = useMemo(() => {
|
||||
@ -345,17 +314,17 @@ export default function CommandKClient({
|
||||
|
||||
// Years only accessible by search
|
||||
const years = useMemo(() =>
|
||||
_years.filter(({ year }) => queryLive && year.includes(queryLive))
|
||||
, [_years, queryLive]);
|
||||
_years.filter(({ year }) => queryFormatted && year.includes(queryFormatted))
|
||||
, [_years, queryFormatted]);
|
||||
|
||||
const tags = useMemo(() => {
|
||||
const tagsIncludingPrivate = photosCountHidden > 0
|
||||
? addPrivateToTags(_tags, photosCountHidden)
|
||||
: _tags;
|
||||
return HIDE_TAGS_WITH_ONE_PHOTO
|
||||
? limitTagsByCount(tagsIncludingPrivate, 2, queryLive)
|
||||
? limitTagsByCount(tagsIncludingPrivate, 2, queryFormatted)
|
||||
: tagsIncludingPrivate;
|
||||
}, [_tags, photosCountHidden, queryLive]);
|
||||
}, [_tags, photosCountHidden, queryFormatted]);
|
||||
|
||||
const categorySections: CommandKSection[] = useMemo(() =>
|
||||
CATEGORY_VISIBILITY
|
||||
@ -751,9 +720,9 @@ export default function CommandKClient({
|
||||
)}>
|
||||
<Command.Input
|
||||
ref={refInput}
|
||||
value={queryLiveRaw}
|
||||
value={query}
|
||||
onValueChange={value => {
|
||||
setQueryLiveRaw(value);
|
||||
setQuery(value);
|
||||
updateMask();
|
||||
}}
|
||||
className={clsx(
|
||||
@ -782,15 +751,15 @@ export default function CommandKClient({
|
||||
'text-gray-400/90 dark:text-gray-700',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (queryLiveRaw) {
|
||||
setQueryLiveRaw('');
|
||||
if (query) {
|
||||
setQuery('');
|
||||
updateMask();
|
||||
} else {
|
||||
setIsOpen?.(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{queryLiveRaw
|
||||
{query
|
||||
? <IoClose size={17} className="text-dim" />
|
||||
: <>
|
||||
<span className="sm:hidden">
|
||||
@ -889,7 +858,7 @@ export default function CommandKClient({
|
||||
/>;
|
||||
})}
|
||||
</Command.Group>)}
|
||||
{footer && !queryLive &&
|
||||
{footer && !queryFormatted &&
|
||||
<div className={clsx(
|
||||
'text-center text-base text-dim pt-1',
|
||||
'pb-2',
|
||||
|
||||
58
src/photo/usePhotoQuery.ts
Normal file
58
src/photo/usePhotoQuery.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/* eslint-disable react-hooks/set-state-in-effect */
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Photo } from '.';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { searchPhotosAction } from './actions';
|
||||
|
||||
const formatQuery = (query: string) =>
|
||||
query.trim().toLocaleLowerCase();
|
||||
|
||||
export default function usePhotoQuery(
|
||||
query: string,
|
||||
isEnabled = true,
|
||||
minimumQueryLength = 2,
|
||||
) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const queryFormatted = useMemo(() =>
|
||||
formatQuery(query), [query]);
|
||||
const [_queryDebounced] = useDebounce(query, 500, { leading: true });
|
||||
const queryDebounced = useMemo(() =>
|
||||
formatQuery(_queryDebounced), [_queryDebounced]);
|
||||
|
||||
const [photos, setPhotos] = useState<Photo[]>([]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setPhotos([]);
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryDebounced.length >= minimumQueryLength && isEnabled) {
|
||||
setIsLoading(true);
|
||||
searchPhotosAction(queryDebounced)
|
||||
.then(setPhotos)
|
||||
.finally(() => setIsLoading(false));
|
||||
}
|
||||
}, [
|
||||
queryDebounced,
|
||||
minimumQueryLength,
|
||||
isEnabled,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryFormatted.length >= minimumQueryLength) {
|
||||
setIsLoading(true);
|
||||
} else {
|
||||
setPhotos([]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [minimumQueryLength, queryFormatted]);
|
||||
|
||||
return {
|
||||
queryFormatted,
|
||||
photos,
|
||||
isLoading,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user