mirador
Version:
An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.
215 lines (189 loc) • 6.45 kB
JavaScript
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import { Canvas, AnnotationPage, Annotation } from 'manifesto.js';
import { getIiifResourceImageService } from './iiif';
/**
* MiradorCanvas - adds additional, testable logic around Manifesto's Canvas
* https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html
*/
export default class MiradorCanvas {
/**
* @param {MiradorCanvas} canvas
*/
constructor(canvas) {
this.canvas = canvas;
}
/** */
get id() {
return this.canvas.id;
}
/** */
getWidth() {
return this.canvas.getWidth();
}
/** */
getHeight() {
return this.canvas.getHeight();
}
/**
*/
get aspectRatio() {
return this.canvas.getWidth() / this.canvas.getHeight();
}
/**
* Fetches AnnotationList URIs from canvas's otherContent property
*
* Supported otherContent types:
* - Objects having @type property of "sc:AnnotationList" and URI in @id
* - Strings being the URIs
*/
get annotationListUris() {
return flatten(new Array(this.canvas.__jsonld.otherContent))
.filter(
(otherContent) => otherContent && (typeof otherContent === 'string' || otherContent['@type'] === 'sc:AnnotationList'),
)
.map((otherContent) => (typeof otherContent === 'string' ? otherContent : otherContent['@id']));
}
/** */
get canvasAnnotationPages() {
return flatten(new Array(this.canvas.__jsonld.annotations)).filter(
(annotations) => annotations && annotations.type === 'AnnotationPage',
);
}
/**
* Will negotiate a v2 or v3 type of resource
*/
get imageResource() {
return this.imageResources[0];
}
/** */
get imageResources() {
// TODO Clean up the following hack as soon as manifesto.js provides any information if an annotation body is a Choice option, and if so, whether it is the preferred one.
const resources = flattenDeep([
this.canvas.getImages().map((i) => i.getResource()),
this.canvas.getContent().map((i) => (i.__jsonld.body.type === 'Choice' ? i.__jsonld.body : i.getBody())),
]);
return flatten(
resources.map((resource) => {
const type = resource.type || resource.getProperty('type');
switch (type) {
case 'Choice': {
return new Canvas({ images: resource.items.map((r) => ({ resource: r })) }, this.canvas.options)
.getImages()
.map((img, index) => {
const r = img.getResource();
if (r) {
r.preferred = !index;
}
return r;
});
}
case 'oa:Choice': {
return new Canvas(
{
images: flattenDeep([resource.getProperty('default'), resource.getProperty('item')]).map((r) => ({
resource: r,
})),
},
this.canvas.options,
)
.getImages()
.map((img, index) => {
const r = img.getResource();
if (r) {
r.preferred = !index;
}
return r;
});
}
default: {
const r = resource;
r.preferred = true;
return r;
}
}
}),
);
}
/** */
get textResources() {
const resources = flattenDeep([this.canvas.getContent().map((i) => i.getBody())]);
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Text'));
}
/** */
get videoResources() {
const resources = flattenDeep([this.canvas.getContent().map((i) => i.getBody())]);
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video'));
}
/** */
get audioResources() {
const resources = flattenDeep([this.canvas.getContent().map((i) => i.getBody())]);
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound'));
}
/** */
get v2VttContent() {
const resources = flattenDeep([this.canvas.getContent().map((i) => i.getBody())]);
return flatten(resources.filter((resource) => resource.getProperty('format') === 'text/vtt'));
}
/** IIIF v3 captions are stored as 'supplementing' Annotations rather than in the resource content itself */
get v3VttContent() {
const resources = flattenDeep(
this.canvasAnnotationPages.map((annoPage) => {
const manifestoAnnoPage = new AnnotationPage(annoPage, this.canvas.options);
return manifestoAnnoPage.getItems().map((item) => {
const manifestoAnnotation = new Annotation(item, this.canvas.options);
return manifestoAnnotation.getBody();
});
}),
);
return flatten(resources.filter((resource) => resource.getProperty('format') === 'text/vtt'));
}
/** */
get resourceAnnotations() {
return flattenDeep([this.canvas.getImages(), this.canvas.getContent()]);
}
/**
* Returns a given resource Annotation, based on a contained resource or body
* id
*/
resourceAnnotation(id) {
return this.resourceAnnotations.find(
(anno) => anno.getResource().id === id || flatten(new Array(anno.getBody())).some((body) => body.id === id),
);
}
/**
* Returns the fragment placement values if a resourceAnnotation is placed on
* a canvas somewhere besides the full extent
*/
onFragment(id) {
const resourceAnnotation = this.resourceAnnotation(id);
if (!resourceAnnotation) return undefined;
// IIIF v2
const on = resourceAnnotation.getProperty('on');
// IIIF v3
const target = resourceAnnotation.getProperty('target');
const fragmentMatch = (on || target).match(/xywh=(.*)$/);
if (!fragmentMatch) return undefined;
return fragmentMatch[1].split(',').map((str) => parseInt(str, 10));
}
/** */
get iiifImageResources() {
return this.imageResources.filter((r) => r && getIiifResourceImageService(r)?.id);
}
/** */
get imageServiceIds() {
return this.iiifImageResources.map((r) => r && getIiifResourceImageService(r)?.id);
}
/**
* Get the canvas service
*/
get service() {
return this.canvas.__jsonld.service;
}
/**
* Get the canvas label
*/
getLabel(locale = undefined) {
return this.canvas.getLabel().length > 0 ? this.canvas.getLabel().getValue(locale) : String(this.canvas.index + 1);
}
}