From 8d4687428fac1f078a1c2acab1abe4c0b13054a5 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 12 Nov 2023 19:04:40 +0100 Subject: [PATCH] Fix/app search (#456) * Auto open search correctly in global search * Update search input when navigating to previous/next location * Revert: Navigate to previous page when canceling search Has to many edge cases and fixing them would require a global history stack which gets saved in the location state. This then would lead to the apps internal back navigation to be the same as the browsers which is not wanted, since you should not get moved deeper into the apps navigation when clicking the back button * Correctly autofocus search input Sometimes the autofocus did not work, since the input did not exist yet by the time the timeout was triggered * Restore search input open state when navigation to previous/next location * Restore search input focus when navigating to previous/next location --- src/components/util/AppbarSearch.tsx | 75 +++++++++++++++++++--------- src/screens/SearchAll.tsx | 2 +- src/screens/SourceMangas.tsx | 14 +++--- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/components/util/AppbarSearch.tsx b/src/components/util/AppbarSearch.tsx index 7108f42f..9a6e546d 100644 --- a/src/components/util/AppbarSearch.tsx +++ b/src/components/util/AppbarSearch.tsx @@ -12,7 +12,7 @@ import { IconButton, Input, Tooltip } from '@mui/material'; import CancelIcon from '@mui/icons-material/Cancel'; import { useQueryParam, StringParam } from 'use-query-params'; import { useTranslation } from 'react-i18next'; -import { useBackButton } from '@/util/useBackButton.ts'; +import { useLocation, useNavigate } from 'react-router-dom'; interface IProps { autoOpen?: boolean; @@ -23,19 +23,33 @@ const defaultProps = { }; export const AppbarSearch: React.FunctionComponent = (props) => { - const { t } = useTranslation(); - const handleBack = useBackButton(); - const { autoOpen } = props; + + const { t } = useTranslation(); + + const { pathname, search: locationSearch, state: fullLocationState } = useLocation<{ wasSearchOpen?: boolean }>(); + const { wasSearchOpen, ...locationState } = fullLocationState ?? {}; + + const navigate = useNavigate(); + const [query, setQuery] = useQueryParam('query', StringParam); const [searchOpen, setSearchOpen] = useState(!!query); const inputRef = React.useRef(); const [searchString, setSearchString] = useState(query ?? ''); + const updateSearchOpenState = (open: boolean) => { + setSearchOpen(open); + + // try to focus input component since in case of navigating to the previous/next page in the browser history + // the "openSearch" state might not change and thus, won't trigger a focus + if (open) { + inputRef.current?.focus(); + } + }; + function handleChange(newQuery: string) { if (newQuery === '') { - handleBack(); return; } @@ -44,25 +58,17 @@ export const AppbarSearch: React.FunctionComponent = (props) => { const cancelSearch = () => { setSearchString(''); - setSearchOpen(false); - - handleBack(); + setQuery(undefined); + updateSearchOpenState(false); }; const handleBlur = () => { - if (!searchString) setSearchOpen(false); - }; - const openSearch = () => { - setSearchOpen(true); - // Put Focus Action at the end of the Callstack so Input actually exists on the dom - setTimeout(() => { - if (inputRef && inputRef.current) inputRef.current.focus(); - }); + if (!searchString) updateSearchOpenState(false); }; const handleKeyboardEvent = (e: KeyboardEvent) => { if (e.code === 'F3' || (e.ctrlKey && e.code === 'KeyF')) { e.preventDefault(); - openSearch(); + updateSearchOpenState(true); return; } @@ -73,21 +79,42 @@ export const AppbarSearch: React.FunctionComponent = (props) => { }; useEffect(() => { - if (autoOpen) { - openSearch(); + if ((autoOpen && wasSearchOpen === undefined) || (wasSearchOpen && query)) { + updateSearchOpenState(true); + return; } - }, []); + + updateSearchOpenState(false); + }, [autoOpen, pathname]); + + useEffect(() => { + if (!searchOpen || !inputRef.current) { + return; + } + + inputRef.current.focus(); + }, [searchOpen, inputRef.current]); + + useEffect(() => { + if (wasSearchOpen === searchOpen) { + return; + } + + navigate( + { pathname, search: locationSearch }, + { replace: true, state: { ...locationState, wasSearchOpen: searchOpen } }, + ); + }, [searchOpen]); useEffect(() => { if (query === undefined && searchString !== undefined) { setSearchString(''); - setSearchOpen(false); return; } - if (searchString === '' && !!query) { + if (query && searchString !== query) { setSearchString(query); - setSearchOpen(true); + updateSearchOpenState(true); } }, [query]); @@ -117,7 +144,7 @@ export const AppbarSearch: React.FunctionComponent = (props) => { return ( - + updateSearchOpenState(true)}> diff --git a/src/screens/SearchAll.tsx b/src/screens/SearchAll.tsx index 365f0e7f..cc1f0e3c 100644 --- a/src/screens/SearchAll.tsx +++ b/src/screens/SearchAll.tsx @@ -161,7 +161,7 @@ export const SearchAll: React.FC = () => { const { setTitle, setAction } = useContext(NavBarContext); - useSetDefaultBackTo('sources/all/search'); + useSetDefaultBackTo('sources'); const [query] = useQueryParam('query', StringParam); const searchString = useDebounce(query, TRIGGER_SEARCH_THRESHOLD); diff --git a/src/screens/SourceMangas.tsx b/src/screens/SourceMangas.tsx index c080d91e..dceeba7e 100644 --- a/src/screens/SourceMangas.tsx +++ b/src/screens/SourceMangas.tsx @@ -205,19 +205,17 @@ export function SourceMangas() { const { sourceId } = useParams<{ sourceId: string }>(); const navigate = useNavigate(); - const { pathname, state: locationState } = - useLocation<{ - contentType: SourceContentType; - filtersToApply: IPos[]; - clearCache: boolean; - }>() ?? {}; const { contentType: currentLocationContentType = SourceContentType.POPULAR, filtersToApply: currentLocationFiltersToApply = [], clearCache = false, - } = locationState ?? {}; + } = useLocation<{ + contentType: SourceContentType; + filtersToApply: IPos[]; + clearCache: boolean; + }>().state ?? {}; - useSetDefaultBackTo(pathname); + useSetDefaultBackTo('sources'); const { options } = useLibraryOptionsContext(); const [query] = useQueryParam('query', StringParam);