Skip to content
Snippets Groups Projects
Unverified Commit eb278f01 authored by Jack Reed's avatar Jack Reed Committed by GitHub
Browse files

Merge pull request #3150 from ProjectMirador/sagas

Convert the remaining component-level data fetching into sagas
parents 4ab92cf5 a8e386b4
No related branches found
No related tags found
No related merge requests found
Showing
with 279 additions and 344 deletions
......@@ -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';
......
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';
import otherContentFixture from '../../fixtures/version-2/299843.json';
let currentCanvases = Utils.parseManifest(fixture).getSequences()[0].getCanvases();
/** create wrapper */
function createWrapper(props) {
return shallow(
<WindowViewer
canvasIndex={0}
canvasLabel="label"
infoResponses={{}}
fetchInfoResponse={() => {}}
fetchAnnotation={() => {}}
currentCanvases={[currentCanvases[1]]}
view="single"
windowId="xyz"
{...props}
/>,
......@@ -37,121 +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);
});
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', () => {
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);
});
});
});
......@@ -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);
......
......@@ -5,17 +5,6 @@ jest.unmock('react-i18next');
jest.mock('react-dom');
jest.mock('isomorphic-unfetch', () => jest.fn(() => Promise.resolve({ json: () => ({}) })));
jest.mock('../../../src/state/selectors', () => ({
getCanvasGrouping: () => [],
getCompanionWindowIdsForPosition: () => ['cwid'],
getManifestoInstance: () => {},
getManifests: () => (
{ 'https://iiif.harvardartmuseums.org/manifests/object/299843': { isFetching: true } }
),
getManifestSearchService: () => ({ id: 'http://example.com/search' }),
getSearchForWindow: () => {},
}));
describe('MiradorViewer', () => {
let instance;
beforeAll(() => {
......@@ -61,7 +50,7 @@ describe('MiradorViewer', () => {
expect(windows[windowIds[0]].layoutOrder).toBe(0);
expect(windows[windowIds[1]].layoutOrder).toBe(1);
expect(windows[windowIds[0]].thumbnailNavigationPosition).toBe('far-bottom');
expect(windows[windowIds[1]].thumbnailNavigationPosition).toBe('off');
expect(windows[windowIds[1]].thumbnailNavigationPosition).toBe(undefined);
expect(windows[windowIds[0]].view).toBe(undefined);
expect(windows[windowIds[1]].view).toBe('book');
......
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();
});
});
});
import { call } from 'redux-saga/effects';
import { testSaga } from 'redux-saga-test-plan';
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import { importState } from '../../../src/state/sagas/app';
import { importConfig, importState } from '../../../src/state/sagas/app';
import { fetchManifest } from '../../../src/state/sagas/iiif';
import { fetchWindowManifest } from '../../../src/state/sagas/windows';
import { addWindow } from '../../../src/state/actions';
describe('app-level sagas', () => {
describe('importState', () => {
......@@ -52,4 +53,31 @@ describe('app-level sagas', () => {
.all([]);
});
});
describe('importConfig', () => {
it('adds windows from the provided config', () => {
const action = {
config: {
thumbnailNavigation: {},
windows: [
{ id: 'x', manifestId: 'a' },
{ id: 'y', manifestId: 'b' },
],
},
};
return expectSaga(importConfig, action)
.provide([
[call(addWindow, {
id: 'x', layoutOrder: 0, manifestId: 'a', thumbnailNavigationPosition: undefined,
}), { type: 'thunk1' }],
[call(addWindow, {
id: 'y', layoutOrder: 1, manifestId: 'b', thumbnailNavigationPosition: undefined,
}), { type: 'thunk2' }],
])
.put({ type: 'thunk1' })
.put({ type: 'thunk2' })
.run();
});
});
});
......@@ -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();
});
});
});
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,93 +20,6 @@ export class WindowViewer extends Component {
return { hasError: true };
}
/**
* componentDidMount - React lifecycle method
* Request the initial canvas on mount
*/
componentDidMount() {
const {
currentCanvases, fetchInfoResponse, fetchAnnotation, receiveAnnotation,
} = this.props;
if (!this.infoResponseIsInStore()) {
currentCanvases.forEach((canvas) => {
const miradorCanvas = new MiradorCanvas(canvas);
miradorCanvas.iiifImageResources.forEach((imageResource) => {
fetchInfoResponse({ imageResource });
});
miradorCanvas.processAnnotations(fetchAnnotation, receiveAnnotation);
});
}
}
/**
* componentDidUpdate - React lifecycle method
* Request a new canvas if it is needed
*/
componentDidUpdate(prevProps) {
const {
currentCanvases, fetchInfoResponse, fetchAnnotation, receiveAnnotation,
} = 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 });
});
miradorCanvas.processAnnotations(fetchAnnotation, receiveAnnotation);
});
}
}
/**
* 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
*/
......@@ -124,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,
};
......@@ -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 }),
),
);
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
......
......@@ -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
......
......@@ -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
*/
......
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { v4 as uuid } from 'uuid';
import HotApp from '../components/App';
import createStore from '../state/createStore';
import * as actions from '../state/actions';
......@@ -46,22 +45,6 @@ class MiradorViewer {
/** merge type for arrays */
const action = actions.importConfig(this.config);
this.store.dispatch(action);
const { config: storedConfig } = this.store.getState();
storedConfig.windows.forEach((miradorWindow, layoutOrder) => {
const windowId = `window-${uuid()}`;
const manifestId = miradorWindow.manifestId || miradorWindow.loadedManifest;
this.store.dispatch(actions.addWindow({
// these are default values ...
id: windowId,
layoutOrder,
manifestId,
thumbnailNavigationPosition: storedConfig.thumbnailNavigation.defaultPosition,
// ... overridden by values from the window configuration ...
...miradorWindow,
}));
});
}
}
......
......@@ -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
*
......
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),
]);
}
import {
all, call, takeEvery,
all, call, put, takeEvery,
} from 'redux-saga/effects';
import { v4 as uuid } from 'uuid';
import { fetchManifest } from './iiif';
import { fetchWindowManifest } from './windows';
import { addWindow } from '../actions';
import ActionTypes from '../actions/action-types';
/** */
......@@ -16,9 +18,34 @@ export function* importState(action) {
]);
}
/** Add windows from the imported config */
export function* importConfig({ config: { thumbnailNavigation, windows } }) {
if (!windows || windows.length === 0) return;
const thunks = yield all(
windows.map((miradorWindow, layoutOrder) => {
const windowId = `window-${uuid()}`;
const manifestId = miradorWindow.manifestId || miradorWindow.loadedManifest;
return call(addWindow, {
// these are default values ...
id: windowId,
layoutOrder,
manifestId,
thumbnailNavigationPosition: thumbnailNavigation && thumbnailNavigation.defaultPosition,
// ... overridden by values from the window configuration ...
...miradorWindow,
});
}),
);
yield all(thunks.map(thunk => put(thunk)));
}
/** */
export default function* appSaga() {
yield all([
takeEvery(ActionTypes.IMPORT_MIRADOR_STATE, importState),
takeEvery(ActionTypes.IMPORT_CONFIG, importConfig),
]);
}
......@@ -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,
......
......@@ -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),
......
......@@ -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 [];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment