triiiceratops 0.15.5 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,7 @@ This project is heavily inspired by Mirador 4, which I still view as the premier
22
22
  - **Multi-language**: Language-aware metadata with fallback chain; UI translations for English and German
23
23
  - **Image Services**: Detects and uses IIIF Image API services (v1, v2, v3) for tiled deep-zoom
24
24
  - **Theming**: 35 built-in DaisyUI themes plus custom theme configuration
25
+ - **OpenSeadragon Customization**: Pass custom OSD options (e.g. max zoom level, animation speed) via `openSeadragonConfig`
25
26
 
26
27
  ## Current Limitations
27
28
 
@@ -184,6 +184,8 @@
184
184
  animationTime: 0.5,
185
185
  springStiffness: 7.0,
186
186
  zoomPerClick: 2.0,
187
+ // Consumer-provided OSD overrides
188
+ ...(viewerState.config?.openSeadragonConfig ?? {}),
187
189
  // Enable double-click to zoom, but keep clickToZoom disabled for Annotorious
188
190
  gestureSettingsMouse: {
189
191
  clickToZoom: false,
@@ -227,6 +229,16 @@
227
229
  };
228
230
  });
229
231
 
232
+ // Apply consumer OSD config overrides reactively
233
+ $effect(() => {
234
+ if (!viewer) return;
235
+ const overrides = viewerState.config?.openSeadragonConfig;
236
+ if (!overrides) return;
237
+ for (const [key, value] of Object.entries(overrides)) {
238
+ viewer[key] = value;
239
+ }
240
+ });
241
+
230
242
  // Load tile source when it changes
231
243
  $effect(() => {
232
244
  if (!viewer || !tileSources) return;
@@ -282,6 +294,26 @@
282
294
  };
283
295
  });
284
296
  viewer.open(spread);
297
+
298
+ // Zoom to the active canvas once OSD finishes loading,
299
+ // so the user doesn't see the default zoomed-out view of all canvases.
300
+ viewer.addOnceHandler('open', () => {
301
+ const currentIndex = viewerState.currentCanvasIndex;
302
+ let imageIndex = 0;
303
+ for (let i = 0; i < currentIndex; i++) {
304
+ const canvas = viewerState.canvases[i];
305
+ let imgs = canvas.getImages?.() || [];
306
+ if ((!imgs || !imgs.length) && canvas.getContent) {
307
+ imgs = canvas.getContent();
308
+ }
309
+ const count = imgs ? imgs.length : 0;
310
+ imageIndex += count;
311
+ }
312
+ const item = viewer.world.getItemAt(imageIndex);
313
+ if (item) {
314
+ viewer.viewport.fitBounds(item.getBounds(), true);
315
+ }
316
+ });
285
317
  } else if (mode === 'paged' && sources.length === 2) {
286
318
  const gap = 0.025;
287
319
  const offset = 1 + gap;
@@ -642,14 +642,17 @@
642
642
  <!-- Floating Window -->
643
643
  <div
644
644
  bind:this={galleryElement}
645
- class={(dockSide !== 'none'
646
- ? `relative z-50 bg-base-100 shadow-xl border-base-300 flex transition-all duration-200 select-none w-full h-full
647
- ${dockSide === 'bottom' || dockSide === 'top' ? 'flex-row border-t' : ''}
648
- ${dockSide === 'left' || dockSide === 'right' ? 'flex-col border-x' : ''}`
649
- : 'fixed z-900 bg-base-100 shadow-2xl rounded-lg flex flex-col border border-base-300 overflow-hidden select-none') +
650
- (viewerState.isGalleryDragging
651
- ? ' pointer-events-none opacity-80'
652
- : '')}
645
+ class={[
646
+ dockSide !== 'none' &&
647
+ 'relative z-50 bg-base-100 shadow-xl border-base-300 flex transition-all duration-200 select-none w-full h-full',
648
+ dockSide === 'none' &&
649
+ 'fixed z-900 bg-base-100 shadow-2xl rounded-lg flex flex-col border border-base-300 overflow-hidden select-none',
650
+ (dockSide === 'bottom' || dockSide === 'top') &&
651
+ 'flex-row border-t',
652
+ (dockSide === 'left' || dockSide === 'right') &&
653
+ 'flex-col border-x',
654
+ viewerState.isGalleryDragging && 'pointer-events-none opacity-80',
655
+ ]}
653
656
  style={dockSide !== 'none'
654
657
  ? ''
655
658
  : `left: ${viewerState.galleryPosition.x}px; top: ${viewerState.galleryPosition.y}px; width: ${viewerState.gallerySize.width}px; height: ${viewerState.gallerySize.height}px;`}
@@ -668,27 +671,39 @@
668
671
  <!-- Header Area (only show drag handle when draggable OR when floating) -->
669
672
  {#if draggable || dockSide === 'none'}
670
673
  <div
671
- class={'bg-base-100 flex shrink-0 select-none relative ' +
672
- (dockSide === 'bottom' || dockSide === 'top'
673
- ? 'flex-row h-full items-center border-r border-base-200'
674
- : 'flex-col w-full border-b border-base-200')}
674
+ class={[
675
+ 'bg-base-100 flex shrink-0 select-none relative',
676
+ (dockSide === 'bottom' || dockSide === 'top') &&
677
+ 'flex-row h-full items-center border-r border-base-200',
678
+ dockSide !== 'bottom' &&
679
+ dockSide !== 'top' &&
680
+ 'flex-col w-full border-b border-base-200',
681
+ ]}
675
682
  >
676
683
  <!-- Drag Handle -->
677
684
  <div
678
- class={'cursor-move flex items-center justify-center hover:bg-base-200/50 active:bg-base-200 transition-colors ' +
679
- (dockSide === 'bottom' || dockSide === 'top'
680
- ? 'w-8 h-full'
681
- : 'h-6 w-full')}
685
+ class={[
686
+ 'cursor-move flex items-center justify-center hover:bg-base-200/50 active:bg-base-200 transition-colors',
687
+ (dockSide === 'bottom' || dockSide === 'top') &&
688
+ 'w-8 h-full',
689
+ dockSide !== 'bottom' &&
690
+ dockSide !== 'top' &&
691
+ 'h-6 w-full',
692
+ ]}
682
693
  onmousedown={startDrag}
683
694
  role="button"
684
695
  tabindex="0"
685
696
  aria-label="Drag Gallery"
686
697
  >
687
698
  <div
688
- class={'bg-base-300 rounded-full ' +
689
- (dockSide === 'bottom' || dockSide === 'top'
690
- ? 'w-1.5 h-12'
691
- : 'w-12 h-1.5')}
699
+ class={[
700
+ 'bg-base-300 rounded-full',
701
+ (dockSide === 'bottom' || dockSide === 'top') &&
702
+ 'w-1.5 h-12',
703
+ dockSide !== 'bottom' &&
704
+ dockSide !== 'top' &&
705
+ 'w-12 h-1.5',
706
+ ]}
692
707
  ></div>
693
708
  </div>
694
709
  </div>
@@ -696,14 +711,17 @@
696
711
 
697
712
  <!-- Content (Grid or Horizontal Scroll) -->
698
713
  <div
699
- class="flex-1 p-1 bg-base-100 {isHorizontal
700
- ? 'overflow-x-auto overflow-y-hidden h-full'
701
- : 'overflow-y-auto overflow-x-hidden'}"
714
+ class={[
715
+ 'flex-1 p-1 bg-base-100',
716
+ isHorizontal && 'overflow-x-auto overflow-y-hidden h-full',
717
+ !isHorizontal && 'overflow-y-auto overflow-x-hidden',
718
+ ]}
702
719
  >
703
720
  <div
704
- class={isHorizontal
705
- ? 'flex flex-row gap-2 h-full items-center'
706
- : 'grid gap-2'}
721
+ class={[
722
+ isHorizontal && 'flex flex-row gap-2 h-full items-center',
723
+ !isHorizontal && 'grid gap-2',
724
+ ]}
707
725
  style={isHorizontal
708
726
  ? ''
709
727
  : `grid-template-columns: repeat(auto-fill, minmax(${fixedHeight}px, 1fr));`}
@@ -714,20 +732,27 @@
714
732
  {@const isGroupSelected = (() => {
715
733
  const idx = thumbGroup.index;
716
734
  const first = thumbnails[idx];
717
- const second = thumbnails[idx + 1];
735
+ // Only check second canvas if this is a paired group (not a single page)
736
+ const isPairedGroup =
737
+ idx >= viewerState.pagedOffset;
738
+ const second = isPairedGroup
739
+ ? thumbnails[idx + 1]
740
+ : null;
718
741
  return (
719
742
  viewerState.canvasId === first?.id ||
720
- viewerState.canvasId === second?.id
743
+ (second && viewerState.canvasId === second.id)
721
744
  );
722
745
  })()}
723
746
  <button
724
- class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 overflow-hidden {isHorizontal
725
- ? 'w-auto'
726
- : thumbGroup.srcs.length > 1
727
- ? 'col-span-2'
728
- : ''} {isGroupSelected
729
- ? 'ring-2 ring-primary bg-primary/5'
730
- : ''}"
747
+ class={[
748
+ 'group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 overflow-hidden',
749
+ isHorizontal && 'w-auto',
750
+ !isHorizontal &&
751
+ thumbGroup.srcs.length > 1 &&
752
+ 'col-span-2',
753
+ isGroupSelected &&
754
+ 'ring-2 ring-primary bg-primary/5',
755
+ ]}
731
756
  style={isHorizontal
732
757
  ? `height: ${fixedHeight + (thumbGroup.labels.length > 1 ? 40 : 24)}px`
733
758
  : ''}
@@ -738,35 +763,48 @@
738
763
  )}"
739
764
  >
740
765
  <div
741
- class="{isHorizontal
742
- ? 'h-full w-auto flex-row'
743
- : thumbGroup.srcs.length > 1
744
- ? 'aspect-3/2 w-full'
745
- : 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center gap-px {isRTL
746
- ? 'flex-row-reverse'
747
- : ''}"
766
+ class={[
767
+ isHorizontal && 'h-full w-auto flex-row',
768
+ !isHorizontal &&
769
+ thumbGroup.srcs.length > 1 &&
770
+ 'aspect-3/2 w-full',
771
+ !isHorizontal &&
772
+ thumbGroup.srcs.length <= 1 &&
773
+ 'aspect-3/4 w-full',
774
+ 'bg-base-300 rounded overflow-hidden relative flex items-center justify-center gap-px',
775
+ isRTL && 'flex-row-reverse',
776
+ ]}
748
777
  style={isHorizontal
749
778
  ? `height: ${fixedHeight}px`
750
779
  : ''}
751
780
  >
752
781
  <div
753
- class="flex items-center justify-center overflow-hidden {isHorizontal
754
- ? 'h-full w-auto'
755
- : 'h-full ' +
756
- (thumbGroup.srcs.length > 1
757
- ? 'w-1/2'
758
- : 'w-full')}"
782
+ class={[
783
+ 'flex items-center justify-center overflow-hidden',
784
+ isHorizontal && 'h-full w-auto',
785
+ !isHorizontal && 'h-full',
786
+ !isHorizontal &&
787
+ thumbGroup.srcs.length > 1 &&
788
+ 'w-1/2',
789
+ !isHorizontal &&
790
+ thumbGroup.srcs.length <= 1 &&
791
+ 'w-full',
792
+ ]}
759
793
  >
760
794
  {#if thumbGroup.srcs[0]}
761
795
  <img
762
796
  src={thumbGroup.srcs[0]}
763
797
  alt={thumbGroup.labels[0]}
764
- class="object-contain {isHorizontal
765
- ? 'h-full w-auto'
766
- : 'w-full h-full'} {thumbGroup
767
- .srcs.length > 1
768
- ? 'object-right'
769
- : 'object-center'}"
798
+ class={[
799
+ 'object-contain',
800
+ isHorizontal && 'h-full w-auto',
801
+ !isHorizontal &&
802
+ 'w-full h-full',
803
+ thumbGroup.srcs.length > 1 &&
804
+ 'object-right',
805
+ thumbGroup.srcs.length <= 1 &&
806
+ 'object-center',
807
+ ]}
770
808
  loading="lazy"
771
809
  draggable="false"
772
810
  />
@@ -778,17 +816,23 @@
778
816
  </div>
779
817
  {#if thumbGroup.srcs.length > 1}
780
818
  <div
781
- class="flex items-center justify-center overflow-hidden {isHorizontal
782
- ? 'h-full w-auto'
783
- : 'h-full w-1/2'}"
819
+ class={[
820
+ 'flex items-center justify-center overflow-hidden',
821
+ isHorizontal && 'h-full w-auto',
822
+ !isHorizontal && 'h-full w-1/2',
823
+ ]}
784
824
  >
785
825
  {#if thumbGroup.srcs[1]}
786
826
  <img
787
827
  src={thumbGroup.srcs[1]}
788
828
  alt={thumbGroup.labels[1]}
789
- class="object-contain {isHorizontal
790
- ? 'h-full w-auto'
791
- : 'w-full h-full'} object-left"
829
+ class={[
830
+ 'object-contain object-left',
831
+ isHorizontal &&
832
+ 'h-full w-auto',
833
+ !isHorizontal &&
834
+ 'w-full h-full',
835
+ ]}
792
836
  loading="lazy"
793
837
  draggable="false"
794
838
  />
@@ -801,9 +845,11 @@
801
845
  {/if}
802
846
  </div>
803
847
  <div
804
- class="text-xs font-medium opacity-70 group-hover:opacity-100 overflow-hidden {isHorizontal
805
- ? 'w-0 min-w-full'
806
- : 'w-full'}"
848
+ class={[
849
+ 'text-xs font-medium opacity-70 group-hover:opacity-100 overflow-hidden',
850
+ isHorizontal && 'w-0 min-w-full',
851
+ !isHorizontal && 'w-full',
852
+ ]}
807
853
  title="{thumbGroup.index + 1}. {thumbGroup
808
854
  .labels[0]}{thumbGroup.labels.length > 1
809
855
  ? ` / ${thumbGroup.index + 2}. ${thumbGroup.labels[1]}`
@@ -843,11 +889,12 @@
843
889
  {:else}
844
890
  {#each thumbnails as thumb}
845
891
  <button
846
- class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
847
- ? 'w-auto'
848
- : ''} {viewerState.canvasId === thumb.id
849
- ? 'ring-2 ring-primary bg-primary/5'
850
- : ''}"
892
+ class={[
893
+ 'group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0',
894
+ isHorizontal && 'w-auto',
895
+ viewerState.canvasId === thumb.id &&
896
+ 'ring-2 ring-primary bg-primary/5',
897
+ ]}
851
898
  style={isHorizontal
852
899
  ? `height: ${fixedHeight + 24}px`
853
900
  : ''}
@@ -856,9 +903,11 @@
856
903
  aria-label="Select canvas {thumb.label}"
857
904
  >
858
905
  <div
859
- class="{isHorizontal
860
- ? 'h-full w-auto'
861
- : 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center"
906
+ class={[
907
+ isHorizontal && 'h-full w-auto',
908
+ !isHorizontal && 'aspect-3/4 w-full',
909
+ 'bg-base-300 rounded overflow-hidden relative flex items-center justify-center',
910
+ ]}
862
911
  style={isHorizontal
863
912
  ? `height: ${fixedHeight}px`
864
913
  : ''}
@@ -867,9 +916,11 @@
867
916
  <img
868
917
  src={thumb.src}
869
918
  alt={thumb.label}
870
- class="object-contain {isHorizontal
871
- ? 'h-full w-auto'
872
- : 'w-full h-full'}"
919
+ class={[
920
+ 'object-contain',
921
+ isHorizontal && 'h-full w-auto',
922
+ !isHorizontal && 'w-full h-full',
923
+ ]}
873
924
  loading="lazy"
874
925
  draggable="false"
875
926
  />
@@ -916,10 +967,11 @@
916
967
  <!-- Drop Zones -->
917
968
  <!-- Top -->
918
969
  <div
919
- class="absolute top-2 left-2 right-2 h-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200 {viewerState.dragOverSide ===
920
- 'top'
921
- ? 'bg-primary/20 scale-105'
922
- : 'bg-base-100/50'}"
970
+ class={[
971
+ 'absolute top-2 left-2 right-2 h-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200',
972
+ viewerState.dragOverSide === 'top' && 'bg-primary/20 scale-105',
973
+ viewerState.dragOverSide !== 'top' && 'bg-base-100/50',
974
+ ]}
923
975
  role="group"
924
976
  >
925
977
  <span class="font-bold text-primary opacity-50">Dock Top</span>
@@ -927,10 +979,12 @@
927
979
 
928
980
  <!-- Bottom -->
929
981
  <div
930
- class="absolute bottom-2 left-2 right-2 h-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200 {viewerState.dragOverSide ===
931
- 'bottom'
932
- ? 'bg-primary/20 scale-105'
933
- : 'bg-base-100/50'}"
982
+ class={[
983
+ 'absolute bottom-2 left-2 right-2 h-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200',
984
+ viewerState.dragOverSide === 'bottom' &&
985
+ 'bg-primary/20 scale-105',
986
+ viewerState.dragOverSide !== 'bottom' && 'bg-base-100/50',
987
+ ]}
934
988
  role="group"
935
989
  >
936
990
  <span class="font-bold text-primary opacity-50">Dock Bottom</span>
@@ -938,10 +992,12 @@
938
992
 
939
993
  <!-- Left -->
940
994
  <div
941
- class="absolute top-2 bottom-2 left-2 w-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200 {viewerState.dragOverSide ===
942
- 'left'
943
- ? 'bg-primary/20 scale-105'
944
- : 'bg-base-100/50'}"
995
+ class={[
996
+ 'absolute top-2 bottom-2 left-2 w-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-200',
997
+ viewerState.dragOverSide === 'left' &&
998
+ 'bg-primary/20 scale-105',
999
+ viewerState.dragOverSide !== 'left' && 'bg-base-100/50',
1000
+ ]}
945
1001
  role="group"
946
1002
  >
947
1003
  <span
@@ -952,10 +1008,12 @@
952
1008
 
953
1009
  <!-- Right -->
954
1010
  <div
955
- class="absolute top-2 bottom-2 right-2 w-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-300 {viewerState.dragOverSide ===
956
- 'right'
957
- ? 'bg-primary/20 scale-105'
958
- : 'bg-base-100/50'}"
1011
+ class={[
1012
+ 'absolute top-2 bottom-2 right-2 w-16 rounded-xl border-4 border-dashed border-primary/40 z-999 pointer-events-none flex items-center justify-center transition-all duration-300',
1013
+ viewerState.dragOverSide === 'right' &&
1014
+ 'bg-primary/20 scale-105',
1015
+ viewerState.dragOverSide !== 'right' && 'bg-base-100/50',
1016
+ ]}
959
1017
  role="group"
960
1018
  >
961
1019
  <span
@@ -575,7 +575,7 @@
575
575
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'top'}
576
576
  <div
577
577
  class="flex-none w-full pointer-events-auto relative z-20"
578
- style="height: {internalViewerState.galleryFixedHeight + 50}px"
578
+ style="height: {internalViewerState.galleryFixedHeight + 55}px"
579
579
  >
580
580
  <ThumbnailGallery {canvases} />
581
581
  </div>
@@ -642,7 +642,7 @@
642
642
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'bottom'}
643
643
  <div
644
644
  class="flex-none w-full pointer-events-auto relative z-20"
645
- style="height: {internalViewerState.galleryFixedHeight + 50}px"
645
+ style="height: {internalViewerState.galleryFixedHeight + 55}px"
646
646
  >
647
647
  <ThumbnailGallery {canvases} />
648
648
  </div>