Skip to content

Commit e12c127

Browse files
committed
Fixes for P4
1 parent 74a96ab commit e12c127

2 files changed

Lines changed: 91 additions & 44 deletions

File tree

__tests__/presentation-4/helper-parity.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,46 @@ import {
1313
parseSelector,
1414
} from "../../src";
1515
import { compatVault } from "../../src/compat";
16+
import { Vault4 } from "../../src/vault/vault4";
1617

1718
describe("Presentation 4 helper parity", () => {
19+
test("range helpers resolve normalized P4 SpecificResource references", () => {
20+
const canvasId = "https://example.org/canvas/normalized";
21+
const rangeId = "https://example.org/range/normalized";
22+
const manifestId = "https://example.org/manifest/normalized";
23+
const vault = new Vault4();
24+
const manifest = vault.loadManifestSync(manifestId, {
25+
"@context": "http://iiif.io/api/presentation/3/context.json",
26+
id: manifestId,
27+
type: "Manifest",
28+
label: { en: ["Manifest"] },
29+
items: [
30+
{
31+
id: canvasId,
32+
type: "Canvas",
33+
label: { en: ["Canvas"] },
34+
width: 1000,
35+
height: 1000,
36+
items: [],
37+
},
38+
],
39+
structures: [
40+
{
41+
id: rangeId,
42+
type: "Range",
43+
label: { en: ["Range"] },
44+
items: [`${canvasId}#t=0,10`],
45+
},
46+
],
47+
});
48+
const range = vault.get(manifest!.structures[0]);
49+
50+
expect(range.items[0].type).toBe("ContentResource");
51+
expect(findAllCanvasesInRange(vault, range)).toEqual([{ id: canvasId, type: "Canvas" }]);
52+
expect(createSequenceHelper(vault).getManifestSequence(range)[0]).toEqual([{ id: canvasId, type: "Canvas" }]);
53+
expect(findSelectedRange(vault, range, canvasId)).toBe(range);
54+
});
55+
1856
test("ranges expose ordered unique containers while Canvas APIs remain type-safe", () => {
1957
const range = {
2058
id: "https://example.org/range",

src/ranges.ts

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type InternationalString = InternationalStringV3 | InternationalStringV4;
3838
type AnySpecificResource = SpecificResourceV3 | SpecificResourceV4;
3939
type SpecificCanvasResource = SpecificResourceV3<Reference<"Canvas">> | SpecificResourceV4;
4040

41+
function resolveRangeItem(vault: CompatVault, item: any): any {
42+
return vault.get(item, { preserveSpecificResources: true }) ?? item;
43+
}
44+
4145
export function createRangeHelper(vault: CompatVault = compatVault) {
4246
return {
4347
findFirstCanvasFromRange: (range: RangeNormalizedV3 | RangeNormalizedV4) => findFirstCanvasFromRange(vault, range),
@@ -69,20 +73,20 @@ export function findFirstCanvasFromRange(
6973
range: RangeNormalizedV3 | RangeNormalizedV4
7074
): null | Reference<"Canvas"> {
7175
for (const inner of range.items) {
72-
const innerAny = inner as any;
7376
if (typeof inner === "string") {
7477
return { id: inner, type: "Canvas" };
7578
}
76-
if (innerAny.type === "Canvas") {
77-
return inner as any as Reference<"Canvas">;
79+
const item = resolveRangeItem(vault, inner);
80+
if (item.type === "Canvas") {
81+
return item as Reference<"Canvas">;
7882
}
79-
if (innerAny.type === "SpecificResource") {
80-
if (innerAny.source?.type === "Canvas") {
81-
return innerAny.source as Reference<"Canvas">;
83+
if (item.type === "SpecificResource") {
84+
if (item.source?.type === "Canvas") {
85+
return item.source as Reference<"Canvas">;
8286
}
8387
}
84-
if (innerAny.type === "Range") {
85-
const found = findFirstCanvasFromRange(vault, vault.get(inner as any) as any);
88+
if (item.type === "Range") {
89+
const found = findFirstCanvasFromRange(vault, item);
8690
if (found) {
8791
return found;
8892
}
@@ -96,26 +100,26 @@ export function findFirstCanvasFromRangeWithSelector(
96100
range: RangeNormalizedV3 | RangeNormalizedV4
97101
): null | SpecificCanvasResource {
98102
for (const inner of range.items) {
99-
const innerAny = inner as any;
100103
if (typeof inner === "string") {
101104
return {
102105
type: "SpecificResource",
103106
source: { id: inner, type: "Canvas" } as Reference<"Canvas">,
104107
};
105108
}
106-
if (innerAny.type === "Canvas") {
109+
const item = resolveRangeItem(vault, inner);
110+
if (item.type === "Canvas") {
107111
return {
108112
type: "SpecificResource",
109-
source: inner as any as Reference<"Canvas">,
113+
source: item as Reference<"Canvas">,
110114
};
111115
}
112-
if (innerAny.type === "SpecificResource") {
113-
if (innerAny.source?.type === "Canvas") {
114-
return inner as SpecificCanvasResource;
116+
if (item.type === "SpecificResource") {
117+
if (item.source?.type === "Canvas") {
118+
return item as SpecificCanvasResource;
115119
}
116120
}
117-
if (innerAny.type === "Range") {
118-
const found = findFirstCanvasFromRangeWithSelector(vault, vault.get(inner as any) as any);
121+
if (item.type === "Range") {
122+
const found = findFirstCanvasFromRangeWithSelector(vault, item);
119123
if (found) {
120124
return found;
121125
}
@@ -158,10 +162,9 @@ export function findAllContainersInRange(
158162
continue;
159163
}
160164

161-
const item = inner as any;
165+
const item = resolveRangeItem(vault, inner);
162166
if (item.type === "Range") {
163-
const nested = vault.get(item as any) as RangeNormalizedV3 | RangeNormalizedV4 | undefined;
164-
if (nested?.items) visit(nested);
167+
if (item.items) visit(item);
165168
continue;
166169
}
167170

@@ -206,19 +209,20 @@ export function findSelectedRange(
206209
canvasId: string
207210
): null | RangeNormalizedV3 | RangeNormalizedV4 {
208211
for (const inner of range.items) {
209-
const innerAny = inner as any;
210-
const parsedId = (inner as any)?.source?.id?.split("#")[0];
211-
if (innerAny.type === "Canvas" && innerAny.id?.split("#")[0] === canvasId) {
212+
const item = typeof inner === "string" ? inner : resolveRangeItem(vault, inner);
213+
if (typeof item === "string") continue;
214+
const parsedId = item.source?.id?.split("#")[0];
215+
if (item.type === "Canvas" && item.id?.split("#")[0] === canvasId) {
212216
return range;
213217
}
214-
if (innerAny.type === "SpecificResource" && innerAny.source === canvasId) {
218+
if (item.type === "SpecificResource" && item.source === canvasId) {
215219
return range;
216220
}
217-
if (innerAny.type === "SpecificResource" && innerAny.source?.type === "Canvas" && canvasId === parsedId) {
221+
if (item.type === "SpecificResource" && item.source?.type === "Canvas" && canvasId === parsedId) {
218222
return range;
219223
}
220-
if (innerAny.type === "Range") {
221-
const found = findSelectedRange(vault, vault.get(inner as any) as any, canvasId);
224+
if (item.type === "Range") {
225+
const found = findSelectedRange(vault, item, canvasId);
222226
if (found) {
223227
return found;
224228
}
@@ -332,58 +336,59 @@ export function rangeToTableOfContentsTree(
332336
toc.items!.push(foundCanvas);
333337
continue;
334338
}
335-
if (inner.type === "SpecificResource" && inner.source?.type === "Canvas") {
336-
const maybeCanvas = vault.get(inner.source);
337-
const compressed = compressSpecificResource(inner);
339+
const item = resolveRangeItem(vault, inner);
340+
if (item.type === "SpecificResource" && item.source?.type === "Canvas") {
341+
const maybeCanvas = vault.get(item.source);
342+
const compressed = compressSpecificResource(item);
338343

339344
if (!maybeCanvas) {
340345
continue;
341346
}
342347

343348
const foundCanvas: RangeTableOfContentsNode = {
344-
id: compressed.type === "Canvas" ? compressed.id : inner.source.id,
349+
id: compressed.type === "Canvas" ? compressed.id : item.source.id,
345350
type: "Canvas",
346351
isCanvasLeaf: true,
347352
isRangeLeaf: false,
348353
label: maybeCanvas.label || { none: ["Untitled"] },
349354
untitled: !maybeCanvas.label,
350-
resource: inner,
355+
resource: item,
351356
parent: { id: toc.id, type: "Range" },
352357
};
353358
if (seenIds.indexOf(foundCanvas.id) !== -1) {
354-
foundCanvas.id = `vault://${hash(inner)}`;
359+
foundCanvas.id = `vault://${hash(item)}`;
355360
}
356361

357362
seenIds.push(foundCanvas.id);
358363

359364
toc.items!.push(foundCanvas);
360365
continue;
361366
}
362-
if ((inner as any).type === "Canvas") {
367+
if (item.type === "Canvas") {
363368
const foundCanvas: RangeTableOfContentsNode = {
364-
id: (inner as any).id,
369+
id: item.id,
365370
type: "Canvas",
366-
label: (inner as any).label,
371+
label: item.label,
367372
isCanvasLeaf: true,
368373
isRangeLeaf: false,
369374
resource: {
370375
type: "SpecificResource",
371-
source: inner as any,
376+
source: item,
372377
},
373378
parent: { id: toc.id, type: "Range" },
374379
};
375380

376381
if (seenIds.indexOf(foundCanvas.id) !== -1) {
377-
foundCanvas.id = `vault://${hash(inner)}`;
382+
foundCanvas.id = `vault://${hash(item)}`;
378383
}
379384

380385
seenIds.push(foundCanvas.id);
381386

382387
toc.items!.push(foundCanvas);
383388
continue;
384389
}
385-
if ((inner as any).type === "Range") {
386-
const foundRange = rangeToTableOfContentsTree(vault, inner as any, seenIds, {
390+
if (item.type === "Range") {
391+
const foundRange = rangeToTableOfContentsTree(vault, item, seenIds, {
387392
...options,
388393
parentRange: { id: toc.id, type: "Range" },
389394
});
@@ -412,10 +417,14 @@ function getCanvasesFromRange(
412417
return canvases;
413418
}
414419

415-
for (const item of range.items) {
416-
if (typeof item === "string") {
417-
canvases.push({ canvas: { id: item, type: "Canvas" }, path: currentPath });
418-
} else if (item.type === "SpecificResource") {
420+
for (const inner of range.items) {
421+
if (typeof inner === "string") {
422+
canvases.push({ canvas: { id: inner, type: "Canvas" }, path: currentPath });
423+
continue;
424+
}
425+
426+
const item = resolveRangeItem(vault, inner);
427+
if (item.type === "SpecificResource") {
419428
const canvas = item.source;
420429
if (canvas?.type === "Canvas") {
421430
canvases.push({ canvas: canvas as CanvasLike, path: currentPath });
@@ -424,7 +433,7 @@ function getCanvasesFromRange(
424433
}
425434
} else if (item.type === "Range") {
426435
canvases.push(...getCanvasesFromRange(vault, item as RangeLike, currentPath));
427-
} else if ((item as any).type === "Canvas" || (item as any).type === "Timeline" || (item as any).type === "Scene") {
436+
} else if (item.type === "Canvas" || item.type === "Timeline" || item.type === "Scene") {
428437
// P4 normalization stores container references directly (not wrapped in SpecificResource)
429438
canvases.push({ canvas: item as unknown as CanvasLike, path: currentPath });
430439
}

0 commit comments

Comments
 (0)