diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..5f2acb8d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,35 @@
+name: Test
+
+on:
+ push:
+ branches:
+ - develop
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: '24'
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Install Elm globally
+ run: npm install -g elm
+
+ - name: Run Elm tests
+ run: yarn test
+
+ - name: Build
+ run: make build build-dev
diff --git a/build/diva.debug.js b/build/diva.debug.js
index 572e5dcc..378c46a9 100644
--- a/build/diva.debug.js
+++ b/build/diva.debug.js
@@ -16421,7 +16421,6 @@
var $author$project$Model$iiifImageToPageImage = F3(
function(language, allImages, image) {
var tileSource = $rism_digital$elm_iiif$IIIF$Image$createImageAddress(image.id);
- var thumbUrl = $rism_digital$elm_iiif$IIIF$Image$thumbnailUrlFromInfo(tileSource);
var label = A2(
$elm$core$Maybe$withDefault,
"Image",
@@ -16431,6 +16430,8 @@
image.label
)
);
+ var isStatic = $elm$core$List$isEmpty(image.service);
+ var thumbUrl = isStatic ? tileSource : $rism_digital$elm_iiif$IIIF$Image$thumbnailUrlFromInfo(tileSource);
var isPrimaryImage = $author$project$Utilities$isNothing(
A2(
$author$project$Utilities$find,
@@ -16445,7 +16446,7 @@
$elm$core$List$head(allImages)
);
var isPrimary = _Utils_eq(image.imageType, $rism_digital$elm_iiif$IIIF$Presentation$PrimaryImage) || isPrimaryImage && isFirst;
- return { isPrimary, label, thumbUrl, tileSource };
+ return { isPrimary, isStatic, label, thumbUrl, tileSource };
}
);
var $author$project$Model$canvasToPage = F2(
@@ -16519,7 +16520,24 @@
};
var $author$project$Main$tileSourcesUpdated = _Platform_outgoingPort(
"tileSourcesUpdated",
- $elm$json$Json$Encode$list($elm$json$Json$Encode$string)
+ $elm$json$Json$Encode$list(
+ function($) {
+ return $elm$json$Json$Encode$object(
+ _List_fromArray(
+ [
+ _Utils_Tuple2(
+ "isStatic",
+ $elm$json$Json$Encode$bool($.isStatic)
+ ),
+ _Utils_Tuple2(
+ "url",
+ $elm$json$Json$Encode$string($.url)
+ )
+ ]
+ )
+ );
+ }
+ )
);
var $rism_digital$elm_iiif$IIIF$Presentation$toRanges = $rism_digital$elm_iiif$IIIF$Presentation$withManifest(
function($) {
@@ -16554,8 +16572,8 @@
$elm$core$Basics$composeR,
$author$project$Model$primaryImage,
$elm$core$Maybe$map(
- function($) {
- return $.tileSource;
+ function(image) {
+ return { isStatic: image.isStatic, url: image.tileSource };
}
)
),
@@ -17076,6 +17094,10 @@
);
})($.filters)
),
+ _Utils_Tuple2(
+ "isStatic",
+ $elm$json$Json$Encode$bool($.isStatic)
+ ),
_Utils_Tuple2(
"tileSource",
$elm$json$Json$Encode$string($.tileSource)
@@ -17103,7 +17125,7 @@
$elm$core$Maybe$map,
function(image) {
return $author$project$Main$filterPreviewUpdated(
- { aspect: page.aspect, filters: model.filters, tileSource: image.tileSource }
+ { aspect: page.aspect, filters: model.filters, isStatic: image.isStatic, tileSource: image.tileSource }
);
},
$elm$core$List$head(
@@ -28757,6 +28779,7 @@
target.appendChild(styleEl);
};
injectStyles(diva_default);
+ var toViewerTileSource = (url, isStatic) => isStatic ? { type: "image", url } : url;
var Diva = class {
constructor(rootId, flags) {
this.mainViewer = null;
@@ -28817,7 +28840,10 @@
}
bindPorts() {
this.getPort("tileSourcesUpdated").subscribe((tileSources) => {
- this.callViewerMethod("setTileSources", tileSources);
+ this.callViewerMethod(
+ "setTileSources",
+ tileSources.map((entry) => toViewerTileSource(entry.url, entry.isStatic))
+ );
});
this.getPort("pageAspectsUpdated").subscribe((aspects) => {
this.callViewerMethod("setPageAspects", aspects);
@@ -28894,7 +28920,7 @@
const tileSourceChanged = this.currentFilterTileSource !== payload.tileSource;
if (tileSourceChanged) {
this.currentFilterTileSource = payload.tileSource;
- this.filterViewer.open(payload.tileSource);
+ this.filterViewer.open(toViewerTileSource(payload.tileSource, payload.isStatic));
} else {
this.applyFilterOptions();
}
diff --git a/build/diva.esm.js b/build/diva.esm.js
index 2cdcd4a8..70c41671 100644
--- a/build/diva.esm.js
+++ b/build/diva.esm.js
@@ -1,17 +1,17 @@
-var Un=class extends HTMLElement{constructor(){super();this.container=null;this.viewer=null;this.loadToken=0;this.tileSources=[];this.pageLabels=[];this.pageAspects=[];this.pageOffsets=[];this.pageHeights=[];this.pageRowHeights=[];this.pageXOffsets=[];this.layoutMode="single";this.viewingDirection="ltr";this.hasFitFirstPage=!1;this.loadedIndexes=new Set;this.loadingIndexes=new Set;this.loadedItems=new Map;this.pageOverlayElements=new Map;this.targetIndex=null;this.scrollPlaneItem=null;this.isViewportInitialized=!1;this.lastReportedIndex=null;this.lastReportedZoom=null;this.loadingTimer=null;this.isLoading=!1;this.isClamping=!1;this.suppressPageChange=!0;this.suppressZoomChange=!0;this.scrollbarTrack=null;this.scrollbarThumb=null;this.isScrollbarDragging=!1;this.scrollbarMouseMove=null;this.scrollbarMouseUp=null;this.handleWheelBound=this.handleWheel.bind(this),this.handleDoubleClickBound=this.handleDoubleClick.bind(this),this.handleViewportChangeBound=this.handleViewportChange.bind(this),this.handleAnimationFinishBound=this.handleAnimationFinish.bind(this)}connectedCallback(){this.style.display="block",this.container||(this.container=document.createElement("div"),this.container.className="osd-container",this.container.style.width="100%",this.container.style.height="100%",this.container.addEventListener("wheel",this.handleWheelBound,{passive:!1,capture:!0}),this.container.addEventListener("dblclick",this.handleDoubleClickBound),this.appendChild(this.container),this.createScrollbar()),this.syncViewer()}disconnectedCallback(){this.loadingTimer!==null&&(window.clearTimeout(this.loadingTimer),this.loadingTimer=null),this.container&&(this.container.removeEventListener("wheel",this.handleWheelBound),this.container.removeEventListener("dblclick",this.handleDoubleClickBound)),this.scrollbarMouseMove&&(document.removeEventListener("mousemove",this.scrollbarMouseMove),this.scrollbarMouseMove=null),this.scrollbarMouseUp&&(document.removeEventListener("mouseup",this.scrollbarMouseUp),this.scrollbarMouseUp=null),this.viewer&&(this.clearPageOverlays(),this.viewer.destroy(),this.viewer=null),this.scrollbarTrack=null,this.scrollbarThumb=null}syncViewer(){if(!(!window.OpenSeadragon||!this.container)&&!this.viewer){let n={element:this.container,animationTime:.8,showNavigationControl:!1,preserveViewport:!0,visibilityRatio:0,constrainDuringPan:!1,minZoomLevel:.1,minZoomImageRatio:.1,maxZoomPixelRatio:2,defaultZoomLevel:0,sequenceMode:!1,zoomPerScroll:1,crossOriginPolicy:"Anonymous",loadTilesWithAjax:!0,ajaxWithCredentials:!1,gestureSettingsTrackpad:{pinchToZoom:!0,scrollToZoom:!1,flickEnabled:!0,dragToPan:!0},gestureSettingsMouse:{scrollToZoom:!1,clickToZoom:!1,dblClickToZoom:!1,dragToPan:!0},gestureSettingsTouch:{pinchToZoom:!1,dragToPan:!0}};this.viewer=OpenSeadragon(n);let o=this.viewer;o.addHandler("pan",this.handleViewportChangeBound),o.addHandler("zoom",this.handleViewportChangeBound),o.addHandler("pan",()=>this.updateScrollbar()),o.addHandler("zoom",()=>this.updateScrollbar()),o.addHandler("animation-finish",this.handleAnimationFinishBound)}}setLayoutMode(a){let n=a==="spread"||a==="spread-shift"?a:"single";this.applyLayoutChange({mode:n})}setViewingDirection(a){let n=a==="rtl"?"rtl":"ltr";this.applyLayoutChange({direction:n})}setLayoutConfig(a,n){let o=a==="spread"||a==="spread-shift"?a:"single",u=n==="rtl"?"rtl":"ltr";this.applyLayoutChange({mode:o,direction:u})}setTileSources(a){!Array.isArray(a)||a.length===0||(this.syncViewer(),this.resetTileSources(a))}setPageLabels(a){Array.isArray(a)&&(this.pageLabels=a,this.pageOverlayElements.forEach((n,o)=>{this.addOrUpdatePageOverlay(o)}))}resetTileSources(a){this.viewer&&(typeof this.viewer.close=="function"?this.viewer.close():this.viewer.world.removeAll(),this.loadToken+=1,this.tileSources=a,this.hasFitFirstPage=!1,this.isViewportInitialized=!1,this.loadedIndexes.clear(),this.loadingIndexes.clear(),this.loadedItems.clear(),this.clearPageOverlays(),this.targetIndex=null,this.clearScrollPlane(),this.buildOffsets(),this.ensureScrollPlane(),this.resetLoadingState(),this.suppressPageChange=!0,this.suppressZoomChange=!0,this.maybeLoadMore())}handleViewportChange(){var n;if(this.isClamping)return;let a=(n=this.viewer)==null?void 0:n.viewport;a&&(this.maybeLoadMore(a),this.maybeEmitPageChange(a),this.clampTop(a),this.clampBottom(a))}handleAnimationFinish(){this.updateScrollbar(),this.maybeEmitZoomChange()}maybeLoadMore(a){var h;if(this.tileSources.length===0)return;let n=a!=null?a:(h=this.viewer)==null?void 0:h.viewport;if(!n)return;let o=n.getBounds(!0),u=o.height*1.5,s=Math.max(0,o.y-u),c=o.y+o.height+u,$=this.indicesForRange(s,c);if(!$)return;let f=$[0],d=$[1],m=f;for(;m<=d;)this.ensurePageLoaded(m),m+=1;this.targetIndex!==null&&this.ensurePageLoaded(this.targetIndex)}ensurePageLoaded(a){a<0||a>=this.tileSources.length||a>=this.pageOffsets.length||this.loadedIndexes.has(a)||this.loadingIndexes.has(a)||this.loadTile(a)}loadTile(a){if(!this.viewer)return;let n=this.loadToken,o=this.tileSources[a];this.loadingIndexes.add(a),this.updateLoadingState();let u=this.pageOffsets[a]||0,s=this.pageXOffsets[a]||0,c=this.pageHeights[a]||1;this.viewer.addTiledImage({tileSource:o,x:s,y:u,width:1,success:$=>{if(n!==this.loadToken)return;let f=$.item;if(f.setPosition(new OpenSeadragon.Point(s,u),!0),f.setWidth(1,!0),f.setHeight(c,!0),this.loadedIndexes.add(a),this.loadedItems.set(a,f),this.addOrUpdatePageOverlay(a),this.loadingIndexes.delete(a),this.updateLoadingState(),!this.hasFitFirstPage){this.hasFitFirstPage=!0;let d=this.viewer;if(!d||!d.viewport)return;let m=this.isSingleCanvasLayout(),h=m?f.getBounds():this.isSpreadMode()?this.getRowBounds(a):f.getBounds();d.viewport.fitBounds(h,!0),m||d.viewport.zoomBy(.95,d.viewport.getCenter(!0),!0),this.alignTopAfterFit(),d.viewport.applyConstraints(),this.isViewportInitialized=!0,this.lockHorizontalPan(),this.recenterAfterFit(),this.flushInitialPageChange(),this.flushInitialZoomChange()}this.targetIndex===a&&(this.targetIndex=null),this.maybeLoadMore()},error:()=>{n===this.loadToken&&(this.loadingIndexes.delete(a),this.updateLoadingState(),this.maybeLoadMore())}})}setPageAspects(a){Array.isArray(a)&&(this.pageAspects=a.filter(n=>n>0),this.buildOffsets(),this.updateLoadedItemPositions(),this.ensureScrollPlane(),this.maybeLoadMore())}buildOffsets(){let a=this.tileSources.length>0?this.tileSources.length:this.pageAspects.length;this.pageOffsets=new Array(a),this.pageHeights=new Array(a),this.pageRowHeights=new Array(a),this.pageXOffsets=new Array(a),this.isSpreadMode()?this.buildSpreadOffsets(a):this.buildSingleOffsets(a)}buildSingleOffsets(a){let o=0,u=this.pageAspects[0]||1;for(let s=0;s0){let $=this.pageAspects[0]||c;c=$,this.pageOffsets[0]=u,this.pageHeights[0]=$,this.pageRowHeights[0]=$,this.pageXOffsets[0]=o?0:1,u+=$+.06,s=1}for(;s{let o=this.pageOffsets[n],u=this.pageHeights[n],s=this.pageXOffsets[n];a.setPosition(new OpenSeadragon.Point(s,o),!0),a.setWidth(1,!0),a.setHeight(u,!0),this.addOrUpdatePageOverlay(n)})}addOrUpdatePageOverlay(a){if(!this.viewer)return;let n=this.pageOverlayElements.get(a),o=a%2===0;n||(n=document.createElement("div"),n.className="diva-page-overlay-label",n.style.pointerEvents="none",n.style.color="#ffffff",n.style.background="transparent",n.style.padding="0",n.style.borderRadius="0",n.style.fontSize="12px",n.style.fontWeight="600",n.style.lineHeight="1.2",n.style.whiteSpace="nowrap",n.style.paddingBottom="6px",n.style.paddingLeft=o?"0":"12px",n.style.paddingRight=o?"12px":"0",this.pageOverlayElements.set(a,n)),n.textContent=this.pageLabels[a]||`Page ${a+1}`;let u=(this.pageXOffsets[a]||0)+(o?1:0),s=this.pageOffsets[a]||0;try{this.viewer.removeOverlay(n)}catch{}this.viewer.addOverlay({element:n,location:new OpenSeadragon.Point(u,s),placement:o?OpenSeadragon.Placement.BOTTOM_RIGHT:OpenSeadragon.Placement.BOTTOM_LEFT})}clearPageOverlays(){this.viewer&&this.pageOverlayElements.forEach(a=>{var n;try{(n=this.viewer)==null||n.removeOverlay(a)}catch{}a.remove()}),this.pageOverlayElements.clear()}ensureScrollPlane(){if(!this.viewer||this.pageOffsets.length===0)return;let a=this.getTotalHeight(),n=this.isSpreadMode()?2:1;if(this.scrollPlaneItem){this.scrollPlaneItem.setPosition(new OpenSeadragon.Point(0,0),!0),this.scrollPlaneItem.setWidth(n,!0),this.scrollPlaneItem.setHeight(a,!0);return}let u=`data:image/svg+xml;charset=utf-8,${encodeURIComponent('')}`;this.viewer.addTiledImage({tileSource:{type:"image",url:u},x:0,y:0,width:n,success:s=>{let c=s.item;c.setOpacity(0),c.setPosition(new OpenSeadragon.Point(0,0),!0),c.setWidth(n,!0),c.setHeight(a,!0),this.scrollPlaneItem=c}})}clearScrollPlane(){!this.viewer||!this.scrollPlaneItem||(this.viewer.world.removeItem(this.scrollPlaneItem),this.scrollPlaneItem=null)}indicesForRange(a,n){if(this.pageOffsets.length===0)return null;let o=this.findIndexForOffset(a),u=this.getRowEndIndex(this.findIndexForOffset(n));return[o,u]}findIndexForOffset(a){let n=0,o=this.pageOffsets.length-1;for(;n<=o;){let s=Math.floor((n+o)/2),c=this.pageOffsets[s],$=this.pageRowHeights[s]||this.pageHeights[s]||1;if(a