diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js index 6c4e66698e7a8def24169fef778a8145838ece62..cce744544cc69697dac7491a23c8972e1c6dfa56 100644 --- a/__tests__/src/components/OpenSeadragonViewer.test.js +++ b/__tests__/src/components/OpenSeadragonViewer.test.js @@ -4,6 +4,7 @@ import OpenSeadragon from 'openseadragon'; import { OpenSeadragonViewer } from '../../../src/components/OpenSeadragonViewer'; import OpenSeadragonCanvasOverlay from '../../../src/lib/OpenSeadragonCanvasOverlay'; import Annotation from '../../../src/lib/Annotation'; +import CanvasWorld from '../../../src/lib/CanvasWorld'; jest.mock('openseadragon'); jest.mock('../../../src/lib/OpenSeadragonCanvasOverlay'); @@ -34,6 +35,7 @@ describe('OpenSeadragonViewer', () => { updateViewport={updateViewport} t={k => k} classes={{ controls: 'controls' }} + canvasWorld={new CanvasWorld([])} > <div className="foo" /> </OpenSeadragonViewer>, @@ -108,6 +110,7 @@ describe('OpenSeadragonViewer', () => { viewer={{ x: 1, y: 0, zoom: 0.5 }} config={{}} updateViewport={updateViewport} + canvasWorld={new CanvasWorld([])} t={k => k} > <div className="foo" /> diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js index f66019f014df5d493b0b4f0457cf2b0191697231..1fc591a9bb0d30bab47b8aef2dc71735704cf8a9 100644 --- a/__tests__/src/components/WindowViewer.test.js +++ b/__tests__/src/components/WindowViewer.test.js @@ -8,7 +8,7 @@ import fixture from '../../fixtures/version-2/019.json'; import emptyCanvasFixture from '../../fixtures/version-2/emptyCanvas.json'; import otherContentFixture from '../../fixtures/version-2/299843.json'; -let canvases = manifesto.create(fixture).getSequences()[0].getCanvases(); +let currentCanvases = manifesto.create(fixture).getSequences()[0].getCanvases(); let mockWindow = { canvasIndex: 0, @@ -23,7 +23,7 @@ function createWrapper(props) { infoResponses={{}} fetchInfoResponse={() => {}} fetchAnnotation={() => {}} - canvases={canvases} + currentCanvases={[currentCanvases[1]]} window={mockWindow} {...props} />, @@ -32,10 +32,8 @@ function createWrapper(props) { describe('WindowViewer', () => { let wrapper; - beforeEach(() => { - wrapper = createWrapper(); - }); it('renders properly', () => { + wrapper = createWrapper(); expect(wrapper.matchesElement( <> <OSDViewer> @@ -44,31 +42,6 @@ describe('WindowViewer', () => { </>, )).toBe(true); }); - describe('currentCanvases', () => { - it('by default returns a single canvas', () => { - expect(wrapper.instance().currentCanvases().length).toEqual(1); - }); - describe('book view', () => { - it('when the first canvas is selected', () => { - wrapper = createWrapper({ - window: { - canvasIndex: 0, - view: 'book', - }, - }); - expect(wrapper.instance().currentCanvases().length).toEqual(1); - }); - it('when the second canvas is selected', () => { - wrapper = createWrapper({ - window: { - canvasIndex: 1, - view: 'book', - }, - }); - expect(wrapper.instance().currentCanvases().length).toEqual(2); - }); - }); - }); describe('currentInfoResponses', () => { describe('returns only available infoResponses', () => { it('isFetching is false', () => { @@ -131,38 +104,32 @@ describe('WindowViewer', () => { }); }); }); - it('when view type changes', () => { - expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(3); - wrapper.setProps({ - window: { - canvasIndex: 0, - view: 'book', - }, - }); - expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(2); - }); describe('componentDidMount', () => { it('does not call fetchInfoResponse for a canvas that has no images', () => { const mockFnCanvas0 = jest.fn(); const mockFnCanvas2 = jest.fn(); - canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases(); + const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases(); + + currentCanvases = [canvases[0]]; + mockWindow = { canvasIndex: 0, view: 'single', }; wrapper = createWrapper( { - canvases, + currentCanvases, fetchInfoResponse: mockFnCanvas0, window: mockWindow, }, ); expect(mockFnCanvas0).toHaveBeenCalledTimes(1); + currentCanvases = [canvases[2]]; wrapper = createWrapper( { - canvases, + currentCanvases, fetchInfoResponse: mockFnCanvas2, window: { canvasIndex: 2, view: 'single' }, }, @@ -171,9 +138,11 @@ describe('WindowViewer', () => { }); it('calls fetchAnnotation when otherContent is present', () => { const mockFnAnno = jest.fn(); - canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases(); + const canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases(); + currentCanvases = [canvases[0]]; + wrapper = createWrapper( - { canvases, fetchAnnotation: mockFnAnno }, + { currentCanvases, fetchAnnotation: mockFnAnno }, ); expect(mockFnAnno).toHaveBeenCalledTimes(1); }); @@ -182,16 +151,17 @@ describe('WindowViewer', () => { describe('componentDidUpdate', () => { it('does not call fetchInfoResponse for a canvas that has no images', () => { const mockFn = jest.fn(); - canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases(); + const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases(); + currentCanvases = [canvases[2]]; mockWindow = { canvasIndex: 2, view: 'single', }; wrapper = createWrapper( - { canvases, fetchInfoResponse: mockFn, window: mockWindow }, + { currentCanvases, fetchInfoResponse: mockFn, window: mockWindow }, ); - wrapper.setProps({ window: { canvasIndex: 3, view: 'single' } }); + wrapper.setProps({ currentCanvases: [canvases[3]], window: { canvasIndex: 3, view: 'single' } }); expect(mockFn).toHaveBeenCalledTimes(0); }); diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js index 4c1f9533088aa6d75e18b69215982d846135eb9c..2fa636830ef3cb6f297fb21d0e29ce32c74e9f0a 100644 --- a/src/components/OpenSeadragonViewer.js +++ b/src/components/OpenSeadragonViewer.js @@ -132,11 +132,11 @@ export class OpenSeadragonViewer extends Component { * annotationsToContext - converts anontations to a canvas context */ annotationsToContext(annotations) { - const { currentCanvases } = this.props; + const { canvasWorld } = this.props; const context = this.osdCanvasOverlay.context2d; annotations.forEach((annotation) => { annotation.resources.forEach((resource) => { - const offset = new CanvasWorld(currentCanvases).offsetByCanvas(resource.targetId); + const offset = canvasWorld.offsetByCanvas(resource.targetId); const fragment = resource.fragmentSelector; fragment[0] += offset.x; context.strokeStyle = 'yellow'; @@ -149,7 +149,7 @@ export class OpenSeadragonViewer extends Component { /** */ addTileSource(tileSource, i = 0) { - const { currentCanvases } = this.props; + const { canvasWorld } = this.props; return new Promise((resolve, reject) => { if (!this.viewer) { return; @@ -157,7 +157,7 @@ export class OpenSeadragonViewer extends Component { this.viewer.addTiledImage({ error: event => reject(event), fitBounds: new OpenSeadragon.Rect( - ...new CanvasWorld(currentCanvases).canvasToWorldCoordinates(i), + ...canvasWorld.canvasToWorldCoordinates(i), ), success: event => resolve(event), tileSource, @@ -197,8 +197,8 @@ export class OpenSeadragonViewer extends Component { * zoomToWorld - zooms the viewer to the extent of the canvas world */ zoomToWorld(immediately = true) { - const { currentCanvases } = this.props; - this.fitBounds(...new CanvasWorld(currentCanvases).worldBounds(), immediately); + const { canvasWorld } = this.props; + this.fitBounds(...canvasWorld.worldBounds(), immediately); } /** @@ -262,7 +262,6 @@ OpenSeadragonViewer.defaultProps = { annotations: [], children: null, classes: {}, - currentCanvases: [], label: null, tileSources: [], viewer: null, @@ -271,9 +270,9 @@ OpenSeadragonViewer.defaultProps = { OpenSeadragonViewer.propTypes = { annotations: PropTypes.arrayOf(PropTypes.object), + canvasWorld: PropTypes.instanceOf(CanvasWorld).isRequired, children: PropTypes.element, classes: PropTypes.object, // eslint-disable-line react/forbid-prop-types - currentCanvases: PropTypes.arrayOf(PropTypes.object), label: PropTypes.string, t: PropTypes.func.isRequired, tileSources: PropTypes.arrayOf(PropTypes.object), diff --git a/src/components/WindowCanvasNavigationControls.js b/src/components/WindowCanvasNavigationControls.js index f8315c7070d58e412c5cfddca70c6623ad48c5a9..db6ddaad9cb6fd1562dc5caa4da00ac99376846b 100644 --- a/src/components/WindowCanvasNavigationControls.js +++ b/src/components/WindowCanvasNavigationControls.js @@ -12,7 +12,7 @@ export class WindowCanvasNavigationControls extends Component { /** */ render() { const { - canvases, visible, window, zoomToWorld, + visible, window, zoomToWorld, } = this.props; if (!visible) return (<></>); @@ -20,7 +20,7 @@ export class WindowCanvasNavigationControls extends Component { return ( <div className={ns('canvas-nav')}> <ZoomControls windowId={window.id} zoomToWorld={zoomToWorld} /> - <ViewerNavigation window={window} canvases={canvases} /> + <ViewerNavigation window={window} /> <ViewerInfo windowId={window.id} /> </div> ); @@ -29,7 +29,6 @@ export class WindowCanvasNavigationControls extends Component { WindowCanvasNavigationControls.propTypes = { - canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types visible: PropTypes.bool, window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types zoomToWorld: PropTypes.func.isRequired, diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js index 87fabc8dc80882e46edc6007e30d8d98a4c8e7bd..65337429efa7c7d97c8c9eb0b4bb76826abdb08b 100644 --- a/src/components/WindowViewer.js +++ b/src/components/WindowViewer.js @@ -3,33 +3,21 @@ import PropTypes from 'prop-types'; import OSDViewer from '../containers/OpenSeadragonViewer'; import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls'; import ManifestoCanvas from '../lib/ManifestoCanvas'; -import CanvasGroupings from '../lib/CanvasGroupings'; /** * Represents a WindowViewer in the mirador workspace. Responsible for mounting * OSD and Navigation */ export class WindowViewer extends Component { - /** - * @param {Object} props - */ - constructor(props) { - super(props); - - const { canvases, window } = this.props; - this.canvases = canvases; - this.canvasGroupings = new CanvasGroupings(this.canvases, window.view); - } - /** * componentDidMount - React lifecycle method * Request the initial canvas on mount */ componentDidMount() { - const { fetchInfoResponse, fetchAnnotation } = this.props; + const { currentCanvases, fetchInfoResponse, fetchAnnotation } = this.props; if (!this.infoResponseIsInStore()) { - this.currentCanvases().forEach((canvas) => { + currentCanvases.forEach((canvas) => { const manifestoCanvas = new ManifestoCanvas(canvas); const { imageInformationUri } = manifestoCanvas; if (imageInformationUri) { @@ -47,11 +35,14 @@ export class WindowViewer extends Component { * Request a new canvas if it is needed */ componentDidUpdate(prevProps) { - const { window, fetchInfoResponse, fetchAnnotation } = this.props; + const { + currentCanvases, window, fetchInfoResponse, fetchAnnotation, + } = this.props; + if (prevProps.window.view !== window.view || (prevProps.window.canvasIndex !== window.canvasIndex && !this.infoResponseIsInStore()) ) { - this.currentCanvases().forEach((canvas) => { + currentCanvases.forEach((canvas) => { const manifestoCanvas = new ManifestoCanvas(canvas); const { imageInformationUri } = manifestoCanvas; if (imageInformationUri) { @@ -62,10 +53,6 @@ export class WindowViewer extends Component { }); }); } - // If the view changes, create a new instance - if (prevProps.window.view !== window.view) { - this.canvasGroupings = new CanvasGroupings(this.canvases, window.view); - } } /** @@ -74,29 +61,22 @@ export class WindowViewer extends Component { * @return [Boolean] */ infoResponseIsInStore() { + const { currentCanvases } = this.props; + const responses = this.currentInfoResponses(); - if (responses.length === this.currentCanvases().length) { + if (responses.length === currentCanvases.length) { return true; } return false; } - /** - * Uses CanvasGroupings to figure out how many and what canvases to present to - * a user based off of the view, number of canvases, and canvasIndex. - */ - currentCanvases() { - const { window } = this.props; - return this.canvasGroupings.getCanvases(window.canvasIndex); - } - /** * currentInfoResponses - Selects infoResponses that are relevent to existing * canvases to be displayed. */ currentInfoResponses() { - const { infoResponses } = this.props; - const currentCanvases = this.currentCanvases(); + const { currentCanvases, infoResponses } = this.props; + return currentCanvases.map(canvas => ( infoResponses[new ManifestoCanvas(canvas).imageInformationUri] )).filter(infoResponse => (infoResponse !== undefined @@ -108,10 +88,12 @@ export class WindowViewer extends Component { * Return an image information response from the store for the correct image */ tileInfoFetchedFromStore() { + const { currentCanvases } = this.props; + const responses = this.currentInfoResponses() .map(infoResponse => infoResponse.json); // Only return actual tileSources when all current canvases have completed. - if (responses.length === this.currentCanvases().length) { + if (responses.length === currentCanvases.length) { return responses; } return []; @@ -126,10 +108,9 @@ export class WindowViewer extends Component { <> <OSDViewer tileSources={this.tileInfoFetchedFromStore()} - currentCanvases={this.currentCanvases()} windowId={window.id} > - <WindowCanvasNavigationControls windowId={window.id} canvases={this.canvases} /> + <WindowCanvasNavigationControls windowId={window.id} /> </OSDViewer> </> ); @@ -137,7 +118,7 @@ export class WindowViewer extends Component { } WindowViewer.propTypes = { - canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types + currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types fetchAnnotation: PropTypes.func.isRequired, fetchInfoResponse: PropTypes.func.isRequired, infoResponses: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index 392f9d6e64aa0aabdca3c3ed2ebe6fab1fabc799..a4426d2b4bdb414b3664d8778c235b36578fa1b5 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -6,10 +6,12 @@ import { fade } from '@material-ui/core/styles/colorManipulator'; import { withPlugins } from '../extend'; import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer'; import * as actions from '../state/actions'; +import CanvasWorld from '../lib/CanvasWorld'; import { getAllOrSelectedAnnotations, getCanvasLabel, getSelectedAnnotationIds, + getSelectedCanvases, } from '../state/selectors'; /** @@ -17,23 +19,16 @@ import { * @memberof Window * @private */ -const mapStateToProps = ({ - viewers, windows, manifests, annotations, -}, { windowId, currentCanvases }) => ({ +const mapStateToProps = (state, { windowId }) => ({ annotations: getAllOrSelectedAnnotations( - { annotations, windows }, + state, windowId, - currentCanvases.map(c => c.id), - getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)), + getSelectedCanvases(state, { windowId }), + getSelectedAnnotationIds(state, windowId, getSelectedCanvases(state, { windowId })), ), - label: getCanvasLabel({ - manifests, - windows, - }, { - canvasIndex: 'selected', - windowId, - }), - viewer: viewers[windowId], + canvasWorld: new CanvasWorld(getSelectedCanvases(state, { windowId })), + label: getCanvasLabel(state, { canvasIndex: 'selected', windowId }), + viewer: state.viewers[windowId], }); /** diff --git a/src/containers/ViewerNavigation.js b/src/containers/ViewerNavigation.js index d1edbed061c9122b2d08c78cebf871e955bd42ee..e5e804d7b0aa9163a657bd7f5a2f2af663cc0c76 100644 --- a/src/containers/ViewerNavigation.js +++ b/src/containers/ViewerNavigation.js @@ -3,8 +3,14 @@ import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withPlugins } from '../extend'; import * as actions from '../state/actions'; +import { getManifestCanvases } from '../state/selectors'; import { ViewerNavigation } from '../components/ViewerNavigation'; +/** */ +const mapStateToProps = (state, { window }) => ({ + canvases: getManifestCanvases(state, { windowId: window.id }), +}); + /** * mapDispatchToProps - used to hook up connect to action creators * @memberof ManifestForm @@ -16,7 +22,7 @@ const mapDispatchToProps = { const enhance = compose( withTranslation(), - connect(null, mapDispatchToProps), + connect(mapStateToProps, mapDispatchToProps), withPlugins('ViewerNavigation'), // further HOC go here ); diff --git a/src/containers/WindowViewer.js b/src/containers/WindowViewer.js index 0aa4e8a7cc6cd761465f1be2810bebb7f6db1e71..1f109343d74a4d9a0dd4d78b91f3a1d3723e604e 100644 --- a/src/containers/WindowViewer.js +++ b/src/containers/WindowViewer.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { withPlugins } from '../extend'; import * as actions from '../state/actions'; import { WindowViewer } from '../components/WindowViewer'; -import { getManifestCanvases } from '../state/selectors'; +import { getSelectedCanvases } from '../state/selectors'; /** * mapStateToProps - to hook up connect @@ -12,7 +12,7 @@ import { getManifestCanvases } from '../state/selectors'; */ const mapStateToProps = (state, { window }) => ( { - canvases: getManifestCanvases(state, { windowId: window.id }), + currentCanvases: getSelectedCanvases(state, { windowId: window.id }), infoResponses: state.infoResponses, } );