cozy-iiif 0.1.4 → 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/core/index.d.ts +1 -0
- package/dist/core/manifest.d.ts +2 -0
- package/dist/helpers/import-annotations.d.ts +3 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/index.js +451 -308
- package/dist/types.d.ts +20 -1
- package/package.json +5 -4
- package/src/Cozy.ts +144 -108
- package/src/core/index.ts +1 -0
- package/src/core/manifest.ts +38 -0
- package/src/helpers/import-annotations.ts +115 -0
- package/src/helpers/index.ts +1 -0
- package/src/types.ts +39 -1
- package/test/Cozy.test.ts +18 -4
- package/test/annotations/fixtures.ts +135 -0
- package/test/annotations/import-annotations.test.ts +46 -0
- package/test/fixtures.ts +4 -1
package/dist/types.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Manifest, Canvas, ImageService2, ImageService3, IIIFExternalWebResource, Collection } 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;
|
@@ -41,15 +41,26 @@ export interface CozyManifest {
|
|
41
41
|
readonly source: Manifest;
|
42
42
|
readonly id: string;
|
43
43
|
readonly canvases: CozyCanvas[];
|
44
|
+
readonly structure: CozyRange[];
|
44
45
|
getLabel(locale?: string): string | undefined;
|
46
|
+
getTableOfContents(): CozyTOCNode[];
|
45
47
|
getMetadata(locale?: string): CozyMetadata[];
|
46
48
|
}
|
49
|
+
export interface CozyRange {
|
50
|
+
readonly source: Range;
|
51
|
+
readonly id: string;
|
52
|
+
readonly items: (CozyCanvas | CozyRange)[];
|
53
|
+
readonly canvases: CozyCanvas[];
|
54
|
+
readonly ranges: CozyRange[];
|
55
|
+
getLabel(locale?: string): string | undefined;
|
56
|
+
}
|
47
57
|
export interface CozyCanvas {
|
48
58
|
readonly source: Canvas;
|
49
59
|
readonly id: string;
|
50
60
|
readonly width: number;
|
51
61
|
readonly height: number;
|
52
62
|
readonly images: CozyImageResource[];
|
63
|
+
readonly annotations: AnnotationPage[];
|
53
64
|
getLabel(locale?: string): string;
|
54
65
|
getMetadata(locale?: string): CozyMetadata[];
|
55
66
|
getThumbnailURL(minSize?: number): string;
|
@@ -58,6 +69,14 @@ export interface CozyMetadata {
|
|
58
69
|
readonly label: string;
|
59
70
|
readonly value: string;
|
60
71
|
}
|
72
|
+
export interface CozyTOCNode {
|
73
|
+
readonly id: string;
|
74
|
+
readonly type: 'range' | 'canvas';
|
75
|
+
getLabel(locale?: string): string | undefined;
|
76
|
+
children: CozyTOCNode[];
|
77
|
+
parent?: CozyTOCNode;
|
78
|
+
level: number;
|
79
|
+
}
|
61
80
|
export type CozyImageResource = StaticImageResource | ImageServiceResource;
|
62
81
|
export type ImageServiceResource = DynamicImageServiceResource | Level0ImageServiceResource;
|
63
82
|
interface BaseImageResource {
|
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",
|
@@ -27,13 +27,14 @@
|
|
27
27
|
"./level-0": "./dist/level-0/index.js"
|
28
28
|
},
|
29
29
|
"devDependencies": {
|
30
|
-
"vite": "^6.2.
|
30
|
+
"vite": "^6.2.5",
|
31
31
|
"vite-plugin-dts": "^4.5.3",
|
32
|
-
"vitest": "^3.
|
32
|
+
"vitest": "^3.1.1"
|
33
33
|
},
|
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,11 +1,13 @@
|
|
1
|
-
import type { Canvas, Collection, Manifest } from '@iiif/presentation-3';
|
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,
|
7
8
|
getMetadata,
|
8
9
|
getPropertyValue,
|
10
|
+
getTableOfContents,
|
9
11
|
getThumbnailURL,
|
10
12
|
normalizeServiceUrl,
|
11
13
|
parseImageService
|
@@ -16,126 +18,125 @@ import type {
|
|
16
18
|
CozyCollectionItem,
|
17
19
|
CozyManifest,
|
18
20
|
CozyParseResult,
|
21
|
+
CozyRange,
|
19
22
|
ImageServiceResource
|
20
23
|
} from './types';
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
};
|
33
|
-
}
|
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
|
+
}
|
34
35
|
|
35
|
-
|
36
|
+
let response: Response;
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
return {
|
41
|
-
type: 'error',
|
42
|
-
code: 'INVALID_HTTP_RESPONSE',
|
43
|
-
message: `Server responded: HTTP ${response.status} ${response.statusText ? `(${response.statusText})` : ''}`
|
44
|
-
}
|
45
|
-
}
|
46
|
-
} catch (error) {
|
38
|
+
try {
|
39
|
+
response = await fetch(input);
|
40
|
+
if (!response.ok) {
|
47
41
|
return {
|
48
42
|
type: 'error',
|
49
|
-
code: '
|
50
|
-
message:
|
51
|
-
}
|
43
|
+
code: 'INVALID_HTTP_RESPONSE',
|
44
|
+
message: `Server responded: HTTP ${response.status} ${response.statusText ? `(${response.statusText})` : ''}`
|
45
|
+
}
|
52
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
|
+
}
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
+
}
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
if (contentType?.includes('text/html')) {
|
65
|
+
return {
|
66
|
+
type: 'webpage',
|
67
|
+
url: input
|
68
|
+
};
|
69
|
+
}
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
message: 'Missing @context'
|
82
|
-
}
|
83
|
-
};
|
84
|
-
|
85
|
-
const id = getPropertyValue<string>(json, 'id');
|
86
|
-
|
87
|
-
if (!id) {
|
88
|
-
return {
|
89
|
-
type: 'error',
|
90
|
-
code: 'INVALID_MANIFEST',
|
91
|
-
message: 'Missing id property'
|
92
|
-
}
|
93
|
-
}
|
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
|
+
}
|
94
82
|
|
95
|
-
|
96
|
-
|
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'];
|
97
87
|
|
98
|
-
|
88
|
+
if (!context) {
|
89
|
+
return {
|
90
|
+
type: 'error',
|
91
|
+
code: 'INVALID_MANIFEST',
|
92
|
+
message: 'Missing @context'
|
93
|
+
}
|
94
|
+
};
|
99
95
|
|
100
|
-
|
101
|
-
type: 'collection',
|
102
|
-
url: input,
|
103
|
-
resource: parseCollectionResource(json, majorVersion)
|
104
|
-
} : {
|
105
|
-
type: 'manifest',
|
106
|
-
url: input,
|
107
|
-
resource: parseManifestResource(json, majorVersion)
|
108
|
-
};
|
109
|
-
}
|
110
|
-
|
111
|
-
if (context.includes('image/2') || context.includes('image/3')) {
|
112
|
-
const resource = parseImageResource(json);
|
113
|
-
return resource ? {
|
114
|
-
type: 'iiif-image',
|
115
|
-
url: input,
|
116
|
-
resource
|
117
|
-
} : {
|
118
|
-
type: 'error',
|
119
|
-
code: 'INVALID_MANIFEST',
|
120
|
-
message: 'Invalid image service definition'
|
121
|
-
}
|
122
|
-
}
|
96
|
+
const id = getPropertyValue<string>(json, 'id');
|
123
97
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
} catch {
|
130
|
-
return {
|
131
|
-
type: 'error',
|
132
|
-
code: 'UNSUPPORTED_FORMAT',
|
133
|
-
message: 'Could not parse resource'
|
134
|
-
};
|
98
|
+
if (!id) {
|
99
|
+
return {
|
100
|
+
type: 'error',
|
101
|
+
code: 'INVALID_MANIFEST',
|
102
|
+
message: 'Missing id property'
|
135
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');
|
136
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
|
+
}
|
137
133
|
}
|
138
134
|
|
135
|
+
return {
|
136
|
+
type: 'error',
|
137
|
+
code: 'INVALID_MANIFEST',
|
138
|
+
message: 'JSON resource is not a recognized IIIF format'
|
139
|
+
};
|
139
140
|
}
|
140
141
|
|
141
142
|
const parseCollectionResource = (resource: any, majorVersion: number): CozyCollection => {
|
@@ -174,15 +175,17 @@ const parseCollectionResource = (resource: any, majorVersion: number): CozyColle
|
|
174
175
|
const parseManifestResource = (resource: any, majorVersion: number): CozyManifest => {
|
175
176
|
|
176
177
|
const parseV3 = (manifest: Manifest) => {
|
177
|
-
const
|
178
|
+
const sourceCanvases: Canvas[] = [];
|
179
|
+
const sourceRanges: Range[] = [];
|
178
180
|
|
179
181
|
const modelBuilder = new Traverse({
|
180
|
-
canvas: [canvas => { if (canvas.items)
|
182
|
+
canvas: [canvas => { if (canvas.items) sourceCanvases.push(canvas) }],
|
183
|
+
range: [range => { if (range.type === 'Range') sourceRanges.push(range) }]
|
181
184
|
});
|
182
185
|
|
183
186
|
modelBuilder.traverseManifest(manifest);
|
184
187
|
|
185
|
-
|
188
|
+
const canvases = sourceCanvases.map((c: Canvas) => {
|
186
189
|
const images = getImages(c);
|
187
190
|
return {
|
188
191
|
source: c,
|
@@ -190,24 +193,55 @@ const parseManifestResource = (resource: any, majorVersion: number): CozyManifes
|
|
190
193
|
width: c.width,
|
191
194
|
height: c.height,
|
192
195
|
images,
|
196
|
+
annotations: (c.annotations || []),
|
193
197
|
getLabel: getLabel(c),
|
194
198
|
getMetadata: getMetadata(c),
|
195
199
|
getThumbnailURL: getThumbnailURL(c, images)
|
196
200
|
} as CozyCanvas;
|
197
201
|
});
|
202
|
+
|
203
|
+
const toRange = (source: Range): CozyRange => {
|
204
|
+
const items = source.items || [];
|
205
|
+
|
206
|
+
const nestedCanvases: CozyCanvas[] = items
|
207
|
+
.filter((item: any) => item.type === 'Canvas')
|
208
|
+
.map((item: any) => canvases.find(c => c.id === item.id)!)
|
209
|
+
.filter(Boolean);
|
210
|
+
|
211
|
+
const nestedRanges = items
|
212
|
+
.filter((item: any) => item.type === 'Range')
|
213
|
+
.map((item: any) => toRange(item));
|
214
|
+
|
215
|
+
const nestedItems = [...nestedCanvases, ...nestedRanges];
|
216
|
+
|
217
|
+
return {
|
218
|
+
source,
|
219
|
+
id: source.id,
|
220
|
+
// Maintain original order
|
221
|
+
items: items.map((i: any) => nestedItems.find(cozy => cozy.id === i.id)),
|
222
|
+
canvases: nestedCanvases,
|
223
|
+
ranges: nestedRanges,
|
224
|
+
getLabel: getLabel(source)
|
225
|
+
} as CozyRange;
|
226
|
+
}
|
227
|
+
|
228
|
+
const ranges = sourceRanges.map((source: Range) => toRange(source));
|
229
|
+
return { canvases, ranges };
|
198
230
|
}
|
199
231
|
|
200
232
|
const v3: Manifest = majorVersion === 2 ? convertPresentation2(resource) : resource;
|
201
233
|
|
202
|
-
const canvases = parseV3(v3);
|
234
|
+
const { canvases, ranges } = parseV3(v3);
|
203
235
|
|
204
236
|
return {
|
205
237
|
source: v3,
|
206
238
|
id: v3.id,
|
207
239
|
majorVersion,
|
208
240
|
canvases,
|
241
|
+
structure: ranges,
|
209
242
|
getLabel: getLabel(v3),
|
210
|
-
getMetadata: getMetadata(v3)
|
243
|
+
getMetadata: getMetadata(v3),
|
244
|
+
getTableOfContents: getTableOfContents(ranges)
|
211
245
|
}
|
212
246
|
}
|
213
247
|
|
@@ -225,4 +259,6 @@ const parseImageResource = (resource: any) => {
|
|
225
259
|
serviceUrl: normalizeServiceUrl(getPropertyValue<string>(resource, 'id'))
|
226
260
|
} as ImageServiceResource;
|
227
261
|
}
|
228
|
-
}
|
262
|
+
}
|
263
|
+
|
264
|
+
export const Cozy = { parse, parseURL, Helpers };
|
package/src/core/index.ts
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
import type { CozyRange, CozyTOCNode } from '../types';
|
2
|
+
|
3
|
+
export const getTableOfContents = (ranges: CozyRange[]) => () => {
|
4
|
+
|
5
|
+
const buildTree = (range: CozyRange, parent: CozyTOCNode | undefined, level: number = 0): CozyTOCNode => {
|
6
|
+
const node: CozyTOCNode = {
|
7
|
+
id: range.id,
|
8
|
+
type: 'range',
|
9
|
+
getLabel: range.getLabel,
|
10
|
+
children: [],
|
11
|
+
parent,
|
12
|
+
level
|
13
|
+
};
|
14
|
+
|
15
|
+
if (range.items && range.items.length > 0) {
|
16
|
+
range.items.forEach(item => {
|
17
|
+
if (item.source.type === 'Range') {
|
18
|
+
const childNode = buildTree(item as CozyRange, node, level + 1);
|
19
|
+
node.children.push(childNode);
|
20
|
+
} else {
|
21
|
+
node.children.push({
|
22
|
+
id: item.id,
|
23
|
+
type: 'canvas',
|
24
|
+
getLabel: item.getLabel,
|
25
|
+
children: [],
|
26
|
+
parent: node,
|
27
|
+
level: level + 1
|
28
|
+
});
|
29
|
+
}
|
30
|
+
});
|
31
|
+
}
|
32
|
+
|
33
|
+
return node;
|
34
|
+
};
|
35
|
+
|
36
|
+
const topRanges = ranges.filter(range => range.source.behavior?.includes('top'));
|
37
|
+
return topRanges.map(range => buildTree(range, undefined));
|
38
|
+
}
|
@@ -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 } 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 }
|
@@ -50,12 +50,32 @@ export interface CozyManifest {
|
|
50
50
|
|
51
51
|
readonly canvases: CozyCanvas[];
|
52
52
|
|
53
|
+
readonly structure: CozyRange[];
|
54
|
+
|
53
55
|
getLabel(locale?: string): string | undefined;
|
54
56
|
|
57
|
+
getTableOfContents(): CozyTOCNode[];
|
58
|
+
|
55
59
|
getMetadata(locale?: string): CozyMetadata[];
|
56
60
|
|
57
61
|
}
|
58
62
|
|
63
|
+
export interface CozyRange {
|
64
|
+
|
65
|
+
readonly source: Range;
|
66
|
+
|
67
|
+
readonly id: string;
|
68
|
+
|
69
|
+
readonly items: (CozyCanvas | CozyRange)[];
|
70
|
+
|
71
|
+
readonly canvases: CozyCanvas[];
|
72
|
+
|
73
|
+
readonly ranges: CozyRange[];
|
74
|
+
|
75
|
+
getLabel(locale?: string): string | undefined;
|
76
|
+
|
77
|
+
}
|
78
|
+
|
59
79
|
export interface CozyCanvas {
|
60
80
|
|
61
81
|
readonly source: Canvas;
|
@@ -68,6 +88,8 @@ export interface CozyCanvas {
|
|
68
88
|
|
69
89
|
readonly images: CozyImageResource[];
|
70
90
|
|
91
|
+
readonly annotations: AnnotationPage[];
|
92
|
+
|
71
93
|
getLabel(locale?: string): string;
|
72
94
|
|
73
95
|
getMetadata(locale?: string): CozyMetadata[];
|
@@ -84,6 +106,22 @@ export interface CozyMetadata {
|
|
84
106
|
|
85
107
|
}
|
86
108
|
|
109
|
+
export interface CozyTOCNode {
|
110
|
+
|
111
|
+
readonly id: string;
|
112
|
+
|
113
|
+
readonly type: 'range' | 'canvas';
|
114
|
+
|
115
|
+
getLabel(locale?: string): string | undefined;
|
116
|
+
|
117
|
+
children: CozyTOCNode[];
|
118
|
+
|
119
|
+
parent?: CozyTOCNode;
|
120
|
+
|
121
|
+
level: number;
|
122
|
+
|
123
|
+
}
|
124
|
+
|
87
125
|
export type CozyImageResource =
|
88
126
|
| StaticImageResource
|
89
127
|
| ImageServiceResource;
|
package/test/Cozy.test.ts
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
2
|
-
import { Cozy } from '../src';
|
2
|
+
import { Cozy, CozyManifest } from '../src';
|
3
3
|
|
4
|
-
import { COLLECTION } from './fixtures';
|
4
|
+
import { COLLECTION, WITH_STRUCTURES } from './fixtures';
|
5
5
|
|
6
6
|
describe('Cozy', () => {
|
7
7
|
|
8
8
|
it('should parse collection manifests correctly', async () => {
|
9
|
-
const result = await Cozy.parseURL(COLLECTION)
|
9
|
+
const result = await Cozy.parseURL(COLLECTION);
|
10
10
|
expect(result.type).toBe('collection');
|
11
|
-
})
|
11
|
+
});
|
12
|
+
|
13
|
+
it('should parse strctures in presentation manifests', async () => {
|
14
|
+
const result = await Cozy.parseURL(WITH_STRUCTURES);
|
15
|
+
expect(result.type).toBe('manifest');
|
16
|
+
expect('resource' in result).toBeTruthy();
|
17
|
+
|
18
|
+
const manifest = (result as any).resource as CozyManifest;
|
19
|
+
expect(manifest.structure.length > 0).toBeTruthy();
|
20
|
+
|
21
|
+
const tableOfContents = manifest.getTableOfContents();
|
22
|
+
expect(tableOfContents.length).toBe(1);
|
23
|
+
expect(tableOfContents[0].children.length).toBe(14);
|
24
|
+
});
|
12
25
|
|
13
26
|
});
|
27
|
+
|