UNPKG

cspace-ui

Version:
270 lines (226 loc) 7.51 kB
/* global window */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Immutable from 'immutable'; import get from 'lodash/get'; import ImageGallery from 'react-image-gallery'; import { defineMessages, intlShape } from 'react-intl'; import { baseComponents as inputComponents } from 'cspace-input'; import ImageContainer from '../../containers/media/ImageContainer'; import { getContentPath } from '../../helpers/contentHelpers'; import { VIEWER_WINDOW_NAME, getImageViewerPath, } from '../../helpers/blobHelpers'; import styles from '../../../styles/cspace-ui/MediaViewer.css'; // eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved import '!style-loader!css-loader!react-image-gallery/styles/css/image-gallery.css'; const { MiniButton } = inputComponents; const messages = defineMessages({ previous: { id: 'mediaViewer.previous', description: 'Label of the button to show the previous image in the media viewer.', defaultMessage: 'Previous image', }, next: { id: 'mediaViewer.next', description: 'Label of the button to show the next image in the media viewer.', defaultMessage: 'Next image', }, }); const renderItem = (item) => ( <div className="image-gallery-image"> <ImageContainer src={item.snapshot} alt={item.snapshotAlt} srcSet={item.srcSet} sizes={item.sizes} title={item.snapshotTitle} data-csid={item.blobCsid} /> </div> ); const renderThumbInner = (item) => ( <div> <ImageContainer src={item.thumbnail} alt={item.thumbnailAlt} title={item.thumbnailTitle} /> { item.thumbnailLabel && ( <div className="image-gallery-thumbnail-label"> {item.thumbnailLabel} </div> ) } </div> ); const sortByPriority = (items, priorityOrder) => { if (!priorityOrder) { return items; } const priorityRefNames = Immutable.List.isList(priorityOrder) ? priorityOrder : Immutable.List.of(priorityOrder); const indexByRefName = Immutable.Map( priorityRefNames.map((refName, index) => [refName, index]), ); // sortBy is stable, so items not in the priority order keep their relative // positions at the end. return items.sortBy((item) => indexByRefName.get(item.get('refName'), Infinity)); }; const makeRenderNav = (name, symbol, label) => (onClick, disabled) => ( <MiniButton name={name} disabled={disabled} onClick={onClick} aria-label={label}> {symbol} </MiniButton> ); const propTypes = { config: PropTypes.shape({ listTypes: PropTypes.object, recordTypes: PropTypes.object, }).isRequired, isSearchPending: PropTypes.bool, listType: PropTypes.string, ownFields: PropTypes.shape({ ownBlobCsid: PropTypes.string, ownAltText: PropTypes.string, ownIdentificationNumber: PropTypes.string, }), priorityOrder: PropTypes.oneOfType( [PropTypes.string, PropTypes.instanceOf(Immutable.List)], ), searchResult: PropTypes.instanceOf(Immutable.Map), readRecord: PropTypes.func, }; const defaultProps = { listType: 'common', readRecord: () => Promise.resolve(), }; export default class MediaViewer extends Component { constructor() { super(); this.handleImageGalleryClick = this.handleImageGalleryClick.bind(this); } handleImageGalleryClick(event) { const { target, } = event; const { config, } = this.props; if (target.nodeName === 'IMG') { this.getPopupImagePath(target.dataset.csid) .then((popupImagePath) => { window.open(getImageViewerPath(config, popupImagePath), VIEWER_WINDOW_NAME); }); } } getPopupImagePath(blobCsid) { const { config, readRecord, } = this.props; const recordTypeConfig = get(config, ['recordTypes', 'blob']); const popupSubresource = get(recordTypeConfig, ['content', 'popup', 'subresource']); return readRecord(config, recordTypeConfig, undefined, blobCsid) .then((blobData) => getContentPath(config, 'blob', undefined, blobCsid, popupSubresource, blobData)); } createGalleryImage(blobCsid, altText, identificationNumber) { const { config, } = this.props; const content = get(config, ['recordTypes', 'blob', 'content']); const snapshotSubresource = get(content, ['snapshot', 'subresource']); const thumbnailSubresource = get(content, ['thumbnail', 'subresource']); return { blobCsid, // note: react-image-gallery requires item.original to be non-null, so it might be best to // move from snapshot to original here to keep similar semantics original: getContentPath(config, 'blob', undefined, blobCsid, snapshotSubresource), snapshot: getContentPath(config, 'blob', undefined, blobCsid, snapshotSubresource), snapshotAlt: altText ?? identificationNumber, snapshotTitle: identificationNumber, thumbnail: getContentPath(config, 'blob', undefined, blobCsid, thumbnailSubresource), thumbnailAlt: altText ?? identificationNumber, thumbnailTitle: identificationNumber, }; } render() { const { config, isSearchPending, listType, ownFields, priorityOrder, searchResult, } = this.props; const { intl } = this.context; const renderLeftNav = makeRenderNav( 'mediaViewerPrev', '<', intl.formatMessage(messages.previous), ); const renderRightNav = makeRenderNav( 'mediaViewerNext', '>', intl.formatMessage(messages.next), ); const images = []; if (ownFields) { const { ownBlobCsid, ownAltText, ownIdentificationNumber } = ownFields; images.push(this.createGalleryImage(ownBlobCsid, ownAltText, ownIdentificationNumber)); } if (searchResult) { const listTypeConfig = config.listTypes[listType]; const { listNodeName, itemNodeName, } = listTypeConfig; const list = searchResult.get(listNodeName); const totalItems = parseInt(list.get('totalItems'), 10); if (!Number.isNaN(totalItems)) { let items = list.get(itemNodeName); if (items) { if (!Immutable.List.isList(items)) { items = Immutable.List.of(items); } sortByPriority(items, priorityOrder).forEach((item) => { const blobCsid = item.get('blobCsid'); const altText = item.get('altText'); const identificationNumber = item.get('identificationNumber'); if (blobCsid) { images.push(this.createGalleryImage(blobCsid, altText, identificationNumber)); } }); } } } if (images.length > 0) { return ( <div className={styles.normal}> <ImageGallery items={images} disableKeyDown lazyLoad renderLeftNav={renderLeftNav} renderRightNav={renderRightNav} showThumbnails={images.length > 1} showFullscreenButton={false} showPlayButton={false} onClick={this.handleImageGalleryClick} renderItem={renderItem} renderThumbInner={renderThumbInner} /> </div> ); } if (isSearchPending) { return null; } return ( <div className={styles.empty} /> ); } } MediaViewer.propTypes = propTypes; MediaViewer.defaultProps = defaultProps; MediaViewer.contextTypes = { intl: intlShape, };