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.
143 lines (135 loc) • 4.05 kB
JSX
import { useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import Button from '@mui/material/Button';
import List from '@mui/material/List';
import Typography from '@mui/material/Typography';
import BackIcon from '@mui/icons-material/ArrowBackSharp';
import { announce } from '@react-aria/live-announcer';
import { useTranslation } from 'react-i18next';
import SearchHit from '../containers/SearchHit';
import { ScrollTo } from './ScrollTo';
/**
* Return SearchHits for every hit in the response
* Return SearchHits for every annotation in the response if there are no hits
*/
function SearchHitsAndAnnotations({
companionWindowId,
containerRef,
searchAnnotations,
searchHits,
windowId,
focused,
toggleFocus,
}) {
if (searchHits.length === 0 && searchAnnotations.length > 0) {
return searchAnnotations.map((anno, index) => (
<SearchHit
announcer={announce}
annotationId={anno.id}
companionWindowId={companionWindowId}
containerRef={containerRef}
key={anno.id}
focused={focused}
index={index}
total={searchAnnotations.length}
windowId={windowId}
showDetails={toggleFocus}
/>
));
}
return searchHits.map((hit, index) => (
<SearchHit
announcer={announce}
containerRef={containerRef}
companionWindowId={companionWindowId}
key={hit.annotations[0]}
focused={focused}
hit={hit}
index={index}
total={searchHits.length}
windowId={windowId}
showDetails={toggleFocus}
/>
));
}
/** */
export function SearchResults({
companionWindowId,
containerRef = undefined,
isFetching = false,
fetchSearch,
nextSearch = undefined,
query = undefined,
searchAnnotations = [],
searchHits = [],
searchNumTotal = undefined,
windowId,
}) {
const { t } = useTranslation();
const [focused, setFocused] = useState(false);
/** */
const toggleFocus = useCallback(() => {
setFocused(!focused);
}, [setFocused, focused]);
const noResultsState = (
query && !isFetching && searchHits.length === 0 && searchAnnotations.length === 0
);
return (
<>
{ focused && (
<ScrollTo containerRef={containerRef} offsetTop={96} scrollTo>
<Button onClick={toggleFocus} sx={{ textTransform: 'none' }} size="small">
<BackIcon />
{t('backToResults')}
</Button>
</ScrollTo>
)}
{noResultsState && (
<Typography sx={{
padding: 2,
typography: 'h6',
}}
>
{t('searchNoResults')}
</Typography>
)}
<List disablePadding>
<SearchHitsAndAnnotations
companionWindowId={companionWindowId}
containerRef={containerRef}
searchAnnotations={searchAnnotations}
searchHits={searchHits}
windowId={windowId}
focused={focused}
toggleFocus={toggleFocus}
/>
</List>
{ nextSearch && (
<Button
sx={{ width: '100%' }}
color="secondary"
onClick={() => fetchSearch(windowId, companionWindowId, nextSearch, query)}
>
{t('moreResults')}
<br />
{`(${t('searchResultsRemaining', { numLeft: searchNumTotal - searchHits.length })})`}
</Button>
)}
</>
);
}
SearchResults.propTypes = {
companionWindowId: PropTypes.string.isRequired,
containerRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
]),
fetchSearch: PropTypes.func.isRequired,
isFetching: PropTypes.bool,
nextSearch: PropTypes.string,
query: PropTypes.string,
searchAnnotations: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
searchHits: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
searchNumTotal: PropTypes.number,
windowId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};