diff --git a/__tests__/src/components/GalleryViewThumbnail.test.js b/__tests__/src/components/GalleryViewThumbnail.test.js
index 4a21a67547..53c4b06a6f 100644
--- a/__tests__/src/components/GalleryViewThumbnail.test.js
+++ b/__tests__/src/components/GalleryViewThumbnail.test.js
@@ -73,6 +73,7 @@ describe('GalleryView', () => {
describe('on-demand annotation fetching', () => {
const canvas = {
getHeight: () => 50,
+ getLabel: () => ({ getValue: () => 'label' }),
getServices: vi.fn(),
getThumbnail: vi.fn(),
getType: vi.fn(),
diff --git a/__tests__/src/components/WindowSideBarCanvasPanel.test.js b/__tests__/src/components/WindowSideBarCanvasPanel.test.js
index 0a7549a55d..d0fcdb79f2 100644
--- a/__tests__/src/components/WindowSideBarCanvasPanel.test.js
+++ b/__tests__/src/components/WindowSideBarCanvasPanel.test.js
@@ -13,8 +13,8 @@ function createWrapper(props) {
let sequences;
if (props.multipleSequences) {
- sequences = [{ id: 'a', label: 'seq1' },
- { id: 'b', label: 'seq2' }];
+ sequences = [{ getLabel: () => ({ getValue: () => undefined }), id: 'a', label: 'seq1' },
+ { getLabel: () => ({ getValue: () => undefined }), id: 'b', label: 'seq2' }];
} else {
sequences = Utils.parseManifest(manifestJson).getSequences();
}
diff --git a/__tests__/src/selectors/manifests.test.js b/__tests__/src/selectors/manifests.test.js
index 1cf9bf50a3..3c10199d2a 100644
--- a/__tests__/src/selectors/manifests.test.js
+++ b/__tests__/src/selectors/manifests.test.js
@@ -59,12 +59,6 @@ describe('getManifestoInstance', () => {
const received = getManifestoInstance(state, { manifestId: 'x' });
expect(received.id).toEqual('http://iiif.io/api/presentation/2.1/example/fixtures/19/manifest.json');
});
- it('is cached based off of input props', () => {
- const state = { manifests: { x: { json: manifestFixture019 } } };
- const received = getManifestoInstance(state, { manifestId: 'x' });
- expect(getManifestoInstance(state, { manifestId: 'x' })).toBe(received);
- expect(getManifestoInstance(state, { manifestId: 'x', windowId: 'y' })).not.toBe(received);
- });
});
describe('getManifestLogo()', () => {
diff --git a/src/components/AppProviders.js b/src/components/AppProviders.js
index 541b7480e9..514fc41af9 100644
--- a/src/components/AppProviders.js
+++ b/src/components/AppProviders.js
@@ -14,6 +14,7 @@ import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import createI18nInstance from '../i18n';
import FullScreenContext from '../contexts/FullScreenContext';
+import LocaleContext from '../contexts/LocaleContext';
/**
* Allow applications to opt-out of (or provide their own) drag and drop context
@@ -119,15 +120,17 @@ export function AppProviders({
return (
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
);
diff --git a/src/components/CanvasLayers.js b/src/components/CanvasLayers.js
index ed1abd71e2..b58266431d 100644
--- a/src/components/CanvasLayers.js
+++ b/src/components/CanvasLayers.js
@@ -17,6 +17,7 @@ import { useTranslation } from 'react-i18next';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import MiradorMenuButton from '../containers/MiradorMenuButton';
import IIIFThumbnail from '../containers/IIIFThumbnail';
+import { IIIFResourceLabel } from './IIIFResourceLabel';
const StyledDragHandle = styled('div')(({ theme }) => ({
alignItems: 'center',
@@ -40,15 +41,6 @@ const reorder = (list, startIndex, endIndex) => {
return result;
};
-/** */
-function getUseableLabel(resource, index) {
- return (resource
- && resource.getLabel
- && resource.getLabel().length > 0)
- ? resource.getLabel().getValue()
- : String(index + 1);
-}
-
/** @private */
function Layer({
resource, layerMetadata = {}, index, handleOpacityChange, setLayerVisibility, moveToTop,
@@ -78,7 +70,7 @@ function Layer({
component="div"
variant="body1"
>
- {getUseableLabel(resource, index)}
+
{ setLayerVisibility(resource.id, !layer.visibility); }}>
{ layer.visibility ? : }
diff --git a/src/components/CollectionDialog.js b/src/components/CollectionDialog.js
index f44bde133b..56067e263d 100644
--- a/src/components/CollectionDialog.js
+++ b/src/components/CollectionDialog.js
@@ -21,6 +21,7 @@ import CollapsibleSection from '../containers/CollapsibleSection';
import ScrollIndicatedDialogContent from '../containers/ScrollIndicatedDialogContent';
import ManifestInfo from '../containers/ManifestInfo';
import WorkspaceContext from '../contexts/WorkspaceContext';
+import { IIIFResourceLabel } from './IIIFResourceLabel';
const StyledScrollIndicatedDialogContent = styled(ScrollIndicatedDialogContent)(() => ({
padding: (theme) => theme.spacing(1),
@@ -38,15 +39,6 @@ const StyledCollectionFilter = styled('div')(() => ({
paddingTop: 0,
}));
-/** */
-function getUseableLabel(resource, index) {
- return (resource
- && resource.getLabel
- && resource.getLabel().length > 0)
- ? resource.getLabel().getValue()
- : String(index + 1);
-}
-
/** */
const Placeholder = ({ onClose, container }) => (
+ )
+ : (
+ <>
+ {
+ updateCompanionWindow && (
+
{ updateCompanionWindow({ position: position === 'bottom' ? 'right' : 'bottom' }); }}
+ >
+
+
+ )
+ }
+
+
+
+ >
+ )
+ }
+ {
+ titleControls && (
+
+ {titleControls}
+
)
- }
- {
- titleControls && (
-
- {titleControls}
-
- )
- }
-
-
- {childrenWithAdditionalProps}
-
-
+ }
+
+
+ {childrenWithAdditionalProps}
+
+
+
);
});
@@ -193,6 +199,7 @@ CompanionWindow.propTypes = {
defaultSidebarPanelHeight: PropTypes.number,
defaultSidebarPanelWidth: PropTypes.number,
direction: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
isDisplayed: PropTypes.bool,
onCloseClick: PropTypes.func,
paperClassName: PropTypes.string,
diff --git a/src/components/IIIFResourceLabel.js b/src/components/IIIFResourceLabel.js
new file mode 100644
index 0000000000..4784458298
--- /dev/null
+++ b/src/components/IIIFResourceLabel.js
@@ -0,0 +1,28 @@
+import { useContext } from 'react';
+import { useSelector } from 'react-redux';
+import PropTypes from 'prop-types';
+import {
+ getLocale,
+} from '../state/selectors';
+import LocaleContext from '../contexts/LocaleContext';
+
+/**
+ * Render the contextually appropriate label for the resource
+ */
+export function IIIFResourceLabel({ fallback, resource }) {
+ const contextLocale = useContext(LocaleContext);
+ const fallbackLocale = useSelector(state => getLocale(state, {}));
+
+ if (!resource) return fallback;
+
+ const label = resource.getLabel();
+
+ if (!label) return fallback;
+
+ return label.getValue(contextLocale || fallbackLocale || '') ?? (fallback || resource.id);
+}
+
+IIIFResourceLabel.propTypes = {
+ fallback: PropTypes.string,
+ resource: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+};
diff --git a/src/components/IIIFThumbnail.js b/src/components/IIIFThumbnail.js
index 2feea6651a..301cc68b0f 100644
--- a/src/components/IIIFThumbnail.js
+++ b/src/components/IIIFThumbnail.js
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import { useInView } from 'react-intersection-observer';
import getThumbnail from '../lib/ThumbnailFactory';
+import { IIIFResourceLabel } from './IIIFResourceLabel';
const Root = styled('div', { name: 'IIIFThumbnail', slot: 'root' })({});
@@ -129,14 +130,6 @@ LazyLoadedImage.propTypes = {
}),
thumbnailsConfig: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
-/** */
-function getUseableLabel(resource, index) {
- return (resource
- && resource.getLabel
- && resource.getLabel().length > 0)
- ? resource.getLabel().getValue()
- : String(index + 1);
-}
const defaultPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mMMDQmtBwADgwF/Op8FmAAAAABJRU5ErkJggg==';
@@ -173,7 +166,7 @@ export function IIIFThumbnail({
{ labelled && (
)}
{children}
diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js
index 2b30297c4b..a407a7877c 100644
--- a/src/components/WindowSideBarCanvasPanel.js
+++ b/src/components/WindowSideBarCanvasPanel.js
@@ -17,21 +17,13 @@ import { useTranslation } from 'react-i18next';
import CompanionWindow from '../containers/CompanionWindow';
import SidebarIndexList from '../containers/SidebarIndexList';
import SidebarIndexTableOfContents from '../containers/SidebarIndexTableOfContents';
+import { IIIFResourceLabel } from './IIIFResourceLabel';
const StyledBreak = styled('div')(() => ({
flexBasis: '100%',
height: 0,
}));
-/** */
-function getUseableLabel(resource, index) {
- return (resource
- && resource.getLabel
- && resource.getLabel().length > 0)
- ? resource.getLabel().getValue()
- : resource.id;
-}
-
/**
* a panel showing the canvases for a given manifest
*/
@@ -113,7 +105,7 @@ export function WindowSideBarCanvasPanel({
}}
data-testid="sequence-select"
>
- { sequences.map((s, i) =>
) }
+ { sequences.map((s, i) =>
) }
)
@@ -143,7 +135,7 @@ export function WindowSideBarCanvasPanel({
endIcon={
}
>
- {getUseableLabel(collection)}
+
)}
diff --git a/src/components/WindowSideBarCollectionPanel.js b/src/components/WindowSideBarCollectionPanel.js
index 6f71e38ab5..2661538170 100644
--- a/src/components/WindowSideBarCollectionPanel.js
+++ b/src/components/WindowSideBarCollectionPanel.js
@@ -11,15 +11,7 @@ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpwardSharp';
import { useTranslation } from 'react-i18next';
import CompanionWindow from '../containers/CompanionWindow';
import IIIFThumbnail from '../containers/IIIFThumbnail';
-
-/** */
-function getUseableLabel(resource, index) {
- return (resource
- && resource.getLabel
- && resource.getLabel().length > 0)
- ? resource.getLabel().getValue()
- : resource.id;
-}
+import { IIIFResourceLabel } from './IIIFResourceLabel';
/** */
function Item({
@@ -46,7 +38,7 @@ function Item({
/>
)}
-
{getUseableLabel(manifest)}
+
);
}
@@ -105,13 +97,13 @@ export function WindowSideBarCollectionPanel({
- {getUseableLabel(parentCollection)}
+
)}
- { collection && getUseableLabel(collection)}
+ { collection && }
{ isFetching && }
>
diff --git a/src/contexts/LocaleContext.js b/src/contexts/LocaleContext.js
new file mode 100644
index 0000000000..dfa092bab3
--- /dev/null
+++ b/src/contexts/LocaleContext.js
@@ -0,0 +1,5 @@
+import { createContext } from 'react';
+
+const LocaleContext = createContext();
+
+export default LocaleContext;
diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js
index 6e17fad668..6d09450e21 100644
--- a/src/lib/MiradorCanvas.js
+++ b/src/lib/MiradorCanvas.js
@@ -177,9 +177,9 @@ export default class MiradorCanvas {
/**
* Get the canvas label
*/
- getLabel() {
+ getLabel(locale = undefined) {
return this.canvas.getLabel().length > 0
- ? this.canvas.getLabel().getValue()
+ ? this.canvas.getLabel().getValue(locale)
: String(this.canvas.index + 1);
}
}
diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js
index f93daa8411..164c7408df 100644
--- a/src/state/selectors/canvases.js
+++ b/src/state/selectors/canvases.js
@@ -6,6 +6,7 @@ import { miradorSlice } from './utils';
import { getWindow } from './getters';
import { getSequence } from './sequences';
import { getWindowViewType } from './windows';
+import { getManifestLocale } from './manifests';
/**
* Returns the info response.
@@ -183,10 +184,10 @@ export const getPreviousCanvasGrouping = createSelector(
* @returns {string|number}
*/
export const getCanvasLabel = createSelector(
- [getCanvas],
- canvas => (canvas && (
+ [getCanvas, getManifestLocale],
+ (canvas, locale) => (canvas && (
canvas.getLabel().length > 0
- ? canvas.getLabel().getValue()
+ ? canvas.getLabel().getValue(locale)
: String(canvas.index + 1)
)),
);
diff --git a/src/state/selectors/companionWindows.js b/src/state/selectors/companionWindows.js
index 392240ee35..5a0218dd24 100644
--- a/src/state/selectors/companionWindows.js
+++ b/src/state/selectors/companionWindows.js
@@ -29,6 +29,18 @@ export const getCompanionWindow = createSelector(
(companionWindows, companionWindowId) => companionWindowId && companionWindows[companionWindowId],
);
+/**
+ * Returns the companion window locale.
+ * @param {object} state
+ * @param {object} props
+ * @param {string} props.companionWindowId
+ * @returns {string|undefined}
+ */
+export const getCompanionWindowLocale = createSelector(
+ [getCompanionWindow],
+ companionWindow => companionWindow && companionWindow.locale,
+);
+
/**
* Return position of thumbnail navigation in a certain window.
* @param {object} state
diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js
index d7a9c7c1ef..04e3661e68 100644
--- a/src/state/selectors/manifests.js
+++ b/src/state/selectors/manifests.js
@@ -1,9 +1,8 @@
import { createSelector } from 'reselect';
-import { createCachedSelector } from 're-reselect';
import { PropertyValue, Utils, Resource } from 'manifesto.js';
import getThumbnail from '../../lib/ThumbnailFactory';
import asArray from '../../lib/asArray';
-import { getCompanionWindow } from './companionWindows';
+import { getCompanionWindowLocale } from './companionWindows';
import { getManifest } from './getters';
import { getConfig } from './config';
@@ -20,13 +19,14 @@ function createManifestoInstance(json, locale) {
}
/** */
-const getLocale = createSelector(
+export const getLocale = createSelector(
[
- getCompanionWindow,
+ getCompanionWindowLocale,
getConfig,
+ (state, { locale }) => locale,
],
- (companionWindow = {}, config = {}) => (
- companionWindow.locale || config.language
+ (companionWindowLocale, config = {}, locale) => (
+ locale || companionWindowLocale || config.language || config.fallbackLanguages
),
);
@@ -57,17 +57,9 @@ export const getManifestError = createSelector(
);
/** Instantiate a manifesto instance */
-const getContextualManifestoInstance = createCachedSelector(
+const getContextualManifestoInstance = createSelector(
getManifest,
- getLocale,
- (manifest, locale) => manifest
- && createManifestoInstance(manifest.json, locale),
-)(
- (state, { companionWindowId, manifestId, windowId }) => [
- manifestId,
- windowId,
- getLocale(state, { companionWindowId }),
- ].join(' - '), // Cache key consisting of manifestId, windowId, and locale
+ manifest => manifest && createManifestoInstance(manifest.json),
);
/**
@@ -80,15 +72,14 @@ const getContextualManifestoInstance = createCachedSelector(
export const getManifestoInstance = createSelector(
getContextualManifestoInstance,
(state, { json }) => json,
- getLocale,
- (manifesto, manifestJson, locale) => (
+ (manifesto, manifestJson) => (
manifestJson && createManifestoInstance(manifestJson, locale)
) || manifesto,
);
export const getManifestLocale = createSelector(
- [getManifestoInstance],
- manifest => manifest && manifest.options && manifest.options.locale && manifest.options.locale.replace(/-.*$/, ''),
+ [getManifestoInstance, getLocale],
+ (manifest, locale) => locale ?? (manifest && manifest.options && manifest.options.locale && manifest.options.locale.replace(/-.*$/, '')),
);
/** */
@@ -114,7 +105,7 @@ export const getManifestProviderName = createSelector(
],
(provider, locale) => provider
&& provider[0].label
- && PropertyValue.parse(provider[0].label, locale).getValue(),
+ && PropertyValue.parse(provider[0].label).getValue(locale),
);
/**
@@ -159,8 +150,8 @@ export const getManifestHomepage = createSelector(
(homepages, locale) => homepages
&& asArray(homepages).map(homepage => (
{
- label: PropertyValue.parse(homepage.label, locale)
- .getValue(),
+ label: PropertyValue.parse(homepage.label)
+ .getValue(locale),
value: homepage.id || homepage['@id'],
}
)),
@@ -175,11 +166,11 @@ export const getManifestHomepage = createSelector(
* @returns {string|null}
*/
export const getManifestRenderings = createSelector(
- [getManifestoInstance],
- manifest => manifest
+ [getManifestoInstance, getManifestLocale],
+ (manifest, locale) => manifest
&& manifest.getRenderings().map(rendering => (
{
- label: rendering.getLabel().getValue(),
+ label: rendering.getLabel().getValue(locale),
value: rendering.id,
}
)),
@@ -202,8 +193,8 @@ export const getManifestSeeAlso = createSelector(
&& asArray(seeAlso).map(related => (
{
format: related.format,
- label: PropertyValue.parse(related.label, locale)
- .getValue(),
+ label: PropertyValue.parse(related.label)
+ .getValue(locale),
value: related.id || related['@id'],
}
)),
@@ -243,8 +234,8 @@ export const getManifestRelated = createSelector(
}
: {
format: related.format,
- label: PropertyValue.parse(related.label, locale)
- .getValue(),
+ label: PropertyValue.parse(related.label)
+ .getValue(locale),
value: related.id || related['@id'],
}
)),
@@ -259,13 +250,13 @@ export const getManifestRelated = createSelector(
* @returns {string|null}
*/
export const getRequiredStatement = createSelector(
- [getManifestoInstance],
- manifest => manifest
+ [getManifestoInstance, getManifestLocale],
+ (manifest, locale) => manifest
&& asArray(manifest.getRequiredStatement())
.filter(l => l && l.getValues().some(v => v))
.map(labelValuePair => ({
- label: (labelValuePair.label && labelValuePair.label.getValue()) || null,
- values: labelValuePair.getValues(),
+ label: (labelValuePair.label && labelValuePair.label.getValue(locale)) || null,
+ values: labelValuePair.getValues(locale),
})),
);
@@ -285,7 +276,7 @@ export const getRights = createSelector(
],
(rights, license, locale) => {
const data = rights || license;
- return asArray(PropertyValue.parse(data, locale).getValues());
+ return asArray(PropertyValue.parse(data).getValues(locale));
},
);
@@ -319,9 +310,9 @@ export function getManifestThumbnail(state, props) {
* @returns {string}
*/
export const getManifestTitle = createSelector(
- [getManifestoInstance],
- manifest => manifest
- && manifest.getLabel().getValue(),
+ [getManifestoInstance, getManifestLocale],
+ (manifest, locale) => manifest
+ && manifest.getLabel().getValue(locale),
);
/**
@@ -352,7 +343,7 @@ export const getManifestSummary = createSelector(
getManifestLocale,
],
(summary, locale) => summary
- && PropertyValue.parse(summary, locale).getValue(locale),
+ && PropertyValue.parse(summary).getValue(locale),
);
/**
@@ -377,11 +368,11 @@ export const getManifestUrl = createSelector(
* @param iiifResource
* @returns {Array[Object]}
*/
-export function getDestructuredMetadata(iiifResource) {
+export function getDestructuredMetadata(iiifResource, locale = undefined) {
return (iiifResource
&& iiifResource.getMetadata().map(labelValuePair => ({
- label: labelValuePair.getLabel(),
- values: labelValuePair.getValues(),
+ label: labelValuePair.getLabel(locale),
+ values: labelValuePair.getValues(locale),
}))
);
}
@@ -395,8 +386,8 @@ export function getDestructuredMetadata(iiifResource) {
* @returns {Array[Object]}
*/
export const getManifestMetadata = createSelector(
- [getManifestoInstance],
- manifest => manifest && getDestructuredMetadata(manifest),
+ [getManifestoInstance, getManifestLocale],
+ (manifest, locale) => manifest && getDestructuredMetadata(manifest, locale),
);
/** */
diff --git a/src/state/selectors/searches.js b/src/state/selectors/searches.js
index f402d1e8c8..f1cb5eedf4 100644
--- a/src/state/selectors/searches.js
+++ b/src/state/selectors/searches.js
@@ -294,7 +294,7 @@ export const getResourceAnnotationLabel = createSelector(
!(resourceAnnotation && resourceAnnotation.resource && resourceAnnotation.resource.label)
) return [];
- return PropertyValue.parse(resourceAnnotation.resource.label, locale).getValues();
+ return PropertyValue.parse(resourceAnnotation.resource.label).getValues(locale);
},
);