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
This commit is contained in:
@@ -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<IProps> = (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<HTMLInputElement>();
|
||||
|
||||
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<IProps> = (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<IProps> = (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<IProps> = (props) => {
|
||||
|
||||
return (
|
||||
<Tooltip title={t('search.title.search')}>
|
||||
<IconButton onClick={openSearch}>
|
||||
<IconButton onClick={() => updateSearchOpenState(true)}>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user