From 8dae6b6e64ccc5e85dc6e72e94ebcf2b9d6e9b1e Mon Sep 17 00:00:00 2001 From: Lutz Helm <helm@ub.uni-leipzig.de> Date: Wed, 16 Feb 2022 09:59:22 +0100 Subject: [PATCH] Fix #3522, add selector and UI for related --- __tests__/fixtures/version-2/related.json | 16 ++++++ .../components/ManifestRelatedLinks.test.js | 46 +++++++++++++++-- __tests__/src/selectors/manifests.test.js | 49 +++++++++++++++++++ src/components/ManifestRelatedLinks.js | 36 +++++++++++--- src/containers/ManifestRelatedLinks.js | 6 ++- src/locales/de/translation.json | 1 + src/locales/en/translation.json | 1 + src/state/selectors/manifests.js | 45 ++++++++++++++++- 8 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 __tests__/fixtures/version-2/related.json diff --git a/__tests__/fixtures/version-2/related.json b/__tests__/fixtures/version-2/related.json new file mode 100644 index 000000000..f87bf9663 --- /dev/null +++ b/__tests__/fixtures/version-2/related.json @@ -0,0 +1,16 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "http://example.com/iiif/manifest/related-urls.json", + "@type": "sc:Manifest", + "related": [ + "http://example.com/related1", + { + "@id": "http://example.com/related2" + }, + { + "@id": "http://example.com/related3", + "format": "text/html", + "label": "Something related" + } + ] +} diff --git a/__tests__/src/components/ManifestRelatedLinks.test.js b/__tests__/src/components/ManifestRelatedLinks.test.js index 234b5bab9..4bf7994f7 100644 --- a/__tests__/src/components/ManifestRelatedLinks.test.js +++ b/__tests__/src/components/ManifestRelatedLinks.test.js @@ -21,6 +21,16 @@ describe('ManifestRelatedLinks', () => { }, ]} manifestUrl="http://example.com/" + related={[ + { + value: 'http://example.com/related', + }, + { + format: 'video/ogg', + label: 'Video', + value: 'http://example.com/video', + }, + ]} renderings={[ { label: 'PDF Version', @@ -81,16 +91,42 @@ describe('ManifestRelatedLinks', () => { ).toBe(true); }); - it('renders manifest seeAlso information', () => { + it('renders related information', () => { expect( wrapper.find(Typography).at(5) .matchesElement( - <Typography component="dt">iiif_seeAlso</Typography>, + <Typography component="dt">iiif_related</Typography>, ), ).toBe(true); expect( wrapper.find(Typography).at(6) + .matchesElement( + <Typography component="dd"><Link href="http://example.com/related">http://example.com/related</Link></Typography>, + ), + ).toBe(true); + + expect( + wrapper.find(Typography).at(7) + .matchesElement( + <Typography component="dd"> + <Link href="http://example.com/video">Video</Link> + <Typography>(video/ogg)</Typography> + </Typography>, + ), + ).toBe(true); + }); + + it('renders manifest seeAlso information', () => { + expect( + wrapper.find(Typography).at(9) + .matchesElement( + <Typography component="dt">iiif_seeAlso</Typography>, + ), + ).toBe(true); + + expect( + wrapper.find(Typography).at(10) .matchesElement( <Typography component="dd"> <Link href="http://example.com/a">A</Link> @@ -100,7 +136,7 @@ describe('ManifestRelatedLinks', () => { ).toBe(true); expect( - wrapper.find(Typography).at(8) + wrapper.find(Typography).at(12) .matchesElement( <Typography component="dd"><Link href="http://example.com/b">http://example.com/b</Link></Typography>, ), @@ -109,14 +145,14 @@ describe('ManifestRelatedLinks', () => { it('renders manifest links', () => { expect( - wrapper.find(Typography).at(9) + wrapper.find(Typography).at(13) .matchesElement( <Typography component="dt">iiif_manifest</Typography>, ), ).toBe(true); expect( - wrapper.find(Typography).at(10) + wrapper.find(Typography).at(14) .matchesElement( <Typography component="dd"><Link href="http://example.com/">http://example.com/</Link></Typography>, ), diff --git a/__tests__/src/selectors/manifests.test.js b/__tests__/src/selectors/manifests.test.js index 3c174506f..28b0461ad 100644 --- a/__tests__/src/selectors/manifests.test.js +++ b/__tests__/src/selectors/manifests.test.js @@ -6,6 +6,7 @@ import manifestFixtureSn904cj3429 from '../../fixtures/version-2/sn904cj3429.jso import manifestFixturev3001 from '../../fixtures/version-3/001.json'; import manifestFixtureWithAProvider from '../../fixtures/version-3/with_a_provider.json'; import manifestFixtureFg165hz3589 from '../../fixtures/version-2/fg165hz3589.json'; +import manifestFixtureRelated from '../../fixtures/version-2/related.json'; import { getManifestoInstance, getManifestLocale, @@ -18,8 +19,10 @@ import { getManifestTitle, getManifestThumbnail, getManifestMetadata, + getManifestRelated, getManifestRelatedContent, getManifestRenderings, + getManifestSeeAlso, getManifestUrl, getMetadataLocales, getRequiredStatement, @@ -210,6 +213,33 @@ describe('getManifestRenderings', () => { }); }); +describe('getManifestRelated', () => { + it('should return manifest related', () => { + const state = { manifests: { x: { json: manifestFixtureRelated } } }; + const received = getManifestRelated(state, { manifestId: 'x' }); + expect(received).toEqual([ + { + value: 'http://example.com/related1', + }, + { + format: undefined, + label: null, + value: 'http://example.com/related2', + }, + { + format: 'text/html', + label: 'Something related', + value: 'http://example.com/related3', + }, + ]); + }); + + it('should return undefined if manifest undefined', () => { + const received = getManifestRelated({ manifests: {} }, { manifestId: 'x' }); + expect(received).toBeUndefined(); + }); +}); + describe('getManifestRelatedContent', () => { it('should return manifest seeAlso content', () => { const state = { manifests: { x: { json: manifestFixtureSn904cj3429 } } }; @@ -229,6 +259,25 @@ describe('getManifestRelatedContent', () => { }); }); +describe('getManifestSeeAlso', () => { + it('should return manifest seeAlso content', () => { + const state = { manifests: { x: { json: manifestFixtureSn904cj3429 } } }; + const received = getManifestSeeAlso(state, { manifestId: 'x' }); + expect(received).toEqual([ + { + format: 'application/mods+xml', + label: null, + value: 'https://purl.stanford.edu/sn904cj3429.mods', + }, + ]); + }); + + it('should return undefined if manifest undefined', () => { + const received = getManifestSeeAlso({ manifests: {} }, { manifestId: 'x' }); + expect(received).toBeUndefined(); + }); +}); + describe('getManifestUrl', () => { it('should return manifest url', () => { const state = { manifests: { x: { json: manifestFixtureWithAProvider } } }; diff --git a/src/components/ManifestRelatedLinks.js b/src/components/ManifestRelatedLinks.js index 533b4522d..943d287ff 100644 --- a/src/components/ManifestRelatedLinks.js +++ b/src/components/ManifestRelatedLinks.js @@ -20,6 +20,7 @@ export class ManifestRelatedLinks extends Component { classes, homepage, manifestUrl, + related, renderings, seeAlso, id, @@ -68,17 +69,34 @@ export class ManifestRelatedLinks extends Component { } </> )} + { related && ( + <> + <Typography variant="subtitle2" component="dt">{t('iiif_related')}</Typography> + { + related.map(relatedItem => ( + <Typography key={relatedItem.value} variant="body1" component="dd"> + <Link target="_blank" rel="noopener noreferrer" href={relatedItem.value}> + {relatedItem.label || relatedItem.value} + </Link> + { relatedItem.format && ( + <Typography component="span">{` (${relatedItem.format})`}</Typography> + )} + </Typography> + )) + } + </> + )} { seeAlso && ( <> <Typography variant="subtitle2" component="dt">{t('iiif_seeAlso')}</Typography> { - seeAlso.map(related => ( - <Typography key={related.value} variant="body1" component="dd"> - <Link target="_blank" rel="noopener noreferrer" href={related.value}> - {related.label || related.value} + seeAlso.map(seeAlsoItem => ( + <Typography key={seeAlsoItem.value} variant="body1" component="dd"> + <Link target="_blank" rel="noopener noreferrer" href={seeAlsoItem.value}> + {seeAlsoItem.label || seeAlsoItem.value} </Link> - { related.format && ( - <Typography component="span">{` (${related.format})`}</Typography> + { seeAlsoItem.format && ( + <Typography component="span">{` (${seeAlsoItem.format})`}</Typography> )} </Typography> )) @@ -110,6 +128,11 @@ ManifestRelatedLinks.propTypes = { })), id: PropTypes.string.isRequired, manifestUrl: PropTypes.string, + related: PropTypes.arrayOf(PropTypes.shape({ + format: PropTypes.string, + label: PropTypes.string, + value: PropTypes.string, + })), renderings: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string, value: PropTypes.string, @@ -125,6 +148,7 @@ ManifestRelatedLinks.propTypes = { ManifestRelatedLinks.defaultProps = { homepage: null, manifestUrl: null, + related: null, renderings: null, seeAlso: null, t: key => key, diff --git a/src/containers/ManifestRelatedLinks.js b/src/containers/ManifestRelatedLinks.js index 005029f22..6bcc35677 100644 --- a/src/containers/ManifestRelatedLinks.js +++ b/src/containers/ManifestRelatedLinks.js @@ -5,8 +5,9 @@ import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { getManifestHomepage, - getManifestRelatedContent, + getManifestRelated, getManifestRenderings, + getManifestSeeAlso, getManifestUrl, } from '../state/selectors'; import { ManifestRelatedLinks } from '../components/ManifestRelatedLinks'; @@ -19,8 +20,9 @@ import { ManifestRelatedLinks } from '../components/ManifestRelatedLinks'; const mapStateToProps = (state, { id, windowId }) => ({ homepage: getManifestHomepage(state, { windowId }), manifestUrl: getManifestUrl(state, { windowId }), + related: getManifestRelated(state, { windowId }), renderings: getManifestRenderings(state, { windowId }), - seeAlso: getManifestRelatedContent(state, { windowId }), + seeAlso: getManifestSeeAlso(state, { windowId }), }); const styles = { diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index b1d3ca8b7..c27976c6b 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -57,6 +57,7 @@ "hideZoomControls": "Zoomsteuerung verbergen", "iiif_homepage": "Über diese Ressource", "iiif_manifest": "IIIF-Manifest", + "iiif_related": "Verwandtes", "iiif_renderings": "Alternative Formate", "iiif_seeAlso": "Siehe auch", "import" : "Importieren", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 1fe15e741..a7f8b6993 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -59,6 +59,7 @@ "hideZoomControls": "Hide zoom controls", "iiif_homepage": "About this resource", "iiif_manifest": "IIIF manifest", + "iiif_related": "Related", "iiif_renderings": "Alternate formats", "iiif_seeAlso": "See also", "import" : "Import", diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js index 44abe0435..48f25eefc 100644 --- a/src/state/selectors/manifests.js +++ b/src/state/selectors/manifests.js @@ -153,13 +153,14 @@ export const getManifestRenderings = createSelector( /** * Return the IIIF v2/v3 seeAlso data from a manifest or null +* * @param {object} state * @param {object} props * @param {string} props.manifestId * @param {string} props.windowId * @return {String|null} */ -export const getManifestRelatedContent = createSelector( +export const getManifestSeeAlso = createSelector( [ getProperty('seeAlso'), getManifestLocale, @@ -175,6 +176,48 @@ export const getManifestRelatedContent = createSelector( )), ); +/** +* Return the IIIF v2/v3 seeAlso data from a manifest or null +* +* @param {object} state +* @param {object} props +* @param {string} props.manifestId +* @param {string} props.windowId +* @return {String|null} +* @deprecated This does not actually return the content of "related" and +* might be removed in a future version. +* @see getManifestSeeAlso +*/ +export const getManifestRelatedContent = getManifestSeeAlso; + +/** +* Return the IIIF v2 realated links manifest or null +* @param {object} state +* @param {object} props +* @param {string} props.manifestId +* @param {string} props.windowId +* @return {String|null} +*/ +export const getManifestRelated = createSelector( + [ + getProperty('related'), + getManifestLocale, + ], + (relatedLinks, locale) => relatedLinks + && asArray(relatedLinks).map(related => ( + typeof related === 'string' + ? { + value: related, + } + : { + format: related.format, + label: PropertyValue.parse(related.label, locale) + .getValue(), + value: related.id || related['@id'], + } + )), +); + /** * Return the IIIF requiredStatement (v3) or attribution (v2) data from a manifest or null * @param {object} state -- GitLab