From e40e7e36f5ebf6907a7eeb7dc5b57fab96f8ab21 Mon Sep 17 00:00:00 2001 From: etosphere Date: Wed, 7 Oct 2026 19:11:55 -0400 Subject: [PATCH 1/2] Bump vendored Diva to v7.5.2 v7.5 adds the Web Annotation API that OMR search highlighting builds on. The public API changes are additions only; nothing Cantus already calls has changed. --- nginx/app/dependencies/diva.js/Makefile | 23 +- nginx/app/dependencies/diva.js/README.md | 2 +- .../app/dependencies/diva.js/build/auth.d.ts | 16 +- .../app/dependencies/diva.js/build/diva.d.ts | 71 +- .../dependencies/diva.js/build/diva.debug.js | 7007 ++++++++--------- .../dependencies/diva.js/build/diva.esm.js | 2 +- nginx/app/dependencies/diva.js/build/diva.js | 2 +- .../dependencies/diva.js/build/filters.d.ts | 2 - .../diva.js/build/image-utils.d.ts | 15 + .../diva.js/build/public-api.d.ts | 70 + nginx/app/dependencies/diva.js/elm.json | 8 +- nginx/app/dependencies/diva.js/package.json | 6 +- .../dependencies/diva.js/src/Annotation.elm | 55 + nginx/app/dependencies/diva.js/src/Auth.elm | 21 +- nginx/app/dependencies/diva.js/src/Main.elm | 197 +- nginx/app/dependencies/diva.js/src/Model.elm | 10 +- nginx/app/dependencies/diva.js/src/Msg.elm | 6 +- .../diva.js/src/View/HtmlRenderer.elm | 542 +- .../dependencies/diva.js/src/View/Icons.elm | 26 +- .../dependencies/diva.js/src/View/Toolbar.elm | 22 + nginx/app/dependencies/diva.js/src/auth.ts | 246 +- .../app/dependencies/diva.js/src/diva-esm.ts | 3 +- nginx/app/dependencies/diva.js/src/diva.ts | 1262 ++- nginx/app/dependencies/diva.js/src/filters.ts | 742 +- .../dependencies/diva.js/src/image-utils.ts | 75 + .../dependencies/diva.js/src/public-api.ts | 174 +- .../dependencies/diva.js/src/styles/app.css | 205 +- .../diva.js/src/styles/collection.css | 1 - .../dependencies/diva.js/src/styles/modal.css | 38 +- .../diva.js/src/styles/sidebar.css | 33 - .../dependencies/diva.js/src/styles/theme.css | 19 +- .../diva.js/src/viewer-element.ts | 1067 ++- .../diva.js/testing/annotations.html | 232 + .../diva.js/testing/auth-harness.html | 4 +- .../dependencies/diva.js/testing/index.html | 15 +- .../dependencies/diva.js/testing/testing.html | 9 + nginx/static/js/app/cantus.chunk.diva.min.js | 2 +- .../js/app/cantus.chunk.diva.min.js.map | 2 +- 38 files changed, 7309 insertions(+), 4923 deletions(-) create mode 100644 nginx/app/dependencies/diva.js/build/image-utils.d.ts create mode 100644 nginx/app/dependencies/diva.js/src/Annotation.elm create mode 100644 nginx/app/dependencies/diva.js/src/image-utils.ts create mode 100644 nginx/app/dependencies/diva.js/testing/annotations.html diff --git a/nginx/app/dependencies/diva.js/Makefile b/nginx/app/dependencies/diva.js/Makefile index f60a58c5..8847dc6f 100644 --- a/nginx/app/dependencies/diva.js/Makefile +++ b/nginx/app/dependencies/diva.js/Makefile @@ -6,6 +6,7 @@ TS_ESM_SRC := src/diva-esm.ts TS_VE_SRC := src/viewer-element.ts TS_FT_SRC := src/filters.ts TS_AUTH_SRC := src/auth.ts +TS_UTILS_SRC := src/image-utils.ts CSS_SRC := $(wildcard src/styles/*.css) DIVA_CSS := cache/diva.css ELM_OUT := cache/elm.js @@ -38,7 +39,7 @@ define print_bundle_size printf "%-18s %10s (%7s)\n" "Gzipped size:" "$$GZIPPED_SIZE bytes" "$$GZIPPED_HR"; endef -.PHONY: all build build-dev clean clean-cache docs docs-check release report-build-sizes test +.PHONY: all build build-dev clean clean-cache docs docs-check release report-build-sizes review serve test all: build @@ -53,6 +54,15 @@ build-dev: test: yarn test +test-browser: + yarn test:browser + +serve: + python3 -m http.server 8003 + +format: + yarn format + docs: yarn docs @@ -69,14 +79,14 @@ $(ELM_ESM): $(ELM_OUT) $(ELM_ESM_SCRIPT) $(DIVA_CSS): $(CSS_SRC) scripts/minify-css.mjs node ./scripts/minify-css.mjs -$(DIVA_DEBUG): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM) +$(DIVA_DEBUG): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM) mkdir -p public $(ESBUILD) $(TS_SRC) $(ESBUILD_COMMON_FLAGS) --format=iife --outfile=$(DIVA_DEBUG) -$(DIVA_IIFE_BUNDLE): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM) +$(DIVA_IIFE_BUNDLE): $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM) @$(ESBUILD) $(TS_SRC) $(ESBUILD_COMMON_FLAGS) --format=iife --outfile=$(DIVA_IIFE_BUNDLE) -$(DIVA_ESM_BUNDLE): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(DIVA_CSS) $(ELM_ESM) +$(DIVA_ESM_BUNDLE): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) $(TS_UTILS_SRC) $(DIVA_CSS) $(ELM_ESM) @$(ESBUILD) $(TS_ESM_SRC) $(ESBUILD_COMMON_FLAGS) --format=esm --outfile=$(DIVA_ESM_BUNDLE) $(DIVA_JS): $(DIVA_IIFE_BUNDLE) .swcrc @@ -87,7 +97,7 @@ $(DIVA_ESM): $(DIVA_ESM_BUNDLE) .swcrc @mkdir -p build @$(SWC) $(DIVA_ESM_BUNDLE) --out-file $(DIVA_ESM) -$(DIVA_TYPES): $(TS_ESM_SRC) $(TS_SRC) $(TS_VE_SRC) $(TS_FT_SRC) $(TS_AUTH_SRC) src/public-api.ts tsconfig.json +$(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 @mkdir -p build @yarn -s tsc -p tsconfig.json --declaration --emitDeclarationOnly --outDir build @@ -116,3 +126,6 @@ release: clean build build-dev publish: clean build npm publish + +review: + elm-review --elmjson elm.json --config review diff --git a/nginx/app/dependencies/diva.js/README.md b/nginx/app/dependencies/diva.js/README.md index 77703f82..d642cd5e 100644 --- a/nginx/app/dependencies/diva.js/README.md +++ b/nginx/app/dependencies/diva.js/README.md @@ -174,7 +174,7 @@ both versions using local npm packages. Diva supports the IIIF Authorization Flow API 2.0 `external` and `active` access profiles, including optional logout services. When both profiles are available, Diva first attempts the silent `external` flow and falls back to the interactive `active` flow when external authorization does not provide a usable token. Logging out clears the associated local access tokens and re-resolves affected images anonymously. The `kiosk` profile, redirects, substitutes, and tiered access are intentionally unsupported. -Static image resources must opt into cross-origin access with an appropriate CORS response header. Diva does not use a non-CORS fallback because doing so would break canvas-based image processing and could expose inconsistent behavior. Consequently, [GitHub issue #564](https://github.com/DDMAL/diva.js/issues/564) remains unresolved by policy. +Static image resources use anonymous CORS by default so their pixels remain available to Page View filters and Save view. Set `staticImageCorsPolicy: "fallback"` to retry a failed static-image request once without CORS, or `"none"` to use non-CORS loading immediately. Non-CORS static images remain viewable, but their pixels cannot be filtered or saved because the browser taints their canvas. [GitHub issue #564](https://github.com/DDMAL/diva.js/issues/564) describes this compatibility case. ## Development diff --git a/nginx/app/dependencies/diva.js/build/auth.d.ts b/nginx/app/dependencies/diva.js/build/auth.d.ts index f583a1ab..fcc16c25 100644 --- a/nginx/app/dependencies/diva.js/build/auth.d.ts +++ b/nginx/app/dependencies/diva.js/build/auth.d.ts @@ -1,9 +1,6 @@ -export type TileSourceDescriptor = { - sourceId: string; - url: string; - isStatic: boolean; -}; -export type ResolvedTileSource = string | Record; +import { type ResolvedTileSource, type TileSourceDescriptor } from "./image-utils"; +import type { DivaStaticImageCorsPolicy } from "./public-api"; +export type { ResolvedTileSource, TileSourceDescriptor } from "./image-utils"; type SendPort = { send: (value: T) => void; }; @@ -98,11 +95,15 @@ export declare class AuthBrowser { private nextConsumer; private destroyed; private readonly clickHandler; - constructor(ports: BrowserPorts, root: HTMLElement); + private readonly staticImageCorsPolicy; + constructor(ports: BrowserPorts, root: HTMLElement, staticImageCorsPolicy?: DivaStaticImageCorsPolicy); resolve(source: TileSourceDescriptor, signal: AbortSignal): Promise; registerSources(_sources: TileSourceDescriptor[]): void; invalidateSources(sourceIds: string[]): void; + useNonCorsStaticSource(sourceId: string): void; destroy(): void; + private cancelBrowserWork; + private closePopups; private succeed; private fail; private startResolution; @@ -117,4 +118,3 @@ export declare class AuthBrowser { private startFrame; private cancelFrame; } -export {}; diff --git a/nginx/app/dependencies/diva.js/build/diva.d.ts b/nginx/app/dependencies/diva.js/build/diva.d.ts index 69cf08a7..c107df11 100644 --- a/nginx/app/dependencies/diva.js/build/diva.d.ts +++ b/nginx/app/dependencies/diva.js/build/diva.d.ts @@ -1,6 +1,6 @@ import "./viewer-element"; -import type { DivaEventMap, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaRegion, DivaState, ZoomToRegionOptions } from "./public-api"; -export type { DivaEventMap, DivaImage, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaPageTarget, DivaRegion, DivaSidebarPanel, DivaState, DivaViewingDirection, ZoomToRegionOptions } from "./public-api"; +import type { DivaAnnotation, DivaEventMap, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaRegion, DivaState, ZoomToRegionOptions } from "./public-api"; +export type { DivaAnnotation, DivaEventMap, DivaImage, DivaLayoutMode, DivaOptions, DivaPage, DivaPageSelector, DivaPageTarget, DivaRegion, DivaSidebarPanel, DivaState, DivaStaticImageCorsPolicy, DivaViewingDirection, ZoomToRegionOptions, } from "./public-api"; /** * A browser viewer for IIIF Presentation manifests and collections. * @@ -34,6 +34,7 @@ export declare class Diva extends EventTarget { private readonly rootId; private readonly root; private readonly auth; + private readonly staticImageCorsPolicy; private app; private mainViewer; private readonly tileSourceResolver; @@ -44,6 +45,7 @@ export declare class Diva extends EventTarget { private filterViewerElement; private filterOptions; private filterViewerFlipped; + private filterAccessBlocked; private currentFilterSourceKey; private pendingFilterPreview; private filterPreviewVersion; @@ -52,15 +54,23 @@ export declare class Diva extends EventTarget { private filterPreviewController; private isDestroyed; private readonly handlePageChangeBound; + private readonly handleAnnotationSelectBound; private readonly handlePageLoadedBound; private readonly handleZoomChangeBound; private readonly handleLoadingChangeBound; + private readonly handleStaticImageCorsFallbackBound; private readonly handlePageLoadErrorBound; private readonly handleFullscreenChangeBound; private readonly handleRootClickBound; private pages; private readonly pagesByCanvasId; private readonly pagesByLabel; + private readonly manifestAnnotationsByCanvas; + private readonly apiAnnotationsByCanvas; + private readonly clearedAnnotationCanvases; + private readonly annotationImageServicesByCanvas; + private annotationSelectionEnabled; + private annotationResourceId; private state; private readyResolve; private readyReject; @@ -107,6 +117,9 @@ export declare class Diva extends EventTarget { private bindPorts; private ensureMainViewer; private copyPage; + private effectiveAnnotationsForCanvas; + private applyAnnotationsForCanvas; + private findStoredAnnotation; private rebuildPageIndexes; private pageForSelector; private copyState; @@ -157,6 +170,56 @@ export declare class Diva extends EventTarget { * @returns Pages in zero-based display order. Auth tokens and resolved loading URLs are never included. */ getPages(): readonly DivaPage[]; + /** + * Add or replace one API-supplied Web Annotation on a loaded Canvas. + */ + setAnnotation(annotation: DivaAnnotation): void; + /** + * Replace the API-supplied annotation set for the loaded manifest. + */ + setAnnotations(annotations: readonly DivaAnnotation[]): void; + /** + * Return defensive Web Annotation snapshots for one Canvas. + */ + getAnnotationsForCanvas(uri: string): readonly DivaAnnotation[]; + /** + * Return defensive Web Annotation snapshots for the loaded manifest. + */ + getAllAnnotations(): readonly DivaAnnotation[]; + /** + * Clear both manifest and API-supplied annotations for one Canvas. + */ + clearAnnotationsForCanvas(uri: string): void; + /** + * Clear both manifest and API-supplied annotations for every Canvas. + */ + clearAllAnnotations(): void; + /** + * Select an annotation and open its details panel. + */ + selectAnnotation(annotationId: string): void; + /** + * Return whether user annotation selection is enabled. + */ + getAnnotationSelectionEnabled(): boolean; + /** + * Enable or disable user annotation selection. + */ + setAnnotationSelectionEnabled(enabled: boolean): void; + /** + * Toggle user annotation selection and return the new state. + */ + toggleAnnotationSelectionEnabled(): boolean; + /** + * Return the IIIF Image API extract URL for an annotation, when available. + * + * @returns The extract URL, or `null` when the annotation is unknown or has no usable image region. + */ + getImageRegionForAnnotation(annotationId: string): string | null; + /** + * Return an annotation body's textual or HTML value, if present. + */ + getAnnotationBody(annotationId: string): string | undefined; /** * Find a displayed page by Canvas identifier or localized display label. * @@ -370,15 +433,19 @@ export declare class Diva extends EventTarget { private handleRootClick; private toggleFullscreenFromUserActivation; private handlePageChange; + private handleAnnotationSelect; private handlePageLoaded; private handleZoomChange; private handleLoadingChange; + private handleStaticImageCorsFallback; private handlePageLoadError; private handleFullscreenChange; private ensureFilterViewer; private ensureFilterViewerElement; private applyFilterOptions; private saveFilteredImage; + private isNonCorsStaticSource; + private setFilterAccessBlocked; private getPort; private callViewerMethod; private callViewerMethodAsync; diff --git a/nginx/app/dependencies/diva.js/build/diva.debug.js b/nginx/app/dependencies/diva.js/build/diva.debug.js index f6d1d8fb..8c7de82c 100644 --- a/nginx/app/dependencies/diva.js/build/diva.debug.js +++ b/nginx/app/dependencies/diva.js/build/diva.debug.js @@ -1,5 +1,53 @@ "use strict"; (() => { + // src/image-utils.ts + var iiifImageRegionUrl = (imageService, region) => { + const { x, y, width, height } = region; + if (!imageService || ![x, y, width, height].every(Number.isFinite)) { + return null; + } + const left = Math.floor(x); + const top = Math.floor(y); + const right = Math.ceil(x + width); + const bottom = Math.ceil(y + height); + if (left < 0 || top < 0 || right <= left || bottom <= top) { + return null; + } + try { + const service = new URL(imageService); + if (!service.pathname.endsWith("/info.json")) { + return null; + } + service.search = ""; + service.hash = ""; + service.pathname = service.pathname.slice(0, -"/info.json".length); + return `${service.toString().replace(/\/$/, "")}/${left},${top},${right - left},${bottom - top}/!320,320/0/default.jpg`; + } catch (_error) { + return null; + } + }; + var staticImageTileSource = (url, credentialed = false, useNonCors = false) => ({ + type: "image", + url, + crossOriginPolicy: useNonCors ? false : credentialed ? "use-credentials" : "Anonymous", + ajaxWithCredentials: credentialed, + loadTilesWithAjax: !useNonCors, + buildPyramid: !useNonCors, + useCanvas: !useNonCors + }); + var nonCorsStaticTileSource = (tileSource) => { + const source = typeof tileSource === "object" && tileSource !== null ? tileSource : { type: "image", url: tileSource }; + return { + ...source, + type: "image", + crossOriginPolicy: false, + ajaxWithCredentials: false, + loadTilesWithAjax: false, + buildPyramid: false, + useCanvas: false + }; + }; + // src/viewer-element.ts var ZOOM_IN_FACTOR = 1.6; var ZOOM_OUT_FACTOR = 1 / ZOOM_IN_FACTOR; @@ -116,10 +164,18 @@ this.loadingIndexes = /* @__PURE__ */ new Set(); this.unavailableIndexes = /* @__PURE__ */ new Map(); this.loadControllers = /* @__PURE__ */ new Map(); - this.tileSourceResolver = async (source) => source.isStatic ? { type: "image", url: source.url, crossOriginPolicy: "Anonymous" } : source.url; + this.staticImageCorsPolicy = "required"; + this.tileSourceResolver = async (source) => source.isStatic ? staticImageTileSource(source.url, false, this.staticImageCorsPolicy === "none") : source.url; this.loadedItems = /* @__PURE__ */ new Map(); this.pageWaiters = /* @__PURE__ */ new Map(); this.pageOverlayElements = /* @__PURE__ */ new Map(); + this.annotationData = /* @__PURE__ */ new Map(); + this.annotationOverlayElements = /* @__PURE__ */ new Map(); + this.annotationsVisible = true; + this.annotationSelectionEnabled = true; + this.selectedAnnotationId = null; + this.annotationPanel = null; + this.annotationPanelIgnoreCloseUntil = 0; this.targetIndex = null; this.initialPageIndex = 0; this.resourceId = "initial"; @@ -144,6 +200,7 @@ } connectedCallback() { this.style.display = "block"; + this.style.position = "relative"; if (!this.container) { this.container = document.createElement("div"); this.container.className = "osd-container"; @@ -151,7 +208,23 @@ this.container.style.height = "100%"; this.container.addEventListener("wheel", this.handleWheelBound, { passive: false, capture: true }); this.container.addEventListener("dblclick", this.handleDoubleClickBound); + this.container.addEventListener("click", (event) => { + if (performance.now() < this.annotationPanelIgnoreCloseUntil) { + return; + } + if (event.target instanceof Element && event.target.closest(".diva-annotation-overlay")) { + return; + } + this.closeAnnotationPanel(); + }); + this.container.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + this.closeAnnotationPanel(); + } + }); this.appendChild(this.container); + } + if (!this.scrollbarTrack) { this.createScrollbar(); } const hadViewer = Boolean(this.viewer); @@ -161,6 +234,7 @@ } } disconnectedCallback() { + var _a; this.cancelLoads(); if (this.loadingTimer !== null) { window.clearTimeout(this.loadingTimer); @@ -180,9 +254,11 @@ } if (this.viewer) { this.clearPageOverlays(); + this.clearAnnotationOverlays(); this.viewer.destroy(); this.viewer = null; } + (_a = this.scrollbarTrack) == null ? void 0 : _a.remove(); this.scrollbarTrack = null; this.scrollbarThumb = null; } @@ -208,17 +284,29 @@ defaultZoomLevel: 0, sequenceMode: false, zoomPerScroll: 1, + ...this.staticImageCorsPolicy === "required" ? {} : { drawer: ["canvas", "html"] }, crossOriginPolicy: "Anonymous", loadTilesWithAjax: true, ajaxWithCredentials: false, - gestureSettingsTrackpad: { pinchToZoom: true, scrollToZoom: false, flickEnabled: true, dragToPan: true }, - gestureSettingsMouse: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: false, dragToPan: true }, + gestureSettingsTrackpad: { + pinchToZoom: true, + scrollToZoom: false, + flickEnabled: true, + dragToPan: true + }, + gestureSettingsMouse: { + scrollToZoom: false, + clickToZoom: false, + dblClickToZoom: false, + dragToPan: true + }, gestureSettingsTouch: { pinchToZoom: false, dragToPan: true } }; this.viewer = OpenSeadragon(options); const viewer = this.viewer; viewer.addHandler("pan", this.handleViewportChangeBound); viewer.addHandler("zoom", this.handleViewportChangeBound); + viewer.addHandler("canvas-click", (event) => this.handleAnnotationCanvasClick(event)); viewer.addHandler("pan", () => this.updateScrollbar()); viewer.addHandler("zoom", () => this.updateScrollbar()); viewer.addHandler("animation-finish", this.handleAnimationFinishBound); @@ -241,6 +329,7 @@ if (!Array.isArray(tileSources)) { return; } + this.annotationData.clear(); this.tileSources = tileSources.slice(); this.resourceId = resourceId; this.initialPageIndex = Number.isInteger(initialPageIndex) && initialPageIndex >= 0 && initialPageIndex < tileSources.length ? initialPageIndex : 0; @@ -251,9 +340,101 @@ } this.resetTileSources(this.tileSources.slice(), this.initialPageIndex); } + setAnnotations(canvasId, annotations) { + if (!canvasId || !Array.isArray(annotations)) { + return; + } + this.annotationData.set(canvasId, annotations); + this.tileSources.forEach((source, index) => { + if (source.canvasId === canvasId) { + this.addOrUpdateAnnotationOverlay(index); + } + }); + } + setAnnotationsVisible(visible) { + this.annotationsVisible = visible; + this.annotationOverlayElements.forEach((element) => { + element.classList.toggle("is-hidden", !visible); + }); + if (!visible) { + this.closeAnnotationPanel(); + this.annotationOverlayElements.forEach((element) => { + element.querySelectorAll(".diva-annotation-tooltip").forEach((tooltip) => { + tooltip.hidden = true; + }); + }); + } + } + getAnnotationSelectionEnabled() { + return this.annotationSelectionEnabled; + } + setAnnotationSelectionEnabled(enabled) { + this.annotationSelectionEnabled = enabled; + if (!enabled) { + this.annotationOverlayElements.forEach((element) => { + element.querySelectorAll(".diva-annotation-tooltip").forEach((tooltip) => { + tooltip.hidden = true; + }); + }); + } + } + selectAnnotation(annotationId) { + this.selectedAnnotationId = annotationId; + this.annotationOverlayElements.forEach((element) => { + element.querySelectorAll("g[data-annotation-id]").forEach((group) => { + group.classList.toggle("is-selected", group.dataset.annotationId === annotationId); + }); + }); + for (const annotations of this.annotationData.values()) { + const annotation = annotations.find((candidate) => candidate.id === annotationId); + if (annotation) { + const group = Array.from(this.annotationOverlayElements.values()).map( + (element) => element.querySelector(`g[data-annotation-id="${CSS.escape(annotationId)}"]`) + ).find((candidate) => candidate !== null); + this.openAnnotationPanel(annotation, group); + return; + } + } + } + getImageRegionForAnnotation(annotationId) { + var _a, _b; + for (const [canvasId, annotations] of this.annotationData) { + const annotation = annotations.find((candidate) => candidate.id === annotationId); + if (!annotation) { + continue; + } + const index = this.tileSources.findIndex((source) => source.canvasId === canvasId); + const group = index >= 0 ? (_b = (_a = this.annotationOverlayElements.get(index)) == null ? void 0 : _a.querySelector( + `g[data-annotation-id="${CSS.escape(annotationId)}"]` + )) != null ? _b : void 0 : void 0; + return this.annotationExtractUrl(annotation, group); + } + return null; + } setTileSourceResolver(resolver) { this.tileSourceResolver = resolver; } + setStaticImageCorsPolicy(policy) { + var _a; + const nextPolicy = policy === "fallback" || policy === "none" ? policy : "required"; + if (nextPolicy === this.staticImageCorsPolicy) { + return; + } + this.staticImageCorsPolicy = nextPolicy; + if (!this.viewer) { + return; + } + this.clearPageOverlays(); + this.clearAnnotationOverlays(); + this.closeAnnotationPanel(); + this.viewer.destroy(); + this.viewer = null; + this.scrollPlaneItem = null; + this.syncViewer(); + if (this.viewer && this.tileSources.length > 0) { + this.resetTileSources(this.tileSources.slice(), (_a = this.lastReportedIndex) != null ? _a : this.initialPageIndex); + } + } invalidateTileSources(sourceIds) { var _a; const affected = new Set(sourceIds); @@ -290,6 +471,7 @@ this.unavailableIndexes.clear(); this.loadedItems.clear(); this.clearPageOverlays(); + this.clearAnnotationOverlays(); this.targetIndex = tileSources.length > 0 ? this.initialPageIndex : null; this.lastReportedIndex = this.targetIndex; this.clearScrollPlane(); @@ -331,7 +513,10 @@ this.ensurePageLoaded(this.targetIndex); const targetTop = this.pageOffsets[this.targetIndex] || 0; const targetHeight = this.pageRowHeights[this.targetIndex] || this.pageHeights[this.targetIndex] || 1; - const range2 = this.indicesForRange(Math.max(0, targetTop - targetHeight * 1.5), targetTop + targetHeight * 2.5); + const range2 = this.indicesForRange( + Math.max(0, targetTop - targetHeight * 1.5), + targetTop + targetHeight * 2.5 + ); if (range2) { for (let index2 = range2[0]; index2 <= range2[1]; index2 += 1) { this.ensurePageLoaded(index2); @@ -372,7 +557,7 @@ } void this.loadTile(index); } - async loadTile(index) { + async loadTile(index, nonCorsFallback = false, resolvedTileSource) { if (!this.viewer) { return; } @@ -386,10 +571,11 @@ this.updateLoadingState(); let tileSource; try { - tileSource = await this.tileSourceResolver(descriptor, controller.signal); + tileSource = resolvedTileSource != null ? resolvedTileSource : await this.tileSourceResolver(descriptor, controller.signal); } catch (error) { - if (token === this.loadToken && !controller.signal.aborted) + if (token === this.loadToken && !controller.signal.aborted) { this.markUnavailable(index, error instanceof Error ? error.message : "This image is unavailable."); + } this.finishLoad(index, controller); return; } @@ -397,6 +583,9 @@ this.finishLoad(index, controller); return; } + if (nonCorsFallback) { + tileSource = nonCorsStaticTileSource(tileSource); + } const yOffset = this.pageOffsets[index] || 0; const xOffset = this.pageXOffsets[index] || 0; this.viewer.addTiledImage({ @@ -420,6 +609,7 @@ this.loadedItems.set(index, item); this.resolvePageWaiters(index, item); this.addOrUpdatePageOverlay(index); + this.addOrUpdateAnnotationOverlay(index); this.loadingIndexes.delete(index); this.loadControllers.delete(index); this.updateLoadingState(); @@ -444,6 +634,12 @@ this.flushInitialZoomChange(); } this.emitCustomEvent("diva-page-loaded", { index, resourceId: this.resourceId }); + if (nonCorsFallback) { + requestAnimationFrame(() => { + var _a; + return (_a = this.viewer) == null ? void 0 : _a.forceRedraw(); + }); + } if (this.targetIndex === index) { this.targetIndex = null; } @@ -453,7 +649,13 @@ if (token !== this.loadToken) { return; } - this.markUnavailable(index, (event == null ? void 0 : event.message) || "This image could not be loaded. Static images must allow CORS."); + if (this.shouldRetryWithoutCors(descriptor, tileSource, nonCorsFallback)) { + this.finishLoad(index, controller); + this.emitCustomEvent("diva-static-image-cors-fallback", { sourceId: descriptor.sourceId }); + void this.loadTile(index, true, tileSource); + return; + } + this.markUnavailable(index, (event == null ? void 0 : event.message) || "This image could not be loaded."); this.finishLoad(index, controller); this.maybeLoadMore(); } @@ -471,8 +673,17 @@ if (typeof source.crossOriginPolicy === "string" || typeof source.crossOriginPolicy === "boolean") { options.crossOriginPolicy = source.crossOriginPolicy; } + if (typeof source.loadTilesWithAjax === "boolean") { + options.loadTilesWithAjax = source.loadTilesWithAjax; + } return options; } + shouldRetryWithoutCors(descriptor, tileSource, alreadyRetried) { + if (this.staticImageCorsPolicy !== "fallback" || !descriptor.isStatic || alreadyRetried) { + return false; + } + return !(typeof tileSource === "object" && tileSource !== null && typeof tileSource.crossOriginPolicy === "string" && tileSource.crossOriginPolicy.toLowerCase() === "use-credentials"); + } finishLoad(index, controller) { if (this.loadControllers.get(index) !== controller) { return; @@ -528,8 +739,9 @@ }); } addUnavailableOverlay(index, message) { - if (!this.viewer) + if (!this.viewer) { return; + } this.removeUnavailableOverlay(index); const element = document.createElement("div"); element.className = "diva-image-unavailable"; @@ -547,13 +759,19 @@ element.append(text, retry); this.viewer.addOverlay({ element, - location: new OpenSeadragon.Rect(this.pageXOffsets[index] || 0, this.pageOffsets[index] || 0, 1, this.pageHeights[index] || 1) + location: new OpenSeadragon.Rect( + this.pageXOffsets[index] || 0, + this.pageOffsets[index] || 0, + 1, + this.pageHeights[index] || 1 + ) }); } removeUnavailableOverlay(index) { const element = this.querySelector(`.diva-image-unavailable[data-index="${index}"]`); - if (element && this.viewer) - this.viewer.removeOverlay(element); + if (element) { + this.removeOverlay(element); + } element == null ? void 0 : element.remove(); } setPageAspects(aspects) { @@ -643,6 +861,7 @@ item.setWidth(1, true); item.setHeight(height, true); this.addOrUpdatePageOverlay(index); + this.addOrUpdateAnnotationOverlay(index); }); } addOrUpdatePageOverlay(index) { @@ -671,10 +890,7 @@ element.textContent = this.pageLabels[index] || `Page ${index + 1}`; const xOffset = (this.pageXOffsets[index] || 0) + (isRightAligned ? 1 : 0); const yOffset = this.pageOffsets[index] || 0; - try { - this.viewer.removeOverlay(element); - } catch (_error) { - } + this.removeOverlay(element); this.viewer.addOverlay({ element, location: new OpenSeadragon.Point(xOffset, yOffset), @@ -682,17 +898,470 @@ }); } clearPageOverlays() { - if (this.viewer) { - this.pageOverlayElements.forEach((element) => { - var _a; - try { - (_a = this.viewer) == null ? void 0 : _a.removeOverlay(element); - } catch (_error) { - } + this.clearOverlays(this.pageOverlayElements); + } + addOrUpdateAnnotationOverlay(index) { + var _a; + if (!this.viewer || !this.loadedItems.has(index)) { + return; + } + const canvasId = (_a = this.tileSources[index]) == null ? void 0 : _a.canvasId; + const annotations = typeof canvasId === "string" ? this.annotationData.get(canvasId) : void 0; + let element = this.annotationOverlayElements.get(index); + if (!annotations || annotations.length === 0) { + if (element) { + this.removeOverlay(element); element.remove(); + this.annotationOverlayElements.delete(index); + } + return; + } + if (!element) { + element = document.createElement("div"); + element.className = "diva-annotation-overlay"; + this.annotationOverlayElements.set(index, element); + } + element.classList.toggle("is-hidden", !this.annotationsVisible); + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", `0 0 ${this.pageWidth(index)} ${this.pageHeightPixels(index)}`); + svg.setAttribute("preserveAspectRatio", "none"); + svg.setAttribute("width", "100%"); + svg.setAttribute("height", "100%"); + const tooltip = document.createElement("div"); + tooltip.className = "diva-annotation-tooltip"; + tooltip.setAttribute("role", "tooltip"); + annotations.forEach((annotation) => { + const group = document.createElementNS("http://www.w3.org/2000/svg", "g"); + group.setAttribute("data-annotation-id", annotation.id); + group.setAttribute("tabindex", "0"); + group.classList.toggle("is-selected", annotation.id === this.selectedAnnotationId); + if (annotation.text) { + group.setAttribute("aria-label", annotation.text); + const showTooltip = (event) => { + const bounds = element.getBoundingClientRect(); + if (!this.annotationsVisible || !this.annotationSelectionEnabled) { + tooltip.hidden = true; + return; + } + const x = event ? event.clientX - bounds.left : bounds.width / 2; + const y = event ? event.clientY - bounds.top : bounds.height / 2; + tooltip.textContent = annotation.text; + tooltip.style.left = `${x}px`; + tooltip.style.top = `${y}px`; + tooltip.hidden = false; + }; + group.addEventListener("mouseenter", showTooltip); + group.addEventListener("mousemove", showTooltip); + group.addEventListener("mouseleave", () => { + tooltip.hidden = true; + }); + group.addEventListener("focus", () => showTooltip()); + group.addEventListener("blur", () => { + tooltip.hidden = true; + }); + } + group.addEventListener("click", (event) => { + if (!this.annotationSelectionEnabled) { + return; + } + event.stopPropagation(); + this.openAnnotationPanel(annotation, group); + }); + group.addEventListener("pointerup", (event) => { + if (!this.annotationSelectionEnabled) { + return; + } + event.stopPropagation(); + this.openAnnotationPanel(annotation, group); + }); + group.addEventListener("keydown", (event) => { + if (!this.annotationSelectionEnabled) { + return; + } + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + this.openAnnotationPanel(annotation, group); + } + }); + if (annotation.shape.kind === "rect") { + const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + rect.setAttribute("x", String(annotation.shape.x)); + rect.setAttribute("y", String(annotation.shape.y)); + rect.setAttribute("width", String(annotation.shape.width)); + rect.setAttribute("height", String(annotation.shape.height)); + rect.setAttribute("class", "diva-annotation-rect"); + group.appendChild(rect); + } else if (annotation.shape.kind === "svg" && annotation.shape.value) { + this.appendSanitizedSvg(group, annotation.shape.value); + } + svg.appendChild(group); + }); + tooltip.hidden = true; + element.replaceChildren(svg, tooltip); + this.removeOverlay(element); + this.viewer.addOverlay({ + element, + location: new OpenSeadragon.Rect( + this.pageXOffsets[index] || 0, + this.pageOffsets[index] || 0, + 1, + this.pageHeights[index] || 1 + ) + }); + } + pageWidth(index) { + var _a, _b; + const item = this.loadedItems.get(index); + return ((_b = (_a = item == null ? void 0 : item.source) == null ? void 0 : _a.dimensions) == null ? void 0 : _b.x) || 1; + } + pageHeightPixels(index) { + var _a, _b; + const item = this.loadedItems.get(index); + return ((_b = (_a = item == null ? void 0 : item.source) == null ? void 0 : _a.dimensions) == null ? void 0 : _b.y) || 1; + } + appendSanitizedSvg(target, markup) { + const parsed = new DOMParser().parseFromString(markup, "image/svg+xml"); + const source = parsed.documentElement; + const allowed = /* @__PURE__ */ new Set(["svg", "g", "path", "rect", "circle", "ellipse", "polygon", "polyline", "line"]); + const attributes = /* @__PURE__ */ new Set([ + "d", + "points", + "x", + "y", + "x1", + "x2", + "y1", + "y2", + "cx", + "cy", + "rx", + "ry", + "r", + "width", + "height", + "fill", + "fill-opacity", + "fill-rule", + "stroke", + "stroke-width", + "stroke-opacity", + "stroke-linecap", + "stroke-linejoin", + "stroke-dasharray", + "transform", + "viewBox" + ]); + const copy = (node, parent) => { + const name = node.localName.toLowerCase(); + if (!allowed.has(name)) { + return; + } + const child = document.createElementNS("http://www.w3.org/2000/svg", name); + if (name !== "svg" && name !== "g") { + child.classList.add("diva-annotation-svg-shape"); + } + Array.from(node.attributes).forEach((attribute) => { + if (attributes.has(attribute.name)) { + child.setAttribute(attribute.name, attribute.value); + } + }); + parent.appendChild(child); + Array.from(node.children).forEach((next) => copy(next, child)); + }; + if (source.localName.toLowerCase() === "svg") { + Array.from(source.children).forEach((child) => copy(child, target)); + } + } + clearAnnotationOverlays() { + this.clearOverlays(this.annotationOverlayElements); + } + removeOverlay(element) { + var _a; + try { + (_a = this.viewer) == null ? void 0 : _a.removeOverlay(element); + } catch (_error) { + } + } + clearOverlays(overlays) { + overlays.forEach((element) => { + this.removeOverlay(element); + element.remove(); + }); + overlays.clear(); + } + handleAnnotationCanvasClick(event) { + var _a; + if (!this.annotationSelectionEnabled || !this.annotationsVisible || !event.quick || !event.position || !this.container) { + return; + } + const viewerBounds = this.container.getBoundingClientRect(); + const clientX = viewerBounds.left + event.position.x; + const clientY = viewerBounds.top + event.position.y; + for (const [index, element] of Array.from(this.annotationOverlayElements.entries()).reverse()) { + const canvasId = (_a = this.tileSources[index]) == null ? void 0 : _a.canvasId; + const annotations = typeof canvasId === "string" ? this.annotationData.get(canvasId) : void 0; + if (!annotations) { + continue; + } + const groups = Array.from(element.querySelectorAll("g[data-annotation-id]")).reverse(); + for (const group of groups) { + const bounds = group.getBoundingClientRect(); + if (clientX < bounds.left || clientX > bounds.right || clientY < bounds.top || clientY > bounds.bottom) { + continue; + } + const annotation = annotations.find((candidate) => candidate.id === group.dataset.annotationId); + if (annotation) { + this.openAnnotationPanel(annotation, group); + return; + } + } + } + } + openAnnotationPanel(annotation, annotationGroup) { + if (!this.container) { + return; + } + this.selectedAnnotationId = annotation.id; + this.annotationOverlayElements.forEach((element) => { + element.querySelectorAll("g[data-annotation-id]").forEach((group) => { + group.classList.toggle("is-selected", group.dataset.annotationId === annotation.id); + }); + }); + this.emitCustomEvent("diva-annotation-select", { annotationId: annotation.id }); + if (!this.annotationPanel) { + const panel2 = document.createElement("aside"); + panel2.className = "diva-annotation-panel"; + panel2.setAttribute("aria-label", "Annotation details"); + panel2.addEventListener("click", (event) => event.stopPropagation()); + this.appendChild(panel2); + this.annotationPanel = panel2; + } + this.annotationPanelIgnoreCloseUntil = performance.now() + 500; + const close = document.createElement("button"); + close.type = "button"; + close.className = "diva-annotation-panel-close"; + close.setAttribute("aria-label", "Close annotation details"); + close.title = "Close"; + const closeIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + closeIcon.setAttribute("viewBox", "0 0 512 512"); + closeIcon.setAttribute("aria-hidden", "true"); + const closePath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + closePath.setAttribute( + "d", + "M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm175 79c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z" + ); + closeIcon.appendChild(closePath); + close.appendChild(closeIcon); + close.addEventListener("click", (event) => { + event.stopPropagation(); + this.closeAnnotationPanel(); + }); + const content = document.createElement("div"); + content.className = "diva-annotation-panel-content"; + const extractUrl = this.annotationExtractUrl(annotation, annotationGroup); + let imageLink = null; + if (extractUrl) { + const previewControls = document.createElement("div"); + previewControls.className = "diva-annotation-panel-preview-controls"; + Object.assign(previewControls.style, { + display: "flex", + justifyContent: "flex-end", + gap: "4px", + margin: "0 0 4px" + }); + const previewFrame = document.createElement("div"); + Object.assign(previewFrame.style, { + position: "relative", + maxWidth: "100%", + margin: "0 auto 10px", + border: "1px solid rgba(255, 255, 255, 0.2)" + }); + const preview = document.createElement("img"); + Object.assign(preview.style, { + position: "absolute", + top: "50%", + left: "50%", + maxWidth: "none", + transition: "transform 120ms ease-out" + }); + preview.src = extractUrl; + preview.alt = annotation.text ? `Image extract: ${annotation.text}` : "Annotation image extract"; + preview.addEventListener("error", () => { + previewControls.remove(); + previewFrame.remove(); }); + let rotation = 0; + const updatePreviewLayout = () => { + if (preview.naturalWidth <= 0 || preview.naturalHeight <= 0) { + return; + } + const largestSide = Math.min(content.clientWidth || 320, 320); + const aspect = preview.naturalWidth / preview.naturalHeight; + const baseWidth = aspect >= 1 ? largestSide : largestSide * aspect; + const baseHeight = aspect >= 1 ? largestSide / aspect : largestSide; + const isQuarterTurn = Math.abs(Math.round(rotation / 90)) % 2 === 1; + previewFrame.style.width = `${isQuarterTurn ? baseHeight : baseWidth}px`; + previewFrame.style.height = `${isQuarterTurn ? baseWidth : baseHeight}px`; + preview.style.width = `${baseWidth}px`; + preview.style.height = `${baseHeight}px`; + preview.style.transform = `translate(-50%, -50%) rotate(${rotation}deg)`; + }; + const rotate = (degrees) => { + rotation += degrees; + updatePreviewLayout(); + }; + preview.addEventListener("load", updatePreviewLayout); + const rotateCounterClockwise = document.createElement("button"); + rotateCounterClockwise.type = "button"; + rotateCounterClockwise.className = "diva-annotation-panel-rotate"; + rotateCounterClockwise.textContent = "\u21BA"; + rotateCounterClockwise.setAttribute("aria-label", "Rotate image counter-clockwise 90 degrees"); + rotateCounterClockwise.addEventListener("click", () => rotate(-90)); + previewControls.appendChild(rotateCounterClockwise); + previewFrame.appendChild(preview); + imageLink = document.createElement("a"); + imageLink.className = "diva-annotation-panel-image-link"; + imageLink.href = extractUrl; + imageLink.target = "_blank"; + imageLink.rel = "noopener noreferrer"; + imageLink.textContent = extractUrl; + imageLink.setAttribute("aria-label", "Open the IIIF Image API extract"); + content.append(previewControls, previewFrame); + window.requestAnimationFrame(updatePreviewLayout); + } + content.appendChild(this.sanitizeAnnotationHtml(annotation.html || annotation.text)); + if (imageLink) { + content.appendChild(imageLink); + } + const panel = this.annotationPanel; + panel.replaceChildren(close, content); + panel.hidden = false; + close.focus(); + } + annotationExtractUrl(annotation, annotationGroup) { + if (!annotation.imageService) { + return null; + } + let region = null; + if (annotation.shape.kind === "rect") { + const { x, y, width, height } = annotation.shape; + if ([x, y, width, height].every((value) => typeof value === "number" && Number.isFinite(value)) && width > 0 && height > 0) { + region = { x, y, width, height }; + } + } else if (annotation.shape.kind === "svg" && annotationGroup) { + const overlay = annotationGroup.closest(".diva-annotation-overlay"); + const svg = overlay == null ? void 0 : overlay.querySelector("svg"); + if (svg) { + const shapeBounds = annotationGroup.getBoundingClientRect(); + const svgBounds = svg.getBoundingClientRect(); + const viewBox = svg.viewBox.baseVal; + if (shapeBounds.width > 0 && shapeBounds.height > 0 && svgBounds.width > 0 && svgBounds.height > 0 && viewBox.width > 0 && viewBox.height > 0) { + region = { + x: viewBox.x + (shapeBounds.left - svgBounds.left) / svgBounds.width * viewBox.width, + y: viewBox.y + (shapeBounds.top - svgBounds.top) / svgBounds.height * viewBox.height, + width: shapeBounds.width / svgBounds.width * viewBox.width, + height: shapeBounds.height / svgBounds.height * viewBox.height + }; + } + } + } else if (annotation.shape.kind === "svg" && annotation.shape.value) { + region = this.svgBoundingRegion(annotation.shape.value); + } + if (!region && annotation.shape.kind === "svg" && annotation.shape.value) { + region = this.svgBoundingRegion(annotation.shape.value); + } + if (!region) { + return null; + } + return iiifImageRegionUrl(annotation.imageService, region); + } + svgBoundingRegion(markup) { + if (!document.body) { + return null; + } + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const group = document.createElementNS("http://www.w3.org/2000/svg", "g"); + svg.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;left:-10000px;top:-10000px;"; + svg.appendChild(group); + this.appendSanitizedSvg(group, markup); + document.body.appendChild(svg); + try { + const bounds = group.getBBox(); + return bounds.width > 0 && bounds.height > 0 ? { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height } : null; + } catch (_error) { + return null; + } finally { + svg.remove(); } - this.pageOverlayElements.clear(); + } + closeAnnotationPanel() { + if (this.annotationPanel) { + this.annotationPanel.hidden = true; + } + } + sanitizeAnnotationHtml(markup) { + const template = document.createElement("template"); + template.innerHTML = markup; + const allowedTags = /* @__PURE__ */ new Set([ + "div", + "p", + "br", + "em", + "i", + "strong", + "b", + "a", + "ul", + "ol", + "li", + "dl", + "dt", + "dd", + "span", + "img" + ]); + const safeUrl = (value) => { + const normalized = value.trim().toLowerCase(); + return /^(https?:|mailto:|tel:|\/|\.\/|\.\.\/|#|\?)/.test(normalized) || !normalized.includes(":"); + }; + const copy = (node, parent) => { + if (node.nodeType === Node.TEXT_NODE) { + parent.appendChild(document.createTextNode(node.textContent || "")); + return; + } + if (!(node instanceof Element)) { + return; + } + const tag = node.localName.toLowerCase(); + if (!allowedTags.has(tag)) { + Array.from(node.childNodes).forEach((child) => copy(child, parent)); + return; + } + const element = document.createElement(tag); + if (tag === "a") { + const href = node.getAttribute("href"); + if (href && safeUrl(href)) { + element.setAttribute("href", href); + } + element.setAttribute("target", "_blank"); + element.setAttribute("rel", "noopener noreferrer"); + } + if (tag === "img") { + const src = node.getAttribute("src"); + if (src && safeUrl(src)) { + element.setAttribute("src", src); + } + const alt = node.getAttribute("alt"); + if (alt) { + element.setAttribute("alt", alt); + } + } + parent.appendChild(element); + Array.from(node.childNodes).forEach((child) => copy(child, element)); + }; + const result = document.createDocumentFragment(); + Array.from(template.content.childNodes).forEach((node) => copy(node, result)); + return result; } ensureScrollPlane() { if (!this.viewer || this.pageOffsets.length === 0) { @@ -898,9 +1567,8 @@ const speed = 0.75; const panY = rect.height > 0 ? deltaY / rect.height * speed : 0; const center = viewport.getCenter(true); - const nextCenter = new OpenSeadragon.Point(this.getCenterX(), center.y + panY); + const nextCenter = new OpenSeadragon.Point(center.x, center.y + panY); viewport.panTo(nextCenter, true); - this.recenterBoundsX(); this.clampTop(); this.clampBottom(); this.maybeEmitPageChange(); @@ -962,7 +1630,7 @@ } const clampedCenterY = bounds.height / 2 + minTop; this.isClamping = true; - vp.panTo(new OpenSeadragon.Point(this.getCenterX(), clampedCenterY), true); + vp.panTo(new OpenSeadragon.Point(vp.getCenter(false).x, clampedCenterY), true); vp.applyConstraints(); this.isClamping = false; } @@ -986,7 +1654,7 @@ } const clampedCenterY = maxBottom - bounds.height / 2; this.isClamping = true; - vp.panTo(new OpenSeadragon.Point(this.getCenterX(), clampedCenterY), true); + vp.panTo(new OpenSeadragon.Point(vp.getCenter(false).x, clampedCenterY), true); vp.applyConstraints(); this.isClamping = false; } @@ -1256,7 +1924,8 @@ const viewportHeight = this.viewer.viewport.getBounds(true).height; const maxScroll = totalHeight - viewportHeight; const newScrollY = scrollProgress * maxScroll; - this.scrollToOffset(newScrollY); + const center = this.viewer.viewport.getCenter(true); + this.viewer.viewport.panTo(new OpenSeadragon.Point(center.x, newScrollY + viewportHeight / 2), true); }; const onMouseUp = () => { if (isDragging) { @@ -1291,7 +1960,8 @@ const viewportHeight = this.viewer.viewport.getBounds(true).height; const maxScroll = totalHeight - viewportHeight; const newScrollY = scrollProgress * maxScroll; - this.scrollToOffset(newScrollY); + const center = this.viewer.viewport.getCenter(true); + this.viewer.viewport.panTo(new OpenSeadragon.Point(center.x, newScrollY + viewportHeight / 2), true); }); } }; @@ -1299,7 +1969,7 @@ customElements.define("osd-viewer", OsdViewer); // cache/diva.css - var diva_default = ":root{--diva-accent:#5a6bff;--diva-accent-light:#9aa4ff;--diva-border:#d9d4ce;--diva-danger:#d32f2f;--diva-dark-bg:#1c1d22;--diva-dark-border:#2c2d33;--diva-overlay-bg:#10111499;--diva-page-bg:#f7f5f1;--diva-shadow-dark:#00000026;--diva-shadow-focus:#9aa4ff59;--diva-shadow-modal:#00000040;--diva-surface:#e6e1dc;--diva-text-muted:#5c5a55;--diva-text-muted-on-dark:#ffffffb3;--diva-text-primary:#1b1b1b;--diva-toolbar-button-bg:#5258626b;--diva-toolbar-button-bg-hover:#5c636e85;--diva-toolbar-button-bg-fullscreen:#52586285;--diva-toolbar-button-bg-fullscreen-hover:#6068749e;--diva-toolbar-button-border:#ffffff59;--diva-toolbar-button-border-hover:#ffffff73;--diva-toolbar-button-border-fullscreen-hover:#fff9;--diva-toolbar-button-icon:#2c2d33;--diva-toolbar-button-shadow:inset 0 1px 0 #ffffff8c, inset 0 -1px 0 #ffffff2e;--diva-toolbar-button-shadow-hover:inset 0 1px 0 #ffffffb3, inset 0 -1px 0 #ffffff3d;--diva-white:#fff;--diva-font-lg:16px;--diva-font-md:13px;--diva-font-sm:11px;--diva-font-xs:10px;--diva-font-xl:20px}.diva-app{color-scheme:light}.diva-app,.diva-app *{box-sizing:border-box}.diva-list-reset{margin:0;padding:0;list-style:none}.diva-ui-button{text-align:left;cursor:pointer;color:var(--diva-text-primary);font-size:var(--diva-font-lg);background-color:#0000;border:none;padding:0}.diva-ui-button:hover{background-color:var(--diva-surface)}.diva-ui-card{cursor:pointer;border-radius:0;width:100%;padding:6px}.diva-ui-card--dark{background-color:var(--diva-dark-bg)}.diva-app{color-scheme:light;flex-direction:column;flex:1;height:100%;min-height:0;padding:12px 24px;display:flex}.diva-app.is-fullscreen{height:100vh;min-height:100vh;padding:0}.diva-app-header{font-size:var(--diva-font-lg);align-items:center;gap:12px;margin-bottom:8px;font-weight:600;display:flex}.diva-app-title{font-size:var(--diva-font-xl);text-align:left;color:var(--diva-text-primary);margin-bottom:6px;font-weight:600}.diva-app-title.is-fullscreen{color:var(--diva-white)}.diva-app-body{flex:1;align-items:stretch;gap:0;height:100%;min-height:0;display:flex;position:relative}.diva-app-body.is-fullscreen{flex:1;min-height:0}.diva-canvas-column{flex-direction:column;flex:1;gap:24px;min-width:0;min-height:0;display:flex}.diva-canvas-column.is-fullscreen{flex:1;height:100%;min-height:0}.diva-canvas-wrapper{flex:1;min-height:0;position:relative}.diva-canvas{background-color:var(--diva-dark-bg);border:1px solid var(--diva-dark-border);border-radius:0;width:100%;height:100%;overflow:hidden}.diva-canvas.is-fullscreen{border-radius:0;flex:1;height:100%}.diva-canvas.has-collection{border-radius:0}.diva-metadata-panel{height:100%;padding:12px;overflow:auto}.diva-metadata-body{flex-direction:column;gap:10px;display:flex}.diva-metadata-item{flex-direction:column;gap:0;display:flex}.diva-metadata-label{font-size:var(--diva-font-lg);color:var(--diva-text-muted);letter-spacing:.05em;font-weight:600;line-height:1.3}.diva-metadata-value{font-size:var(--diva-font-lg);color:var(--diva-text-muted);padding-left:12px;line-height:1.3}.diva-metadata-value>:first-child{margin-top:0}.diva-metadata-value>:last-child{margin-bottom:0}.diva-contents-empty{font-size:var(--diva-font-lg);color:var(--diva-text-muted);padding-left:12px}.diva-sidebar-resizer,.diva-collection-resizer{width:12px;font-size:var(--diva-font-xl);color:var(--diva-white);background-color:var(--diva-text-muted);cursor:col-resize;user-select:none;touch-action:none;flex:0 0 12px;justify-content:center;align-self:stretch;align-items:center;line-height:1;display:flex}.diva-sidebar-resizer:hover,.diva-sidebar-resizer:active,.diva-collection-resizer:hover,.diva-collection-resizer:active{cursor:col-resize}.diva-sidebar-resizer.is-hidden,.diva-collection-resizer.is-hidden{display:none}.diva-sidebar-panel.is-fullscreen,.diva-collection-panel.is-fullscreen{border-radius:0;height:100%}.diva-sidebar-panel.is-hidden,.diva-collection-panel.is-hidden{opacity:0;pointer-events:none;border-width:0;padding:0;overflow:hidden}.diva-required-statement-dock{justify-content:flex-end;width:100%;margin-top:12px;padding-right:8px;display:flex}.diva-required-statement{font-size:var(--diva-font-md);color:var(--diva-text-muted);text-align:right;min-width:250px;max-width:20vw;line-height:1.4}.diva-scrollbar-track{background:var(--diva-surface);border:1px solid var(--diva-border);z-index:100;border-radius:0;width:12px;position:absolute;top:4px;bottom:4px;right:4px}.diva-scrollbar-thumb{background:var(--diva-text-muted);cursor:pointer;border-radius:0;min-height:30px;position:absolute;left:1px;right:1px}.diva-scrollbar-thumb:hover{background:var(--diva-text-primary)}.diva-scrollbar-thumb:active{background:var(--diva-dark-border)}.diva-throbber-overlay{pointer-events:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.diva-viewer-zoom-indicator{z-index:30;pointer-events:none;font-size:var(--diva-font-sm);color:var(--diva-white);background-color:#0000008c;border-radius:0;padding:4px 8px;font-weight:600;position:absolute;bottom:12px;left:12px}.diva-throbber{background-color:var(--diva-white);width:64px;height:64px;box-shadow:0 8px 16px var(--diva-shadow-dark);border-radius:0;flex-wrap:wrap;padding:8px;display:flex}.diva-throbber-cube{background-color:var(--diva-accent);width:16px;height:16px;animation-name:diva-cube-grid;animation-duration:1.3s;animation-timing-function:ease-in-out;animation-iteration-count:infinite}@keyframes diva-cube-grid{0%{transform:scale(1)}35%{transform:scale(0)}70%{transform:scale(1)}to{transform:scale(1)}}@media (width<=720px){.diva-app{padding:12px}.diva-app-body{flex-direction:column;gap:12px}.diva-sidebar-resizer,.diva-collection-resizer{display:none}}.diva-image-unavailable{background:color-mix(in srgb, var(--diva-background-color,#fff) 92%, transparent);box-sizing:border-box;text-align:center;border:1px solid #999;flex-direction:column;justify-content:center;align-items:center;padding:1rem;display:flex}.diva-image-unavailable button{cursor:pointer}.diva-sidebar-panel{border:1px solid var(--diva-dark-border);background-color:var(--diva-page-bg);border-radius:0;flex-direction:column;flex:none;width:320px;height:100%;min-height:0;display:flex;overflow:hidden}.diva-sidebar-tabs{border:1px solid var(--diva-surface);background-color:var(--diva-surface);border-radius:0;display:flex}.diva-sidebar-tab-button{font-size:var(--diva-font-md);text-transform:uppercase;cursor:pointer;color:var(--diva-text-muted);background-color:#0000;border:none;flex:1;padding:10px 12px}.diva-sidebar-tab-button.is-active{background-color:var(--diva-white);font-weight:600}.diva-sidebar-content{background-color:var(--diva-page-bg);flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.diva-sidebar-pane{flex:1;width:100%;min-height:0}.diva-sidebar-pane.is-hidden{display:none}.diva-thumbs{scroll-behavior:smooth;background-color:var(--diva-dark-bg);flex:1;grid-template-columns:repeat(3,minmax(0,1fr));align-content:start;gap:10px;width:100%;height:100%;min-height:0;padding:12px;display:grid;overflow-y:auto}.diva-thumbs.is-fullscreen{height:100%}.diva-thumbs-item{border:1px solid var(--diva-dark-border);text-align:left;flex-direction:column;justify-content:flex-start;align-items:stretch;max-width:none;display:flex}.diva-thumbs-item:focus-visible{outline:2px solid var(--diva-accent);outline-offset:2px}.diva-thumbs-item.is-active{border-color:var(--diva-accent-light);box-shadow:0 0 0 var(--diva-shadow-focus);background-color:var(--diva-dark-bg);outline:2px solid var(--diva-accent-light);outline-offset:2px}.diva-thumbs-image{border-radius:0;width:100%;height:auto;display:block}.diva-thumbs-lazy-image{background:var(--diva-dark-bg);width:100%;min-height:96px;display:block}.diva-thumbs-image--protected,.diva-thumbs-image--unavailable{background:var(--diva-dark-bg);min-height:96px}.diva-thumbs-label{font-size:var(--diva-font-sm);color:var(--diva-text-muted-on-dark);margin-top:6px;line-height:1.3}.diva-thumbs-label.is-active{color:var(--diva-white)}.diva-contents-panel{height:100%;padding:12px;overflow:auto}.diva-contents-title{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:10px;font-weight:600}.diva-contents-view-tabs{gap:8px;margin-bottom:12px;display:flex}.diva-contents-view-button{background-color:var(--diva-surface);border:1px solid var(--diva-border);font-size:var(--diva-font-sm);color:var(--diva-text-muted);cursor:pointer;border-radius:0;padding:4px 10px}.diva-contents-view-button.is-active{background-color:var(--diva-white);border-color:var(--diva-accent);color:var(--diva-text-primary)}.diva-contents-list-nested{border-left:1px solid var(--diva-border);margin-top:6px;margin-left:6px;padding-left:13px}.diva-contents-item{margin-bottom:6px}.diva-contents-heading{align-items:baseline;display:flex}.diva-contents-disclosure{color:var(--diva-text-muted);cursor:pointer;text-align:center;flex:0 0 1.25rem}.diva-contents-disclosure:hover{color:var(--diva-accent)}.diva-contents-meta{border:1px solid var(--diva-dark-border);flex-direction:column;gap:10px;margin-top:6px;padding:8px;display:flex}.diva-contents-meta dl>div+div{border-top:1px solid var(--diva-dark-border);margin-top:.75rem;padding-top:.75rem}.diva-contents-meta dt{font-weight:600}.diva-contents-meta dd{margin:0 0 .5rem 1rem}.diva-contents-meta dd:last-child{margin-bottom:0}.diva-contents-button:hover{color:var(--diva-accent)}.diva-contents-button.is-current{font-weight:600}@media (width<=720px){.diva-sidebar-panel{border-radius:0;height:auto;width:100%!important}.diva-sidebar-panel.is-overlay{z-index:100;width:100%;height:100%;box-shadow:0 12px 24px var(--diva-shadow-dark);border-radius:0;position:absolute;inset:0}.diva-sidebar-panel.is-mobile-hidden{display:none}.diva-thumbs{grid-template-columns:repeat(3,minmax(0,1fr));width:100%;height:auto;overflow:auto hidden}.diva-thumbs-item{min-width:120px}}.diva-canvas-toolbar-stack{flex-direction:column;gap:4px;width:100%;min-width:0;display:flex}.diva-canvas-toolbar{align-items:center;width:100%;margin-bottom:0;display:flex}.diva-canvas-toolbar-section{flex:none;align-items:center;gap:5px;min-width:0;display:flex}.diva-canvas-toolbar-section.is-right{flex:none;margin-left:auto}.diva-canvas-toolbar-end{align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.diva-canvas-toolbar-end .diva-canvas-toolbar-section.is-right{margin-left:0}.diva-canvas-toolbar-item{flex:none;justify-content:center;align-items:center;width:34px;height:34px;display:flex;position:relative}.diva-canvas-toolbar-button{width:30px;height:30px;color:var(--diva-toolbar-button-icon);box-shadow:none;cursor:pointer;background-color:#e2e2e2f0;border:1px solid #2c2d3373;border-radius:0;justify-content:center;align-items:center;padding:6px;display:flex}.diva-canvas-toolbar-button svg{flex:none;width:16px;height:16px}.diva-canvas-toolbar-button:focus-visible{outline:2px solid var(--diva-accent);outline-offset:2px}.diva-canvas-toolbar-button:hover{border-color:var(--diva-toolbar-button-icon);background-color:#d6d6d6fa}.diva-canvas-toolbar-button.is-fullscreen{color:var(--diva-white);background-color:#52586294;border-color:#ffffff85}.diva-canvas-toolbar-button.is-fullscreen:hover{border-color:var(--diva-white);background-color:#606874b8}.diva-canvas-toolbar-button.is-disabled{opacity:.4;cursor:not-allowed}.diva-canvas-toolbar-button.is-disabled:hover{background-color:#e2e2e2f0;border-color:#2c2d3373}.diva-canvas-toolbar-button.is-fullscreen.is-disabled:hover{background-color:#52586294;border-color:#ffffff85}.diva-canvas-toolbar-item:after{z-index:80;max-width:160px;color:var(--diva-white);content:attr(data-tooltip);font-size:var(--diva-font-xs);letter-spacing:.02em;opacity:0;pointer-events:none;text-align:center;visibility:hidden;white-space:normal;word-break:normal;background-color:#1c1d22f2;border:1px solid #ffffff2e;border-radius:0;width:max-content;padding:5px 7px;font-weight:600;line-height:1.2;transition:opacity .12s,transform .12s;position:absolute;top:calc(100% + 7px);left:50%;transform:translate(-50%,-3px);box-shadow:0 4px 10px #0003}.diva-canvas-toolbar-item:hover:after,.diva-canvas-toolbar-item:has(.diva-canvas-toolbar-button:focus-visible):after{opacity:1;visibility:visible;transform:translate(-50%)}.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:after{left:auto;right:0;transform:translateY(-3px)}.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:hover:after,.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:has(.diva-canvas-toolbar-button:focus-visible):after{transform:translate(0)}.diva-canvas-label{font-size:var(--diva-font-lg);color:var(--diva-text-muted);text-align:left;white-space:normal;overflow-wrap:anywhere;word-break:break-word;width:100%}.diva-canvas-toolbar-end .diva-canvas-label{overflow-wrap:normal;text-align:right;white-space:nowrap;word-break:normal;flex:auto;width:auto;min-width:0;max-width:min(42vw,42rem);overflow:hidden}.diva-canvas-label.is-fullscreen{color:var(--diva-white)}.diva-status{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:0}.diva-status.is-error{color:var(--diva-danger)}@media (width<=720px){.diva-canvas-toolbar{flex-wrap:wrap;gap:5px}.diva-canvas-toolbar-end{justify-content:flex-end;width:100%;margin-left:0}.diva-canvas-toolbar-item{width:32px;height:32px}.diva-canvas-toolbar-button{width:28px;height:28px;padding:6px}.diva-canvas-toolbar-button svg{width:15px;height:15px}.diva-canvas-label,.diva-status{display:none}}.diva-modal-overlay{background-color:var(--diva-overlay-bg);z-index:100;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.diva-viewer-status-overlay{background-color:var(--diva-overlay-bg);z-index:40;justify-content:center;align-items:center;padding:24px;display:flex;position:absolute;inset:0}.diva-modal-overlay.is-fullscreen{padding:0}.diva-modal{background-color:var(--diva-page-bg);color:var(--diva-text-primary);width:min(1440px,96vw);max-height:90vh;box-shadow:0 20px 40px var(--diva-shadow-modal);border-radius:0;flex-direction:column;display:flex}.diva-modal.is-narrow{width:min(960px,94vw)}.diva-modal.is-page-view{height:80vh;max-height:80vh}.diva-modal.is-fullscreen{border-radius:0;width:100vw;height:100vh;max-height:100vh}.diva-modal-header{justify-content:space-between;align-items:center;padding:16px 20px 0;display:flex}.diva-modal-actions{gap:8px;display:flex}.diva-modal-close-action .diva-canvas-toolbar-button{color:var(--diva-danger);box-shadow:none;-webkit-backdrop-filter:none;background-color:#0000;border:none;width:auto;height:auto;padding:2px}.diva-modal-close-action .diva-canvas-toolbar-button:hover{background-color:#d32f2f1f;border-color:#0000}.diva-modal-close-action .diva-canvas-toolbar-item{width:32px}.diva-modal-title-stack{flex-direction:column;gap:4px;display:flex}.diva-modal-title{font-size:var(--diva-font-lg);font-weight:600}.diva-modal-subtitle{font-size:var(--diva-font-lg);color:var(--diva-text-primary)}.diva-modal-subtitle.is-muted{font-size:var(--diva-font-md)}.diva-modal-body{flex:1;grid-template-columns:minmax(0,1fr) 240px;gap:16px;min-height:0;padding:16px 20px 20px;display:grid}.diva-modal-body.is-no-gap{gap:0}.diva-modal-body.is-two-column{grid-template-columns:minmax(0,1fr) 200px;align-items:start}.diva-modal-body.is-no-sidebar{grid-template-columns:minmax(0,1fr)}.diva-modal-body.is-fullscreen{flex:1;min-height:0}.diva-modal-body.is-with-choices{grid-template-columns:120px minmax(0,1fr) 240px}.diva-modal-body.is-with-choices-no-sidebar{grid-template-columns:120px minmax(0,1fr)}.diva-modal-viewer{background-color:var(--diva-dark-bg);border:1px solid var(--diva-dark-border);height:100%;overflow:hidden}.diva-modal-viewer.is-fullscreen{border-radius:0;height:100%}.diva-modal-viewer.is-outer-left{border-radius:0}.diva-modal-canvas{width:100%;height:100%;display:block}.diva-modal-sidebar{background-color:var(--diva-white);border-top:1px solid var(--diva-border);border-right:1px solid var(--diva-border);border-bottom:1px solid var(--diva-border);border-radius:0;padding:16px;overflow:auto}.diva-manifest-info-logo-wrap{text-align:center;flex-direction:column;align-items:center;gap:8px;display:flex}.diva-manifest-info-logo{width:100%;max-width:180px;height:auto}.diva-page-view-choices{background-color:var(--diva-dark-bg);border-radius:0;flex-direction:column;gap:8px;padding:8px;display:flex;overflow:auto}.diva-page-view-choice{border:2px solid #0000;flex-direction:column;gap:4px;display:flex}.diva-page-view-choice:focus-visible{outline:2px solid var(--diva-accent);outline-offset:2px}.diva-page-view-choice:hover{background-color:var(--diva-dark-bg)}.diva-page-view-choice.is-active{border-color:var(--diva-accent-light);background-color:var(--diva-dark-bg)}.diva-page-view-choice-thumb{border-radius:0;width:100%;height:auto;display:block}.diva-page-view-choice-thumb--protected{background:var(--diva-dark-bg);min-height:72px}.diva-page-view-choice-label{font-size:var(--diva-font-xs);color:var(--diva-text-muted);text-overflow:ellipsis;white-space:nowrap;line-height:1.2;overflow:hidden}.diva-filter-group{border-bottom:1px solid var(--diva-border);margin-bottom:12px;padding-bottom:12px}.diva-filter-title-button{text-align:left;cursor:pointer;width:100%;font-size:var(--diva-font-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--diva-text-muted);background-color:#0000;border:none;align-items:center;gap:8px;margin-bottom:8px;padding:0;font-weight:600;display:flex}.diva-filter-title-button.is-collapsed{margin-bottom:0}.diva-filter-title-icon{border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:6px solid var(--diva-text-muted);width:0;height:0;transition:transform .15s;display:inline-block}.diva-filter-title-icon.is-expanded{transform:rotate(90deg)}.diva-filter-row{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px;display:flex}.diva-filter-toggle{font-size:var(--diva-font-md);align-items:center;gap:8px;margin-bottom:8px;display:flex}.diva-filter-toggle.is-inline{margin-bottom:0}.diva-filter-range-group{flex-direction:column;gap:6px;margin-bottom:10px;display:flex}.diva-filter-range-header{justify-content:space-between;align-items:center;gap:8px;display:flex}.diva-filter-range-header-right{align-items:center;gap:8px;display:flex}.diva-filter-range-input{width:100%}.diva-filter-value{font-size:var(--diva-font-sm);color:var(--diva-text-muted);text-align:right;width:40px}.diva-filter-reset{font-size:var(--diva-font-xs);background-color:var(--diva-surface);border:1px solid var(--diva-border);cursor:pointer;color:var(--diva-text-muted);border-radius:0;padding:2px 6px}.diva-filter-reset:hover{background-color:var(--diva-border)}.diva-filter-json{width:100%;min-height:120px;font-size:var(--diva-font-sm);border:1px solid var(--diva-border);background-color:var(--diva-white);resize:vertical;border-radius:0;padding:6px 8px;font-family:Menlo,Monaco,Consolas,Liberation Mono,monospace}.diva-filter-json-error{font-size:var(--diva-font-sm);color:var(--diva-danger);margin-top:4px}.diva-filter-label{font-size:var(--diva-font-sm);color:var(--diva-text-muted)}.diva-filter-select{border:1px solid var(--diva-border);background-color:var(--diva-white);font-size:var(--diva-font-sm);border-radius:0;padding:4px 6px}.diva-filter-color-input{border:1px solid var(--diva-border);background-color:var(--diva-white);border-radius:0;width:42px;height:28px;padding:0}.diva-auth-overlay{z-index:10000;background:#0000008c;place-items:center;display:grid;position:fixed;inset:0}.diva-auth-dialog{box-sizing:border-box;border:1px solid var(--diva-border,#aaa);background:var(--diva-background,#fff);width:min(30rem,100vw - 2rem);color:var(--diva-text-primary,#222);border-radius:0;padding:1.25rem}.diva-auth-dialog button{min-height:2.25rem;color:inherit;cursor:pointer;background:0 0;border:1px solid;border-radius:0;padding:.35rem .8rem}.diva-auth-dialog button:focus-visible{outline:2px solid var(--diva-accent,#1769aa);outline-offset:2px}.diva-collection-panel{border:1px solid var(--diva-dark-border);background-color:var(--diva-page-bg);border-radius:0;flex-direction:column;height:100%;min-height:0;display:flex;overflow:hidden}.diva-collection-header{background-color:var(--diva-surface);border-bottom:1px solid var(--diva-border);border-radius:0;padding:12px}.diva-collection-title{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:4px;font-weight:600}.diva-collection-summary{font-size:var(--diva-font-md);color:var(--diva-text-muted);line-height:1.4}.diva-collection-tree-item{padding-left:12px}.diva-collection-node-button{align-items:center;gap:6px;width:100%;padding:6px 8px;display:flex}.diva-collection-expand-icon{flex-shrink:0;justify-content:center;align-items:center;width:16px;height:16px;display:flex}.diva-manifest-tree-item{padding:6px 8px 6px 30px}.diva-manifest-tree-item.is-active{background-color:var(--diva-border);font-weight:600}.diva-sidebar-pane.is-scroll{overflow-y:auto}@media (width<=720px){.diva-collection-panel{border-radius:0;width:100%;height:auto}}"; + var diva_default = ":root{--diva-accent:#5a6bff;--diva-accent-light:#9aa4ff;--diva-annotation-fill:#00bfff1f;--diva-annotation-fill-hover:#3030307a;--diva-annotation-stroke:#00bfff;--diva-annotation-stroke-hover:#d83a3a;--diva-annotation-stroke-width:2;--diva-background-color:#fff;--diva-border:#d9d4ce;--diva-danger:#d32f2f;--diva-dark-bg:#1c1d22;--diva-dark-border:#2c2d33;--diva-overlay-bg:#10111499;--diva-page-bg:#f7f5f1;--diva-shadow-dark:#00000026;--diva-shadow-focus:#9aa4ff59;--diva-shadow-modal:#00000040;--diva-surface:#e6e1dc;--diva-text-muted:#5c5a55;--diva-text-muted-on-dark:#ffffffb3;--diva-text-primary:#1b1b1b;--diva-toolbar-button-icon:#2c2d33;--diva-white:#fff;--diva-font-lg:16px;--diva-font-md:13px;--diva-font-sm:11px;--diva-font-xs:10px;--diva-font-xl:20px}.diva-app,.diva-app *{box-sizing:border-box}.diva-list-reset{margin:0;padding:0;list-style:none}.diva-ui-button{text-align:left;cursor:pointer;color:var(--diva-text-primary);font-size:var(--diva-font-lg);background-color:#0000;border:none;padding:0}.diva-ui-button:hover{background-color:var(--diva-surface)}.diva-ui-card{cursor:pointer;border-radius:0;width:100%;padding:6px}.diva-ui-card--dark{background-color:var(--diva-dark-bg)}.diva-thumbs-item:focus-visible,.diva-page-view-choice:focus-visible,.diva-canvas-toolbar-button:focus-visible{outline:2px solid var(--diva-accent);outline-offset:2px}.diva-annotation-overlay{pointer-events:none;width:100%;height:100%;position:relative}.diva-annotation-overlay.is-hidden{visibility:hidden!important}.diva-annotation-overlay svg{width:100%;height:100%;overflow:visible}.diva-annotation-overlay g{pointer-events:all;cursor:help}.diva-annotation-tooltip{z-index:101;color:#fff;max-width:min(18rem,75%);font-size:var(--diva-font-sm);pointer-events:none;background:#1c1d22f0;border:1px solid #ffffff2e;padding:6px 8px;line-height:1.35;position:absolute;transform:translate(10px,calc(-100% - 10px));box-shadow:0 4px 10px #00000047}.diva-annotation-panel{z-index:1000;color:#fff;background:#1c1d22f7;border:1px solid #fff3;width:min(24rem,100% - 28px);max-height:min(50%,24rem);padding:12px;position:absolute;bottom:14px;left:14px;overflow:auto;box-shadow:0 8px 20px #00000059}.osd-container{position:relative}.diva-annotation-panel-close{color:#fff;cursor:pointer;background-color:#000;border:1px solid #fff;border-radius:0;justify-content:center;align-items:center;height:30px;margin:0;padding:6px;display:inline-flex;position:absolute;top:12px;right:12px;width:30px!important}.diva-annotation-panel-close svg{fill:currentColor;flex:none;width:16px;height:16px}.diva-annotation-panel-close:hover,.diva-annotation-panel-close:focus-visible{background-color:#222}.diva-annotation-panel-content{padding-top:36px}.diva-annotation-panel-preview-controls{position:absolute;top:12px;right:50px}.diva-annotation-panel-content a{color:#9aa4ff}.diva-annotation-panel-content img{max-width:100%;height:auto}.diva-annotation-panel-rotate{color:#fff;cursor:pointer;background-color:#000;border:1px solid #fff;border-radius:0;justify-content:center;align-items:center;height:30px;padding:6px;font-size:18px;line-height:1;display:inline-flex;width:30px!important;margin:0!important}.diva-annotation-panel-rotate:hover,.diva-annotation-panel-rotate:focus-visible{background-color:#222}.diva-annotation-panel-image-link{color:#9aa4ff;font-size:var(--diva-font-sm);overflow-wrap:anywhere;margin:0 0 10px;line-height:1.3;display:block}.diva-annotation-rect,.diva-annotation-svg-shape{fill:var(--diva-annotation-fill,#00bfff1f);fill-opacity:1;stroke:var(--diva-annotation-stroke,#00bfff);stroke-width:var(--diva-annotation-stroke-width,2);vector-effect:non-scaling-stroke;transition:fill .12s ease-out,stroke .12s ease-out}.diva-annotation-overlay g[data-annotation-id]:hover .diva-annotation-rect,.diva-annotation-overlay g[data-annotation-id]:hover .diva-annotation-svg-shape,.diva-annotation-overlay g[data-annotation-id]:focus .diva-annotation-rect,.diva-annotation-overlay g[data-annotation-id]:focus .diva-annotation-svg-shape,.diva-annotation-overlay g[data-annotation-id].is-selected .diva-annotation-rect,.diva-annotation-overlay g[data-annotation-id].is-selected .diva-annotation-svg-shape{fill:var(--diva-annotation-fill-hover,#3030307a);stroke:var(--diva-annotation-stroke-hover,#d83a3a)}.diva-thumbs-image,.diva-page-view-choice-thumb{border-radius:0;width:100%;height:auto;display:block}.diva-metadata-panel,.diva-contents-panel{height:100%;padding:12px;overflow:auto}.diva-app{color-scheme:light;flex-direction:column;flex:1;height:100%;min-height:0;padding:12px 24px;display:flex}.diva-app.is-fullscreen{height:100vh;min-height:100vh;padding:0}.diva-app-header{font-size:var(--diva-font-lg);align-items:center;gap:12px;margin-bottom:8px;font-weight:600;display:flex}.diva-app-title{font-size:var(--diva-font-xl);text-align:left;color:var(--diva-text-primary);margin-bottom:6px;font-weight:600}.diva-app-title.is-fullscreen{color:var(--diva-white)}.diva-app-body{flex:1;align-items:stretch;gap:0;height:100%;min-height:0;display:flex;position:relative}.diva-canvas-column{flex-direction:column;flex:1;gap:24px;min-width:0;min-height:0;display:flex}.diva-canvas-column.is-fullscreen{height:100%}.diva-canvas-wrapper{flex:1;min-height:0;position:relative}.diva-canvas{background-color:var(--diva-dark-bg);border:1px solid var(--diva-dark-border);border-radius:0;width:100%;height:100%;overflow:hidden}.diva-canvas.is-fullscreen{flex:1}.diva-metadata-body{flex-direction:column;gap:10px;display:flex}.diva-metadata-item{flex-direction:column;gap:0;display:flex}.diva-metadata-label{font-size:var(--diva-font-lg);color:var(--diva-text-muted);letter-spacing:.05em;font-weight:600;line-height:1.3}.diva-metadata-value{font-size:var(--diva-font-lg);color:var(--diva-text-muted);padding-left:12px;line-height:1.3}.diva-metadata-value>:first-child{margin-top:0}.diva-metadata-value>:last-child{margin-bottom:0}.diva-contents-empty{font-size:var(--diva-font-lg);color:var(--diva-text-muted);padding-left:12px}.diva-sidebar-resizer,.diva-collection-resizer{width:12px;font-size:var(--diva-font-xl);color:var(--diva-white);background-color:var(--diva-text-muted);cursor:col-resize;user-select:none;touch-action:none;flex:0 0 12px;justify-content:center;align-self:stretch;align-items:center;line-height:1;display:flex}.diva-sidebar-resizer:hover,.diva-sidebar-resizer:active,.diva-collection-resizer:hover,.diva-collection-resizer:active{cursor:col-resize}.diva-sidebar-resizer.is-hidden,.diva-collection-resizer.is-hidden{display:none}.diva-sidebar-panel.is-hidden,.diva-collection-panel.is-hidden{opacity:0;pointer-events:none;border-width:0;padding:0;overflow:hidden}.diva-required-statement-dock{justify-content:flex-end;width:100%;margin-top:12px;padding-right:8px;display:flex}.diva-required-statement{font-size:var(--diva-font-md);color:var(--diva-text-muted);text-align:right;min-width:250px;max-width:20vw;line-height:1.4}.diva-scrollbar-track{background:var(--diva-surface);border:1px solid var(--diva-border);z-index:100;border-radius:0;width:12px;position:absolute;top:4px;bottom:4px;right:4px}.diva-scrollbar-thumb{background:var(--diva-text-muted);cursor:pointer;border-radius:0;min-height:30px;position:absolute;left:1px;right:1px}.diva-scrollbar-thumb:hover{background:var(--diva-text-primary)}.diva-scrollbar-thumb:active{background:var(--diva-dark-border)}.diva-throbber-overlay{pointer-events:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.diva-viewer-zoom-indicator{z-index:30;pointer-events:none;font-size:var(--diva-font-sm);color:var(--diva-white);background-color:#0000008c;border-radius:0;padding:4px 8px;font-weight:600;position:absolute;bottom:12px;left:12px}.diva-throbber{background-color:var(--diva-white);width:64px;height:64px;box-shadow:0 8px 16px var(--diva-shadow-dark);border-radius:0;flex-wrap:wrap;padding:8px;display:flex}.diva-throbber-cube{background-color:var(--diva-accent);width:16px;height:16px;animation-name:diva-cube-grid;animation-duration:1.3s;animation-timing-function:ease-in-out;animation-iteration-count:infinite}@keyframes diva-cube-grid{0%{transform:scale(1)}35%{transform:scale(0)}70%{transform:scale(1)}to{transform:scale(1)}}@media (width<=720px){.diva-app{padding:12px}.diva-app-body{flex-direction:column;gap:12px}.diva-sidebar-resizer,.diva-collection-resizer{display:none}}.diva-image-unavailable{background:color-mix(in srgb, var(--diva-background-color,#fff) 92%, transparent);box-sizing:border-box;text-align:center;border:1px solid #999;flex-direction:column;justify-content:center;align-items:center;padding:1rem;display:flex}.diva-image-unavailable button{cursor:pointer}.diva-sidebar-panel{border:1px solid var(--diva-dark-border);background-color:var(--diva-page-bg);border-radius:0;flex-direction:column;flex:none;width:320px;height:100%;min-height:0;display:flex;overflow:hidden}.diva-sidebar-tabs{border:1px solid var(--diva-surface);background-color:var(--diva-surface);border-radius:0;display:flex}.diva-sidebar-tab-button{font-size:var(--diva-font-md);text-transform:uppercase;cursor:pointer;color:var(--diva-text-muted);background-color:#0000;border:none;flex:1;padding:10px 12px}.diva-sidebar-tab-button.is-active{background-color:var(--diva-white);font-weight:600}.diva-sidebar-content{background-color:var(--diva-page-bg);flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.diva-sidebar-pane{flex:1;width:100%;min-height:0}.diva-sidebar-pane.is-hidden{display:none}.diva-thumbs{scroll-behavior:smooth;background-color:var(--diva-dark-bg);flex:1;grid-template-columns:repeat(3,minmax(0,1fr));align-content:start;gap:10px;width:100%;height:100%;min-height:0;padding:12px;display:grid;overflow-y:auto}.diva-thumbs-item{border:1px solid var(--diva-dark-border);text-align:left;flex-direction:column;justify-content:flex-start;align-items:stretch;max-width:none;display:flex}.diva-thumbs-item.is-active{border-color:var(--diva-accent-light);box-shadow:0 0 0 var(--diva-shadow-focus);background-color:var(--diva-dark-bg);outline:2px solid var(--diva-accent-light);outline-offset:2px}.diva-thumbs-lazy-image{background:var(--diva-dark-bg);width:100%;min-height:96px;display:block}.diva-thumbs-image--protected,.diva-thumbs-image--unavailable{background:var(--diva-dark-bg);min-height:96px}.diva-thumbs-label{font-size:var(--diva-font-sm);color:var(--diva-text-muted-on-dark);margin-top:6px;line-height:1.3}.diva-thumbs-label.is-active{color:var(--diva-white)}.diva-contents-title{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:10px;font-weight:600}.diva-contents-view-tabs{gap:8px;margin-bottom:12px;display:flex}.diva-contents-view-button{background-color:var(--diva-surface);border:1px solid var(--diva-border);font-size:var(--diva-font-sm);color:var(--diva-text-muted);cursor:pointer;border-radius:0;padding:4px 10px}.diva-contents-view-button.is-active{background-color:var(--diva-white);border-color:var(--diva-accent);color:var(--diva-text-primary)}.diva-contents-list-nested{border-left:1px solid var(--diva-border);margin-top:6px;margin-left:6px;padding-left:13px}.diva-contents-item{margin-bottom:6px}.diva-contents-heading{align-items:baseline;display:flex}.diva-contents-disclosure{color:var(--diva-text-muted);cursor:pointer;text-align:center;flex:0 0 1.25rem}.diva-contents-disclosure:hover{color:var(--diva-accent)}.diva-contents-meta{border:1px solid var(--diva-dark-border);flex-direction:column;gap:10px;margin-top:6px;padding:8px;display:flex}.diva-contents-meta dl>div+div{border-top:1px solid var(--diva-dark-border);margin-top:.75rem;padding-top:.75rem}.diva-contents-meta dt{font-weight:600}.diva-contents-meta dd{margin:0 0 .5rem 1rem}.diva-contents-meta dd:last-child{margin-bottom:0}.diva-contents-button:hover{color:var(--diva-accent)}.diva-contents-button.is-current{font-weight:600}@media (width<=720px){.diva-sidebar-panel{height:auto;width:100%!important}.diva-sidebar-panel.is-overlay{z-index:100;width:100%;height:100%;box-shadow:0 12px 24px var(--diva-shadow-dark);border-radius:0;position:absolute;inset:0}.diva-sidebar-panel.is-mobile-hidden{display:none}.diva-thumbs{height:auto;overflow:auto hidden}.diva-thumbs-item{min-width:120px}}.diva-canvas-toolbar-stack{flex-direction:column;gap:4px;width:100%;min-width:0;display:flex}.diva-canvas-toolbar{align-items:center;width:100%;margin-bottom:0;display:flex}.diva-canvas-toolbar-section{flex:none;align-items:center;gap:5px;min-width:0;display:flex}.diva-canvas-toolbar-section.is-right{flex:none;margin-left:auto}.diva-canvas-toolbar-end{align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.diva-canvas-toolbar-end .diva-canvas-toolbar-section.is-right{margin-left:0}.diva-canvas-toolbar-item{flex:none;justify-content:center;align-items:center;width:34px;height:34px;display:flex;position:relative}.diva-canvas-toolbar-button{width:30px;height:30px;color:var(--diva-toolbar-button-icon);box-shadow:none;cursor:pointer;background-color:#e2e2e2f0;border:1px solid #2c2d3373;border-radius:0;justify-content:center;align-items:center;padding:6px;display:flex}.diva-canvas-toolbar-button svg{flex:none;width:16px;height:16px}.diva-canvas-toolbar-button:focus-visible{outline:2px solid var(--diva-accent);outline-offset:2px}.diva-canvas-toolbar-button:hover{border-color:var(--diva-toolbar-button-icon);background-color:#d6d6d6fa}.diva-canvas-toolbar-button.is-fullscreen{color:var(--diva-white);background-color:#52586294;border-color:#ffffff85}.diva-canvas-toolbar-button.is-fullscreen:hover{border-color:var(--diva-white);background-color:#606874b8}.diva-canvas-toolbar-button.is-disabled{opacity:.4;cursor:not-allowed}.diva-canvas-toolbar-button.is-disabled:hover{background-color:#e2e2e2f0;border-color:#2c2d3373}.diva-canvas-toolbar-button.is-fullscreen.is-disabled:hover{background-color:#52586294;border-color:#ffffff85}.diva-canvas-toolbar-item:after{z-index:80;max-width:160px;color:var(--diva-white);content:attr(data-tooltip);font-size:var(--diva-font-xs);letter-spacing:.02em;opacity:0;pointer-events:none;text-align:center;visibility:hidden;white-space:normal;word-break:normal;background-color:#1c1d22f2;border:1px solid #ffffff2e;border-radius:0;width:max-content;padding:5px 7px;font-weight:600;line-height:1.2;transition:opacity .12s,transform .12s;position:absolute;top:calc(100% + 7px);left:50%;transform:translate(-50%,-3px);box-shadow:0 4px 10px #0003}.diva-canvas-toolbar-item:hover:after,.diva-canvas-toolbar-item:has(.diva-canvas-toolbar-button:focus-visible):after{opacity:1;visibility:visible;transform:translate(-50%)}.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:after{left:auto;right:0;transform:translateY(-3px)}.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:hover:after,.diva-canvas-toolbar-section.is-right .diva-canvas-toolbar-item:last-child:has(.diva-canvas-toolbar-button:focus-visible):after{transform:translate(0)}.diva-canvas-label{font-size:var(--diva-font-lg);color:var(--diva-text-muted);text-align:left;white-space:normal;overflow-wrap:anywhere;word-break:break-word;width:100%}.diva-canvas-toolbar-end .diva-canvas-label{overflow-wrap:normal;text-align:right;white-space:nowrap;word-break:normal;flex:auto;width:auto;min-width:0;max-width:min(42vw,42rem);overflow:hidden}.diva-canvas-label.is-fullscreen{color:var(--diva-white)}.diva-status{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:0}.diva-status.is-error{color:var(--diva-danger)}@media (width<=720px){.diva-canvas-toolbar{flex-wrap:wrap;gap:5px}.diva-canvas-toolbar-end{justify-content:flex-end;width:100%;margin-left:0}.diva-canvas-toolbar-item{width:32px;height:32px}.diva-canvas-toolbar-button{width:28px;height:28px;padding:6px}.diva-canvas-toolbar-button svg{width:15px;height:15px}.diva-canvas-label,.diva-status{display:none}}.diva-modal-overlay{background-color:var(--diva-overlay-bg);z-index:100;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.diva-viewer-status-overlay{background-color:var(--diva-overlay-bg);z-index:40;justify-content:center;align-items:center;padding:24px;display:flex;position:absolute;inset:0}.diva-modal-overlay.is-fullscreen{padding:0}.diva-modal{background-color:var(--diva-page-bg);color:var(--diva-text-primary);width:min(1440px,96vw);max-height:90vh;box-shadow:0 20px 40px var(--diva-shadow-modal);border-radius:0;flex-direction:column;display:flex}.diva-modal.is-narrow{width:min(960px,94vw)}.diva-modal.is-page-view{height:80vh;max-height:80vh}.diva-modal.is-fullscreen{border-radius:0;width:100vw;height:100vh;max-height:100vh}.diva-modal-header{justify-content:space-between;align-items:center;padding:16px 20px 0;display:flex}.diva-modal-actions{gap:8px;display:flex}.diva-modal-close-action .diva-canvas-toolbar-button{color:var(--diva-danger);box-shadow:none;-webkit-backdrop-filter:none;background-color:#0000;border:none;width:auto;height:auto;padding:2px}.diva-modal-close-action .diva-canvas-toolbar-button:hover{background-color:#d32f2f1f;border-color:#0000}.diva-modal-close-action .diva-canvas-toolbar-item{width:32px}.diva-modal-title-stack{flex-direction:column;gap:4px;display:flex}.diva-modal-title{font-size:var(--diva-font-lg);font-weight:600}.diva-modal-subtitle{font-size:var(--diva-font-lg);color:var(--diva-text-primary)}.diva-modal-subtitle.is-muted{font-size:var(--diva-font-md)}.diva-modal-body{flex:1;grid-template-columns:minmax(0,1fr) 240px;gap:16px;min-height:0;padding:16px 20px 20px;display:grid}.diva-modal-body.is-no-gap{gap:0}.diva-modal-body.is-two-column{grid-template-columns:minmax(0,1fr) 200px;align-items:start}.diva-modal-body.is-no-sidebar{grid-template-columns:minmax(0,1fr)}.diva-modal-body.is-with-choices{grid-template-columns:120px minmax(0,1fr) 240px}.diva-modal-body.is-with-choices-no-sidebar{grid-template-columns:120px minmax(0,1fr)}.diva-modal-viewer{background-color:var(--diva-dark-bg);border:1px solid var(--diva-dark-border);height:100%;overflow:hidden}.diva-modal-canvas{width:100%;height:100%;display:block}.diva-modal-sidebar{background-color:var(--diva-white);border-top:1px solid var(--diva-border);border-right:1px solid var(--diva-border);border-bottom:1px solid var(--diva-border);border-radius:0;padding:16px;overflow:auto}.diva-filter-access-warning{color:var(--diva-danger);background-color:var(--diva-surface);border:1px solid var(--diva-border);font-size:var(--diva-font-sm);margin:0 0 12px;padding:8px;line-height:1.4}.diva-manifest-info-logo-wrap{text-align:center;flex-direction:column;align-items:center;gap:8px;display:flex}.diva-manifest-info-logo{width:100%;max-width:180px;height:auto}.diva-page-view-choices{background-color:var(--diva-dark-bg);border-radius:0;flex-direction:column;gap:8px;padding:8px;display:flex;overflow:auto}.diva-page-view-choice{border:2px solid #0000;flex-direction:column;gap:4px;display:flex}.diva-page-view-choice:hover{background-color:var(--diva-dark-bg)}.diva-page-view-choice.is-active{border-color:var(--diva-accent-light);background-color:var(--diva-dark-bg)}.diva-page-view-choice-thumb--protected{background:var(--diva-dark-bg);min-height:72px}.diva-page-view-choice-label{font-size:var(--diva-font-xs);color:var(--diva-text-muted);text-overflow:ellipsis;white-space:nowrap;line-height:1.2;overflow:hidden}.diva-filter-group{border-bottom:1px solid var(--diva-border);margin-bottom:12px;padding-bottom:12px}.diva-filter-title-button{text-align:left;cursor:pointer;width:100%;font-size:var(--diva-font-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--diva-text-muted);background-color:#0000;border:none;align-items:center;gap:8px;margin-bottom:8px;padding:0;font-weight:600;display:flex}.diva-filter-title-button.is-collapsed{margin-bottom:0}.diva-filter-title-icon{border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:6px solid var(--diva-text-muted);width:0;height:0;transition:transform .15s;display:inline-block}.diva-filter-title-icon.is-expanded{transform:rotate(90deg)}.diva-filter-row{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px;display:flex}.diva-filter-toggle{font-size:var(--diva-font-md);align-items:center;gap:8px;margin-bottom:8px;display:flex}.diva-filter-toggle.is-inline{margin-bottom:0}.diva-filter-range-group{flex-direction:column;gap:6px;margin-bottom:10px;display:flex}.diva-filter-range-header{justify-content:space-between;align-items:center;gap:8px;display:flex}.diva-filter-range-header-right{align-items:center;gap:8px;display:flex}.diva-filter-range-input{width:100%}.diva-filter-value{font-size:var(--diva-font-sm);color:var(--diva-text-muted);text-align:right;width:40px}.diva-filter-reset{font-size:var(--diva-font-xs);background-color:var(--diva-surface);border:1px solid var(--diva-border);cursor:pointer;color:var(--diva-text-muted);border-radius:0;padding:2px 6px}.diva-filter-reset:hover{background-color:var(--diva-border)}.diva-filter-json{width:100%;min-height:120px;font-size:var(--diva-font-sm);border:1px solid var(--diva-border);background-color:var(--diva-white);resize:vertical;border-radius:0;padding:6px 8px;font-family:Menlo,Monaco,Consolas,Liberation Mono,monospace}.diva-filter-json-error{font-size:var(--diva-font-sm);color:var(--diva-danger);margin-top:4px}.diva-filter-label{font-size:var(--diva-font-sm);color:var(--diva-text-muted)}.diva-filter-select{border:1px solid var(--diva-border);background-color:var(--diva-white);font-size:var(--diva-font-sm);border-radius:0;padding:4px 6px}.diva-filter-color-input{border:1px solid var(--diva-border);background-color:var(--diva-white);border-radius:0;width:42px;height:28px;padding:0}.diva-auth-overlay{z-index:10000;background:#0000008c;place-items:center;display:grid;position:fixed;inset:0}.diva-auth-dialog{box-sizing:border-box;border:1px solid var(--diva-border,#aaa);background:var(--diva-background-color,#fff);width:min(30rem,100vw - 2rem);color:var(--diva-text-primary,#222);border-radius:0;padding:1.25rem}.diva-auth-dialog button{min-height:2.25rem;color:inherit;cursor:pointer;background:0 0;border:1px solid;border-radius:0;padding:.35rem .8rem}.diva-auth-dialog button:focus-visible{outline:2px solid var(--diva-accent,#1769aa);outline-offset:2px}.diva-collection-panel{border:1px solid var(--diva-dark-border);background-color:var(--diva-page-bg);border-radius:0;flex-direction:column;height:100%;min-height:0;display:flex;overflow:hidden}.diva-collection-header{background-color:var(--diva-surface);border-bottom:1px solid var(--diva-border);border-radius:0;padding:12px}.diva-collection-title{font-size:var(--diva-font-lg);color:var(--diva-text-muted);margin-bottom:4px;font-weight:600}.diva-collection-summary{font-size:var(--diva-font-md);color:var(--diva-text-muted);line-height:1.4}.diva-collection-tree-item{padding-left:12px}.diva-collection-node-button{align-items:center;gap:6px;width:100%;padding:6px 8px;display:flex}.diva-collection-expand-icon{flex-shrink:0;justify-content:center;align-items:center;width:16px;height:16px;display:flex}.diva-manifest-tree-item{padding:6px 8px 6px 30px}.diva-manifest-tree-item.is-active{background-color:var(--diva-border);font-weight:600}.diva-sidebar-pane.is-scroll{overflow-y:auto}@media (width<=720px){.diva-collection-panel{width:100%;height:auto}}"; // cache/elm-esm.js function F(arity, fun, wrapper) { @@ -2004,6 +2674,9 @@ var _String_slice = F3(function(start, end, str) { return str.slice(start, end); }); + function _String_trim(str) { + return str.trim(); + } function _String_toLower(str) { return str.toLowerCase(); } @@ -4553,6 +5226,9 @@ })))); }); } + function _Url_percentEncode(string) { + return encodeURIComponent(string); + } var _Parser_isSubString = F5(function(smallString, offset, row, col, bigString) { var smallLength = smallString.length; var isGood = offset + smallLength <= bigString.length; @@ -4609,6 +5285,10 @@ } return _Utils_Tuple3(index < 0 ? -1 : target, row, col); }); + var $elm$core$Maybe$Just = function(a) { + return { $: "Just", a }; + }; + var $elm$core$Maybe$Nothing = { $: "Nothing" }; var $elm$core$List$cons = _List_cons; var $elm$core$Elm$JsArray$foldr = _JsArray_foldr; var $elm$core$Array$foldr = F3( @@ -4721,10 +5401,6 @@ var $elm$json$Json$Decode$OneOf = function(a) { return { $: "OneOf", a }; }; - var $elm$core$Maybe$Just = function(a) { - return { $: "Just", a }; - }; - var $elm$core$Maybe$Nothing = { $: "Nothing" }; var $elm$core$String$all = _String_all; var $elm$json$Json$Encode$encode = _Json_encode; var $elm$core$String$fromInt = _String_fromNumber; @@ -5610,9 +6286,6 @@ ); } ); - var $elm$core$Basics$negate = function(n) { - return -n; - }; var $elm$virtual_dom$VirtualDom$nodeNS = F2( function(namespace, tag) { return A2( @@ -10896,11 +11569,9 @@ return { $: "ServerRespondedWithResource", a }; }; var $author$project$Model$SidebarHidden = { $: "SidebarHidden" }; - var $author$project$Msg$ViewportChanged = F2( - function(a, b) { - return { $: "ViewportChanged", a, b }; - } - ); + var $author$project$Msg$ViewportChanged = function(a) { + return { $: "ViewportChanged", a }; + }; var $elm$core$Basics$clamp = F3( function(low, high, number) { return _Utils_cmp(number, low) < 0 ? low : _Utils_cmp(number, high) > 0 ? high : number; @@ -11291,9 +11962,9 @@ var $rism_digital$elm_iiif$IIIF$Presentation$ResourceRange = function(a) { return { $: "ResourceRange", a }; }; - var $rism_digital$elm_iiif$IIIF$Presentation$Canvas = F7( - function(id, label, width, height, images, thumbnail, viewingLayout) { - return { height, id, images, label, thumbnail, viewingLayout, width }; + var $rism_digital$elm_iiif$IIIF$Presentation$Canvas = F8( + function(id, label, width, height, images, thumbnail, viewingLayout, annotationSources) { + return { annotationSources, height, id, images, label, thumbnail, viewingLayout, width }; } ); var $elm$json$Json$Decode$maybe = function(decoder) { @@ -12378,6 +13049,26 @@ ] ) ); + var $rism_digital$elm_iiif$IIIF$Presentation$AnnotationSourceUrl = function(a) { + return { $: "AnnotationSourceUrl", a }; + }; + var $rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2AnnotationSourceDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2( + $elm$json$Json$Decode$map, + $rism_digital$elm_iiif$IIIF$Presentation$AnnotationSourceUrl, + A2($elm$json$Json$Decode$field, "@id", $elm$json$Json$Decode$string) + ), + A2( + $elm$json$Json$Decode$map, + $rism_digital$elm_iiif$IIIF$Presentation$AnnotationSourceUrl, + A2($elm$json$Json$Decode$field, "id", $elm$json$Json$Decode$string) + ), + A2($elm$json$Json$Decode$map, $rism_digital$elm_iiif$IIIF$Presentation$AnnotationSourceUrl, $elm$json$Json$Decode$string) + ] + ) + ); var $rism_digital$elm_iiif$IIIF$Image$staticImageUriFromUrl = function(url) { var _v0 = $rism_digital$elm_iiif$IIIF$Image$decomposeUrl(url); var address = _v0.address; @@ -12473,39 +13164,45 @@ ); var $rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2CanvasDecoder = A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "viewingHint", - $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$viewingHintDecoder), - $elm$core$Maybe$Nothing, + "otherContent", + $elm$json$Json$Decode$list($rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2AnnotationSourceDecoder), + _List_Nil, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "thumbnail", - $rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$thumbnailDecoder($rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2ThumbnailImageDecoder), + "viewingHint", + $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$viewingHintDecoder), $elm$core$Maybe$Nothing, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "images", - $rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2AnnotationListDecoder, - _List_Nil, + "thumbnail", + $rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$thumbnailDecoder($rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2ThumbnailImageDecoder), + $elm$core$Maybe$Nothing, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "height", - $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), - $elm$core$Maybe$Nothing, + "images", + $rism_digital$elm_iiif$IIIF$Internal$V2PresentationDecoders$v2AnnotationListDecoder, + _List_Nil, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "width", + "height", $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), $elm$core$Maybe$Nothing, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "label", - $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Language$v2LanguageMapLabelDecoder), + "width", + $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), $elm$core$Maybe$Nothing, - A3( - $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, - "@id", - $elm$json$Json$Decode$string, - $elm$json$Json$Decode$succeed($rism_digital$elm_iiif$IIIF$Presentation$Canvas) + A4( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, + "label", + $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Language$v2LanguageMapLabelDecoder), + $elm$core$Maybe$Nothing, + A3( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, + "@id", + $elm$json$Json$Decode$string, + $elm$json$Json$Decode$succeed($rism_digital$elm_iiif$IIIF$Presentation$Canvas) + ) ) ) ) @@ -13406,6 +14103,25 @@ ), A2($elm$json$Json$Decode$index, 0, $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3AnnotationBodyDecoder) ); + var $rism_digital$elm_iiif$IIIF$Presentation$InlineAnnotationPage = function(a) { + return { $: "InlineAnnotationPage", a }; + }; + var $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3AnnotationSourceDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2( + $elm$json$Json$Decode$map, + $rism_digital$elm_iiif$IIIF$Presentation$InlineAnnotationPage, + A2($elm$json$Json$Decode$field, "items", $elm$json$Json$Decode$value) + ), + A2( + $elm$json$Json$Decode$map, + $rism_digital$elm_iiif$IIIF$Presentation$AnnotationSourceUrl, + A2($elm$json$Json$Decode$field, "id", $elm$json$Json$Decode$string) + ) + ] + ) + ); var $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3ThumbnailImageIdDecoder = $elm$json$Json$Decode$oneOf( _List_fromArray( [ @@ -13447,36 +14163,42 @@ ); var $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3CanvasDecoder = A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "behavior", - $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$behaviourDecoder), - $elm$core$Maybe$Nothing, + "annotations", + $elm$json$Json$Decode$list($rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3AnnotationSourceDecoder), + _List_Nil, A4( $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "thumbnail", - $rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$thumbnailDecoder($rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3ThumbnailImageDecoder), + "behavior", + $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$behaviourDecoder), $elm$core$Maybe$Nothing, - A3( - $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, - "items", - A2($elm$json$Json$Decode$index, 0, $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3AnnotationPageDecoder), + A4( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, + "thumbnail", + $rism_digital$elm_iiif$IIIF$Internal$SharedDecoders$thumbnailDecoder($rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3ThumbnailImageDecoder), + $elm$core$Maybe$Nothing, A3( $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, - "height", - $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), + "items", + A2($elm$json$Json$Decode$index, 0, $rism_digital$elm_iiif$IIIF$Internal$V3PresentationDecoders$v3AnnotationPageDecoder), A3( $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, - "width", + "height", $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), - A4( - $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, - "label", - $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Language$languageMapLabelDecoder), - $elm$core$Maybe$Nothing, - A3( - $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, - "id", - $elm$json$Json$Decode$string, - $elm$json$Json$Decode$succeed($rism_digital$elm_iiif$IIIF$Presentation$Canvas) + A3( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, + "width", + $elm$json$Json$Decode$maybe($elm$json$Json$Decode$int), + A4( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$optional, + "label", + $elm$json$Json$Decode$maybe($rism_digital$elm_iiif$IIIF$Language$languageMapLabelDecoder), + $elm$core$Maybe$Nothing, + A3( + $rism_digital$elm_iiif$IIIF$Internal$Utilities$required, + "id", + $elm$json$Json$Decode$string, + $elm$json$Json$Decode$succeed($rism_digital$elm_iiif$IIIF$Presentation$Canvas) + ) ) ) ) @@ -14120,6 +14842,11 @@ return _Utils_Tuple2( { acceptHeaders: flags.acceptHeaders, + annotationLoading: $elm$core$Set$empty, + annotationServer: flags.annotationServer, + annotationSources: $elm$core$Dict$empty, + annotationsByCanvas: $elm$core$Dict$empty, + annotationsVisible: flags.enableAnnotations, auth: $author$project$Auth$init, collectionSidebarDrag: $elm$core$Maybe$Nothing, collectionSidebarVisible: true, @@ -14127,6 +14854,8 @@ contentsView: $author$project$Model$ContentsIndex, currentZoom: $elm$core$Maybe$Nothing, detectedLanguage: userLanguage, + enableAnnotationSelection: flags.enableAnnotationSelection, + enableAnnotations: flags.enableAnnotations, filterGroupExpanded: $elm$core$Set$empty, filters: $author$project$Filters$resetFilters, filtersJsonError: $elm$core$Maybe$Nothing, @@ -14170,10 +14899,8 @@ A2( $elm$core$Task$perform, function(viewport) { - return A2( - $author$project$Msg$ViewportChanged, - $elm$core$Basics$round(viewport.viewport.width), - $elm$core$Basics$round(viewport.viewport.height) + return $author$project$Msg$ViewportChanged( + $elm$core$Basics$round(viewport.viewport.width) ); }, $elm$browser$Browser$Dom$getViewport @@ -14183,6 +14910,7 @@ ) ); }; + var $elm$json$Json$Decode$null = _Json_decodeNull; var $author$project$Msg$AuthEvent = function(a) { return { $: "AuthEvent", a }; }; @@ -14248,10 +14976,12 @@ }; var $author$project$Msg$UserEndedCollectionSidebarResize = { $: "UserEndedCollectionSidebarResize" }; var $author$project$Msg$UserEndedSidebarResize = { $: "UserEndedSidebarResize" }; + var $author$project$Msg$ViewerLoadedPage = function(a) { + return { $: "ViewerLoadedPage", a }; + }; var $author$project$Msg$ViewerLoadingChanged = function(a) { return { $: "ViewerLoadingChanged", a }; }; - var $elm$json$Json$Decode$null = _Json_decodeNull; var $author$project$Main$authDestroyed = _Platform_incomingPort( "authDestroyed", $elm$json$Json$Decode$null(_Utils_Tuple0) @@ -14726,6 +15456,7 @@ ) ); var $author$project$Main$viewerLoadingChanged = _Platform_incomingPort("viewerLoadingChanged", $elm$json$Json$Decode$bool); + var $author$project$Main$viewerPageLoaded = _Platform_incomingPort("viewerPageLoaded", $elm$json$Json$Decode$int); var $author$project$Main$zoomChanged = _Platform_incomingPort("zoomChanged", $elm$json$Json$Decode$float); var $author$project$Main$subscriptions = function(model) { return $elm$core$Platform$Sub$batch( @@ -14794,10 +15525,17 @@ $author$project$Main$fullscreenChanged($author$project$Msg$ClientNotifiedFullscreenChanged), $author$project$Main$zoomChanged($author$project$Msg$UserChangedZoomLevel), $author$project$Main$viewerLoadingChanged($author$project$Msg$ViewerLoadingChanged), - $elm$browser$Browser$Events$onResize($author$project$Msg$ViewportChanged), + $author$project$Main$viewerPageLoaded($author$project$Msg$ViewerLoadedPage), + $elm$browser$Browser$Events$onResize( + F2( + function(width, _v1) { + return $author$project$Msg$ViewportChanged(width); + } + ) + ), (function() { - var _v1 = model.sidebarDrag; - if (_v1.$ === "Just") { + var _v2 = model.sidebarDrag; + if (_v2.$ === "Just") { return $elm$core$Platform$Sub$batch( _List_fromArray( [ @@ -14819,8 +15557,8 @@ } })(), (function() { - var _v2 = model.collectionSidebarDrag; - if (_v2.$ === "Just") { + var _v3 = model.collectionSidebarDrag; + if (_v3.$ === "Just") { return $elm$core$Platform$Sub$batch( _List_fromArray( [ @@ -14875,6 +15613,94 @@ } ); var $author$project$Model$TwoUp = { $: "TwoUp" }; + var $author$project$Utilities$find = F2( + function(predicate, list) { + find: + while (true) { + if (!list.b) { + return $elm$core$Maybe$Nothing; + } else { + var first = list.a; + var rest = list.b; + if (predicate(first)) { + return $elm$core$Maybe$Just(first); + } else { + var $temp$predicate = predicate, $temp$list = rest; + predicate = $temp$predicate; + list = $temp$list; + continue find; + } + } + } + } + ); + var $author$project$Model$primaryImage = function(page) { + var _v0 = A2( + $author$project$Utilities$find, + function($) { + return $.isPrimary; + }, + page.images + ); + if (_v0.$ === "Just") { + var image = _v0.a; + return $elm$core$Maybe$Just(image); + } else { + return $elm$core$List$head(page.images); + } + }; + var $author$project$Auth$requiresAuthorization = function(sourceAuth) { + if (sourceAuth.$ === "Discovered") { + var discovery = sourceAuth.a; + return !$elm$core$List$isEmpty(discovery.probes); + } else { + return false; + } + }; + var $author$project$Main$annotationImageServiceForCanvas = F2( + function(canvasId, model) { + return A2( + $elm$core$Maybe$andThen, + function(image) { + return image.isStatic || $author$project$Auth$requiresAuthorization(image.auth) ? $elm$core$Maybe$Nothing : $elm$core$Maybe$Just(image.tileSource); + }, + A2( + $elm$core$Maybe$andThen, + $author$project$Model$primaryImage, + $elm$core$List$head( + A2( + $elm$core$List$filter, + function(page) { + return _Utils_eq(page.canvasId, canvasId); + }, + model.pages + ) + ) + ) + ); + } + ); + var $author$project$Main$annotationsUpdated = _Platform_outgoingPort( + "annotationsUpdated", + function($) { + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + "annotations", + $elm$json$Json$Encode$list($elm$core$Basics$identity)($.annotations) + ), + _Utils_Tuple2( + "canvasId", + $elm$json$Json$Encode$string($.canvasId) + ) + ] + ) + ); + } + ); + var $elm$json$Json$Encode$bool = _Json_wrap; + var $author$project$Main$annotationsVisibilityUpdated = _Platform_outgoingPort("annotationsVisibilityUpdated", $elm$json$Json$Encode$bool); var $author$project$Filters$applyFilterToggle = F3( function(toggle, enabled, filters) { switch (toggle.$) { @@ -15897,7 +16723,6 @@ return A2(config.set, nextValue, filters); } ); - var $elm$json$Json$Encode$bool = _Json_wrap; var $elm$core$Maybe$destruct = F3( function(_default, func, maybe) { if (maybe.$ === "Just") { @@ -16390,6 +17215,10 @@ return $elm$json$Json$Encode$object( _List_fromArray( [ + _Utils_Tuple2( + "canvasId", + $elm$json$Json$Encode$string($2.canvasId) + ), _Utils_Tuple2( "isStatic", $elm$json$Json$Encode$bool($2.isStatic) @@ -16423,7 +17252,10 @@ $author$project$Main$pagesUpdated(_List_Nil), $author$project$Main$pageAspectsUpdated(_List_Nil), $author$project$Main$pageLabelsUpdated(_List_Nil), - $author$project$Main$filterPreviewUpdated($elm$core$Maybe$Nothing) + $author$project$Main$filterPreviewUpdated($elm$core$Maybe$Nothing), + $author$project$Main$annotationsUpdated( + { annotations: _List_Nil, canvasId: "" } + ) ] ) ); @@ -17151,6 +17983,148 @@ ); } }; + var $elm$core$String$cons = _String_cons; + var $elm$core$String$fromChar = function(_char) { + return A2($elm$core$String$cons, _char, ""); + }; + var $author$project$Annotation$stripTagsHelp = F3( + function(inTag, remaining, result) { + stripTagsHelp: + while (true) { + var _v0 = $elm$core$String$uncons(remaining); + if (_v0.$ === "Just") { + var _v1 = _v0.a; + var character = _v1.a; + var rest = _v1.b; + if (_Utils_eq( + character, + _Utils_chr("<") + )) { + var $temp$inTag = true, $temp$remaining = rest, $temp$result = result; + inTag = $temp$inTag; + remaining = $temp$remaining; + result = $temp$result; + continue stripTagsHelp; + } else { + if (_Utils_eq( + character, + _Utils_chr(">") + )) { + var $temp$inTag = false, $temp$remaining = rest, $temp$result = result; + inTag = $temp$inTag; + remaining = $temp$remaining; + result = $temp$result; + continue stripTagsHelp; + } else { + if (inTag) { + var $temp$inTag = true, $temp$remaining = rest, $temp$result = result; + inTag = $temp$inTag; + remaining = $temp$remaining; + result = $temp$result; + continue stripTagsHelp; + } else { + var $temp$inTag = false, $temp$remaining = rest, $temp$result = _Utils_ap( + result, + $elm$core$String$fromChar(character) + ); + inTag = $temp$inTag; + remaining = $temp$remaining; + result = $temp$result; + continue stripTagsHelp; + } + } + } + } else { + return result; + } + } + } + ); + var $author$project$Annotation$stripTags = function(value) { + return A3($author$project$Annotation$stripTagsHelp, false, value, ""); + }; + var $author$project$Annotation$encode = F2( + function(imageService, annotation) { + var shapeValue = (function() { + var _v0 = annotation.target.selector; + if (_v0.$ === "Rectangle") { + var x = _v0.a; + var y = _v0.b; + var width = _v0.c; + var height = _v0.d; + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + "kind", + $elm$json$Json$Encode$string("rect") + ), + _Utils_Tuple2( + "x", + $elm$json$Json$Encode$float(x) + ), + _Utils_Tuple2( + "y", + $elm$json$Json$Encode$float(y) + ), + _Utils_Tuple2( + "width", + $elm$json$Json$Encode$float(width) + ), + _Utils_Tuple2( + "height", + $elm$json$Json$Encode$float(height) + ) + ] + ) + ); + } else { + var value = _v0.a; + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + "kind", + $elm$json$Json$Encode$string("svg") + ), + _Utils_Tuple2( + "value", + $elm$json$Json$Encode$string(value) + ) + ] + ) + ); + } + })(); + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + "id", + $elm$json$Json$Encode$string(annotation.id) + ), + _Utils_Tuple2("shape", shapeValue), + _Utils_Tuple2( + "text", + $elm$json$Json$Encode$string( + $author$project$Annotation$stripTags(annotation.body.value) + ) + ), + _Utils_Tuple2( + "html", + $elm$json$Json$Encode$string(annotation.body.value) + ), + _Utils_Tuple2( + "imageService", + $elm$json$Json$Encode$string( + A2($elm$core$Maybe$withDefault, "", imageService) + ) + ) + ] + ) + ); + } + ); var $author$project$Filters$addIf = F4( function(condition, key, value, acc) { return condition ? A2( @@ -17517,9 +18491,43 @@ ); } ); + var $author$project$Model$getPageAt = F2( + function(index, pageList) { + return $elm$core$List$head( + A2($elm$core$List$drop, index, pageList) + ); + } + ); var $author$project$Model$Loaded = function(a) { return { $: "Loaded", a }; }; + var $rism_digital$elm_iiif$IIIF$Presentation$toManifest = function(_v0) { + var iiifManifest = _v0.b; + return iiifManifest; + }; + var $rism_digital$elm_iiif$IIIF$Presentation$withManifest = F2( + function(accessor, manifest) { + return accessor( + $rism_digital$elm_iiif$IIIF$Presentation$toManifest(manifest) + ); + } + ); + var $rism_digital$elm_iiif$IIIF$Presentation$toCanvases = $rism_digital$elm_iiif$IIIF$Presentation$withManifest( + function($) { + return $.canvases; + } + ); + var $author$project$Main$annotationSourcesForManifest = function(manifest) { + return $elm$core$Dict$fromList( + A2( + $elm$core$List$map, + function(canvas) { + return _Utils_Tuple2(canvas.id, canvas.annotationSources); + }, + $rism_digital$elm_iiif$IIIF$Presentation$toCanvases(manifest) + ) + ); + }; var $author$project$Main$rangeIndexMapForRange = F2( function(canvasIndex, range) { var _v4 = A2($author$project$Main$rangeItemsIndexMap, canvasIndex, range.items); @@ -18429,27 +19437,6 @@ }, $elm$json$Json$Decode$value ); - var $author$project$Utilities$find = F2( - function(predicate, list) { - find: - while (true) { - if (!list.b) { - return $elm$core$Maybe$Nothing; - } else { - var first = list.a; - var rest = list.b; - if (predicate(first)) { - return $elm$core$Maybe$Just(first); - } else { - var $temp$predicate = predicate, $temp$list = rest; - predicate = $temp$predicate; - list = $temp$list; - continue find; - } - } - } - } - ); var $author$project$Utilities$isJust = function(maybeVal) { if (maybeVal.$ === "Just") { return true; @@ -18560,22 +19547,6 @@ } } ); - var $rism_digital$elm_iiif$IIIF$Presentation$toManifest = function(_v0) { - var iiifManifest = _v0.b; - return iiifManifest; - }; - var $rism_digital$elm_iiif$IIIF$Presentation$withManifest = F2( - function(accessor, manifest) { - return accessor( - $rism_digital$elm_iiif$IIIF$Presentation$toManifest(manifest) - ); - } - ); - var $rism_digital$elm_iiif$IIIF$Presentation$toCanvases = $rism_digital$elm_iiif$IIIF$Presentation$withManifest( - function($) { - return $.canvases; - } - ); var $author$project$Model$manifestToPages = F2( function(language, iiifManifest) { return A2( @@ -18590,21 +19561,6 @@ return $.viewingLayout; } ); - var $author$project$Model$primaryImage = function(page) { - var _v0 = A2( - $author$project$Utilities$find, - function($) { - return $.isPrimary; - }, - page.images - ); - if (_v0.$ === "Just") { - var image = _v0.a; - return $elm$core$Maybe$Just(image); - } else { - return $elm$core$List$head(page.images); - } - }; var $author$project$Main$publicPages = function(pages) { return A2( $elm$core$List$filterMap, @@ -18844,15 +19800,15 @@ var pages = A2($author$project$Model$manifestToPages, model.detectedLanguage, manifest); var tileSources = A2( $elm$core$List$filterMap, - A2( - $elm$core$Basics$composeR, - $author$project$Model$primaryImage, - $elm$core$Maybe$map( + function(page) { + return A2( + $elm$core$Maybe$map, function(image) { - return { isStatic: image.isStatic, sourceId: image.sourceId, url: image.tileSource }; - } - ) - ), + return { canvasId: page.canvasId, isStatic: image.isStatic, sourceId: image.sourceId, url: image.tileSource }; + }, + $author$project$Model$primaryImage(page) + ); + }, pages ); var pagedLayout = $rism_digital$elm_iiif$IIIF$Presentation$isPagedLayout( @@ -18910,6 +19866,9 @@ _Utils_update( model, { + annotationLoading: $elm$core$Set$empty, + annotationSources: $author$project$Main$annotationSourcesForManifest(manifest), + annotationsByCanvas: $elm$core$Dict$empty, auth: A2($author$project$Auth$registerSources, authSources, $author$project$Auth$init), currentZoom: $elm$core$Maybe$Nothing, filters: $author$project$Filters$resetFilters, @@ -19055,13 +20014,6 @@ } } ); - var $author$project$Model$getPageAt = F2( - function(index, pageList) { - return $elm$core$List$head( - A2($elm$core$List$drop, index, pageList) - ); - } - ); var $author$project$Main$sendPageViewPreview = function(model) { return model.pageViewOpen ? A2( $elm$core$Maybe$withDefault, @@ -19294,6 +20246,514 @@ ); } ); + var $author$project$Msg$ServerRespondedWithAnnotations = F2( + function(a, b) { + return { $: "ServerRespondedWithAnnotations", a, b }; + } + ); + var $elm$url$Url$Builder$QueryParameter = F2( + function(a, b) { + return { $: "QueryParameter", a, b }; + } + ); + var $elm$url$Url$percentEncode = _Url_percentEncode; + var $elm$url$Url$Builder$string = F2( + function(key, value) { + return A2( + $elm$url$Url$Builder$QueryParameter, + $elm$url$Url$percentEncode(key), + $elm$url$Url$percentEncode(value) + ); + } + ); + var $elm$url$Url$Builder$toQueryPair = function(_v0) { + var key = _v0.a; + var value = _v0.b; + return key + ("=" + value); + }; + var $elm$url$Url$Builder$toQuery = function(parameters) { + if (!parameters.b) { + return ""; + } else { + return "?" + A2( + $elm$core$String$join, + "&", + A2($elm$core$List$map, $elm$url$Url$Builder$toQueryPair, parameters) + ); + } + }; + var $author$project$Main$annotationServerUrl = F3( + function(server, canvasId, version) { + var query = $elm$url$Url$Builder$toQuery( + _List_fromArray( + [ + A2($elm$url$Url$Builder$string, "canvasURI", canvasId), + A2($elm$url$Url$Builder$string, "iiifVersion", version) + ] + ) + ); + return A2($elm$core$String$contains, "?", server) ? server + ("&" + A2($elm$core$String$dropLeft, 1, query)) : _Utils_ap(server, query); + } + ); + var $author$project$Model$currentManifest = function(model) { + var _v0 = model.resourceResponse; + switch (_v0.$) { + case "ResourceLoadedManifest": + var manifest = _v0.a; + return $elm$core$Maybe$Just(manifest); + case "ResourceLoadedCollection": + var _v1 = model.response; + if (_v1.$ === "Loaded") { + var manifest = _v1.a; + return $elm$core$Maybe$Just(manifest); + } else { + return $elm$core$Maybe$Nothing; + } + default: + return $elm$core$Maybe$Nothing; + } + }; + var $rism_digital$elm_iiif$IIIF$Annotation$bodyFromString = function(value) { + return { format: $elm$core$Maybe$Nothing, language: $elm$core$Maybe$Nothing, value }; + }; + var $rism_digital$elm_iiif$IIIF$Annotation$emptyBody = $rism_digital$elm_iiif$IIIF$Annotation$bodyFromString(""); + function $rism_digital$elm_iiif$IIIF$Annotation$cyclic$bodyValueDecoder() { + return $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A4( + $elm$json$Json$Decode$map3, + F3( + function(value, format, language) { + return { format, language, value }; + } + ), + $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "value", $elm$json$Json$Decode$string), + A2($elm$json$Json$Decode$field, "chars", $elm$json$Json$Decode$string) + ] + ) + ), + $elm$json$Json$Decode$maybe( + A2($elm$json$Json$Decode$field, "format", $elm$json$Json$Decode$string) + ), + $elm$json$Json$Decode$maybe( + A2($elm$json$Json$Decode$field, "language", $elm$json$Json$Decode$string) + ) + ), + A2( + $elm$json$Json$Decode$map, + A2( + $elm$core$Basics$composeR, + $elm$core$List$head, + $elm$core$Maybe$withDefault($rism_digital$elm_iiif$IIIF$Annotation$emptyBody) + ), + $elm$json$Json$Decode$list( + $elm$json$Json$Decode$lazy( + function(_v0) { + return $rism_digital$elm_iiif$IIIF$Annotation$cyclic$bodyValueDecoder(); + } + ) + ) + ), + A2($elm$json$Json$Decode$map, $rism_digital$elm_iiif$IIIF$Annotation$bodyFromString, $elm$json$Json$Decode$string) + ] + ) + ); + } + try { + $rism_digital$elm_iiif$IIIF$Annotation$bodyValueDecoder = $rism_digital$elm_iiif$IIIF$Annotation$cyclic$bodyValueDecoder(); + $rism_digital$elm_iiif$IIIF$Annotation$cyclic$bodyValueDecoder = function() { + return $rism_digital$elm_iiif$IIIF$Annotation$bodyValueDecoder; + }; + } catch ($) { + throw "Some top-level definitions from `IIIF.Annotation` are causing infinite recursion:\n\n \u250C\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 bodyValueDecoder\n \u2514\u2500\u2500\u2500\u2500\u2500\u2518\n\nThese errors are very tricky, so read https://elm-lang.org/0.19.1/bad-recursion to learn how to fix it!"; + } + var $rism_digital$elm_iiif$IIIF$Annotation$bodyValueDecoder; + var $rism_digital$elm_iiif$IIIF$Annotation$annotationBodyDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "body", $rism_digital$elm_iiif$IIIF$Annotation$bodyValueDecoder), + A2($elm$json$Json$Decode$field, "resource", $rism_digital$elm_iiif$IIIF$Annotation$bodyValueDecoder), + $elm$json$Json$Decode$succeed($rism_digital$elm_iiif$IIIF$Annotation$emptyBody) + ] + ) + ); + var $rism_digital$elm_iiif$IIIF$Annotation$Rectangle = F4( + function(a, b, c, d) { + return { $: "Rectangle", a, b, c, d }; + } + ); + var $rism_digital$elm_iiif$IIIF$Annotation$rectangleFromString = function(value) { + var coordinates = A2($elm$core$String$startsWith, "xywh=", value) ? A2($elm$core$String$dropLeft, 5, value) : value; + var _v0 = A2( + $elm$core$List$map, + $elm$core$String$toFloat, + A2($elm$core$String$split, ",", coordinates) + ); + if (_v0.b && _v0.a.$ === "Just" && _v0.b.b && _v0.b.a.$ === "Just" && _v0.b.b.b && _v0.b.b.a.$ === "Just" && _v0.b.b.b.b && _v0.b.b.b.a.$ === "Just" && !_v0.b.b.b.b.b) { + var x = _v0.a.a; + var _v1 = _v0.b; + var y = _v1.a.a; + var _v2 = _v1.b; + var width = _v2.a.a; + var _v3 = _v2.b; + var height = _v3.a.a; + return width > 0 && height > 0 ? $elm$json$Json$Decode$succeed( + A4($rism_digital$elm_iiif$IIIF$Annotation$Rectangle, x, y, width, height) + ) : $elm$json$Json$Decode$fail("Annotation rectangle must have a positive size"); + } else { + return $elm$json$Json$Decode$fail("Invalid xywh selector"); + } + }; + var $rism_digital$elm_iiif$IIIF$Annotation$Svg = function(a) { + return { $: "Svg", a }; + }; + var $rism_digital$elm_iiif$IIIF$Annotation$selectorTypeDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "type", $elm$json$Json$Decode$string), + A2($elm$json$Json$Decode$field, "@type", $elm$json$Json$Decode$string) + ] + ) + ); + var $rism_digital$elm_iiif$IIIF$Annotation$svgSelectorDecoder = A2( + $elm$json$Json$Decode$andThen, + function(_v0) { + var selectorType = _v0.a; + var value = _v0.b; + return $elm$core$String$toLower(selectorType) === "svgselector" || $elm$core$String$toLower(selectorType) === "oa:svgselector" ? $elm$json$Json$Decode$succeed( + $rism_digital$elm_iiif$IIIF$Annotation$Svg(value) + ) : $elm$json$Json$Decode$fail("Not an SVG selector"); + }, + A3( + $elm$json$Json$Decode$map2, + $elm$core$Tuple$pair, + $rism_digital$elm_iiif$IIIF$Annotation$selectorTypeDecoder, + A2($elm$json$Json$Decode$field, "value", $elm$json$Json$Decode$string) + ) + ); + function $rism_digital$elm_iiif$IIIF$Annotation$cyclic$selectorDecoder() { + return $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + $rism_digital$elm_iiif$IIIF$Annotation$svgSelectorDecoder, + A2( + $elm$json$Json$Decode$andThen, + $rism_digital$elm_iiif$IIIF$Annotation$rectangleFromString, + A2($elm$json$Json$Decode$field, "value", $elm$json$Json$Decode$string) + ), + A2( + $elm$json$Json$Decode$field, + "item", + $elm$json$Json$Decode$lazy( + function(_v0) { + return $rism_digital$elm_iiif$IIIF$Annotation$cyclic$selectorDecoder(); + } + ) + ), + A2( + $elm$json$Json$Decode$field, + "default", + $elm$json$Json$Decode$lazy( + function(_v1) { + return $rism_digital$elm_iiif$IIIF$Annotation$cyclic$selectorDecoder(); + } + ) + ) + ] + ) + ); + } + try { + $rism_digital$elm_iiif$IIIF$Annotation$selectorDecoder = $rism_digital$elm_iiif$IIIF$Annotation$cyclic$selectorDecoder(); + $rism_digital$elm_iiif$IIIF$Annotation$cyclic$selectorDecoder = function() { + return $rism_digital$elm_iiif$IIIF$Annotation$selectorDecoder; + }; + } catch ($) { + throw "Some top-level definitions from `IIIF.Annotation` are causing infinite recursion:\n\n \u250C\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 selectorDecoder\n \u2514\u2500\u2500\u2500\u2500\u2500\u2518\n\nThese errors are very tricky, so read https://elm-lang.org/0.19.1/bad-recursion to learn how to fix it!"; + } + var $rism_digital$elm_iiif$IIIF$Annotation$selectorDecoder; + var $rism_digital$elm_iiif$IIIF$Annotation$sourceObjectDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "id", $elm$json$Json$Decode$string), + A2($elm$json$Json$Decode$field, "@id", $elm$json$Json$Decode$string) + ] + ) + ); + var $rism_digital$elm_iiif$IIIF$Annotation$sourceDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2( + $elm$json$Json$Decode$map, + $elm$core$Maybe$Just, + A2( + $elm$json$Json$Decode$field, + "source", + $elm$json$Json$Decode$oneOf( + _List_fromArray( + [$elm$json$Json$Decode$string, $rism_digital$elm_iiif$IIIF$Annotation$sourceObjectDecoder] + ) + ) + ) + ), + A2( + $elm$json$Json$Decode$map, + $elm$core$Maybe$Just, + A2( + $elm$json$Json$Decode$field, + "full", + $elm$json$Json$Decode$oneOf( + _List_fromArray( + [$elm$json$Json$Decode$string, $rism_digital$elm_iiif$IIIF$Annotation$sourceObjectDecoder] + ) + ) + ) + ), + $elm$json$Json$Decode$succeed($elm$core$Maybe$Nothing) + ] + ) + ); + var $rism_digital$elm_iiif$IIIF$Annotation$targetFromString = function(target) { + var _v0 = A2($elm$core$String$split, "#xywh=", target); + if (_v0.b && _v0.b.b && !_v0.b.b.b) { + var source = _v0.a; + var _v1 = _v0.b; + var coordinates = _v1.a; + return A2( + $elm$json$Json$Decode$map, + function(selector) { + return { + selector, + source: $elm$core$Maybe$Just(source) + }; + }, + $rism_digital$elm_iiif$IIIF$Annotation$rectangleFromString(coordinates) + ); + } else { + return $elm$json$Json$Decode$fail("Annotation target has no xywh selector"); + } + }; + function $rism_digital$elm_iiif$IIIF$Annotation$cyclic$annotationTargetDecoder() { + return $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2( + $elm$json$Json$Decode$andThen, + function(targets) { + var _v1 = $elm$core$List$head(targets); + if (_v1.$ === "Just") { + var target = _v1.a; + return $elm$json$Json$Decode$succeed(target); + } else { + return $elm$json$Json$Decode$fail("Annotation target list is empty"); + } + }, + $elm$json$Json$Decode$list( + $elm$json$Json$Decode$lazy( + function(_v0) { + return $rism_digital$elm_iiif$IIIF$Annotation$cyclic$annotationTargetDecoder(); + } + ) + ) + ), + A2($elm$json$Json$Decode$andThen, $rism_digital$elm_iiif$IIIF$Annotation$targetFromString, $elm$json$Json$Decode$string), + A3( + $elm$json$Json$Decode$map2, + F2( + function(source, selector) { + return { selector, source }; + } + ), + $rism_digital$elm_iiif$IIIF$Annotation$sourceDecoder, + A2($elm$json$Json$Decode$field, "selector", $rism_digital$elm_iiif$IIIF$Annotation$selectorDecoder) + ) + ] + ) + ); + } + try { + $rism_digital$elm_iiif$IIIF$Annotation$annotationTargetDecoder = $rism_digital$elm_iiif$IIIF$Annotation$cyclic$annotationTargetDecoder(); + $rism_digital$elm_iiif$IIIF$Annotation$cyclic$annotationTargetDecoder = function() { + return $rism_digital$elm_iiif$IIIF$Annotation$annotationTargetDecoder; + }; + } catch ($) { + throw "Some top-level definitions from `IIIF.Annotation` are causing infinite recursion:\n\n \u250C\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 annotationTargetDecoder\n \u2514\u2500\u2500\u2500\u2500\u2500\u2518\n\nThese errors are very tricky, so read https://elm-lang.org/0.19.1/bad-recursion to learn how to fix it!"; + } + var $rism_digital$elm_iiif$IIIF$Annotation$annotationTargetDecoder; + var $rism_digital$elm_iiif$IIIF$Annotation$idDecoder = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "id", $elm$json$Json$Decode$string), + A2($elm$json$Json$Decode$field, "@id", $elm$json$Json$Decode$string), + $elm$json$Json$Decode$succeed("") + ] + ) + ); + var $elm$json$Json$Decode$map4 = _Json_map4; + var $rism_digital$elm_iiif$IIIF$Annotation$annotationDecoder = A5( + $elm$json$Json$Decode$map4, + F4( + function(id, target, body, motivation) { + return { body, id, motivation, target }; + } + ), + $rism_digital$elm_iiif$IIIF$Annotation$idDecoder, + $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2($elm$json$Json$Decode$field, "target", $rism_digital$elm_iiif$IIIF$Annotation$annotationTargetDecoder), + A2($elm$json$Json$Decode$field, "on", $rism_digital$elm_iiif$IIIF$Annotation$annotationTargetDecoder) + ] + ) + ), + $rism_digital$elm_iiif$IIIF$Annotation$annotationBodyDecoder, + $elm$json$Json$Decode$maybe( + A2($elm$json$Json$Decode$field, "motivation", $elm$json$Json$Decode$string) + ) + ); + var $rism_digital$elm_iiif$IIIF$Annotation$decodePage = $elm$json$Json$Decode$oneOf( + _List_fromArray( + [ + A2( + $elm$json$Json$Decode$field, + "resources", + $elm$json$Json$Decode$list($rism_digital$elm_iiif$IIIF$Annotation$annotationDecoder) + ), + A2( + $elm$json$Json$Decode$field, + "items", + $elm$json$Json$Decode$list($rism_digital$elm_iiif$IIIF$Annotation$annotationDecoder) + ), + $elm$json$Json$Decode$list($rism_digital$elm_iiif$IIIF$Annotation$annotationDecoder) + ] + ) + ); + var $rism_digital$elm_iiif$IIIF$requestAnnotationPage = F3( + function(responseMsg, acceptHeaders, url) { + return A3( + $rism_digital$elm_iiif$IIIF$Internal$Request$request, + acceptHeaders, + A2($elm$http$Http$expectJson, responseMsg, $rism_digital$elm_iiif$IIIF$Annotation$decodePage), + url + ); + } + ); + var $author$project$Main$requestAnnotations = F2( + function(canvasId, model) { + if (!model.enableAnnotations || (A2($elm$core$Set$member, canvasId, model.annotationLoading) || A2($elm$core$Dict$member, canvasId, model.annotationsByCanvas))) { + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + } else { + var sources = A2( + $elm$core$Maybe$withDefault, + _List_Nil, + A2($elm$core$Dict$get, canvasId, model.annotationSources) + ); + var urls = A2( + $elm$core$List$filterMap, + function(source) { + if (source.$ === "AnnotationSourceUrl") { + var url = source.a; + return $elm$core$Maybe$Just(url); + } else { + return $elm$core$Maybe$Nothing; + } + }, + sources + ); + var request = function(url) { + return A3( + $rism_digital$elm_iiif$IIIF$requestAnnotationPage, + $author$project$Msg$ServerRespondedWithAnnotations(canvasId), + model.acceptHeaders, + url + ); + }; + var inlineAnnotations = $elm$core$List$concat( + A2( + $elm$core$List$filterMap, + function(source) { + if (source.$ === "AnnotationSourceUrl") { + return $elm$core$Maybe$Nothing; + } else { + var value = source.a; + return $elm$core$Result$toMaybe( + A2($elm$json$Json$Decode$decodeValue, $rism_digital$elm_iiif$IIIF$Annotation$decodePage, value) + ); + } + }, + sources + ) + ); + var nextModel = $elm$core$List$isEmpty(inlineAnnotations) ? model : _Utils_update( + model, + { + annotationsByCanvas: A3($elm$core$Dict$insert, canvasId, inlineAnnotations, model.annotationsByCanvas) + } + ); + var fallback = (function() { + var _v0 = _Utils_Tuple3( + $elm$core$List$isEmpty(sources), + model.annotationServer, + $author$project$Model$currentManifest(model) + ); + if (_v0.a && _v0.b.$ === "Just" && _v0.c.$ === "Just") { + var server = _v0.b.a; + var _v1 = _v0.c.a; + var version = _v1.a; + return _List_fromArray( + [ + A3( + $author$project$Main$annotationServerUrl, + server, + canvasId, + _Utils_eq(version, $rism_digital$elm_iiif$IIIF$Version$IIIFV2) ? "2" : "3" + ) + ] + ); + } else { + return _List_Nil; + } + })(); + return _Utils_Tuple2( + _Utils_update( + nextModel, + { + annotationLoading: A2($elm$core$Set$insert, canvasId, nextModel.annotationLoading) + } + ), + $elm$core$Platform$Cmd$batch( + _Utils_ap( + A2( + $elm$core$List$map, + request, + _Utils_ap(urls, fallback) + ), + _List_fromArray( + [ + $author$project$Main$annotationsUpdated( + { + annotations: A2( + $elm$core$List$map, + $author$project$Annotation$encode( + A2($author$project$Main$annotationImageServiceForCanvas, canvasId, model) + ), + inlineAnnotations + ), + canvasId + } + ) + ] + ) + ) + ) + ); + } + } + ); var $rism_digital$elm_iiif$IIIF$Internal$CoreDecoders$contextListDecoder = function(contextValues) { return A2( $elm$core$List$any, @@ -22853,7 +24313,7 @@ ), $elm$core$Platform$Cmd$none ); - default: + case "ViewportChanged": var width = msg.a; var nextIsMobile = _Utils_cmp(width, $author$project$Main$mobileWidthBreakpoint) < 1; var nextModel = nextIsMobile ? _Utils_update( @@ -22864,6 +24324,66 @@ { isMobile: false, mobileSidebarOpen: false } ); return _Utils_Tuple2(nextModel, $elm$core$Platform$Cmd$none); + case "ViewerLoadedPage": + var index = msg.a; + var _v34 = A2($author$project$Model$getPageAt, index, model.pages); + if (_v34.$ === "Just") { + var page = _v34.a; + return A2($author$project$Main$requestAnnotations, page.canvasId, model); + } else { + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + } + case "ServerRespondedWithAnnotations": + var canvasId = msg.a; + var result = msg.b; + var nextModel = _Utils_update( + model, + { + annotationLoading: A2($elm$core$Set$remove, canvasId, model.annotationLoading) + } + ); + if (result.$ === "Ok") { + var annotations = result.a; + var merged = (function(existing) { + return _Utils_ap(annotations, existing); + })( + A2( + $elm$core$Maybe$withDefault, + _List_Nil, + A2($elm$core$Dict$get, canvasId, nextModel.annotationsByCanvas) + ) + ); + return _Utils_Tuple2( + _Utils_update( + nextModel, + { + annotationsByCanvas: A3($elm$core$Dict$insert, canvasId, merged, nextModel.annotationsByCanvas) + } + ), + $author$project$Main$annotationsUpdated( + { + annotations: A2( + $elm$core$List$map, + $author$project$Annotation$encode( + A2($author$project$Main$annotationImageServiceForCanvas, canvasId, nextModel) + ), + merged + ), + canvasId + } + ) + ); + } else { + return _Utils_Tuple2(nextModel, $elm$core$Platform$Cmd$none); + } + default: + return _Utils_Tuple2( + _Utils_update( + model, + { annotationsVisible: !model.annotationsVisible } + ), + $author$project$Main$annotationsVisibilityUpdated(!model.annotationsVisible) + ); } } ); @@ -22893,24 +24413,6 @@ }; var $elm$virtual_dom$VirtualDom$lazy5 = _VirtualDom_lazy5; var $elm$html$Html$Lazy$lazy5 = $elm$virtual_dom$VirtualDom$lazy5; - var $author$project$Model$currentManifest = function(model) { - var _v0 = model.resourceResponse; - switch (_v0.$) { - case "ResourceLoadedManifest": - var manifest = _v0.a; - return $elm$core$Maybe$Just(manifest); - case "ResourceLoadedCollection": - var _v1 = model.response; - if (_v1.$ === "Loaded") { - var manifest = _v1.a; - return $elm$core$Maybe$Just(manifest); - } else { - return $elm$core$Maybe$Nothing; - } - default: - return $elm$core$Maybe$Nothing; - } - }; var $rism_digital$elm_iiif$IIIF$Presentation$toLabel = $rism_digital$elm_iiif$IIIF$Presentation$withManifest( function($) { return $.label; @@ -23577,88 +25079,165 @@ } }; var $elm$html$Html$Attributes$rel = _VirtualDom_attribute("rel"); - var $author$project$View$HtmlRenderer$normalizeHtml = function(rawHtml) { - return A3( - $elm$core$String$replace, - "