@collectionspace/cspace-public-browser
Version:
CollectionSpace public browser
276 lines (225 loc) • 6.89 kB
JSX
/* global fetch, window, AbortController */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage } from 'react-intl';
import { getItemShortID } from 'cspace-refname';
import Immutable from 'immutable';
import { blobUrl } from '../../../helpers/urlHelpers';
import config from '../../../config';
import styles from '../../../../styles/cspace/SearchResultImage.css';
const propTypes = {
gatewayUrl: PropTypes.string.isRequired,
holdingInstitutions: PropTypes.instanceOf(Immutable.List),
loadImageImmediately: PropTypes.bool,
mediaCsid: PropTypes.string,
referenceValue: PropTypes.string.isRequired,
};
const defaultProps = {
holdingInstitutions: Immutable.List(),
loadImageImmediately: false,
mediaCsid: undefined,
};
const messages = defineMessages({
noimage: {
id: 'searchResultImage.noimage',
defaultMessage: 'no image available',
},
});
export default class SearchResultImage extends Component {
constructor(props) {
super();
this.handleScroll = this.handleScroll.bind(this);
this.ref = React.createRef();
this.state = {
gatewayUrl: props.gatewayUrl,
};
if (AbortController) {
this.abortController = new AbortController();
}
}
componentDidMount() {
const {
holdingInstitutions,
loadImageImmediately,
mediaCsid,
referenceValue,
} = this.props;
window.setTimeout(() => {
if (this.isInView()) {
this.init(referenceValue, mediaCsid, holdingInstitutions);
} else {
window.addEventListener('scroll', this.handleScroll);
}
}, loadImageImmediately ? 0 : config.get('imageLoadDelay'));
}
componentDidUpdate(prevProps) {
const {
gatewayUrl: prevGatewayUrl,
mediaCsid: prevMediaCsid,
referenceValue: prevReferenceValue,
} = prevProps;
const {
holdingInstitutions,
gatewayUrl,
mediaCsid,
referenceValue,
} = this.props;
if (
mediaCsid !== prevMediaCsid
|| gatewayUrl !== prevGatewayUrl
|| referenceValue !== prevReferenceValue
) {
// FIXME: Make this component stateless.
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
gatewayUrl,
});
this.init(referenceValue, mediaCsid, holdingInstitutions);
}
}
componentWillUnmount() {
if (this.abortController) {
this.abortController.abort();
}
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll() {
if (this.isInView()) {
window.setTimeout(() => {
if (this.isInView()) {
const {
holdingInstitutions,
mediaCsid,
referenceValue,
} = this.props;
window.removeEventListener('scroll', this.handleScroll);
this.init(referenceValue, mediaCsid, holdingInstitutions);
}
}, config.get('imageLoadDelay'));
}
}
getMediaCsid(gatewayUrl, indexName, referenceValue) {
const url = `${gatewayUrl}/es/doc/_search`;
const referenceField = config.get('referenceField');
const query = {
_source: 'collectionspace_denorm:mediaCsid',
query: {
term: {
[referenceField]: referenceValue,
},
},
size: 1,
terminate_after: 1,
};
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(query),
signal: this.abortController ? this.abortController.signal : undefined,
})
.then((response) => response.json())
// eslint-disable-next-line no-underscore-dangle
.then((data) => data.hits.hits[0]._source['collectionspace_denorm:mediaCsid'][0])
.catch(() => undefined);
}
init(referenceValue, mediaCsid, holdingInstitutions) {
if (typeof mediaCsid !== 'undefined') {
this.setState({
mediaCsid,
});
return;
}
// Attempt to resove a mediaCsid from holding instutitions. This is really only used by the
// materials browser. In other profiles, the media csid will have been received in the search
// result.
const cachedGatewayMediaCsid = window.sessionStorage.getItem(`image-${referenceValue}`);
if (cachedGatewayMediaCsid) {
const [cachedGatewayUrl, cachedMediaCsid] = cachedGatewayMediaCsid.split(',');
this.setState({
gatewayUrl: cachedGatewayUrl,
mediaCsid: cachedMediaCsid,
});
return;
}
const institutions = holdingInstitutions.filter((value) => !!value);
if (institutions.size === 0) {
this.setState({
mediaCsid: null,
});
return;
}
const findImage = institutions.reduce((promise, institution) => promise.catch(() => {
const instShortId = getItemShortID(institution);
const instGatewayUrl = config.get(['institutions', instShortId, 'gatewayUrl']);
const instIndexName = config.get(['institutions', instShortId, 'esIndexName']);
if (!instGatewayUrl) {
return Promise.reject();
}
return (
this.getMediaCsid(instGatewayUrl, instIndexName, referenceValue)
.then((instMediaCsid) => {
if (!instMediaCsid) {
return Promise.reject();
}
return Promise.resolve({ instGatewayUrl, instMediaCsid });
})
);
}), Promise.reject());
findImage
.then(({ instGatewayUrl, instMediaCsid }) => {
try {
window.sessionStorage.setItem(
`image-${referenceValue}`,
`${instGatewayUrl},${instMediaCsid}`,
);
} catch (err) {
// Ignore storage error.
}
this.setState({
gatewayUrl: instGatewayUrl,
mediaCsid: instMediaCsid,
});
})
.catch(() => {});
}
isInView() {
const domNode = this.ref.current;
if (domNode) {
const rect = domNode.getBoundingClientRect();
return (
rect.top >= 0
&& rect.top < window.innerHeight
);
}
return false;
}
render() {
const {
gatewayUrl,
mediaCsid,
} = this.state;
if (mediaCsid === null) {
return (
<div
aria-hidden
className={styles.noimage}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<FormattedMessage {...messages.noimage} />
</div>
);
}
const imageUrl = mediaCsid && blobUrl(gatewayUrl, mediaCsid, config.get('searchResultImageDerivative'));
let style;
if (imageUrl) {
style = {
backgroundImage: `url(${imageUrl})`,
};
}
return (
<div className={styles.common} style={style} ref={this.ref} />
);
}
}
SearchResultImage.propTypes = propTypes;
SearchResultImage.defaultProps = defaultProps;