cozy-iiif 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Cozy.d.ts +3 -0
- package/dist/helpers/import-annotations.d.ts +3 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/index.js +405 -310
- package/dist/types.d.ts +2 -1
- package/package.json +3 -2
- package/src/Cozy.ts +104 -102
- package/src/helpers/import-annotations.ts +115 -0
- package/src/helpers/index.ts +1 -0
- package/src/types.ts +3 -1
- package/test/Cozy.test.ts +1 -1
- package/test/annotations/fixtures.ts +135 -0
- package/test/annotations/import-annotations.test.ts +46 -0
package/dist/types.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Manifest, Canvas, ImageService2, ImageService3, IIIFExternalWebResource, Collection, Range } from '@iiif/presentation-3';
|
1
|
+
import { Manifest, Canvas, ImageService2, ImageService3, IIIFExternalWebResource, Collection, Range, AnnotationPage } from '@iiif/presentation-3';
|
2
2
|
export type CozyParseResult = {
|
3
3
|
type: 'collection';
|
4
4
|
url: string;
|
@@ -60,6 +60,7 @@ export interface CozyCanvas {
|
|
60
60
|
readonly width: number;
|
61
61
|
readonly height: number;
|
62
62
|
readonly images: CozyImageResource[];
|
63
|
+
readonly annotations: AnnotationPage[];
|
63
64
|
getLabel(locale?: string): string;
|
64
65
|
getMetadata(locale?: string): CozyMetadata[];
|
65
66
|
getThumbnailURL(minSize?: number): string;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "cozy-iiif",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.6",
|
4
4
|
"description": "A developer-friendly collection of abstractions and utilities built on top of @iiif/presentation-3 and @iiif/parser",
|
5
5
|
"license": "MIT",
|
6
6
|
"author": "Rainer Simon",
|
@@ -34,6 +34,7 @@
|
|
34
34
|
"dependencies": {
|
35
35
|
"@iiif/parser": "^2.1.7",
|
36
36
|
"@iiif/presentation-3": "^2.2.3",
|
37
|
-
"p-throttle": "^7.0.0"
|
37
|
+
"p-throttle": "^7.0.0",
|
38
|
+
"uuid": "^11.1.0"
|
38
39
|
}
|
39
40
|
}
|
package/src/Cozy.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import type { Canvas, Collection, Manifest, Range } from '@iiif/presentation-3';
|
2
2
|
import { convertPresentation2 } from '@iiif/parser/presentation-2';
|
3
3
|
import { Traverse } from '@iiif/parser';
|
4
|
+
import * as Helpers from './helpers';
|
4
5
|
import {
|
5
6
|
getImages,
|
6
7
|
getLabel,
|
@@ -21,123 +22,121 @@ import type {
|
|
21
22
|
ImageServiceResource
|
22
23
|
} from './types';
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
};
|
35
|
-
}
|
25
|
+
const parseURL = async (input: string): Promise<CozyParseResult> => {
|
26
|
+
try {
|
27
|
+
new URL(input);
|
28
|
+
} catch {
|
29
|
+
return {
|
30
|
+
type: 'error',
|
31
|
+
code: 'INVALID_URL',
|
32
|
+
message: 'The provided input is not a valid URL'
|
33
|
+
};
|
34
|
+
}
|
36
35
|
|
37
|
-
|
36
|
+
let response: Response;
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
return {
|
43
|
-
type: 'error',
|
44
|
-
code: 'INVALID_HTTP_RESPONSE',
|
45
|
-
message: `Server responded: HTTP ${response.status} ${response.statusText ? `(${response.statusText})` : ''}`
|
46
|
-
}
|
47
|
-
}
|
48
|
-
} catch (error) {
|
38
|
+
try {
|
39
|
+
response = await fetch(input);
|
40
|
+
if (!response.ok) {
|
49
41
|
return {
|
50
42
|
type: 'error',
|
51
|
-
code: '
|
52
|
-
message:
|
53
|
-
}
|
43
|
+
code: 'INVALID_HTTP_RESPONSE',
|
44
|
+
message: `Server responded: HTTP ${response.status} ${response.statusText ? `(${response.statusText})` : ''}`
|
45
|
+
}
|
54
46
|
}
|
47
|
+
} catch (error) {
|
48
|
+
return {
|
49
|
+
type: 'error',
|
50
|
+
code: 'FETCH_ERROR',
|
51
|
+
message: error instanceof Error ? error.message : 'Failed to fetch resource'
|
52
|
+
};
|
53
|
+
}
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
const contentType = response.headers.get('content-type');
|
56
|
+
|
57
|
+
if (contentType?.startsWith('image/')) {
|
58
|
+
return {
|
59
|
+
type: 'plain-image',
|
60
|
+
url: input
|
61
|
+
};
|
62
|
+
}
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
if (contentType?.includes('text/html')) {
|
65
|
+
return {
|
66
|
+
type: 'webpage',
|
67
|
+
url: input
|
68
|
+
};
|
69
|
+
}
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
message: 'Missing @context'
|
84
|
-
}
|
85
|
-
};
|
86
|
-
|
87
|
-
const id = getPropertyValue<string>(json, 'id');
|
88
|
-
|
89
|
-
if (!id) {
|
90
|
-
return {
|
91
|
-
type: 'error',
|
92
|
-
code: 'INVALID_MANIFEST',
|
93
|
-
message: 'Missing id property'
|
94
|
-
}
|
95
|
-
}
|
71
|
+
try {
|
72
|
+
const json = await response.json();
|
73
|
+
return parse(json, input);
|
74
|
+
} catch {
|
75
|
+
return {
|
76
|
+
type: 'error',
|
77
|
+
code: 'UNSUPPORTED_FORMAT',
|
78
|
+
message: 'Could not parse resource'
|
79
|
+
};
|
80
|
+
}
|
81
|
+
}
|
96
82
|
|
97
|
-
|
98
|
-
|
83
|
+
const parse = (json: any, url?: string): CozyParseResult => {
|
84
|
+
const context = Array.isArray(json['@context'])
|
85
|
+
? json['@context'].find(str => str.includes('iiif.io/api/'))
|
86
|
+
: json['@context'];
|
99
87
|
|
100
|
-
|
88
|
+
if (!context) {
|
89
|
+
return {
|
90
|
+
type: 'error',
|
91
|
+
code: 'INVALID_MANIFEST',
|
92
|
+
message: 'Missing @context'
|
93
|
+
}
|
94
|
+
};
|
101
95
|
|
102
|
-
|
103
|
-
type: 'collection',
|
104
|
-
url: input,
|
105
|
-
resource: parseCollectionResource(json, majorVersion)
|
106
|
-
} : {
|
107
|
-
type: 'manifest',
|
108
|
-
url: input,
|
109
|
-
resource: parseManifestResource(json, majorVersion)
|
110
|
-
};
|
111
|
-
}
|
112
|
-
|
113
|
-
if (context.includes('image/2') || context.includes('image/3')) {
|
114
|
-
const resource = parseImageResource(json);
|
115
|
-
return resource ? {
|
116
|
-
type: 'iiif-image',
|
117
|
-
url: input,
|
118
|
-
resource
|
119
|
-
} : {
|
120
|
-
type: 'error',
|
121
|
-
code: 'INVALID_MANIFEST',
|
122
|
-
message: 'Invalid image service definition'
|
123
|
-
}
|
124
|
-
}
|
96
|
+
const id = getPropertyValue<string>(json, 'id');
|
125
97
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
} catch {
|
132
|
-
return {
|
133
|
-
type: 'error',
|
134
|
-
code: 'UNSUPPORTED_FORMAT',
|
135
|
-
message: 'Could not parse resource'
|
136
|
-
};
|
98
|
+
if (!id) {
|
99
|
+
return {
|
100
|
+
type: 'error',
|
101
|
+
code: 'INVALID_MANIFEST',
|
102
|
+
message: 'Missing id property'
|
137
103
|
}
|
104
|
+
}
|
105
|
+
|
106
|
+
if (context.includes('presentation/2') || context.includes('presentation/3')) {
|
107
|
+
const majorVersion = context.includes('presentation/2') ? 2 : 3;
|
108
|
+
|
109
|
+
const type = getPropertyValue(json, 'type');
|
138
110
|
|
111
|
+
return type.includes('Collection') ? {
|
112
|
+
type: 'collection',
|
113
|
+
url: url || id,
|
114
|
+
resource: parseCollectionResource(json, majorVersion)
|
115
|
+
} : {
|
116
|
+
type: 'manifest',
|
117
|
+
url: url || id,
|
118
|
+
resource: parseManifestResource(json, majorVersion)
|
119
|
+
};
|
120
|
+
}
|
121
|
+
|
122
|
+
if (context.includes('image/2') || context.includes('image/3')) {
|
123
|
+
const resource = parseImageResource(json);
|
124
|
+
return resource ? {
|
125
|
+
type: 'iiif-image',
|
126
|
+
url: url || id,
|
127
|
+
resource
|
128
|
+
} : {
|
129
|
+
type: 'error',
|
130
|
+
code: 'INVALID_MANIFEST',
|
131
|
+
message: 'Invalid image service definition'
|
132
|
+
}
|
139
133
|
}
|
140
134
|
|
135
|
+
return {
|
136
|
+
type: 'error',
|
137
|
+
code: 'INVALID_MANIFEST',
|
138
|
+
message: 'JSON resource is not a recognized IIIF format'
|
139
|
+
};
|
141
140
|
}
|
142
141
|
|
143
142
|
const parseCollectionResource = (resource: any, majorVersion: number): CozyCollection => {
|
@@ -194,6 +193,7 @@ const parseManifestResource = (resource: any, majorVersion: number): CozyManifes
|
|
194
193
|
width: c.width,
|
195
194
|
height: c.height,
|
196
195
|
images,
|
196
|
+
annotations: (c.annotations || []),
|
197
197
|
getLabel: getLabel(c),
|
198
198
|
getMetadata: getMetadata(c),
|
199
199
|
getThumbnailURL: getThumbnailURL(c, images)
|
@@ -259,4 +259,6 @@ const parseImageResource = (resource: any) => {
|
|
259
259
|
serviceUrl: normalizeServiceUrl(getPropertyValue<string>(resource, 'id'))
|
260
260
|
} as ImageServiceResource;
|
261
261
|
}
|
262
|
-
}
|
262
|
+
}
|
263
|
+
|
264
|
+
export const Cozy = { parse, parseURL, Helpers };
|
@@ -0,0 +1,115 @@
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
2
|
+
import type { Annotation } from '@iiif/presentation-3';
|
3
|
+
import type { CozyCanvas, CozyManifest } from '../types';
|
4
|
+
|
5
|
+
// Helper to escape special characters in strings used in RegExp
|
6
|
+
const escapeRegExp = (str: string) =>
|
7
|
+
str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
8
|
+
|
9
|
+
const getAnnotationPageId = (canvas: CozyCanvas, namespace?: string) => {
|
10
|
+
if (namespace) {
|
11
|
+
// Use naming convention `{canvas.id}/namespace/page/p{idx}`. This means we need
|
12
|
+
// to find the highest index currently in use. (Index starts with 1.)
|
13
|
+
const pages = canvas.annotations;
|
14
|
+
if (pages.length > 0) {
|
15
|
+
const pattern = new RegExp(`${escapeRegExp(canvas.id)}/${escapeRegExp(namespace)}/page/p(\\d+)$`);
|
16
|
+
|
17
|
+
const highestIdx = pages.reduce<number>((highest, page) => {
|
18
|
+
const match = page.id.match(pattern);
|
19
|
+
if (match && match[1]) {
|
20
|
+
const thisIndex = parseInt(match[1]);
|
21
|
+
return Math.max(highest, thisIndex);
|
22
|
+
} else {
|
23
|
+
return highest;
|
24
|
+
}
|
25
|
+
}, 1);
|
26
|
+
|
27
|
+
return `${canvas.id}/${namespace}/annotations/page/p${highestIdx}`;
|
28
|
+
} else {
|
29
|
+
return `${canvas.id}/${namespace}/annotations/page/p1`;
|
30
|
+
}
|
31
|
+
} else {
|
32
|
+
// Use UUIDs as a fallback naming convention
|
33
|
+
return `${canvas.id}/annotations/page/${uuidv4()}`;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Will blindy attach the annotations to this canvas.
|
39
|
+
*/
|
40
|
+
const importAnnotationsToCanvas = (canvas: CozyCanvas, annotations: Annotation[], namespace?: string) => {
|
41
|
+
const page = {
|
42
|
+
id: getAnnotationPageId(canvas, namespace),
|
43
|
+
type: 'AnnotationPage',
|
44
|
+
items: annotations
|
45
|
+
}
|
46
|
+
|
47
|
+
return {
|
48
|
+
source: {
|
49
|
+
...canvas.source,
|
50
|
+
annotations: [...canvas.annotations, page]
|
51
|
+
},
|
52
|
+
id: canvas.id,
|
53
|
+
width: canvas.width,
|
54
|
+
height: canvas.height,
|
55
|
+
images: [...canvas.images],
|
56
|
+
annotations: [...canvas.annotations, page],
|
57
|
+
getLabel: canvas.getLabel,
|
58
|
+
getMetadata: canvas.getMetadata,
|
59
|
+
getThumbnailURL: canvas.getThumbnailURL
|
60
|
+
} as CozyCanvas;
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Will use 'source' information from the annotation targets to associate annotations with the right
|
65
|
+
* canvases.
|
66
|
+
*/
|
67
|
+
const importAnnotationsToManifest = (manifest: CozyManifest, annotations: Annotation[], namespace?: string) => {
|
68
|
+
const getSource = (annotation: Annotation) => {
|
69
|
+
const target = annotation.target;
|
70
|
+
if (!target) return;
|
71
|
+
|
72
|
+
if (typeof target === 'string')
|
73
|
+
return target.substring(0, target.indexOf('#'));
|
74
|
+
else
|
75
|
+
return (target as any).source;
|
76
|
+
}
|
77
|
+
|
78
|
+
const bySource = annotations.reduce<Record<string, Annotation[]>>((acc, annotation) => {
|
79
|
+
const source = getSource(annotation);
|
80
|
+
if (!source) return acc;
|
81
|
+
|
82
|
+
if (!acc[source]) acc[source] = [];
|
83
|
+
acc[source].push(annotation);
|
84
|
+
|
85
|
+
return acc;
|
86
|
+
}, {});
|
87
|
+
|
88
|
+
const canvases = manifest.canvases.map(canvas => {
|
89
|
+
const toImport = bySource[canvas.id] || [];
|
90
|
+
return toImport.length > 0 ? importAnnotationsToCanvas(canvas, toImport, namespace) : canvas;
|
91
|
+
});
|
92
|
+
|
93
|
+
return {
|
94
|
+
source: {
|
95
|
+
...manifest.source,
|
96
|
+
items: canvases.map(c => c.source)
|
97
|
+
},
|
98
|
+
id: manifest.id,
|
99
|
+
majorVersion: manifest.majorVersion,
|
100
|
+
canvases,
|
101
|
+
structure: manifest.structure,
|
102
|
+
getLabel: manifest.getLabel,
|
103
|
+
getMetadata: manifest.getMetadata,
|
104
|
+
getTableOfContents: manifest.getTableOfContents
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
export const importAnnotations = <T extends CozyManifest | CozyCanvas>(
|
109
|
+
resource: T,
|
110
|
+
annotations: Annotation[],
|
111
|
+
namespace?: string
|
112
|
+
): T extends CozyCanvas ? CozyCanvas : CozyManifest =>
|
113
|
+
resource.source.type === 'Canvas'
|
114
|
+
? importAnnotationsToCanvas(resource as CozyCanvas, annotations, namespace) as T extends CozyCanvas ? CozyCanvas : CozyManifest
|
115
|
+
: importAnnotationsToManifest(resource as CozyManifest, annotations, namespace) as T extends CozyCanvas ? CozyCanvas : CozyManifest;
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './import-annotations';
|
package/src/types.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { Manifest, Canvas, ImageService2, ImageService3, IIIFExternalWebResource, Collection, Range } from '@iiif/presentation-3';
|
1
|
+
import type { Manifest, Canvas, ImageService2, ImageService3, IIIFExternalWebResource, Collection, Range, Annotation, AnnotationPage } from '@iiif/presentation-3';
|
2
2
|
|
3
3
|
export type CozyParseResult =
|
4
4
|
| { type: 'collection', url: string, resource: CozyCollection }
|
@@ -88,6 +88,8 @@ export interface CozyCanvas {
|
|
88
88
|
|
89
89
|
readonly images: CozyImageResource[];
|
90
90
|
|
91
|
+
readonly annotations: AnnotationPage[];
|
92
|
+
|
91
93
|
getLabel(locale?: string): string;
|
92
94
|
|
93
95
|
getMetadata(locale?: string): CozyMetadata[];
|
package/test/Cozy.test.ts
CHANGED
@@ -0,0 +1,135 @@
|
|
1
|
+
// Modified from https://iiif.io/api/cookbook/recipe/0021-tagging/
|
2
|
+
export const ANNOTATIONS = [{
|
3
|
+
id: 'https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0001',
|
4
|
+
type: 'Annotation',
|
5
|
+
motivation: 'tagging',
|
6
|
+
body: {
|
7
|
+
type: 'TextualBody',
|
8
|
+
value: 'Test Annotation 1',
|
9
|
+
format: 'text/plain'
|
10
|
+
},
|
11
|
+
target: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1#xywh=265,661,1260,1239'
|
12
|
+
},{
|
13
|
+
id: 'https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0002',
|
14
|
+
type: 'Annotation',
|
15
|
+
motivation: 'tagging',
|
16
|
+
body: {
|
17
|
+
type: 'TextualBody',
|
18
|
+
value: 'Test Annotation 2',
|
19
|
+
format: 'text/plain'
|
20
|
+
},
|
21
|
+
target: {
|
22
|
+
source: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p2',
|
23
|
+
selector: {
|
24
|
+
type: 'FragmentSelector',
|
25
|
+
value: 'xywh=265,661,1260,1239'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}]
|
29
|
+
|
30
|
+
// https://iiif.io/api/cookbook/recipe/0001-mvm-image/
|
31
|
+
export const SINGLE_CANVAS_NO_ANNOTATIONS = {
|
32
|
+
'@context': 'http://iiif.io/api/presentation/3/context.json',
|
33
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/manifest.json',
|
34
|
+
type: 'Manifest',
|
35
|
+
label: {
|
36
|
+
en: [
|
37
|
+
'Single Image Example'
|
38
|
+
]
|
39
|
+
},
|
40
|
+
items: [
|
41
|
+
{
|
42
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1',
|
43
|
+
type: 'Canvas',
|
44
|
+
height: 1800,
|
45
|
+
width: 1200,
|
46
|
+
items: [
|
47
|
+
{
|
48
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/page/p1/1',
|
49
|
+
type: 'AnnotationPage',
|
50
|
+
items: [
|
51
|
+
{
|
52
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/annotation/p0001-image',
|
53
|
+
type: 'Annotation',
|
54
|
+
motivation: 'painting',
|
55
|
+
body: {
|
56
|
+
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
|
57
|
+
type: 'Image',
|
58
|
+
format: 'image/png',
|
59
|
+
height: 1800,
|
60
|
+
width: 1200
|
61
|
+
},
|
62
|
+
target: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1'
|
63
|
+
}
|
64
|
+
]
|
65
|
+
}
|
66
|
+
]
|
67
|
+
}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
|
71
|
+
export const TWO_CANVASES_NO_ANNOTATIONS = {
|
72
|
+
'@context': 'http://iiif.io/api/presentation/3/context.json',
|
73
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/manifest.json',
|
74
|
+
type: 'Manifest',
|
75
|
+
label: {
|
76
|
+
en: [
|
77
|
+
'Single Image Example'
|
78
|
+
]
|
79
|
+
},
|
80
|
+
items: [
|
81
|
+
{
|
82
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1',
|
83
|
+
type: 'Canvas',
|
84
|
+
height: 1800,
|
85
|
+
width: 1200,
|
86
|
+
items: [
|
87
|
+
{
|
88
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/page/p1/1',
|
89
|
+
type: 'AnnotationPage',
|
90
|
+
items: [
|
91
|
+
{
|
92
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/annotation/p0001-image',
|
93
|
+
type: 'Annotation',
|
94
|
+
motivation: 'painting',
|
95
|
+
body: {
|
96
|
+
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
|
97
|
+
type: 'Image',
|
98
|
+
format: 'image/png',
|
99
|
+
height: 1800,
|
100
|
+
width: 1200
|
101
|
+
},
|
102
|
+
target: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1'
|
103
|
+
}
|
104
|
+
]
|
105
|
+
}
|
106
|
+
]
|
107
|
+
}, {
|
108
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p2',
|
109
|
+
type: 'Canvas',
|
110
|
+
height: 1800,
|
111
|
+
width: 1200,
|
112
|
+
items: [
|
113
|
+
{
|
114
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/page/p1/1',
|
115
|
+
type: 'AnnotationPage',
|
116
|
+
items: [
|
117
|
+
{
|
118
|
+
id: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/annotation/p0001-image',
|
119
|
+
type: 'Annotation',
|
120
|
+
motivation: 'painting',
|
121
|
+
body: {
|
122
|
+
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
|
123
|
+
type: 'Image',
|
124
|
+
format: 'image/png',
|
125
|
+
height: 1800,
|
126
|
+
width: 1200
|
127
|
+
},
|
128
|
+
target: 'https://iiif.io/api/cookbook/recipe/0001-mvm-image/canvas/p1'
|
129
|
+
}
|
130
|
+
]
|
131
|
+
}
|
132
|
+
]
|
133
|
+
}
|
134
|
+
]
|
135
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
2
|
+
import { Annotation } from '@iiif/presentation-3';
|
3
|
+
import { Cozy, CozyManifest } from '../../src';
|
4
|
+
|
5
|
+
import {
|
6
|
+
ANNOTATIONS,
|
7
|
+
SINGLE_CANVAS_NO_ANNOTATIONS,
|
8
|
+
TWO_CANVASES_NO_ANNOTATIONS
|
9
|
+
} from './fixtures';
|
10
|
+
|
11
|
+
describe('import-annotations', () => {
|
12
|
+
|
13
|
+
it('should insert a new page into a canvas with no annotatinos', () => {
|
14
|
+
const result = Cozy.parse(SINGLE_CANVAS_NO_ANNOTATIONS);
|
15
|
+
|
16
|
+
expect(result.type).toBe('manifest');
|
17
|
+
const manifest = (result as any).resource as CozyManifest;
|
18
|
+
|
19
|
+
expect(manifest.canvases.length).toBe(1);
|
20
|
+
const firstCanvas = manifest.canvases[0];
|
21
|
+
|
22
|
+
const annotations = ANNOTATIONS as Annotation[];
|
23
|
+
|
24
|
+
const modified = Cozy.Helpers.importAnnotations(firstCanvas, annotations)
|
25
|
+
expect(modified.annotations.length).toBe(1);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('should correctly insert annotation pages into the test manifest', () => {
|
29
|
+
const result = Cozy.parse(TWO_CANVASES_NO_ANNOTATIONS);
|
30
|
+
|
31
|
+
expect(result.type).toBe('manifest');
|
32
|
+
const manifest = (result as any).resource as CozyManifest;
|
33
|
+
|
34
|
+
expect(manifest.canvases.length).toBe(2);
|
35
|
+
expect(manifest.canvases[0].annotations.length).toBe(0);
|
36
|
+
expect(manifest.canvases[1].annotations.length).toBe(0);
|
37
|
+
|
38
|
+
const annotations = ANNOTATIONS as Annotation[];
|
39
|
+
|
40
|
+
const modified = Cozy.Helpers.importAnnotations(manifest, annotations, 'cozy');
|
41
|
+
expect(modified.canvases[0].annotations.length).toBe(1);
|
42
|
+
expect(modified.canvases[1].annotations.length).toBe(1);
|
43
|
+
});
|
44
|
+
|
45
|
+
});
|
46
|
+
|