Skip to content

Commit 8eab81d

Browse files
committed
New: Annotation Support
Adds support for annotations on manifests, and provisional support for an annotation server. Refs #568
1 parent 1a5c128 commit 8eab81d

30 files changed

Lines changed: 7004 additions & 6347 deletions

Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ TS_ESM_SRC := src/diva-esm.ts
66
TS_VE_SRC := src/viewer-element.ts
77
TS_FT_SRC := src/filters.ts
88
TS_AUTH_SRC := src/auth.ts
9+
TS_UTILS_SRC := src/image-utils.ts
910
CSS_SRC := $(wildcard src/styles/*.css)
1011
DIVA_CSS := cache/diva.css
1112
ELM_OUT := cache/elm.js
@@ -69,14 +70,14 @@ $(ELM_ESM): $(ELM_OUT) $(ELM_ESM_SCRIPT)
6970
$(DIVA_CSS): $(CSS_SRC) scripts/minify-css.mjs
7071
node ./scripts/minify-css.mjs
7172

72-
$(DIVA_DEBUG): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM)
73+
$(DIVA_DEBUG): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM)
7374
mkdir -p public
7475
$(ESBUILD) $(TS_SRC) $(ESBUILD_COMMON_FLAGS) --format=iife --outfile=$(DIVA_DEBUG)
7576

76-
$(DIVA_IIFE_BUNDLE): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM)
77+
$(DIVA_IIFE_BUNDLE): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM)
7778
@$(ESBUILD) $(TS_SRC) $(ESBUILD_COMMON_FLAGS) --format=iife --outfile=$(DIVA_IIFE_BUNDLE)
7879

79-
$(DIVA_ESM_BUNDLE): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM)
80+
$(DIVA_ESM_BUNDLE): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM)
8081
@$(ESBUILD) $(TS_ESM_SRC) $(ESBUILD_COMMON_FLAGS) --format=esm --outfile=$(DIVA_ESM_BUNDLE)
8182

8283
$(DIVA_JS): $(DIVA_IIFE_BUNDLE) .swcrc
@@ -87,7 +88,7 @@ $(DIVA_ESM): $(DIVA_ESM_BUNDLE) .swcrc
8788
@mkdir -p build
8889
@$(SWC) $(DIVA_ESM_BUNDLE) --out-file $(DIVA_ESM)
8990

90-
$(DIVA_TYPES): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) src/public-api.ts tsconfig.json
91+
$(DIVA_TYPES): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) src/public-api.ts tsconfig.json
9192
@mkdir -p build
9293
@yarn -s tsc -p tsconfig.json --declaration --emitDeclarationOnly --outDir build
9394

browser-tests/public-api.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,42 @@ test("exposes immutable IIIF page metadata and state", async ({page}) => {
122122
expect(result.state).toMatchObject({ready : true, pageCount : 4, resourceUrl : `${origin}/api/first/manifest`});
123123
});
124124

125+
test("returns the IIIF Image API region for an API-supplied rectangle annotation", async ({page}) => {
126+
const result = await page.evaluate((canvasId) => {
127+
const diva = (window as any).diva;
128+
diva.setAnnotation({
129+
id : "api-region",
130+
type : "Annotation",
131+
body : {type : "TextualBody", value : "Region"},
132+
target : {
133+
type : "SpecificResource",
134+
source : canvasId,
135+
selector : {type : "FragmentSelector", value : "xywh=10.2,20.8,30.1,40.1"}
136+
}
137+
});
138+
return diva.getImageRegionForAnnotation("api-region");
139+
}, `${origin}/api/first/canvas/1`);
140+
141+
expect(result).toBe(`${origin}/api/first/image/1/10,20,31,41/!320,320/0/default.jpg`);
142+
});
143+
144+
test("returns null when an annotation has no IIIF Image API region", async ({page}) => {
145+
const result = await page.evaluate(() => (window as any).diva.getImageRegionForAnnotation("missing"));
146+
expect(result).toBeNull();
147+
});
148+
149+
test("recreates scrollbar references after its viewer element is reattached", async ({page}) => {
150+
const attached = await page.evaluate(() => {
151+
const viewer = document.querySelector("osd-viewer") as HTMLElement & {scrollbarTrack?: HTMLElement|null};
152+
const parent = viewer.parentElement!;
153+
viewer.remove();
154+
parent.appendChild(viewer);
155+
return Boolean(viewer.scrollbarTrack);
156+
});
157+
158+
expect(attached).toBe(true);
159+
});
160+
125161
test("labels modal close buttons", async ({page}) => {
126162
for (const opener of [ "Manifest Info", "Page View" ])
127163
{

build/auth.d.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import type { DivaStaticImageCorsPolicy } from "./public-api";
2-
export type TileSourceDescriptor = {
3-
sourceId: string;
4-
url: string;
5-
isStatic: boolean;
6-
};
7-
export type ResolvedTileSource = string | Record<string, unknown>;
2+
import { type ResolvedTileSource, type TileSourceDescriptor } from "./image-utils";
3+
export type { ResolvedTileSource, TileSourceDescriptor } from "./image-utils";
84
type SendPort<T> = {
95
send: (value: T) => void;
106
};
@@ -106,6 +102,8 @@ export declare class AuthBrowser {
106102
invalidateSources(sourceIds: string[]): void;
107103
useNonCorsStaticSource(sourceId: string): void;
108104
destroy(): void;
105+
private cancelBrowserWork;
106+
private closePopups;
109107
private succeed;
110108
private fail;
111109
private startResolution;
@@ -120,4 +118,3 @@ export declare class AuthBrowser {
120118
private startFrame;
121119
private cancelFrame;
122120
}
123-
export {};

build/diva.d.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import "./viewer-element";
2-
import type { DivaEventMap, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaRegion, DivaState, ZoomToRegionOptions } from "./public-api";
3-
export type { DivaEventMap, DivaImage, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaPageTarget, DivaRegion, DivaSidebarPanel, DivaStaticImageCorsPolicy, DivaState, DivaViewingDirection, ZoomToRegionOptions } from "./public-api";
2+
import type { DivaEventMap, DivaAnnotation, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaRegion, DivaState, ZoomToRegionOptions } from "./public-api";
3+
export type { DivaEventMap, DivaAnnotation, DivaImage, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaPageTarget, DivaRegion, DivaSidebarPanel, DivaStaticImageCorsPolicy, DivaState, DivaViewingDirection, ZoomToRegionOptions } from "./public-api";
44
/**
55
* A browser viewer for IIIF Presentation manifests and collections.
66
*
@@ -64,6 +64,11 @@ export declare class Diva extends EventTarget {
6464
private pages;
6565
private readonly pagesByCanvasId;
6666
private readonly pagesByLabel;
67+
private readonly manifestAnnotationsByCanvas;
68+
private readonly apiAnnotationsByCanvas;
69+
private readonly clearedAnnotationCanvases;
70+
private readonly annotationImageServicesByCanvas;
71+
private annotationResourceId;
6772
private state;
6873
private readyResolve;
6974
private readyReject;
@@ -110,6 +115,9 @@ export declare class Diva extends EventTarget {
110115
private bindPorts;
111116
private ensureMainViewer;
112117
private copyPage;
118+
private effectiveAnnotationsForCanvas;
119+
private applyAnnotationsForCanvas;
120+
private findStoredAnnotation;
113121
private rebuildPageIndexes;
114122
private pageForSelector;
115123
private copyState;
@@ -160,6 +168,26 @@ export declare class Diva extends EventTarget {
160168
* @returns Pages in zero-based display order. Auth tokens and resolved loading URLs are never included.
161169
*/
162170
getPages(): readonly DivaPage[];
171+
/** Add or replace one API-supplied Web Annotation on a loaded Canvas. */
172+
setAnnotation(annotation: DivaAnnotation): void;
173+
/** Replace the API-supplied annotation set for the loaded manifest. */
174+
setAnnotations(annotations: readonly DivaAnnotation[]): void;
175+
/** Return defensive Web Annotation snapshots for one Canvas. */
176+
getAnnotationsForCanvas(uri: string): readonly DivaAnnotation[];
177+
/** Return defensive Web Annotation snapshots for the loaded manifest. */
178+
getAllAnnotations(): readonly DivaAnnotation[];
179+
/** Clear both manifest and API-supplied annotations for one Canvas. */
180+
clearAnnotationsForCanvas(uri: string): void;
181+
/** Clear both manifest and API-supplied annotations for every Canvas. */
182+
clearAllAnnotations(): void;
183+
/**
184+
* Return the IIIF Image API extract URL for an annotation, when available.
185+
*
186+
* @returns The extract URL, or `null` when the annotation is unknown or has no usable image region.
187+
*/
188+
getImageRegionForAnnotation(annotationId: string): string | null;
189+
/** Return an annotation body's textual or HTML value, if present. */
190+
getAnnotationBody(annotationId: string): string | undefined;
163191
/**
164192
* Find a displayed page by Canvas identifier or localized display label.
165193
*

0 commit comments

Comments
 (0)