diff --git a/__tests__/integration/mirador/video.html b/__tests__/integration/mirador/video.html index 45c8db36dbbbd11ad70e73f814a55bfcf9eecf03..f245fb4c2d21320e5958cf36ae37082ed5ad85a8 100644 --- a/__tests__/integration/mirador/video.html +++ b/__tests__/integration/mirador/video.html @@ -16,11 +16,12 @@ windows: [ { manifestId: 'https://preview.iiif.io/cookbook/master/recipe/0003-mvm-video/manifest.json', - view: 'av' }, { manifestId: 'https://preview.iiif.io/cookbook/0026_0064_0065-opera-recipes/recipe/0064-opera-one-canvas/manifest.json', - view: 'av' + }, + { + manifestId: 'https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/manifest.json' } ], }); diff --git a/src/components/AVViewer.js b/src/components/AVViewer.js deleted file mode 100644 index d06c1cfa06a4e2ee8e67fa9a3f8fefc7d1cf9338..0000000000000000000000000000000000000000 --- a/src/components/AVViewer.js +++ /dev/null @@ -1,23 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -/** */ -export class AVViewer extends Component { - /** */ - render() { - const { currentCanvases } = this.props; - const video = currentCanvases[0].getContent()[0].getBody()[0]; - return ( - <div style={{ display: 'flex', 'alignItems': 'center' }}> - <video controls> - <source src={video.id} type={video.format} /> - </video> - </div> - ); - } -} - -AVViewer.propTypes = { - currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - windowId: PropTypes.string.isRequired, -}; diff --git a/src/components/AudioViewer.js b/src/components/AudioViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..9c5d6c69c51fc8a8944881f33d5421b7eefafac3 --- /dev/null +++ b/src/components/AudioViewer.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** */ +export class AudioViewer extends Component { + /* eslint-disable jsx-a11y/media-has-caption */ + /** */ + render() { + const { classes, audioResources } = this.props; + const audio = audioResources && audioResources[0]; + if (!audio) return <></>; + + return ( + <div className={classes.container}> + <audio controls className={classes.audio}> + <source src={audio.id} type={audio.getFormat()} /> + </audio> + </div> + ); + } + /* eslint-enable jsx-a11y/media-has-caption */ +} + +AudioViewer.propTypes = { + audioResources: PropTypes.arrayOf(PropTypes.object).isRequired, + classes: PropTypes.objectOf(PropTypes.string).isRequired, +}; diff --git a/src/components/PrimaryWindow.js b/src/components/PrimaryWindow.js index df0e9e89ac23a8ec42aa5479329cd139e5b893dd..97728d88907f03a67e4d534209d92601db75ce71 100644 --- a/src/components/PrimaryWindow.js +++ b/src/components/PrimaryWindow.js @@ -6,28 +6,29 @@ import CompanionArea from '../containers/CompanionArea'; import CollectionDialog from '../containers/CollectionDialog'; import ns from '../config/css-ns'; +const AudioViewer = lazy(() => import('../containers/AudioViewer')); const GalleryView = lazy(() => import('../containers/GalleryView')); const SelectCollection = lazy(() => import('../containers/SelectCollection')); const WindowViewer = lazy(() => import('../containers/WindowViewer')); -const AVViewer = lazy(() => import('../containers/AVViewer')); +const VideoViewer = lazy(() => import('../containers/VideoViewer')); GalleryView.displayName = 'GalleryView'; SelectCollection.displayName = 'SelectCollection'; WindowViewer.displayName = 'WindowViewer'; /** - * WindowMiddleContent - component that renders the "middle" area of the - * Mirador Window + * PrimaryWindow - component that renders the primary content of a Mirador + * window. Right now this differentiates between a Image, Video, or Audio viewer. */ export class PrimaryWindow extends Component { /** - * renderViewer + * renderViewer - logic used to determine what type of view to show * * @return {(String|null)} */ renderViewer() { const { - isCollection, isCollectionDialogVisible, isFetching, view, windowId, + audioResources, isCollection, isCollectionDialogVisible, isFetching, videoResources, view, windowId, } = this.props; if (isCollection) { return ( @@ -47,9 +48,16 @@ export class PrimaryWindow extends Component { /> ); } - if (view === 'av') { + if (videoResources.length > 0) { return ( - <AVViewer + <VideoViewer + windowId={windowId} + /> + ); + } + if (audioResources.length > 0) { + return ( + <AudioViewer windowId={windowId} /> ); @@ -81,10 +89,12 @@ export class PrimaryWindow extends Component { } PrimaryWindow.propTypes = { + audioResources: PropTypes.arrayOf(PropTypes.object).isRequired, classes: PropTypes.objectOf(PropTypes.string).isRequired, isCollection: PropTypes.bool, isCollectionDialogVisible: PropTypes.bool, isFetching: PropTypes.bool, + videoResources: PropTypes.arrayOf(PropTypes.object).isRequired, view: PropTypes.string, windowId: PropTypes.string.isRequired, }; diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..7a31969faaf5c1c7547d97a13af4d5cdbe8c089d --- /dev/null +++ b/src/components/VideoViewer.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** */ +export class VideoViewer extends Component { + /* eslint-disable jsx-a11y/media-has-caption */ + /** */ + render() { + const { classes, videoResources } = this.props; + const video = videoResources && videoResources[0]; + if (!video) return <></>; + + return ( + <div className={classes.container}> + <video controls className={classes.video}> + <source src={video.id} type={video.getFormat()} /> + </video> + </div> + ); + } + /* eslint-enable jsx-a11y/media-has-caption */ +} + +VideoViewer.propTypes = { + classes: PropTypes.objectOf(PropTypes.string).isRequired, + videoResources: PropTypes.arrayOf(PropTypes.object).isRequired, +}; diff --git a/src/containers/AVViewer.js b/src/containers/AVViewer.js deleted file mode 100644 index b6e452ef3a26f5fcb633b3fccc4f24fad51ab1a7..0000000000000000000000000000000000000000 --- a/src/containers/AVViewer.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { withTranslation } from 'react-i18next'; -import { withPlugins } from '../extend/withPlugins'; -import { AVViewer } from '../components/AVViewer'; -import { getVisibleCanvases } from '../state/selectors'; - -/** */ -const mapStateToProps = (state, { windowId }) => ( - { - currentCanvases: getVisibleCanvases(state, { windowId }) || [], - } -); - -const enhance = compose( - withTranslation(), - connect(mapStateToProps, null), - withPlugins('AVViewer'), -); - -export default enhance(AVViewer); diff --git a/src/containers/AudioViewer.js b/src/containers/AudioViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..65d198f5ad1be542536bb025064a6c25810c418e --- /dev/null +++ b/src/containers/AudioViewer.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { withTranslation } from 'react-i18next'; +import { withStyles } from '@material-ui/core'; +import { withPlugins } from '../extend/withPlugins'; +import { AudioViewer } from '../components/AudioViewer'; +import { getVisibleCanvasAudioResources } from '../state/selectors'; + +/** */ +const mapStateToProps = (state, { windowId }) => ( + { + audioResources: getVisibleCanvasAudioResources(state, { windowId }) || [], + } +); + +/** */ +const styles = () => ({ + audio: { + width: '100%', + }, + container: { + alignItems: 'center', + display: 'flex', + width: '100%', + }, +}); + +const enhance = compose( + withTranslation(), + withStyles(styles), + connect(mapStateToProps, null), + withPlugins('AudioViewer'), +); + +export default enhance(AudioViewer); diff --git a/src/containers/PrimaryWindow.js b/src/containers/PrimaryWindow.js index 35a86dfa27dd2de03e166223ef6de34a649091e4..336894235f31a71867a2e77da22347d9099c7909 100644 --- a/src/containers/PrimaryWindow.js +++ b/src/containers/PrimaryWindow.js @@ -2,15 +2,19 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; -import { getManifestoInstance, getWindow } from '../state/selectors'; +import { + getManifestoInstance, getVisibleCanvasAudioResources, getVisibleCanvasVideoResources, getWindow, +} from '../state/selectors'; import { PrimaryWindow } from '../components/PrimaryWindow'; /** */ const mapStateToProps = (state, { windowId }) => { const manifestoInstance = getManifestoInstance(state, { windowId }); return { + audioResources: getVisibleCanvasAudioResources(state, { windowId }) || [], isCollection: manifestoInstance && manifestoInstance.isCollection(), isCollectionDialogVisible: getWindow(state, { windowId }).collectionDialogOn, + videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [], }; }; @@ -24,7 +28,7 @@ const styles = { const enhance = compose( withStyles(styles), - connect(mapStateToProps), + connect(mapStateToProps, null), withPlugins('PrimaryWindow'), ); diff --git a/src/containers/VideoViewer.js b/src/containers/VideoViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..97f7620362b99368aeea17222afb3dba83437873 --- /dev/null +++ b/src/containers/VideoViewer.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { withTranslation } from 'react-i18next'; +import { withStyles } from '@material-ui/core'; +import { withPlugins } from '../extend/withPlugins'; +import { VideoViewer } from '../components/VideoViewer'; +import { getVisibleCanvasVideoResources } from '../state/selectors'; + +/** */ +const mapStateToProps = (state, { windowId }) => ( + { + videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [], + } +); + +/** */ +const styles = () => ({ + container: { + alignItems: 'center', + display: 'flex', + width: '100%', + }, + video: { + width: '100%', + }, +}); + +const enhance = compose( + withTranslation(), + withStyles(styles), + connect(mapStateToProps, null), + withPlugins('VideoViewer'), +); + +export default enhance(VideoViewer); diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js index ac375b1df8eb054c7b17c3d75a882b6efb279dd3..ebd45f2f5d81cf9b9ada0f663075f23404a765d9 100644 --- a/src/lib/MiradorCanvas.js +++ b/src/lib/MiradorCanvas.js @@ -81,6 +81,24 @@ export default class MiradorCanvas { })); } + /** */ + get videoResources() { + const resources = flattenDeep([ + this.canvas.getContent().map(i => i.getBody()), + ]); + + return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video')); + } + + /** */ + get audioResources() { + const resources = flattenDeep([ + this.canvas.getContent().map(i => i.getBody()), + ]); + + return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound')); + } + /** */ get resourceAnnotations() { return flattenDeep([ diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index 4a3c9924889b25d1caa071bc965ae2a557d018a8..71b3e274e370b82bf10b81ea86e043f4bddf2a85 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -184,6 +184,22 @@ export const getVisibleCanvasNonTiledResources = createSelector( .filter(resource => resource.getServices().length < 1), ); +export const getVisibleCanvasVideoResources = createSelector( + [ + getVisibleCanvases, + ], + canvases => flatten(canvases + .map(canvas => new MiradorCanvas(canvas).videoResources)), +); + +export const getVisibleCanvasAudioResources = createSelector( + [ + getVisibleCanvases, + ], + canvases => flatten(canvases + .map(canvas => new MiradorCanvas(canvas).audioResources)), +); + export const selectInfoResponse = createSelector( [ (state, { infoId }) => infoId,