import { useEffect, useRef, useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import { ExploreTagEntry, parseSearchValue, SearchCommand, useGalleryStore, } from "../../store"; import { useDismissable } from "../menu"; import { commandPrefix, composeSearchValue } from "./searchValue"; export function useToolbarSearch() { const search = useGalleryStore((state) => state.search); const setSearch = useGalleryStore((state) => state.setSearch); const clearSearch = useGalleryStore((state) => state.clearSearch); const selectedFolderId = useGalleryStore((state) => state.selectedFolderId); const [searchCommand, setSearchCommand] = useState(null); const [searchQuery, setSearchQuery] = useState(search); const [searchPanelOpen, setSearchPanelOpen] = useState(false); const [tagSuggestions, setTagSuggestions] = useState([]); const debounceRef = useRef | null>(null); const suggestDebounceRef = useRef | null>(null); const searchInputRef = useRef(null); const searchShellRef = useRef(null); const userHasTyped = useRef(false); const hasActiveSearch = search.trim().length > 0; const parsedSearch = parseSearchValue(composeSearchValue(searchCommand, searchQuery)); const showTagSuggestions = searchCommand === "tag" && searchPanelOpen; const showCommandHints = !searchCommand && searchPanelOpen; useEffect(() => { if (!userHasTyped.current) return; if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { setSearch(composeSearchValue(searchCommand, searchQuery)); }, 200); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [searchCommand, searchQuery, setSearch]); useEffect(() => { const parsed = parseSearchValue(search); setSearchCommand(parsed.prefix && parsed.mode !== "filename" ? parsed.mode : null); setSearchQuery(parsed.prefix ? parsed.query : search); }, [search]); useEffect(() => { if (searchCommand !== "tag") { setTagSuggestions([]); return; } if (suggestDebounceRef.current) clearTimeout(suggestDebounceRef.current); suggestDebounceRef.current = setTimeout(async () => { try { const results = await invoke("search_tags_autocomplete", { params: { query: searchQuery.trim(), folder_id: selectedFolderId ?? null, limit: 10 }, }); setTagSuggestions(Array.isArray(results) ? results : []); } catch { setTagSuggestions([]); } }, 120); return () => { if (suggestDebounceRef.current) clearTimeout(suggestDebounceRef.current); }; }, [searchCommand, searchQuery, selectedFolderId]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { const activeElement = document.activeElement; const searchFocused = activeElement === searchInputRef.current; if (event.key === "Escape" && (searchFocused || hasActiveSearch)) { event.preventDefault(); setSearchCommand(null); setSearchQuery(""); clearSearch(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [clearSearch, hasActiveSearch]); useDismissable(searchShellRef, () => setSearchPanelOpen(false)); const handleSearchChange = (nextValue: string) => { userHasTyped.current = true; if (!searchCommand) { const parsed = parseSearchValue(nextValue); if (parsed.prefix) { setSearchCommand(parsed.mode === "filename" ? null : parsed.mode); setSearchQuery(parsed.query); return; } } setSearchQuery(nextValue); }; const handleSearchKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Backspace" && searchQuery.length === 0 && searchCommand !== null) { event.preventDefault(); setSearchCommand(null); } }; const clearSearchInput = () => { setSearchCommand(null); setSearchQuery(""); setTagSuggestions([]); clearSearch(); }; const removeSearchCommand = () => { setSearchCommand(null); setTagSuggestions([]); }; const chooseTagSuggestion = (tag: string) => { userHasTyped.current = true; setSearchQuery(tag); setSearch(`/t ${tag}`); setSearchPanelOpen(false); searchInputRef.current?.blur(); }; const chooseCommandHint = (command: SearchCommand) => { userHasTyped.current = true; setSearchCommand(command); searchInputRef.current?.focus(); }; return { chooseCommandHint, chooseTagSuggestion, clearSearchInput, commandPrefix, handleSearchChange, handleSearchKeyDown, hasActiveSearch, parsedSearch, removeSearchCommand, searchCommand, searchInputRef, searchPanelOpen, searchQuery, searchShellRef, setSearchPanelOpen, showCommandHints, showTagSuggestions, tagSuggestions, }; }