Skip to content

Commit

Permalink
COLUMBIA: Core mirador behaviors to provide a plugin target for text …
Browse files Browse the repository at this point in the history
…resources

- refactor type-based filters into a module
- MiradorCanvas.imagesResources does not assume any service is an image service
- stub TextViewer shows empty div, source elements for text resources, and canvas navigation
- fixes ProjectMirador#4085
  • Loading branch information
barmintor committed Feb 12, 2025
1 parent 5f72533 commit 2599fa5
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 19 deletions.
31 changes: 31 additions & 0 deletions __tests__/src/lib/resourceFilters.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Utils } from 'manifesto.js';
import flattenDeep from 'lodash/flattenDeep';
import manifestFixture019 from '../../fixtures/version-2/019.json';
import {
filterByProfiles, filterByTypes,
} from '../../../src/lib/resourceFilters';

describe('resourceFilters', () => {
let canvas;
beforeEach(() => {
[canvas] = Utils.parseManifest(manifestFixture019).getSequences()[0].getCanvases();
});
describe('filterByProfiles', () => {
it('filters resources', () => {
const services = flattenDeep(canvas.resourceAnnotations.map((a) => a.getResource().getServices()));
expect(filterByProfiles(services, 'http://iiif.io/api/image/2/level2.json').map((s) => s.id)).toEqual([
'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44',
]);
expect(filterByProfiles(services, 'http://nonexistent.io/api/service.json').map((s) => s.id)).toEqual([]);
});
});
describe('filterByTypes', () => {
it('filters resources', () => {
const resources = flattenDeep(canvas.resourceAnnotations.map((a) => a.getResource()));
expect(filterByTypes(resources, 'dctypes:Image').map((r) => r.id)).toEqual([
'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44/full/full/0/default.jpg',
]);
expect(filterByTypes(resources, 'Nonexistent').map((r) => r.id)).toEqual([]);
});
});
});
39 changes: 20 additions & 19 deletions src/lib/MiradorCanvas.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import { Canvas, AnnotationPage, Annotation } from 'manifesto.js';
import {
audioResourcesFrom, choiceResourcesFrom, hasImageService, imageResourcesFrom, iiifImageResourcesFrom,
textResourcesFrom, videoResourcesFrom,
} from './resourceFilters';
import canvasTypes from './canvasTypes';

/**
* MiradorCanvas - adds additional, testable logic around Manifesto's Canvas
* https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html
Expand Down Expand Up @@ -68,7 +74,8 @@ export default class MiradorCanvas {
get imageResources() {
const resources = flattenDeep([
this.canvas.getImages().map(i => i.getResource()),
this.canvas.getContent().map(i => i.getBody()),
imageResourcesFrom(this.contentBodies),
choiceResourcesFrom(this.contentBodies),
]);

return flatten(resources.map((resource) => {
Expand All @@ -82,35 +89,30 @@ export default class MiradorCanvas {
}

/** */
get textResources() {
const resources = flattenDeep([
get contentBodies() {
return flattenDeep([
this.canvas.getContent().map(i => i.getBody()),
]);
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Text'));
}

/** */
get textResources() {
return textResourcesFrom(this.contentBodies);
}

/** */
get videoResources() {
const resources = flattenDeep([
this.canvas.getContent().map(i => i.getBody()),
]);
return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video'));
return flatten(videoResourcesFrom(this.contentBodies));
}

/** */
get audioResources() {
const resources = flattenDeep([
this.canvas.getContent().map(i => i.getBody()),
]);

return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound'));
return flatten(audioResourcesFrom(this.contentBodies));
}

/** */
get v2VttContent() {
const resources = flattenDeep([
this.canvas.getContent().map(i => i.getBody()),
]);
const resources = this.contentBodies;

return flatten(resources.filter((resource) => resource.getProperty('format') === 'text/vtt'));
}
Expand Down Expand Up @@ -166,13 +168,12 @@ export default class MiradorCanvas {

/** */
get iiifImageResources() {
return this.imageResources
.filter(r => r && r.getServices()[0] && r.getServices()[0].id);
return iiifImageResourcesFrom(this.imageResources);
}

/** */
get imageServiceIds() {
return this.iiifImageResources.map(r => r.getServices()[0].id);
return this.iiifImageResources.map(hasImageService);
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/lib/canvasTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** values for type/@type that indicate an image content resource */
const imageTypes = ['Image', 'StillImage', 'dctypes:Image', 'dctypes:StillImage'];

/** values for type/@type that indicate a sound content resource */
const audioTypes = ['Audio', 'Sound', 'dctypes:Audio', 'dctypes:Sound'];

/** values for type/@type that indicate a choice resource */
const choiceTypes = ['oa:Choice'];

/** values for type/@type that indicate a text content resource */
const textTypes = ['Document', 'Text', 'dctypes:Document', 'dctypes:Text'];

/** values for type/@type that indicate a video content resource */
const videoTypes = ['Video', 'MovingImage', 'dctypes:Video', 'dctypes:MovingImage'];

export default {
audioTypes,
choiceTypes,
imageTypes,
textTypes,
videoTypes,
};
70 changes: 70 additions & 0 deletions src/lib/resourceFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import canvasTypes from './canvasTypes';
import serviceProfiles from './serviceProfiles';

/**
* Filter resources by profile property in given profiles
*/
export function filterByProfiles(resources, profiles) {
if (profiles === undefined || resources === undefined) return [];

if (!Array.isArray(profiles)) {
return resources.filter((resource) => profiles === resource.getProperty('profile'));
}

return resources.filter((resource) => profiles.includes(resource.getProperty('profile')));
}

/**
* Filter resources by type property in given types
*/
export function filterByTypes(resources, types) {
if (types === undefined || resources === undefined) return [];

if (!Array.isArray(types)) {
return resources.filter((resource) => types === resource.getProperty('type'));
}

return resources.filter((resource) => types.includes(resource.getProperty('type')));
}

/** */
export function audioResourcesFrom(resources) {
return filterByTypes(resources, canvasTypes.audioTypes);
}

/** */
export function choiceResourcesFrom(resources) {
return filterByTypes(resources, canvasTypes.choiceTypes);
}

/**
*/
export function imageServicesFrom(services) {
return filterByProfiles(services, serviceProfiles.iiifImageProfiles);
}

/** */
export function hasImageService(resource) {
const imageServices = imageServicesFrom(resource ? resource.getServices() : []);
return imageServices[0] && imageServices[0].id;
}

/** */
export function iiifImageResourcesFrom(resources) {
return imageResourcesFrom(resources).filter((r) => hasImageService(r));
}

/** */
export function imageResourcesFrom(resources) {
return filterByTypes(resources, canvasTypes.imageTypes);
}

/** */
export function textResourcesFrom(resources) {
return filterByTypes(resources, canvasTypes.textTypes);
}

/** */
export function videoResourcesFrom(resources) {
return filterByTypes(resources, canvasTypes.videoTypes);
}
13 changes: 13 additions & 0 deletions src/lib/serviceProfiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** values for profile that indicate an image service */
const iiifImageProfiles = [
'level2',
'level1',
'level0',
'http://iiif.io/api/image/2/level2.json',
'http://iiif.io/api/image/2/level1.json',
'http://iiif.io/api/image/2/level0.json',
];

export default {
iiifImageProfiles,
};

0 comments on commit 2599fa5

Please sign in to comment.