diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js index 8414de7fc16593d5e4df5590747add73f5dc5442..373ca1d41366e07eef2a0b194a9d89f0056bff55 100644 --- a/__tests__/src/components/WindowViewer.test.js +++ b/__tests__/src/components/WindowViewer.test.js @@ -1,24 +1,13 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import { WindowViewer } from '../../../src/components/WindowViewer'; import OSDViewer from '../../../src/containers/OpenSeadragonViewer'; import WindowCanvasNavigationControls from '../../../src/containers/WindowCanvasNavigationControls'; -import fixture from '../../fixtures/version-2/019.json'; -import emptyCanvasFixture from '../../fixtures/version-2/emptyCanvas.json'; - -let currentCanvases = Utils.parseManifest(fixture).getSequences()[0].getCanvases(); /** create wrapper */ function createWrapper(props) { return shallow( <WindowViewer - canvasIndex={0} - canvasLabel="label" - infoResponses={{}} - fetchInfoResponse={() => {}} - currentCanvases={[currentCanvases[1]]} - view="single" windowId="xyz" {...props} />, @@ -35,111 +24,4 @@ describe('WindowViewer', () => { </OSDViewer>, )).toBe(true); }); - describe('currentInfoResponses', () => { - describe('returns only available infoResponses', () => { - it('isFetching is false', () => { - wrapper = createWrapper( - { - currentCanvasId: 1, - infoResponses: { - 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': { - isFetching: false, - }, - 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410': { - isFetching: true, - }, - }, - view: 'book', - }, - ); - expect(wrapper.instance().currentInfoResponses().length).toEqual(1); - }); - it('infoResponse is undefined', () => { - wrapper = createWrapper( - { - currentCanvasId: 1, - infoResponses: { - foo: { - isFetching: false, - }, - 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': { - isFetching: false, - }, - }, - view: 'book', - }, - ); - expect(wrapper.instance().currentInfoResponses().length).toEqual(1); - }); - it('error is not present', () => { - wrapper = createWrapper( - { - currentCanvasId: 1, - infoResponses: { - 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': { - isFetching: false, - }, - 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410': { - error: 'yikes!', - isFetching: false, - }, - }, - view: 'book', - }, - ); - expect(wrapper.instance().currentInfoResponses().length).toEqual(1); - }); - }); - }); - - describe('componentDidMount', () => { - it('does not call fetchInfoResponse for a canvas that has no images', () => { - const mockFnCanvas0 = jest.fn(); - const mockFnCanvas2 = jest.fn(); - const canvases = Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases(); - - currentCanvases = [canvases[0]]; - - wrapper = createWrapper( - { - currentCanvases, - currentCanvasId: 0, - fetchInfoResponse: mockFnCanvas0, - view: 'single', - }, - ); - expect(mockFnCanvas0).toHaveBeenCalledTimes(1); - - currentCanvases = [canvases[2]]; - wrapper = createWrapper( - { - currentCanvases, - currentCanvasId: 2, - fetchInfoResponse: mockFnCanvas2, - view: 'single', - }, - ); - expect(mockFnCanvas2).toHaveBeenCalledTimes(0); - }); - }); - - describe('componentDidUpdate', () => { - it('does not call fetchInfoResponse for a canvas that has no images', () => { - const mockFn = jest.fn(); - const canvases = Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases(); - currentCanvases = [canvases[2]]; - wrapper = createWrapper( - { - currentCanvases, - currentCanvasId: 2, - fetchInfoResponse: mockFn, - view: 'single', - }, - ); - - wrapper.setProps({ currentCanvases: [canvases[3]], currentCanvasId: 3, view: 'single' }); - - expect(mockFn).toHaveBeenCalledTimes(0); - }); - }); }); diff --git a/__tests__/src/sagas/windows.test.js b/__tests__/src/sagas/windows.test.js index b557fda944ebf815500d18394bbfdd6fcb2bb84a..772051fa976ff57e3b80b9ecf104fd4f461ddc3f 100644 --- a/__tests__/src/sagas/windows.test.js +++ b/__tests__/src/sagas/windows.test.js @@ -13,6 +13,7 @@ import { getSelectedContentSearchAnnotationIds, getSortedSearchAnnotationsForCompanionWindow, getVisibleCanvasIds, getCanvasForAnnotation, + getCanvases, selectInfoResponses, } from '../../../src/state/selectors'; import { fetchManifest } from '../../../src/state/sagas/iiif'; import { @@ -25,6 +26,7 @@ import { setCanvasforSelectedAnnotation, panToFocusedWindow, setCurrentAnnotationsOnCurrentCanvas, + fetchInfoResponses, } from '../../../src/state/sagas/windows'; import fixture from '../../fixtures/version-2/019.json'; @@ -398,4 +400,50 @@ describe('window-level sagas', () => { .run().then(({ allEffects }) => allEffects.length === 0); }); }); + + describe('fetchInfoResponses', () => { + it('requests info responses for each visible canvas', () => { + const action = { + visibleCanvases: ['http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json'], + windowId: 'foo', + }; + + const manifest = Utils.parseManifest(fixture); + + return expectSaga(fetchInfoResponses, action) + .provide([ + [select(getCanvases, { windowId: 'foo' }), manifest.getSequences()[0].getCanvases()], + [select(selectInfoResponses), {}], + ]) + .put.like({ + action: { + infoId: 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44', + type: 'mirador/REQUEST_INFO_RESPONSE', + }, + }) + .run(); + }); + + it('requests nothing if the response is already in the store', () => { + const action = { + visibleCanvases: ['http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json'], + windowId: 'foo', + }; + + const manifest = Utils.parseManifest(fixture); + + return expectSaga(fetchInfoResponses, action) + .provide([ + [select(getCanvases, { windowId: 'foo' }), manifest.getSequences()[0].getCanvases()], + [select(selectInfoResponses), { 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44': {} }], + ]) + .not.put.like({ + action: { + infoId: 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44', + type: 'mirador/REQUEST_INFO_RESPONSE', + }, + }) + .run(); + }); + }); }); diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js index beeba092c5197419ef70a1961de71761133bd95c..7a7d1acc697ea00338b6ce34db34dad0c2316cb9 100644 --- a/src/components/WindowViewer.js +++ b/src/components/WindowViewer.js @@ -1,10 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import difference from 'lodash/difference'; -import flatten from 'lodash/flatten'; import OSDViewer from '../containers/OpenSeadragonViewer'; import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls'; -import MiradorCanvas from '../lib/MiradorCanvas'; /** * Represents a WindowViewer in the mirador workspace. Responsible for mounting @@ -23,91 +20,6 @@ export class WindowViewer extends Component { return { hasError: true }; } - /** - * componentDidMount - React lifecycle method - * Request the initial canvas on mount - */ - componentDidMount() { - const { - currentCanvases, fetchInfoResponse, - } = this.props; - - if (!this.infoResponseIsInStore()) { - currentCanvases.forEach((canvas) => { - const miradorCanvas = new MiradorCanvas(canvas); - miradorCanvas.iiifImageResources.forEach((imageResource) => { - fetchInfoResponse({ imageResource }); - }); - }); - } - } - - /** - * componentDidUpdate - React lifecycle method - * Request a new canvas if it is needed - */ - componentDidUpdate(prevProps) { - const { - currentCanvases, fetchInfoResponse, - } = this.props; - - if (difference(currentCanvases, prevProps.currentCanvases).length > 0 - && !this.infoResponseIsInStore()) { - currentCanvases.forEach((canvas) => { - const miradorCanvas = new MiradorCanvas(canvas); - miradorCanvas.iiifImageResources.forEach((imageResource) => { - fetchInfoResponse({ imageResource }); - }); - }); - } - } - - /** - * infoResponseIsInStore - checks whether or not an info response is already - * in the store. No need to request it again. - * @return [Boolean] - */ - infoResponseIsInStore() { - const responses = this.currentInfoResponses(); - if (responses.length === this.imageServiceIds().length) { - return true; - } - return false; - } - - /** */ - imageServiceIds() { - const { currentCanvases } = this.props; - - return flatten(currentCanvases.map(canvas => new MiradorCanvas(canvas).imageServiceIds)); - } - - /** - * currentInfoResponses - Selects infoResponses that are relevent to existing - * canvases to be displayed. - */ - currentInfoResponses() { - const { infoResponses } = this.props; - - return this.imageServiceIds().map(imageId => ( - infoResponses[imageId] - )).filter(infoResponse => (infoResponse !== undefined - && infoResponse.isFetching === false - && infoResponse.error === undefined)); - } - - /** - * Return an image information response from the store for the correct image - */ - infoResponsesFetchedFromStore() { - const responses = this.currentInfoResponses(); - // Only return actual tileSources when all current canvases have completed. - if (responses.length === this.imageServiceIds().length) { - return responses; - } - return []; - } - /** * Renders things */ @@ -122,20 +34,14 @@ export class WindowViewer extends Component { return ( <OSDViewer - infoResponses={this.infoResponsesFetchedFromStore()} windowId={windowId} > - <WindowCanvasNavigationControls key="canvas_nav" windowId={windowId} /> + <WindowCanvasNavigationControls windowId={windowId} /> </OSDViewer> ); } } WindowViewer.propTypes = { - 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 - receiveAnnotation: PropTypes.func.isRequired, windowId: PropTypes.string.isRequired, }; diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index 96faa482966383f25357f297faa3ed8742b7e7b7..c733eb33bce2be58a22f31805de13239a1ca870c 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -2,6 +2,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; +import flatten from 'lodash/flatten'; import { withPlugins } from '../extend/withPlugins'; import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer'; import * as actions from '../state/actions'; @@ -14,6 +15,7 @@ import { getViewer, getConfig, getCompanionWindowsForContent, + selectInfoResponses, } from '../state/selectors'; /** @@ -21,19 +23,30 @@ import { * @memberof Window * @private */ -const mapStateToProps = (state, { windowId }) => ({ - canvasWorld: new CanvasWorld( +const mapStateToProps = (state, { windowId }) => { + const canvasWorld = new CanvasWorld( getVisibleCanvases(state, { windowId }), getLayersForVisibleCanvases(state, { windowId }), getSequenceViewingDirection(state, { windowId }), - ), - drawAnnotations: getConfig(state).window.forceDrawAnnotations - || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0 - || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0, - nonTiledImages: getVisibleCanvasNonTiledResources(state, { windowId }), - osdConfig: state.config.osdConfig, - viewerConfig: getViewer(state, { windowId }), -}); + ); + + const infoResponses = selectInfoResponses(state); + const imageServiceIds = flatten(canvasWorld.canvases.map(c => c.imageServiceIds)); + + return { + canvasWorld, + drawAnnotations: getConfig(state).window.forceDrawAnnotations + || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0 + || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0, + infoResponses: imageServiceIds.map(id => infoResponses[id]) + .filter(infoResponse => (infoResponse !== undefined + && infoResponse.isFetching === false + && infoResponse.error === undefined)), + nonTiledImages: getVisibleCanvasNonTiledResources(state, { windowId }), + osdConfig: state.config.osdConfig, + viewerConfig: getViewer(state, { windowId }), + }; +}; /** * mapDispatchToProps - used to hook up connect to action creators diff --git a/src/containers/WindowViewer.js b/src/containers/WindowViewer.js index 1a687a388ab22f2532ee074ccdda52bd47893a58..d61c69c21f37a6179a505e954e7607aef4fb909a 100644 --- a/src/containers/WindowViewer.js +++ b/src/containers/WindowViewer.js @@ -3,19 +3,13 @@ import { connect } from 'react-redux'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; import { WindowViewer } from '../components/WindowViewer'; -import { getVisibleCanvases } from '../state/selectors'; /** * mapStateToProps - to hook up connect * @memberof WindowViewer * @private */ -const mapStateToProps = (state, { windowId }) => ( - { - currentCanvases: getVisibleCanvases(state, { windowId }) || [], - infoResponses: state.infoResponses, - } -); +const mapStateToProps = (state, { windowId }) => ({}); /** * mapDispatchToProps - used to hook up connect to action creators diff --git a/src/state/sagas/windows.js b/src/state/sagas/windows.js index f25da8822aff67334f43643f6d2f9fc47d1eb7a0..656a9f0947511fbb7a1dc3e69615180e1b57451c 100644 --- a/src/state/sagas/windows.js +++ b/src/state/sagas/windows.js @@ -3,6 +3,7 @@ import { } from 'redux-saga/effects'; import ActionTypes from '../actions/action-types'; import MiradorManifest from '../../lib/MiradorManifest'; +import MiradorCanvas from '../../lib/MiradorCanvas'; import { setContentSearchCurrentAnnotation, selectAnnotation, @@ -11,6 +12,7 @@ import { setCanvas, fetchSearch, receiveManifest, + fetchInfoResponse, } from '../actions'; import { getSearchForWindow, getSearchAnnotationsForCompanionWindow, @@ -22,6 +24,8 @@ import { getVisibleCanvasIds, getWorkspace, getElasticLayout, + getCanvases, + selectInfoResponses, } from '../selectors'; import { fetchManifest } from './iiif'; @@ -183,12 +187,28 @@ export function* setCanvasforSelectedAnnotation({ annotationId, windowId }) { yield put(thunk); } +/** Fetch info responses for the visible canvases */ +export function* fetchInfoResponses({ visibleCanvases: visibleCanvasIds, windowId }) { + const canvases = yield select(getCanvases, { windowId }); + const infoResponses = yield select(selectInfoResponses); + const visibleCanvases = (canvases || []).filter(c => visibleCanvasIds.includes(c.id)); + + yield all(visibleCanvases.map((canvas) => { + const miradorCanvas = new MiradorCanvas(canvas); + return all(miradorCanvas.iiifImageResources.map(imageResource => ( + !infoResponses[imageResource.getServices()[0].id] + && put(fetchInfoResponse({ imageResource })) + )).filter(Boolean)); + })); +} + /** */ export default function* windowsSaga() { yield all([ takeEvery(ActionTypes.ADD_WINDOW, fetchWindowManifest), takeEvery(ActionTypes.UPDATE_WINDOW, fetchWindowManifest), takeEvery(ActionTypes.SET_CANVAS, setCurrentAnnotationsOnCurrentCanvas), + takeEvery(ActionTypes.SET_CANVAS, fetchInfoResponses), takeEvery(ActionTypes.SET_WINDOW_VIEW_TYPE, updateVisibleCanvases), takeEvery(ActionTypes.RECEIVE_SEARCH, setCanvasOfFirstSearchResult), takeEvery(ActionTypes.SELECT_ANNOTATION, setCanvasforSelectedAnnotation),