diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js index 8845af7c71683260d335103248879f8234dd2c31..2bed4303aa1be9c591fa3df8d8a0b33db9605b0d 100644 --- a/__tests__/src/components/WindowViewer.test.js +++ b/__tests__/src/components/WindowViewer.test.js @@ -26,4 +26,7 @@ describe('WindowViewer', () => { // do not effectively find elements (even though they are there) expect(wrapper.render().find('.openseadragon-canvas').length).toBe(1); }); + it('has navigation controls', () => { + expect(wrapper.find('.mirador-osd-navigation').length).toBe(1); + }); }); diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..3709ab33b168ac179a9c375ab803db1fa3d09016 --- /dev/null +++ b/src/components/OpenSeadragonViewer.js @@ -0,0 +1,89 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import OpenSeadragon from 'openseadragon'; +import miradorWithPlugins from '../lib/miradorWithPlugins'; +import ns from '../config/css-ns'; + +/** + * Represents a OpenSeadragonViewer in the mirador workspace. Responsible for mounting + * and rendering OSD. + */ +class OpenSeadragonViewer extends Component { + /** + * @param {Object} props + */ + constructor(props) { + super(props); + + this.viewer = null; + this.ref = React.createRef(); + } + + /** + * React lifecycle event + */ + componentDidMount() { + const { tileSources } = this.props; + if (!this.ref.current) { + return; + } + this.viewer = new OpenSeadragon({ + id: this.ref.current.id, + preserveViewport: true, + blendTime: 0.1, + alwaysBlend: false, + showNavigationControl: false, + }); + tileSources.forEach(tileSource => this.addTileSource(tileSource)); + } + + /** + */ + componentDidUpdate() { + const { tileSources } = this.props; + tileSources.forEach(tileSource => this.addTileSource(tileSource)); + } + + /** + */ + componentWillUnmount() { + this.viewer.removeAllHandlers(); + } + + /** + */ + addTileSource(tileSource) { + this.viewer.addTiledImage({ + tileSource, + success: (event) => { + }, + }); + } + + /** + * Renders things + */ + render() { + const { window } = this.props; + return ( + <Fragment> + <div + className={ns('osd-container')} + id={`${window.id}-osd`} + ref={this.ref} + /> + </Fragment> + ); + } +} + +OpenSeadragonViewer.defaultProps = { + tileSources: [], +}; + +OpenSeadragonViewer.propTypes = { + tileSources: PropTypes.arrayOf(PropTypes.object), + window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types +}; + +export default miradorWithPlugins(OpenSeadragonViewer); diff --git a/src/components/ViewerNavigation.js b/src/components/ViewerNavigation.js new file mode 100644 index 0000000000000000000000000000000000000000..a6d989f143db068813cd8de574dcf5242ede5b6a --- /dev/null +++ b/src/components/ViewerNavigation.js @@ -0,0 +1,91 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import miradorWithPlugins from '../lib/miradorWithPlugins'; +import { actions } from '../store'; +import ns from '../config/css-ns'; + +/** + */ +class ViewerNavigation extends Component { + /** + */ + constructor(props) { + super(props); + + this.nextCanvas = this.nextCanvas.bind(this); + this.previousCanvas = this.previousCanvas.bind(this); + } + + /** + */ + nextCanvas() { + const { window, nextCanvas } = this.props; + if (this.hasNextCanvas()) nextCanvas(window.id); + } + + /** + */ + hasNextCanvas() { + const { window, canvases } = this.props; + return window.canvasIndex < canvases.length - 1; + } + + /** + */ + previousCanvas() { + const { window, previousCanvas } = this.props; + if (this.hasPreviousCanvas()) previousCanvas(window.id); + } + + /** + */ + hasPreviousCanvas() { + const { window } = this.props; + return window.canvasIndex > 0; + } + + /** + * Renders things + */ + render() { + return ( + <div className={ns('osd-navigation')}> + <button + type="button" + disabled={!this.hasPreviousCanvas()} + onClick={this.previousCanvas} + > + ‹ + </button> + <button + type="button" + disabled={!this.hasNextCanvas()} + onClick={this.nextCanvas} + > + › + </button> + </div> + ); + } +} + +ViewerNavigation.propTypes = { + canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types + nextCanvas: PropTypes.func.isRequired, + previousCanvas: PropTypes.func.isRequired, + window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types +}; + + +/** + * mapDispatchToProps - used to hook up connect to action creators + * @memberof ManifestForm + * @private + */ +const mapDispatchToProps = dispatch => ({ + nextCanvas: windowId => dispatch(actions.nextCanvas(windowId)), + previousCanvas: windowId => dispatch(actions.previousCanvas(windowId)), +}); + +export default connect(null, mapDispatchToProps)(miradorWithPlugins(ViewerNavigation)); diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js index 0a58b454344197e3835b234cc2d48a6dd81cb2b7..f0624e5f25f26e4333c8d8dabe98984909eb93d7 100644 --- a/src/components/WindowViewer.js +++ b/src/components/WindowViewer.js @@ -1,13 +1,13 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import OpenSeaDragon from 'openseadragon'; import fetch from 'node-fetch'; import miradorWithPlugins from '../lib/miradorWithPlugins'; -import ns from '../config/css-ns'; +import OpenSeadragonViewer from './OpenSeadragonViewer'; +import ViewerNavigation from './ViewerNavigation'; /** * Represents a WindowViewer in the mirador workspace. Responsible for mounting - * and rendering OSD. + * OSD and Navigation */ class WindowViewer extends Component { /** @@ -16,47 +16,43 @@ class WindowViewer extends Component { constructor(props) { super(props); - this.ref = React.createRef(); - this.viewer = null; + const { manifest } = this.props; + this.canvases = manifest.manifestation.getSequences()[0].getCanvases(); + this.state = { + tileSources: [], + }; } /** - * React lifecycle event + * componentDidMount - React lifecycle method + * Request the initial canvas on mount */ componentDidMount() { - const { manifest } = this.props; - if (!this.ref.current) { - return false; + this.requestAndUpdateTileSources(); + } + + /** + * componentDidUpdate - React lifecycle method + * Request a new canvas if it is needed + */ + componentDidUpdate(prevProps) { + const { window } = this.props; + if (prevProps.window.canvasIndex !== window.canvasIndex) { + this.requestAndUpdateTileSources(); } - this.viewer = OpenSeaDragon({ - id: this.ref.current.id, - showNavigationControl: false, - }); - const that = this; - fetch(`${manifest.manifestation.getSequences()[0].getCanvases()[0].getImages()[0].getResource().getServices()[0].id}/info.json`) + } + + /** + */ + requestAndUpdateTileSources() { + const { window } = this.props; + fetch(`${this.canvases[window.canvasIndex].getImages()[0].getResource().getServices()[0].id}/info.json`) .then(response => response.json()) .then((json) => { - that.viewer.addTiledImage({ - tileSource: json, - success: (event) => { - const tiledImage = event.item; - - /** - * A callback for the tile after its drawn - * @param {[type]} e event object - */ - const tileDrawnHandler = (e) => { - if (e.tiledImage === tiledImage) { - that.viewer.removeHandler('tile-drawn', tileDrawnHandler); - that.ref.current.style.display = 'block'; - } - }; - that.viewer.addHandler('tile-drawn', tileDrawnHandler); - }, + this.setState({ + tileSources: [json], }); - }) - .catch(error => console.log(error)); - return false; + }); } /** @@ -64,13 +60,12 @@ class WindowViewer extends Component { */ render() { const { window } = this.props; + const { tileSources } = this.state; return ( - <div - className={ns('osd-container')} - style={{ display: 'none' }} - id={`${window.id}-osd`} - ref={this.ref} - /> + <Fragment> + <OpenSeadragonViewer tileSources={tileSources} window={window} /> + <ViewerNavigation window={window} canvases={this.canvases} /> + </Fragment> ); } } diff --git a/src/styles/index.scss b/src/styles/index.scss index 62ab9468ed4d4187cc3a451b3eb16094b3a4c239..f37eb5dc2f4a4d823401ae7b2cf93931f407bbd7 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -53,4 +53,12 @@ body { top: 0; width: 100%; } + + &-osd-navigation { + bottom: 10px; + left: 0; + position: absolute; + right: 0; + text-align: center; + } }