Skip to content
Snippets Groups Projects
Commit 6b19a669 authored by Chris Beer's avatar Chris Beer
Browse files

Open components to support multiple image resources per canvas

parent 0c50b5b7
No related branches found
No related tags found
No related merge requests found
...@@ -19,9 +19,9 @@ describe('CanvasWorld', () => { ...@@ -19,9 +19,9 @@ describe('CanvasWorld', () => {
}); });
describe('canvasToWorldCoordinates', () => { describe('canvasToWorldCoordinates', () => {
it('converts canvas coordinates to world offset by location', () => { it('converts canvas coordinates to world offset by location', () => {
expect(new CanvasWorld([canvases[1]]).canvasToWorldCoordinates(0)) expect(new CanvasWorld([canvases[1]]).canvasToWorldCoordinates({ '@id': 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005' }))
.toEqual([0, 0, 6501, 4421]); .toEqual([0, 0, 6501, 4421]);
expect(new CanvasWorld(canvasSubset).canvasToWorldCoordinates(1)) expect(new CanvasWorld(canvasSubset).canvasToWorldCoordinates({ '@id': 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410' }))
.toEqual([6305, 0, 2848, 4288]); .toEqual([6305, 0, 2848, 4288]);
}); });
it('supports RTL orientations', () => { it('supports RTL orientations', () => {
......
...@@ -85,13 +85,13 @@ describe('ManifestoCanvas', () => { ...@@ -85,13 +85,13 @@ describe('ManifestoCanvas', () => {
}); });
describe('imageInformationUri', () => { describe('imageInformationUri', () => {
it('correctly returns an image information url for a v2 Image API', () => { it('correctly returns an image information url for a v2 Image API', () => {
expect(instance.imageInformationUri).toEqual('https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44/info.json'); expect(instance.imageInformationUri()).toEqual('https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44/info.json');
}); });
it('correctly returns an image information url for a v1 Image API', () => { it('correctly returns an image information url for a v1 Image API', () => {
const imagev1Instance = new ManifestoCanvas( const imagev1Instance = new ManifestoCanvas(
Utils.parseManifest(imagev1Fixture).getSequences()[0].getCanvases()[0], Utils.parseManifest(imagev1Fixture).getSequences()[0].getCanvases()[0],
); );
expect(imagev1Instance.imageInformationUri).toEqual('https://images.britishart.yale.edu/iiif/b38081da-8991-4464-a71e-d9891226a35f/info.json'); expect(imagev1Instance.imageInformationUri()).toEqual('https://images.britishart.yale.edu/iiif/b38081da-8991-4464-a71e-d9891226a35f/info.json');
}); });
it('is undefined if a canvas is empty (e.g. has no images)', () => { it('is undefined if a canvas is empty (e.g. has no images)', () => {
...@@ -99,7 +99,7 @@ describe('ManifestoCanvas', () => { ...@@ -99,7 +99,7 @@ describe('ManifestoCanvas', () => {
Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases()[3], Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases()[3],
); );
expect(emptyCanvasInstance.imageInformationUri).toBeUndefined(); expect(emptyCanvasInstance.imageInformationUri()).toBeUndefined();
}); });
}); });
describe('aspectRatio', () => { describe('aspectRatio', () => {
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import flatten from 'lodash/flatten';
import OSDViewer from '../containers/OpenSeadragonViewer'; import OSDViewer from '../containers/OpenSeadragonViewer';
import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls'; import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls';
import ManifestoCanvas from '../lib/ManifestoCanvas'; import ManifestoCanvas from '../lib/ManifestoCanvas';
...@@ -33,12 +34,16 @@ export class WindowViewer extends Component { ...@@ -33,12 +34,16 @@ export class WindowViewer extends Component {
if (!this.infoResponseIsInStore()) { if (!this.infoResponseIsInStore()) {
currentCanvases.forEach((canvas) => { currentCanvases.forEach((canvas) => {
const manifestoCanvas = new ManifestoCanvas(canvas); const manifestoCanvas = new ManifestoCanvas(canvas);
const { imageResource } = manifestoCanvas; manifestoCanvas.imageResources.forEach((imageResource) => {
if (imageResource) {
fetchInfoResponse({ imageResource }); fetchInfoResponse({ imageResource });
} });
manifestoCanvas.processAnnotations(fetchAnnotation, receiveAnnotation); manifestoCanvas.processAnnotations(fetchAnnotation, receiveAnnotation);
}); });
currentCanvases.map(canvas => new ManifestoCanvas(canvas))
.map(manifestoCanvas => manifestoCanvas.annotationListUris.forEach((uri) => {
fetchAnnotation(manifestoCanvas.canvas.id, uri);
}));
} }
} }
...@@ -56,12 +61,16 @@ export class WindowViewer extends Component { ...@@ -56,12 +61,16 @@ export class WindowViewer extends Component {
) { ) {
currentCanvases.forEach((canvas) => { currentCanvases.forEach((canvas) => {
const manifestoCanvas = new ManifestoCanvas(canvas); const manifestoCanvas = new ManifestoCanvas(canvas);
const { imageResource } = manifestoCanvas; manifestoCanvas.imageResources.forEach((imageResource) => {
if (imageResource) {
fetchInfoResponse({ imageResource }); fetchInfoResponse({ imageResource });
} });
manifestoCanvas.processAnnotations(fetchAnnotation, receiveAnnotation); manifestoCanvas.processAnnotations(fetchAnnotation, receiveAnnotation);
}); });
currentCanvases.map(canvas => new ManifestoCanvas(canvas))
.map(manifestoCanvas => manifestoCanvas.annotationListUris.forEach((uri) => {
fetchAnnotation(manifestoCanvas.canvas.id, uri);
}));
} }
} }
...@@ -71,24 +80,29 @@ export class WindowViewer extends Component { ...@@ -71,24 +80,29 @@ export class WindowViewer extends Component {
* @return [Boolean] * @return [Boolean]
*/ */
infoResponseIsInStore() { infoResponseIsInStore() {
const { currentCanvases } = this.props;
const responses = this.currentInfoResponses(); const responses = this.currentInfoResponses();
if (responses.length === currentCanvases.length) { if (responses.length === this.imageIds().length) {
return true; return true;
} }
return false; return false;
} }
/** */
imageIds() {
const { currentCanvases } = this.props;
return flatten(currentCanvases.map(canvas => new ManifestoCanvas(canvas).imageIds));
}
/** /**
* currentInfoResponses - Selects infoResponses that are relevent to existing * currentInfoResponses - Selects infoResponses that are relevent to existing
* canvases to be displayed. * canvases to be displayed.
*/ */
currentInfoResponses() { currentInfoResponses() {
const { currentCanvases, infoResponses } = this.props; const { infoResponses } = this.props;
return currentCanvases.map(canvas => ( return this.imageIds().map(imageId => (
infoResponses[new ManifestoCanvas(canvas).imageId] infoResponses[imageId]
)).filter(infoResponse => (infoResponse !== undefined )).filter(infoResponse => (infoResponse !== undefined
&& infoResponse.isFetching === false && infoResponse.isFetching === false
&& infoResponse.error === undefined)); && infoResponse.error === undefined));
...@@ -98,12 +112,10 @@ export class WindowViewer extends Component { ...@@ -98,12 +112,10 @@ export class WindowViewer extends Component {
* Return an image information response from the store for the correct image * Return an image information response from the store for the correct image
*/ */
tileInfoFetchedFromStore() { tileInfoFetchedFromStore() {
const { currentCanvases } = this.props;
const responses = this.currentInfoResponses() const responses = this.currentInfoResponses()
.map(infoResponse => infoResponse.json); .map(infoResponse => infoResponse.json);
// Only return actual tileSources when all current canvases have completed. // Only return actual tileSources when all current canvases have completed.
if (responses.length === currentCanvases.length) { if (responses.length === this.imageIds().length) {
return responses; return responses;
} }
return []; return [];
......
import normalizeUrl from 'normalize-url';
import ManifestoCanvas from './ManifestoCanvas';
/** /**
* CanvasWorld * CanvasWorld
*/ */
...@@ -20,8 +23,9 @@ export default class CanvasWorld { ...@@ -20,8 +23,9 @@ export default class CanvasWorld {
* canvasToWorldCoordinates - calculates the canvas coordinates respective to * canvasToWorldCoordinates - calculates the canvas coordinates respective to
* the world. * the world.
*/ */
canvasToWorldCoordinates(i) { canvasToWorldCoordinates(tileSource) {
const wholeBounds = this.worldBounds(); const wholeBounds = this.worldBounds();
const i = this.indexOfImageResource(tileSource['@id']);
const canvas = this.canvases[i]; const canvas = this.canvases[i];
const aspectRatio = canvas.getWidth() / canvas.getHeight(); const aspectRatio = canvas.getWidth() / canvas.getHeight();
const scaledWidth = Math.floor(wholeBounds[3] * aspectRatio); const scaledWidth = Math.floor(wholeBounds[3] * aspectRatio);
...@@ -50,6 +54,14 @@ export default class CanvasWorld { ...@@ -50,6 +54,14 @@ export default class CanvasWorld {
return this.canvases.map(canvas => canvas.id).indexOf(canvasTarget); return this.canvases.map(canvas => canvas.id).indexOf(canvasTarget);
} }
/** @private */
indexOfImageResource(imageId) {
return this.canvases.findIndex(c => new ManifestoCanvas(c).imageIds.some(id => (
normalizeUrl(id, { stripAuthentication: false })
=== normalizeUrl(imageId, { stripAuthentication: false })
)));
}
/** /**
* offsetByCanvas - calculates the offset for a given canvas target. Currently * offsetByCanvas - calculates the offset for a given canvas target. Currently
* assumes a horrizontal only layout. * assumes a horrizontal only layout.
......
import flatten from 'lodash/flatten'; import flatten from 'lodash/flatten';
import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import flattenDeep from 'lodash/flattenDeep';
import { Canvas, Utils } from 'manifesto.js';
/** /**
* ManifestoCanvas - adds additional, testable logic around Manifesto's Canvas * ManifestoCanvas - adds additional, testable logic around Manifesto's Canvas
* https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html * https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html
...@@ -16,12 +17,13 @@ export default class ManifestoCanvas { ...@@ -16,12 +17,13 @@ export default class ManifestoCanvas {
* Implements Manifesto's canonicalImageUri algorithm to support * Implements Manifesto's canonicalImageUri algorithm to support
* IIIF Presentation v3 * IIIF Presentation v3
*/ */
canonicalImageUri(w, format = 'jpg') { canonicalImageUri(w, format = 'jpg', resourceId = undefined) {
const service = this.imageResource.getServices()[0]; const resource = this.getImageResourceOrDefault(resourceId);
const service = resource && resource.getServices()[0];
if (!(service)) return undefined; if (!(service)) return undefined;
const region = 'full'; const region = 'full';
let size = w; let size = w;
const imageWidth = this.imageResource.getWidth(); const imageWidth = resource.getWidth();
if ((!w) || w === imageWidth) size = 'full'; if ((!w) || w === imageWidth) size = 'full';
const quality = Utils.getImageQuality(service.getProfile()); const quality = Utils.getImageQuality(service.getProfile());
const id = service.id.replace(/\/+$/, ''); const id = service.id.replace(/\/+$/, '');
...@@ -79,36 +81,24 @@ export default class ManifestoCanvas { ...@@ -79,36 +81,24 @@ export default class ManifestoCanvas {
* Will negotiate a v2 or v3 type of resource * Will negotiate a v2 or v3 type of resource
*/ */
get imageResource() { get imageResource() {
return (this.presentation2ImageResource || this.presentation3ImageResource); return this.imageResources[0];
} }
/** */ /** */
get presentation2ImageResource() { get imageResources() {
if (!( const resources = flattenDeep([
this.canvas.getImages()[0] this.canvas.getImages().map(i => i.getResource()),
&& this.canvas.getImages()[0].getResource() this.canvas.getContent().map(i => i.getBody()),
&& this.canvas.getImages()[0].getResource().getServices()[0] ]);
&& this.canvas.getImages()[0].getResource().getServices()[0].id
)) { return flatten(resources.map((resource) => {
return undefined; switch (resource.getProperty('type')) {
} case 'oa:Choice':
return new Canvas({ images: flatten([resource.getProperty('default'), resource.getProperty('item')]).map(r => ({ resource: r })) }, this.canvas.options).getImages().map(i => i.getResource());
return this.canvas.getImages()[0].getResource(); default:
} return resource;
/** */
get presentation3ImageResource() {
if (!(
this.canvas.getContent()[0]
&& this.canvas.getContent()[0]
&& this.canvas.getContent()[0].getBody()[0]
&& this.canvas.getContent()[0].getBody()[0].getServices()[0]
&& this.canvas.getContent()[0].getBody()[0].getServices()[0].id
)) {
return undefined;
} }
})).filter(r => r && r.getServices()[0] && r.getServices()[0].id);
return this.canvas.getContent()[0].getBody()[0];
} }
/** /**
...@@ -121,15 +111,29 @@ export default class ManifestoCanvas { ...@@ -121,15 +111,29 @@ export default class ManifestoCanvas {
return this.imageResource.getServices()[0].id; return this.imageResource.getServices()[0].id;
} }
/** /** */
*/ get imageIds() {
get imageInformationUri() { return this.imageResources.map(r => r.getServices()[0].id);
if (!(this.imageId)) { }
return undefined;
/** */
getImageResourceOrDefault(resourceId) {
const resources = this.imageResources;
if (resourceId) return resources.find(r => r.id === resourceId);
return resources[0];
} }
/** @private */
imageInformationUri(resourceId) {
const image = this.getImageResourceOrDefault(resourceId);
const imageId = image && image.getServices()[0].id;
if (!imageId) return undefined;
return `${ return `${
this.imageId.replace(/\/$/, '') imageId.replace(/\/$/, '')
}/info.json`; }/info.json`;
} }
...@@ -183,11 +187,11 @@ export default class ManifestoCanvas { ...@@ -183,11 +187,11 @@ export default class ManifestoCanvas {
* Creates a canonical image request for a thumb * Creates a canonical image request for a thumb
* @param {Number} height * @param {Number} height
*/ */
thumbnail(maxWidth = undefined, maxHeight = undefined) { thumbnail(maxWidth = undefined, maxHeight = undefined, resourceId = undefined) {
let width; let width;
let height; let height;
if (!this.imageInformationUri) { if (!this.imageInformationUri(resourceId)) {
return undefined; return undefined;
} }
...@@ -205,7 +209,7 @@ export default class ManifestoCanvas { ...@@ -205,7 +209,7 @@ export default class ManifestoCanvas {
// note that, although the IIIF server may support sizeByConfinedWh (e.g. !w,h) // note that, although the IIIF server may support sizeByConfinedWh (e.g. !w,h)
// this is a IIIF level 2 feature, so we're instead providing w, or h,-style requests // this is a IIIF level 2 feature, so we're instead providing w, or h,-style requests
// which are only level 1. // which are only level 1.
return this.canonicalImageUri().replace(/\/full\/.*\/0\//, `/full/${width || ''},${height || ''}/0/`); return this.canonicalImageUri(undefined, undefined, resourceId).replace(/\/full\/.*\/0\//, `/full/${width || ''},${height || ''}/0/`);
} }
/** @private */ /** @private */
......
...@@ -2,6 +2,7 @@ import { createSelector } from 'reselect'; ...@@ -2,6 +2,7 @@ import { createSelector } from 'reselect';
import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import { Utils } from 'manifesto.js/dist-esmodule/Utils';
import flatten from 'lodash/flatten'; import flatten from 'lodash/flatten';
import CanvasGroupings from '../../lib/CanvasGroupings'; import CanvasGroupings from '../../lib/CanvasGroupings';
import ManifestoCanvas from '../../lib/ManifestoCanvas';
import { getManifestoInstance } from './manifests'; import { getManifestoInstance } from './manifests';
import { getWindow, getWindowViewType } from './windows'; import { getWindow, getWindowViewType } from './windows';
...@@ -200,11 +201,16 @@ export const selectInfoResponse = createSelector( ...@@ -200,11 +201,16 @@ export const selectInfoResponse = createSelector(
getCanvas, getCanvas,
selectInfoResponses, selectInfoResponses,
], ],
(canvas, infoResponses) => canvas && canvas.getImages()[0] (canvas, infoResponses) => {
&& canvas.getImages()[0].getResource().getServices()[0] if (!canvas) return undefined;
&& infoResponses[canvas.getImages()[0].getResource().getServices()[0].id] const manifestoCanvas = new ManifestoCanvas(canvas);
&& !infoResponses[canvas.getImages()[0].getResource().getServices()[0].id].isFetching const image = manifestoCanvas.imageResources[0];
&& infoResponses[canvas.getImages()[0].getResource().getServices()[0].id], const iiifServiceId = image && image.getServices()[0].id;
return iiifServiceId && infoResponses[iiifServiceId]
&& !infoResponses[iiifServiceId].isFetching
&& infoResponses[iiifServiceId];
},
); );
const authServiceProfiles = { const authServiceProfiles = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment