Skip to content
Snippets Groups Projects
Unverified Commit 935d4eb8 authored by Michael J. Giarlo's avatar Michael J. Giarlo Committed by GitHub
Browse files

Merge pull request #3442 from ProjectMirador/3393-preferredFormats

Respect the IIIF service preferredFormats list when picking thumbnails
parents 5ba18bb6 486bc672
Branches
No related tags found
No related merge requests found
...@@ -248,6 +248,66 @@ describe('getThumbnail', () => { ...@@ -248,6 +248,66 @@ describe('getThumbnail', () => {
}); });
}); });
describe('picking the best format', () => {
const url = 'http://example.com';
it('defaults to jpg', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas'))
.toMatchObject({ url: `${url}/full/,120/0/default.jpg` });
});
it('uses the preferred format of the service', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
preferredFormats: ['webp'],
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas'))
.toMatchObject({ url: `${url}/full/,120/0/default.webp` });
});
it('can be filtered by application preferred formats', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
preferredFormats: ['webp', 'png'],
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas', { preferredFormats: ['png', 'jpg'] }))
.toMatchObject({ url: `${url}/full/,120/0/default.png` });
});
});
describe('selectBestImageSize', () => { describe('selectBestImageSize', () => {
const targetWidth = 120; const targetWidth = 120;
const targetHeight = 120; const targetHeight = 120;
......
...@@ -115,12 +115,12 @@ export class IIIFThumbnail extends Component { ...@@ -115,12 +115,12 @@ export class IIIFThumbnail extends Component {
/** */ /** */
image() { image() {
const { const {
thumbnail, resource, maxHeight, maxWidth, thumbnail, resource, maxHeight, maxWidth, thumbnailsConfig,
} = this.props; } = this.props;
if (thumbnail) return thumbnail; if (thumbnail) return thumbnail;
const image = getThumbnail(resource, { maxHeight, maxWidth }); const image = getThumbnail(resource, { ...thumbnailsConfig, maxHeight, maxWidth });
if (image && image.url) return image; if (image && image.url) return image;
...@@ -189,6 +189,7 @@ IIIFThumbnail.propTypes = { ...@@ -189,6 +189,7 @@ IIIFThumbnail.propTypes = {
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
width: PropTypes.number, width: PropTypes.number,
}), }),
thumbnailsConfig: PropTypes.object, // eslint-disable-line react/forbid-prop-types
variant: PropTypes.oneOf(['inside', 'outside']), variant: PropTypes.oneOf(['inside', 'outside']),
}; };
...@@ -203,5 +204,6 @@ IIIFThumbnail.defaultProps = { ...@@ -203,5 +204,6 @@ IIIFThumbnail.defaultProps = {
maxWidth: null, maxWidth: null,
style: {}, style: {},
thumbnail: null, thumbnail: null,
thumbnailsConfig: {},
variant: null, variant: null,
}; };
...@@ -298,6 +298,9 @@ export default { ...@@ -298,6 +298,9 @@ export default {
// ../lib/MiradorViewer.js `windowAction` // ../lib/MiradorViewer.js `windowAction`
*/ */
], ],
thumbnails: {
preferredFormats: ['jpg', 'png', 'webp', 'tif'],
},
thumbnailNavigation: { thumbnailNavigation: {
defaultPosition: 'off', // Which position for the thumbnail navigation to be be displayed. Other possible values are "far-bottom" or "far-right" defaultPosition: 'off', // Which position for the thumbnail navigation to be be displayed. Other possible values are "far-bottom" or "far-right"
displaySettings: true, // Display the settings for this in WindowTopMenu displaySettings: true, // Display the settings for this in WindowTopMenu
......
import { compose } from 'redux'; import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withPlugins } from '../extend/withPlugins'; import { withPlugins } from '../extend/withPlugins';
import {
getConfig,
} from '../state/selectors';
import { IIIFThumbnail } from '../components/IIIFThumbnail'; import { IIIFThumbnail } from '../components/IIIFThumbnail';
/**
* mapStateToProps - to hook up connect
* @private
*/
const mapStateToProps = (state) => ({
thumbnailsConfig: getConfig(state).thumbnails,
});
/** /**
* Styles for withStyles HOC * Styles for withStyles HOC
*/ */
...@@ -50,6 +62,7 @@ const styles = theme => ({ ...@@ -50,6 +62,7 @@ const styles = theme => ({
const enhance = compose( const enhance = compose(
withStyles(styles), withStyles(styles),
withTranslation(), withTranslation(),
connect(mapStateToProps),
withPlugins('IIIFThumbnail'), withPlugins('IIIFThumbnail'),
); );
......
...@@ -197,7 +197,7 @@ class ThumbnailFactory { ...@@ -197,7 +197,7 @@ class ThumbnailFactory {
const region = 'full'; const region = 'full';
const quality = Utils.getImageQuality(service.getProfile()); const quality = Utils.getImageQuality(service.getProfile());
const id = service.id.replace(/\/+$/, ''); const id = service.id.replace(/\/+$/, '');
const format = 'jpg'; const format = this.getFormat(service);
return { return {
height, height,
url: [id, region, size, 0, `${quality}.${format}`].join('/'), url: [id, region, size, 0, `${quality}.${format}`].join('/'),
...@@ -205,6 +205,41 @@ class ThumbnailFactory { ...@@ -205,6 +205,41 @@ class ThumbnailFactory {
}; };
} }
/**
* Figure out what format thumbnail to use by looking at the preferred formats
* on offer, and selecting a format shared in common with the application's
* preferred format list.
*
* Fall back to jpg, which is required to work for all IIIF services.
*/
getFormat(service) {
const { preferredFormats = [] } = this.iiifOpts;
const servicePreferredFormats = service.getProperty('preferredFormats');
if (!servicePreferredFormats) return 'jpg';
const filteredFormats = servicePreferredFormats.filter(
value => preferredFormats.includes(value),
);
// this is a format found in common between the preferred formats of the service
// and the application
if (filteredFormats[0]) return filteredFormats[0];
// IIIF Image API guarantees jpg support; if it wasn't provided by the service
// but the application is fine with it, we might as well try it.
if (!servicePreferredFormats.includes('jpg') && preferredFormats.includes('jpg')) {
return 'jpg';
}
// there were no formats in common, and the application didn't want jpg... so
// just trust that the IIIF service is advertising something useful?
if (servicePreferredFormats[0]) return servicePreferredFormats[0];
// JPG support is guaranteed by the spec, so it's a good worst-case fallback
return 'jpg';
}
/** /**
* Determines the content resource from which to derive a thumbnail to represent a given resource. * Determines the content resource from which to derive a thumbnail to represent a given resource.
* This method is recursive. * This method is recursive.
......
...@@ -234,10 +234,13 @@ export const getRights = createSelector( ...@@ -234,10 +234,13 @@ export const getRights = createSelector(
*/ */
export function getManifestThumbnail(state, props) { export function getManifestThumbnail(state, props) {
const manifest = getManifestoInstance(state, props); const manifest = getManifestoInstance(state, props);
const { thumbnails = {} } = getConfig(state);
if (!manifest) return undefined; if (!manifest) return undefined;
const thumbnail = getThumbnail(manifest, { maxHeight: 80, maxWidth: 120 }); const thumbnail = getThumbnail(manifest, {
maxHeight: 80, maxWidth: 120, preferredFormats: thumbnails.preferredFormats,
});
return thumbnail && thumbnail.url; return thumbnail && thumbnail.url;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment