diff --git a/__tests__/integration/mirador/index.html b/__tests__/integration/mirador/index.html index 0d1d65e24fa85abd8f7eb23399e921b323f83fa8..742a85cbf81c7a18386d46e1feb477114920e1ef 100644 --- a/__tests__/integration/mirador/index.html +++ b/__tests__/integration/mirador/index.html @@ -19,7 +19,23 @@ { loadedManifest: 'https://iiif.bodleian.ox.ac.uk/iiif/manifest/e32a277e-91e2-4a6d-8ba6-cc4bad230410.json', thumbnailNavigationPosition: 'off', - }] + }], + manifests: { + "http://media.nga.gov/public/manifests/nga_highlights.json": { provider: "National Gallery of Art"}, + "https://data.ucd.ie/api/img/manifests/ucdlib:33064": { provider: "Irish Architectural Archive"}, + "https://wellcomelibrary.org/iiif/b18035723/manifest": { provider: "Wellcome Library"}, + "http://dams.llgc.org.uk/iiif/2.0/4389767/manifest.json": { provider: "The National Library of Wales"}, + "https://demos.biblissima.fr/iiif/metadata/florus-dispersus/manifest.json": { provider: "Biblissima"}, + "http://beta.biblissima.fr/iiif/manifest/ark:/43093/desc57cb76cd3739a24a9277b6669d95b5f3a590e771": { provider: "Biblissima"}, + "https://www.e-codices.unifr.ch/metadata/iiif/gau-Fragment/manifest.json": { provider: "e-codices - Virtual Manuscript Library of Switzerland"}, + "https://wellcomelibrary.org/iiif/collection/b18031511": { provider: "Wellcome Library"}, + "https://gallica.bnf.fr/iiif/ark:/12148/btv1b10022508f/manifest.json": { provider: "Bibliothèque nationale de France"}, + "https://manifests.britishart.yale.edu/Osbornfa1": { provider: "Beinecke Rare Book and Manuscript Library, Yale University"}, + "https://iiif.biblissima.fr/chateauroux/B360446201_MS0005/manifest.json": { provider: "Biblissima"}, + "https://iiif.durham.ac.uk/manifests/trifle/32150/t1/m4/q7/t1m4q77fr328/manifest": { provider: "Durham University Library"}, + // "https://iiif.vam.ac.uk/collections/O1023003/manifest.json": { provider: "Ocean liners"}, + "http://storiiies.cogapp.com/holbein/manifest.json": { provider: "National Gallery, London"} + } }); </script> </body> diff --git a/__tests__/src/actions/manifest.test.js b/__tests__/src/actions/manifest.test.js index 2fba736d9629f0f8b01259f65e66413969c834cb..4cf1fdccfd713cb8a1e534bd406e22d60ba568e6 100644 --- a/__tests__/src/actions/manifest.test.js +++ b/__tests__/src/actions/manifest.test.js @@ -45,7 +45,7 @@ describe('manifest actions', () => { it('dispatches the REQUEST_MANIFEST action', () => { store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest')); expect(store.getActions()).toEqual([ - { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' }, + { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST', properties: { isFetching: true } }, ]); }); it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => { diff --git a/__tests__/src/components/ManifestListItem.test.js b/__tests__/src/components/ManifestListItem.test.js index 16c46918d7aafe79b6d60d2453520b3f2295679e..b6084286d9edeef8100f8609366faa124823f58b 100644 --- a/__tests__/src/components/ManifestListItem.test.js +++ b/__tests__/src/components/ManifestListItem.test.js @@ -2,60 +2,60 @@ import React from 'react'; import { shallow } from 'enzyme'; import ManifestListItem from '../../../src/components/ManifestListItem'; +/** */ +function createWrapper(props) { + return shallow( + <ManifestListItem + manifestId="http://example.com" + title="xyz" + ready + addWindow={() => {}} + fetchManifest={() => {}} + t={t => t} + {...props} + />, + ).dive(); // to unwrapp HOC created by withStyle() +} + describe('ManifestListItem', () => { it('renders without an error', () => { - const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem - manifestId="http://example.com" - title="xyz" - ready - addWindow={addWindow} - t={t => t} - />, - ).dive(); + const wrapper = createWrapper(); expect(wrapper.find('.mirador-manifest-list-item').length).toBe(1); expect(wrapper.find('WithStyles(ButtonBase)').length).toBe(1); expect(wrapper.find('WithStyles(ButtonBase) WithStyles(Typography)').children().text()).toEqual('xyz'); }); it('renders a placeholder element until real data is available', () => { - const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem manifestId="http://example.com" addWindow={addWindow} />, - ).dive(); + const wrapper = createWrapper({ ready: false }); + expect(wrapper.find('.mirador-manifest-list-item').length).toBe(1); expect(wrapper.find('ReactPlaceholder').length).toBe(1); }); + it('renders an error message if fetching the manifest failed', () => { + const wrapper = createWrapper({ error: 'This is an error message' }); + + expect(wrapper.find('WithStyles(Paper)').length).toBe(1); + expect(wrapper.find('WithStyles(Paper)').children().text()).toEqual('This is an error message'); + }); it('updates and adds window when button clicked', () => { const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem manifestId="http://example.com" title="xyz" addWindow={addWindow} />, - ).dive(); + const wrapper = createWrapper({ addWindow }); wrapper.find('WithStyles(ButtonBase)').simulate('click'); expect(addWindow).toHaveBeenCalledTimes(1); }); it('uses the manifest id if the title is not available', () => { - const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem manifestId="http://example.com" ready addWindow={addWindow} />, - ).dive(); + const wrapper = createWrapper({ ready: true, title: null }); + expect(wrapper.find('WithStyles(ButtonBase)').length).toBe(1); expect(wrapper.find('WithStyles(ButtonBase) WithStyles(Typography)').children().text()).toEqual('http://example.com'); }); it('displays the provider information', () => { - const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem manifestId="http://example.com" ready provider="ACME" addWindow={addWindow} />, - ).dive(); + const wrapper = createWrapper({ provider: 'ACME' }); expect(wrapper.find('WithStyles(Typography).mirador-manifest-list-item-provider').children().text()).toEqual('ACME'); }); it('displays a placeholder provider if no information is given', () => { - const addWindow = jest.fn(); - const wrapper = shallow( - <ManifestListItem manifestId="http://example.com" ready addWindow={addWindow} />, - ).dive(); + const wrapper = createWrapper(); expect(wrapper.find('WithStyles(Typography).mirador-manifest-list-item-provider').children().text()).toEqual('addedFromUrl'); }); }); diff --git a/__tests__/src/lib/MiradorViewer.test.js b/__tests__/src/lib/MiradorViewer.test.js index 58b2ccb21654eb36bd422654e88d8ad7cf1bb119..6a30dfd17342c41fb8d4046502d2e03b4d232afb 100644 --- a/__tests__/src/lib/MiradorViewer.test.js +++ b/__tests__/src/lib/MiradorViewer.test.js @@ -44,19 +44,23 @@ describe('MiradorViewer', () => { it('transforms config values to actions to dispatch to store', () => { instance = new MiradorViewer({ id: 'mirador', - windows: [{ - loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', - canvasIndex: 2, - }, - { - loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', - thumbnailNavigationPosition: 'off', - view: 'book', - }, + windows: [ + { + loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', + canvasIndex: 2, + }, + { + loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', + thumbnailNavigationPosition: 'off', + view: 'book', + }, ], + manifests: { + 'http://media.nga.gov/public/manifests/nga_highlights.json': { provider: 'National Gallery of Art' }, + }, }); - const { windows } = instance.store.getState(); + const { windows, manifests } = instance.store.getState(); const windowIds = Object.keys(windows); expect(Object.keys(windowIds).length).toBe(2); expect(windows[windowIds[0]].canvasIndex).toBe(2); @@ -65,6 +69,10 @@ describe('MiradorViewer', () => { expect(windows[windowIds[1]].thumbnailNavigationPosition).toBe('off'); expect(windows[windowIds[0]].view).toBe('single'); expect(windows[windowIds[1]].view).toBe('book'); + + const manifestIds = Object.keys(manifests); + expect(Object.keys(manifestIds).length).toBe(2); + expect(manifests['http://media.nga.gov/public/manifests/nga_highlights.json'].provider).toBe('National Gallery of Art'); }); }); }); diff --git a/__tests__/src/reducers/manifests.test.js b/__tests__/src/reducers/manifests.test.js index 3eb3c25c209cb2af4f2565721fd2f97c25b17f42..21cbe9bc166dd990d9b6451aab00807e773dad93 100644 --- a/__tests__/src/reducers/manifests.test.js +++ b/__tests__/src/reducers/manifests.test.js @@ -9,7 +9,6 @@ describe('manifests reducer', () => { })).toEqual({ abc123: { id: 'abc123', - isFetching: true, }, }); }); diff --git a/src/components/ManifestListItem.js b/src/components/ManifestListItem.js index ffdbce53f880079801cd7ff7e2af2835665177b8..ade9413cd04a8f91e1062293673c9c11d96b07f7 100644 --- a/src/components/ManifestListItem.js +++ b/src/components/ManifestListItem.js @@ -25,10 +25,30 @@ const handleOpenButtonClick = (event, manifest, addWindow) => { /** */ class ManifestListItem extends React.Component { + /** */ + componentDidMount() { + const { + fetchManifest, manifestId, ready, isFetching, error, + } = this.props; + + if (!ready && !error && !isFetching) fetchManifest(manifestId); + } + /** */ render() { const { - manifestId, ready, title, thumbnail, logo, addWindow, handleClose, size, classes, provider, t, + manifestId, + ready, + title, + thumbnail, + logo, + addWindow, + handleClose, + size, + classes, + provider, + t, + error, } = this.props; const placeholder = ( @@ -48,6 +68,14 @@ class ManifestListItem extends React.Component { </Grid> ); + if (error) { + return ( + <Paper elevation={1} className={classes.root} data-manifestid={manifestId}> + {error} + </Paper> + ); + } + return ( <Paper elevation={1} className={classes.root} data-manifestid={manifestId}> <ReactPlaceholder @@ -113,6 +141,9 @@ ManifestListItem.propTypes = { classes: PropTypes.object, // eslint-disable-line react/forbid-prop-types provider: PropTypes.string, t: PropTypes.func, + fetchManifest: PropTypes.func.isRequired, + error: PropTypes.string, + isFetching: PropTypes.bool, }; ManifestListItem.defaultProps = { @@ -125,6 +156,8 @@ ManifestListItem.defaultProps = { size: 0, provider: null, t: key => key, + error: null, + isFetching: false, }; /** */ diff --git a/src/containers/ManifestListItem.js b/src/containers/ManifestListItem.js index 562f1e3c64bae771f1f56b95e5b95e6d298dbc0e..bd49edaf081db55f7e97be8c193f390ffde2b115 100644 --- a/src/containers/ManifestListItem.js +++ b/src/containers/ManifestListItem.js @@ -13,6 +13,8 @@ const mapStateToProps = (state, { manifestId }) => { return { ready: !!manifest.manifestation, + error: manifest.error, + isFetching: manifest.isFetching, title: getManifestTitle(manifest), logo: getManifestLogo(manifest), thumbnail: getManifestThumbnail(manifest), @@ -26,7 +28,7 @@ const mapStateToProps = (state, { manifestId }) => { * @memberof ManifestListItem * @private */ -const mapDispatchToProps = { addWindow: actions.addWindow }; +const mapDispatchToProps = { addWindow: actions.addWindow, fetchManifest: actions.fetchManifest }; const enhance = compose( connect(mapStateToProps, mapDispatchToProps), diff --git a/src/lib/MiradorViewer.js b/src/lib/MiradorViewer.js index 01740b4c571ca757345f49bd0fd4b420bf2e7c2a..30e9b27f9dea2bf1a04bb9972f189068f372f877 100644 --- a/src/lib/MiradorViewer.js +++ b/src/lib/MiradorViewer.js @@ -63,6 +63,12 @@ class MiradorViewer { thumbnailNavigationPosition, })); }); + + Object.keys(mergedConfig.manifests || {}).forEach((manifestId) => { + this.store.dispatch( + actions.requestManifest(manifestId, mergedConfig.manifests[manifestId]), + ); + }); } /** diff --git a/src/state/actions/manifest.js b/src/state/actions/manifest.js index b41a4f75f71f5e78e48f43c62f9f400b2e5634b5..beda211f010c8bbde7daeb50c4417d7e5e1cb984 100644 --- a/src/state/actions/manifest.js +++ b/src/state/actions/manifest.js @@ -7,10 +7,11 @@ import ActionTypes from './action-types'; * @param {String} manifestId * @memberof ActionCreators */ -export function requestManifest(manifestId) { +export function requestManifest(manifestId, properties) { return { type: ActionTypes.REQUEST_MANIFEST, manifestId, + properties, }; } @@ -50,9 +51,10 @@ export function receiveManifestFailure(manifestId, error) { * @param {String} manifestId * @memberof ActionCreators */ -export function fetchManifest(manifestId) { +export function fetchManifest(manifestId, properties) { return ((dispatch) => { - dispatch(requestManifest(manifestId)); + dispatch(requestManifest(manifestId, { ...properties, isFetching: true })); + return fetch(manifestId) .then(response => response.json()) .then(json => dispatch(receiveManifest(manifestId, json))) diff --git a/src/state/reducers/manifests.js b/src/state/reducers/manifests.js index bc28fad664b449f40aa3f8a419a58daf3ffaa69b..5b1c0ba73bae927896e3f926cdd011719b136d2a 100644 --- a/src/state/reducers/manifests.js +++ b/src/state/reducers/manifests.js @@ -10,14 +10,16 @@ export const manifestsReducer = (state = {}, action) => { return { ...state, [action.manifestId]: { + ...state[action.manifestId], + ...action.properties, id: action.manifestId, - isFetching: true, }, }; case ActionTypes.RECEIVE_MANIFEST: return { ...state, [action.manifestId]: { + ...state[action.manifestId], id: action.manifestId, manifestation: manifesto.create(action.manifestJson), isFetching: false, @@ -27,6 +29,7 @@ export const manifestsReducer = (state = {}, action) => { return { ...state, [action.manifestId]: { + ...state[action.manifestId], id: action.manifestId, error: action.error, isFetching: false, diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js index a09bafd661f4ef2fadfc976af5a87ec833f773b2..557d5d3b4946455dad56fcb12a34cfd07ad6965e 100644 --- a/src/state/selectors/index.js +++ b/src/state/selectors/index.js @@ -60,6 +60,10 @@ export function getManifestCanvases(manifest) { return []; } + if (!manifest.manifestation.getSequences || !manifest.manifestation.getSequences()[0]) { + return []; + } + return manifest.manifestation.getSequences()[0].getCanvases(); }