import { useSignal } from '@preact/signals'; import { useEffect } from 'preact/hooks'; import { Directory, DirectoryFile } from '/lib/types.ts'; import { RequestBody, ResponseBody } from '/routes/api/files/search.tsx'; interface SearchFilesProps {} export default function SearchFiles({}: SearchFilesProps) { const isSearching = useSignal(false); const areResultsVisible = useSignal(false); const matchingDirectories = useSignal([]); const matchingFiles = useSignal([]); const searchTimeout = useSignal>(0); const closeTimeout = useSignal>(0); const dateFormatOptions: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: '2-digit', }; const dateFormat = new Intl.DateTimeFormat('en-GB', dateFormatOptions); function searchFiles(searchTerm: string) { if (searchTimeout.value) { clearTimeout(searchTimeout.value); } if (searchTerm.trim().length < 2) { return; } areResultsVisible.value = false; searchTimeout.value = setTimeout(async () => { isSearching.value = true; try { const requestBody: RequestBody = { searchTerm }; const response = await fetch(`/api/files/search`, { method: 'POST', body: JSON.stringify(requestBody), }); if (!response.ok) { throw new Error(`Failed to search files. ${response.statusText} ${await response.text()}`); } const result = await response.json() as ResponseBody; if (!result.success) { throw new Error('Failed to search files!'); } matchingDirectories.value = [...result.directories]; matchingFiles.value = [...result.files]; if (matchingDirectories.value.length > 0 || matchingFiles.value.length > 0) { areResultsVisible.value = true; } } catch (error) { console.error(error); } isSearching.value = false; }, 500); } function onFocus() { if (matchingDirectories.value.length > 0 || matchingFiles.value.length > 0) { areResultsVisible.value = true; } } function onBlur() { if (closeTimeout.value) { clearTimeout(closeTimeout.value); } closeTimeout.value = setTimeout(() => { areResultsVisible.value = false; }, 300); } useEffect(() => { return () => { if (searchTimeout.value) { clearTimeout(searchTimeout.value); } if (closeTimeout.value) { clearTimeout(closeTimeout.value); } }; }, []); return ( <> searchFiles(event.currentTarget.value)} onFocus={() => onFocus()} onBlur={() => onBlur()} /> {isSearching.value ? : null} {areResultsVisible.value ? (
) : null} ); }