diff --git a/__tests__/src/actions/annotation.test.js b/__tests__/src/actions/annotation.test.js index 7b0be9bf5d98d4ad5d705b9ce7540cb6223430a1..4039f02d48ef1050c7bbf30430e7b793791f6d0f 100644 --- a/__tests__/src/actions/annotation.test.js +++ b/__tests__/src/actions/annotation.test.js @@ -31,23 +31,6 @@ describe('annotation actions', () => { expect(actions.receiveAnnotation(targetId, annotationId, json)).toEqual(expectedAction); }); }); - describe('fetchAnnotation', () => { - describe('success response', () => { - beforeEach(() => { - fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); // eslint-disable-line no-undef - }); - it('dispatches the REQUEST_ANNOTATION action', () => { - expect(actions.fetchAnnotation( - 'https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174896', - 'https://iiif.harvardartmuseums.org/manifests/object/299843/list/47174896', - )).toEqual({ - annotationId: 'https://iiif.harvardartmuseums.org/manifests/object/299843/list/47174896', - targetId: 'https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174896', - type: 'mirador/REQUEST_ANNOTATION', - }); - }); - }); - }); it('handles the selectAnnotation action', () => { const windowId = 'wId1'; diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js index ba266aaa84c9b219a1e41d06ed427a6cef912139..8414de7fc16593d5e4df5590747add73f5dc5442 100644 --- a/__tests__/src/components/WindowViewer.test.js +++ b/__tests__/src/components/WindowViewer.test.js @@ -6,7 +6,6 @@ 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'; -import otherContentFixture from '../../fixtures/version-2/299843.json'; let currentCanvases = Utils.parseManifest(fixture).getSequences()[0].getCanvases(); @@ -18,7 +17,6 @@ function createWrapper(props) { canvasLabel="label" infoResponses={{}} fetchInfoResponse={() => {}} - fetchAnnotation={() => {}} currentCanvases={[currentCanvases[1]]} view="single" windowId="xyz" @@ -123,16 +121,6 @@ describe('WindowViewer', () => { ); expect(mockFnCanvas2).toHaveBeenCalledTimes(0); }); - it('calls fetchAnnotation when otherContent is present', () => { - const mockFnAnno = jest.fn(); - const canvases = Utils.parseManifest(otherContentFixture).getSequences()[0].getCanvases(); - currentCanvases = [canvases[0]]; - - wrapper = createWrapper( - { currentCanvases, fetchAnnotation: mockFnAnno }, - ); - expect(mockFnAnno).toHaveBeenCalledTimes(1); - }); }); describe('componentDidUpdate', () => { diff --git a/__tests__/src/lib/MiradorCanvas.test.js b/__tests__/src/lib/MiradorCanvas.test.js index d503b9a1d2c8dbe2e3ee9a16e71a73a3c2a9a41f..e501add183df05b52033abeca3a6e3017daa4e4a 100644 --- a/__tests__/src/lib/MiradorCanvas.test.js +++ b/__tests__/src/lib/MiradorCanvas.test.js @@ -49,27 +49,7 @@ describe('MiradorCanvas', () => { }); }); }); - describe('processAnnotations', () => { - describe('v2', () => { - it('fetches annotations for each annotationList', () => { - const otherContentInstance = new MiradorCanvas( - Utils.parseManifest(otherContentFixture).getSequences()[0].getCanvases()[0], - ); - const fetchMock = jest.fn(); - otherContentInstance.processAnnotations(fetchMock); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - }); - describe('v3', () => { - it('fetches annotations for external items and receives annotations for items that are embedded', () => { - const receiveMock = jest.fn(); - const fetchMock = jest.fn(); - v3Instance.processAnnotations(fetchMock, receiveMock); - expect(receiveMock).toHaveBeenCalledTimes(1); - expect(fetchMock).toHaveBeenCalledTimes(2); - }); - }); - }); + describe('aspectRatio', () => { it('calculates a width / height aspectRatio', () => { expect(instance.aspectRatio).toBeCloseTo(0.667); diff --git a/__tests__/src/sagas/annotations.test.js b/__tests__/src/sagas/annotations.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d99249da96145fb42eb5d3fbaaa1c7973312261a --- /dev/null +++ b/__tests__/src/sagas/annotations.test.js @@ -0,0 +1,74 @@ +import { select } from 'redux-saga/effects'; +import { expectSaga } from 'redux-saga-test-plan'; + +import { fetchAnnotations } from '../../../src/state/sagas/annotations'; +import { getAnnotations, getCanvases } from '../../../src/state/selectors'; + +describe('annotation sagas', () => { + describe('fetchAnnotations', () => { + it('requests IIIF v2-style annotations for each visible canvas', () => { + const action = { + visibleCanvases: ['a', 'b'], + windowId: 'foo', + }; + + return expectSaga(fetchAnnotations, action) + .provide([ + [select(getCanvases, { windowId: 'foo' }), [ + { __jsonld: { otherContent: 'annoId' }, id: 'a' }, + { __jsonld: { otherContent: ['alreadyFetched'] }, id: 'b' }, + ]], + [select(getAnnotations), { a: {}, b: { alreadyFetched: {} } }], + ]) + .put({ + annotationId: 'annoId', + targetId: 'a', + type: 'mirador/REQUEST_ANNOTATION', + }) + .run(); + }); + it('requests IIIF v3-style annotations for each visible canvas', () => { + const action = { + visibleCanvases: ['a', 'b'], + windowId: 'foo', + }; + + return expectSaga(fetchAnnotations, action) + .provide([ + [select(getCanvases, { windowId: 'foo' }), [ + { __jsonld: { annotations: { id: 'annoId', type: 'AnnotationPage' } }, id: 'a' }, + ]], + [select(getAnnotations), { a: {} }], + ]) + .put({ + annotationId: 'annoId', + targetId: 'a', + type: 'mirador/REQUEST_ANNOTATION', + }) + .run(); + }); + it('handles embedded IIIF v3-style annotations on each visible canvas', () => { + const action = { + visibleCanvases: ['a', 'b'], + windowId: 'foo', + }; + + const annotations = { id: 'annoId', items: [], type: 'AnnotationPage' }; + + return expectSaga(fetchAnnotations, action) + .provide([ + [select(getCanvases, { windowId: 'foo' }), [ + { __jsonld: { annotations }, id: 'a' }, + ]], + [select(getAnnotations), { a: {} }], + ]) + .put({ + annotationId: 'annoId', + annotationJson: annotations, + targetId: 'a', + type: 'mirador/RECEIVE_ANNOTATION', + }) + .run(); + }); + }); +}); diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js index 995e5f1fe86f0010bbaa93e1b321c4650d2e45dd..beeba092c5197419ef70a1961de71761133bd95c 100644 --- a/src/components/WindowViewer.js +++ b/src/components/WindowViewer.js @@ -29,7 +29,7 @@ export class WindowViewer extends Component { */ componentDidMount() { const { - currentCanvases, fetchInfoResponse, fetchAnnotation, receiveAnnotation, + currentCanvases, fetchInfoResponse, } = this.props; if (!this.infoResponseIsInStore()) { @@ -38,7 +38,6 @@ export class WindowViewer extends Component { miradorCanvas.iiifImageResources.forEach((imageResource) => { fetchInfoResponse({ imageResource }); }); - miradorCanvas.processAnnotations(fetchAnnotation, receiveAnnotation); }); } } @@ -49,7 +48,7 @@ export class WindowViewer extends Component { */ componentDidUpdate(prevProps) { const { - currentCanvases, fetchInfoResponse, fetchAnnotation, receiveAnnotation, + currentCanvases, fetchInfoResponse, } = this.props; if (difference(currentCanvases, prevProps.currentCanvases).length > 0 @@ -59,7 +58,6 @@ export class WindowViewer extends Component { miradorCanvas.iiifImageResources.forEach((imageResource) => { fetchInfoResponse({ imageResource }); }); - miradorCanvas.processAnnotations(fetchAnnotation, receiveAnnotation); }); } } diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js index 9515320efe92c59fec484ee6e4122915a9aa318f..0c69710307c16e841abfcbcea260bd389e4219a1 100644 --- a/src/lib/MiradorCanvas.js +++ b/src/lib/MiradorCanvas.js @@ -57,24 +57,6 @@ export default class MiradorCanvas { .filter(annotations => annotations && annotations.type === 'AnnotationPage'); } - /** */ - processAnnotations(fetchAnnotation, receiveAnnotation) { - // IIIF v2 - this.annotationListUris.forEach((uri) => { - fetchAnnotation(this.canvas.id, uri); - }); - // IIIF v3 - this.canvasAnnotationPages.forEach((annotation) => { - // If there are no items, try to retrieve the referenced resource. - // otherwise the resource should be embedded and just add to the store. - if (!annotation.items) { - fetchAnnotation(this.canvas.id, annotation.id); - } else { - receiveAnnotation(this.canvas.id, annotation.id, annotation); - } - }); - } - /** * Will negotiate a v2 or v3 type of resource */ diff --git a/src/state/actions/annotation.js b/src/state/actions/annotation.js index dd61cb0f325e16c9e426a2594b7aa99505f20487..1091d7e4a9b20236d4460bc19520b41296404838 100644 --- a/src/state/actions/annotation.js +++ b/src/state/actions/annotation.js @@ -49,16 +49,6 @@ export function receiveAnnotationFailure(targetId, annotationId, error) { }; } -/** - * fetchAnnotation - action creator - * - * @param {String} annotationId - * @memberof ActionCreators - */ -export function fetchAnnotation(targetId, annotationId) { - return requestAnnotation(targetId, annotationId); -} - /** * selectAnnotation - action creator * diff --git a/src/state/sagas/annotations.js b/src/state/sagas/annotations.js new file mode 100644 index 0000000000000000000000000000000000000000..885c19ead77f0ec627a4f9d87fb2ff5d3145891e --- /dev/null +++ b/src/state/sagas/annotations.js @@ -0,0 +1,45 @@ +import { + all, put, select, takeEvery, +} from 'redux-saga/effects'; +import { receiveAnnotation, requestAnnotation } from '../actions'; +import { getAnnotations, getCanvases } from '../selectors'; +import ActionTypes from '../actions/action-types'; +import MiradorCanvas from '../../lib/MiradorCanvas'; + +/** Fetch annotations for the visible canvases */ +export function* fetchAnnotations({ visibleCanvases: visibleCanvasIds, windowId }) { + const canvases = yield select(getCanvases, { windowId }); + const visibleCanvases = (canvases || []).filter(c => visibleCanvasIds.includes(c.id)); + + const annotations = yield select(getAnnotations); + + yield all(visibleCanvases.map((canvas) => { + const miradorCanvas = new MiradorCanvas(canvas); + + return all([ + // IIIF v2 + ...miradorCanvas.annotationListUris + .filter(uri => !(annotations[canvas.id] && annotations[canvas.id][uri])) + .map(uri => put(requestAnnotation(canvas.id, uri))), + // IIIF v3 + ...miradorCanvas.canvasAnnotationPages + .filter(annotation => !(annotations[canvas.id] && annotations[canvas.id][annotation.id])) + .map((annotation) => { + // If there are no items, try to retrieve the referenced resource. + // otherwise the resource should be embedded and just add to the store. + if (!annotation.items) { + return put(requestAnnotation(canvas.id, annotation.id)); + } + + return put(receiveAnnotation(canvas.id, annotation.id, annotation)); + }), + ]); + })); +} + +/** */ +export default function* appSaga() { + yield all([ + takeEvery(ActionTypes.SET_CANVAS, fetchAnnotations), + ]); +} diff --git a/src/state/sagas/index.js b/src/state/sagas/index.js index fb13462ab9f96cfbff96ee4119e06510901bd936..63ff1cac0bbe1811ed27ed1c2ae34cc16ed2a52a 100644 --- a/src/state/sagas/index.js +++ b/src/state/sagas/index.js @@ -5,6 +5,7 @@ import { import appSaga from './app'; import iiifSaga from './iiif'; import windowSaga from './windows'; +import annotations from './annotations'; /** */ function* launchSaga(saga) { @@ -22,6 +23,7 @@ function* launchSaga(saga) { function getRootSaga(pluginSagas) { return function* rootSaga() { const sagas = [ + annotations, appSaga, iiifSaga, windowSaga, diff --git a/src/state/selectors/annotations.js b/src/state/selectors/annotations.js index c80ba04fc5e06ee7b87d98aeb769124978715481..7fec25ef117cbd398c271ee803481b627ddd619f 100644 --- a/src/state/selectors/annotations.js +++ b/src/state/selectors/annotations.js @@ -4,10 +4,13 @@ import flatten from 'lodash/flatten'; import AnnotationFactory from '../../lib/AnnotationFactory'; import { getCanvas, getVisibleCanvasIds } from './canvases'; +/** */ +export const getAnnotations = state => state.annotations; + const getAnnotationsOnCanvas = createSelector( [ getCanvas, - state => state.annotations, + getAnnotations, ], (canvas, annotations) => { if (!annotations || !canvas) return [];