diff --git a/src/Manifest.ts b/src/Manifest.ts index 024a2e6c..235ef465 100644 --- a/src/Manifest.ts +++ b/src/Manifest.ts @@ -164,14 +164,15 @@ export class Manifest extends IIIFResource { this._parseRanges(item, path + "/" + i, range); } else if ( (item["@type"] && item["@type"].toLowerCase() === "sc:canvas") || - (item["type"] && item["type"].toLowerCase() === "canvas") + (item["type"] && item["type"].toLowerCase() === "canvas") || + (item["type"] && item["type"].toLowerCase() === "specificresource") ) { // store the ids on the __jsonld object to be used by Range.getCanvasIds() if (!range.canvases) { range.canvases = []; } - - const id: string = item.id || item["@id"]; + + const id: string = item.id || item["@id"] || item["source"]; range.canvases.push(id); } diff --git a/src/Range.ts b/src/Range.ts index 8366433e..53fead0a 100644 --- a/src/Range.ts +++ b/src/Range.ts @@ -1,4 +1,5 @@ import { + Canvas, Duration, IManifestoOptions, ManifestResource, @@ -13,6 +14,7 @@ import { } from "@iiif/vocabulary/dist-commonjs"; export class Range extends ManifestResource { + private _canvases: Canvas[] | null = null; private _ranges: Range[] | null = null; public canvases: string[] | null = null; public items: ManifestResource[] = []; @@ -20,7 +22,7 @@ export class Range extends ManifestResource { public path: string; public treeNode: TreeNode; - constructor(jsonld?: any, options?: IManifestoOptions) { + constructor(jsonld ? : any, options ? : IManifestoOptions) { super(jsonld, options); } @@ -34,6 +36,172 @@ export class Range extends ManifestResource { return []; } + getTopRange(range) { + let parentRange = range.parentRange; + if (parentRange) { + this.getTopRange(parentRange); + } + return parentRange; + } + + getTotalCanvases(): number { + return this.getCanvases().length; + } + + getCanvases(): Canvas[] { + if (this._canvases) { + return this._canvases; + } + + let manifestSequence = this.getTopRange( + this + ).options.resource.getSequences()[0]; + let manifestCanvases = + manifestSequence.__jsonld.canvases || manifestSequence.__jsonld.elements; + + const canvasLength = this.canvases ? this.canvases.length : 0; + let canvasItems: (Canvas | null)[] = new Array(canvasLength).fill(null); + + const rangeItems = this.__jsonld.items; + + if (manifestCanvases && this.canvases) { + for (let i = 0; i < manifestCanvases.length; i++) { + let c = manifestCanvases[i]; + + const fragmentCanvas = rangeItems.filter(item => { + return item.source === c.id; + }); + + if (c.id in this.canvases) { + if (fragmentCanvas) { + const fragment = fragmentCanvas[0].selector.value; + const fragmentCanvasId = `${c.id}#${fragment}`; + c = this._updateFragmentIds(c, fragmentCanvasId); + } + + const canvas: Canvas = new Canvas(c, this.options); + canvas.index = this.canvases.indexOf(c.id); + canvasItems.splice(canvas.index, 1, canvas); + } + } + } else if (manifestSequence.__jsonld && this.canvases) { + for (let i = 0; i < manifestSequence.__jsonld.length; i++) { + let c = manifestSequence.__jsonld[i]; + + const fragmentCanvas = rangeItems.filter(item => { + return item.source === c.id; + }); + + if (this.canvases.includes(c.id)) { + const cIndex = this.canvases.indexOf(c.id); + if (fragmentCanvas) { + const fragment = fragmentCanvas[0].selector.value; + const fragmentCanvasId = `${c.id}#${fragment}`; + c = this._updateFragmentIds(c, fragmentCanvasId); + } + + const canvas: Canvas = new Canvas(c, this.options); + canvas.index = cIndex; + + canvasItems.splice(canvas.index, 1, canvas); + } + } + } + + this._canvases = + canvasItems.length > 0 ? + !canvasItems.includes(null) ? + < Canvas[] > canvasItems : + null : + null; + + return this._canvases !== null ? this._canvases : []; + } + + // update __jsonld canvas id's because that is used by other functions in + // the library when working with canvases + _updateFragmentIds(canvasJson: any, newCanvasId: string): any { + // update ids in annotations + const items = canvasJson.items || canvasJson.content; + const annotations = items.length && items[0].items ? items[0].items : []; + + if (annotations && canvasJson.items) { + for (let i = 0; i < annotations.length; i++) { + canvasJson["id"] = newCanvasId; + // update target canvas Id in all canvas annotations + canvasJson.items[0].items[i]["target"] = newCanvasId; + } + } else if (annotations) { + for (let i = 0; i < annotations.length; i++) { + canvasJson["id"] = newCanvasId; + // update target canvas Id in all canvas annotations + // replace this with (something that looks at other contents) + canvasJson.content[0].items[i]["target"] = newCanvasId; + } + } + + return canvasJson; + } + + getCanvasByIndex(canvasIndex: number): any { + return this.getCanvases()[canvasIndex]; + } + + getCanvasById(id: string): Canvas | null { + for (let i = 0; i < this.getTotalCanvases(); i++) { + const canvas = this.getCanvasByIndex(i); + + const canvasId: string = Utils.normaliseUrl(canvas.id); + + if (Utils.normaliseUrl(id) === canvasId) { + return canvas; + } + } + return null; + } + + // Alternative solution can be this + /* getTopLevelCanvases(): Canvas[] { + if(this._canvases){ + return this._canvases; + } + + if (this.items.length) { + let canvasItems: Canvas[] = []; + for (let i = 0; i < this.items.length; i++) { + if (this.items[i].isCanvas()) { + canvasItems.push(this.items[i]); + } + } + return this._canvases = canvasItems; + } + + let items = this.__jsonld.items || this.__jsonld.elements; + + if (items) { + for (let i = 0; i < items.length; i++) { + if (items[i].type.includes("Canvas")) { + const c = items[i]; + const canvas: Canvas = new Canvas(c, this.options); + canvas.index = i; + this.items.push(canvas); + } + } + } else if (this.__jsonld) { + for (let i = 0; i < this.__jsonld.length; i++) { + if (items[i].type.includes("Canvas")) { + const c = this.__jsonld[i]; + const canvas: Canvas = new Canvas(c, this.options); + canvas.index = i; + this.items.push(canvas); + } + } + } + + return this._canvases = this.items; + } + */ + getDuration(): Duration | undefined { // For this implementation, we want to catch SOME of the temporal cases - i.e. when there is a t=1,100 if (this.canvases && this.canvases.length) { @@ -145,7 +313,7 @@ export class Range extends ManifestResource { return this._ranges; } - return (this._ranges = this.items.filter(m => m.isRange())); + return (this._ranges = < Range[] > this.items.filter(m => m.isRange())); } getBehavior(): Behavior | null { @@ -203,7 +371,7 @@ export class Range extends ManifestResource { } private _parseTreeNode(node: TreeNode, range: Range): void { - node.label = range.getLabel().getValue(this.options.locale); + node.label = < string > range.getLabel().getValue(this.options.locale); node.data = range; node.data.type = Utils.normaliseType(TreeNodeType.RANGE); range.treeNode = node; @@ -225,4 +393,4 @@ export class Range extends ManifestResource { } } } -} +} \ No newline at end of file diff --git a/src/Sequence.ts b/src/Sequence.ts index 4c9697db..d78b1fde 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -23,7 +23,6 @@ export class Sequence extends ManifestResource { } let items = this.__jsonld.canvases || this.__jsonld.elements; - if (items) { for (let i = 0; i < items.length; i++) { const c = items[i]; diff --git a/test/fixtures/pres3.json b/test/fixtures/pres3.json index bcbdeb5c..65b6daf2 100644 --- a/test/fixtures/pres3.json +++ b/test/fixtures/pres3.json @@ -142,6 +142,67 @@ ] } ] + }, + { + "id": "http://example.org/iiif/book1/canvas/1", + "type": "Canvas", + "label": { + "@none": [ + "p. 1" + ] + }, + "height": 1000, + "width": 750, + "duration": 180.0, + "items": [ + { + "id": "http://example.org/iiif/book1/canvas/1/annotationpage/0", + "type": "AnnotationPage", + "items": [ + { + "id": "http://example.org/iiif/book1/canvas/1/annotation/0", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "http://example.org/iiif/book1/canvas/1/images/page1.jpg", + "type": "Image", + "format": "image/jpeg", + "label": { + "@none": [ + "Page 1" + ] + } + }, + "target": "http://example.org/iiif/book1/canvas/1" + } + ] + } + ] } + ], + "structures": [ + { + "id": "http://foo.test/1/range/root", + "type": "Range", + "behavior": "sequence", + "items": [ + { + "type": "SpecificResource", + "source": "http://example.org/iiif/book1/canvas/0", + "selector": { + "type": "FragmentSelector", + "value": "xywh=0,0,750,300" + } + }, + { + "type": "SpecificResource", + "source": "http://example.org/iiif/book1/canvas/1", + "selector": { + "type": "FragmentSelector", + "value": "xywh=0,0,750,300" + } + } + ] + } ] } \ No newline at end of file diff --git a/test/tests/pres3.js b/test/tests/pres3.js index adca8c6c..57a40fe2 100644 --- a/test/tests/pres3.js +++ b/test/tests/pres3.js @@ -3,7 +3,7 @@ var should = require('chai').should(); var manifesto = require('../../dist-commonjs/'); var manifests = require('../fixtures/manifests'); -var manifest, sequence, canvas, content, annotation, body; +var manifest, range, sequence, canvas, content, annotation, body; describe('presentation 3', function() { @@ -23,6 +23,11 @@ describe('presentation 3', function() { expect(metadata[0].label[0].value).to.equal('Author'); }); + it('has a range', function() { + range = manifest.getTopRanges()[0].getRanges()[0]; + expect(range).to.exist; + }); + it('has a sequence', function() { sequence = manifest.getSequenceByIndex(0); expect(sequence).to.exist; @@ -31,6 +36,23 @@ describe('presentation 3', function() { it('has a canvas', function() { canvas = sequence.getCanvases()[0]; expect(canvas).to.exist; + var canvasById = sequence.getCanvasById('http://example.org/iiif/book1/canvas/0'); + expect(canvasById).to.exist; + var getCanvasByIndex = sequence.getCanvasByIndex(0); + expect(getCanvasByIndex).to.exist; + }); + + it('range has a canvas', function() { + canvas = range.getCanvases()[0]; + expect(canvas).to.exist; +/* B */ + }); + + it('updates canvas Id if canvas is listed as a fragment in range', function() { + canvas = range.getCanvases()[1]; + const hasFragment = canvas.id.includes('#xywh=') + expect(hasFragment).to.exist; +/* B */ }); it('has an annotation body', function() {