UNPKG

mirador

Version:

An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.

166 lines (149 loc) 5.18 kB
import { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; import deburr from 'lodash/deburr'; import isObject from 'lodash/isObject'; import { useDebouncedCallback } from 'use-debounce'; import Autocomplete from '@mui/material/Autocomplete'; import CircularProgress from '@mui/material/CircularProgress'; import TextField from '@mui/material/TextField'; import InputAdornment from '@mui/material/InputAdornment'; import SearchIcon from '@mui/icons-material/SearchSharp'; import { useTranslation } from 'react-i18next'; import MiradorMenuButton from '../containers/MiradorMenuButton'; import SearchPanelNavigation from '../containers/SearchPanelNavigation'; const StyledForm = styled('form', { name: 'SearchPanelControls', slot: 'form' })(({ theme }) => ({ paddingBottom: theme.spacing(1), paddingRight: theme.spacing(1.5), width: '100%', })); /** Sometimes an autocomplete match can be a simple string, other times an object with a `match` property, this function abstracts that away */ const getMatch = (option) => (isObject(option) ? option.match : option); /** */ export function SearchPanelControls({ autocompleteService = undefined, companionWindowId, fetchSearch, query = '', searchIsFetching = false, searchService, windowId, }) { const { t } = useTranslation(); const [input, setInput] = useState(query); const [search, setSearch] = useState(query); const [suggestions, setSuggestions] = useState([]); useEffect(() => { setInput(query); setSearch(query); setSuggestions([]); }, [query]); useEffect(() => { if (search && search !== '' && searchService) { fetchSearch(windowId, companionWindowId, `${searchService.id}?${new URLSearchParams({ q: search })}`, search); } }, [search, searchService, companionWindowId, fetchSearch, windowId]); /** */ const handleChange = (event, value, reason) => { // For some reason the value gets reset to an empty value from the // useAutocomplete hook sometimes, we just ignore these cases if (reason === 'reset' && !value) { return; } setInput(value); setSuggestions([]); if (value) { fetchAutocomplete(value); } }; /** */ const fetchAutocomplete = useDebouncedCallback(useCallback(() => { if (!autocompleteService) return; if (!input) return; fetch(`${autocompleteService.id}?${new URLSearchParams({ q: input })}`) .then(response => response.json()) .then(receiveAutocomplete); }, [autocompleteService, input]), 500); /** */ const receiveAutocomplete = (json) => { setSuggestions(json.terms); }; /** */ const submitSearch = (event) => { if (!input) return; if (event) { event.preventDefault(); } setSearch(input); }; /** */ const selectItem = (_event, selectedItem, _reason) => { if (selectedItem && getMatch(selectedItem)) { setSearch(getMatch(selectedItem)); } }; const id = `search-${companionWindowId}`; return ( <> <StyledForm aria-label={t('searchTitle')} onSubmit={submitSearch} > <Autocomplete id={id} inputValue={input} options={suggestions} getOptionLabel={getMatch} isOptionEqualToValue={(option, value) => ( deburr(getMatch(option).trim()).toLowerCase() === deburr(getMatch(value).trim()).toLowerCase() )} noOptionsText="" onChange={selectItem} onInputChange={handleChange} freeSolo disableClearable renderInput={params => ( <TextField {...params} label={t('searchInputLabel')} variant="standard" InputProps={{ ...params.InputProps, endAdornment: ( <InputAdornment sx={{ position: 'relative' }} position="end"> <MiradorMenuButton aria-label={t('searchSubmitAria')} type="submit"> <SearchIcon /> </MiradorMenuButton> {Boolean(searchIsFetching) && ( <CircularProgress sx={{ left: '50%', marginLeft: '-25px', marginTop: '-25px', position: 'absolute', top: '50%', }} size={50} /> )} </InputAdornment> ), }} /> )} /> </StyledForm> <SearchPanelNavigation windowId={windowId} companionWindowId={companionWindowId} /> </> ); } SearchPanelControls.propTypes = { autocompleteService: PropTypes.shape({ id: PropTypes.string, }), companionWindowId: PropTypes.string.isRequired, fetchSearch: PropTypes.func.isRequired, query: PropTypes.string, searchIsFetching: PropTypes.bool, searchService: PropTypes.shape({ id: PropTypes.string, }).isRequired, windowId: PropTypes.string.isRequired, };