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.
203 lines (190 loc) • 6.68 kB
JSX
import { useEffect } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import ListItem from '@mui/material/ListItem';
import ButtonBase from '@mui/material/ButtonBase';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Skeleton from '@mui/material/Skeleton';
import { useTranslation } from 'react-i18next';
import { Img } from 'react-image';
import ManifestListItemError from '../containers/ManifestListItemError';
import ns from '../config/css-ns';
const Root = styled(ListItem, { name: 'ManifestListItem', slot: 'root' })(({ ownerState, theme }) => ({
'&:hover,&:focus-within': {
backgroundColor: theme.palette.action.hover,
borderLeftColor: ownerState?.active ? theme.palette.primary.main : theme.palette.action.hover,
},
borderLeft: '4px solid',
borderLeftColor: ownerState?.active ? theme.palette.primary.main : 'transparent',
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
},
}));
const StyledThumbnail = styled(Img, {
name: 'ManifestListItem',
slot: 'thumbnail',
})(({ theme }) => ({
maxWidth: '100%',
objectFit: 'contain',
}));
const StyledLogo = styled(Img, { name: 'ManifestListItem', slot: 'logo' })(({ theme }) => ({
height: '2.5rem',
maxWidth: '100%',
objectFit: 'contain',
paddingRight: 1,
}));
/** */
const Placeholder = () => (
<Grid container className={ns('manifest-list-item')}>
<Grid size={{ sm: 2, xs: 3 }}>
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="rectangular" height={80} width={120} />
</Grid>
<Grid size={{ sm: 6, xs: 9 }}>
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="text" />
</Grid>
<Grid size={{ sm: 2, xs: 8 }}>
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="text" />
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="text" />
</Grid>
<Grid size={{ sm: 2, xs: 4 }}>
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="rectangular" height={60} width={60} />
</Grid>
</Grid>
);
/**
* Represents an item in a list of currently-loaded or loading manifests
* @param {object} props
* @param {object} [props.manifest = string]
*/
export function ManifestListItem({
fetchManifest,
isFetching = false,
addWindow,
handleClose = () => {},
active = false,
buttonRef = undefined,
manifestId,
ready = false,
title = null,
thumbnail = null,
manifestLogo = null,
size = 0,
provider = null,
error = null,
isCollection = false,
isMultipart = false,
}) {
const { t } = useTranslation();
useEffect(() => {
if (!ready && !error && !isFetching && provider !== 'file') fetchManifest(manifestId);
}, [manifestId, provider, fetchManifest, ready, error, isFetching]);
// eslint-disable-next-line prefer-rest-params
const ownerState = arguments[0];
/** */
const handleOpenButtonClick = () => {
addWindow({ manifestId });
handleClose();
};
if (error) {
return (
<Root ownerState={ownerState} divider selected={active} className={active ? 'active' : ''} data-manifestid={manifestId}>
<ManifestListItemError manifestId={manifestId} />
</Root>
);
}
return (
<Root
divider
selected={active}
className={active ? 'active' : ''}
data-manifestid={manifestId}
data-active={active}
sx={{ width: '100%' }}
>
{ready ? (
<Grid container className={ns('manifest-list-item')} sx={{ alignItems: 'center', width: '100%' }}>
<Grid size={{ sm: 5, xs: 12 }}>
<ButtonBase
ref={buttonRef}
className={ns('manifest-list-item-title')}
onClick={handleOpenButtonClick}
sx={{ alignItems: 'center', justifyContent: 'flex-start', width: '100%' }}
>
<Grid container component="div" sx={{ width: '100%' }}>
<Grid size={3} sx={{ alignItems: 'center', display: 'flex', justifyContent: 'flex-start' }}>
{thumbnail ? (
<StyledThumbnail
className={[ns('manifest-list-item-thumb')]}
src={[thumbnail]}
alt=""
height="80"
unloader={
<Skeleton variant="rectangular" animation={false} sx={{ bgcolor: 'grey[300]' }} height={80} width={120} />
}
/>
) : (
<Skeleton sx={{ bgcolor: 'grey[300]' }} variant="rectangular" height={80} width={120} />
)}
</Grid>
<Grid size={9} sx={{ alignContent: 'center', paddingLeft: 2 }}>
{isCollection && (
<Typography component="div" variant="overline" sx={{ textAlign: 'left' }}>
{t(isMultipart ? 'multipartCollection' : 'collection')}
</Typography>
)}
<Typography component="div" variant="h6" sx={{ textAlign: 'left' }}>
{title || manifestId}
</Typography>
</Grid>
</Grid>
</ButtonBase>
</Grid>
<Grid size={{ sm: 4, xs: 8 }}>
<Typography className={ns('manifest-list-item-provider')}>{provider}</Typography>
<Typography>{t('numItems', { count: size, number: size })}</Typography>
</Grid>
<Grid size={{ sm: 3, xs: 4 }}>
{manifestLogo && (
<StyledLogo
src={[manifestLogo]}
alt=""
role="presentation"
unloader={
<Skeleton variant="rectangular" animation={false} sx={{ bgcolor: 'grey[300]' }} height={60} width={60} />
}
/>
)}
</Grid>
</Grid>
) : (
<Placeholder />
)}
</Root>
);
}
ManifestListItem.propTypes = {
active: PropTypes.bool,
addWindow: PropTypes.func.isRequired,
buttonRef: PropTypes.elementType,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.oneOf([null]), // for null
]),
fetchManifest: PropTypes.func.isRequired,
handleClose: PropTypes.func,
isCollection: PropTypes.bool,
isFetching: PropTypes.bool,
isMultipart: PropTypes.bool,
manifestId: PropTypes.string.isRequired,
manifestLogo: PropTypes.string,
provider: PropTypes.string,
ready: PropTypes.bool,
size: PropTypes.number,
thumbnail: PropTypes.string,
title: PropTypes.string,
};