triiiceratops 0.11.1 → 0.12.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.
Files changed (58) hide show
  1. package/README.md +17 -9
  2. package/dist/{ArrowCounterClockwise-CN8KGaI0.js → ArrowCounterClockwise-CM9mGGcp.js} +1 -1
  3. package/dist/X-Bn7S7vUL.js +963 -0
  4. package/dist/{annotation_tool_point-BpZXtX5D.js → annotation_tool_point-LoRp_nrI.js} +1 -1
  5. package/dist/annotorious-openseadragon.es-tb5X-LtF.js +33045 -0
  6. package/dist/components/AnnotationOverlay.svelte +10 -17
  7. package/dist/components/DemoHeader.svelte +73 -5
  8. package/dist/components/MetadataDialog.svelte +4 -1
  9. package/dist/components/OSDViewer.svelte +39 -3
  10. package/dist/components/SearchPanel.svelte +8 -5
  11. package/dist/components/ThemeToggle.svelte +1 -1
  12. package/dist/components/ThumbnailGallery.svelte +229 -38
  13. package/dist/components/Toolbar.svelte +105 -6
  14. package/dist/components/TriiiceratopsViewer.svelte +37 -12
  15. package/dist/components/TriiiceratopsViewerElement.svelte +3 -1
  16. package/dist/custom-element.js +1 -0
  17. package/dist/{image_filters_reset-CyWg622b.js → image_filters_reset-CmWuQiOc.js} +1 -1
  18. package/dist/paraglide/messages/_index.d.ts +9 -0
  19. package/dist/paraglide/messages/_index.js +10 -1
  20. package/dist/paraglide/messages/settings_toggle_show_viewing_mode.d.ts +4 -0
  21. package/dist/paraglide/messages/settings_toggle_show_viewing_mode.js +33 -0
  22. package/dist/paraglide/messages/show_mode_toggle.d.ts +4 -0
  23. package/dist/paraglide/messages/show_mode_toggle.js +33 -0
  24. package/dist/paraglide/messages/toggle_single_page_mode.d.ts +4 -0
  25. package/dist/paraglide/messages/toggle_single_page_mode.js +33 -0
  26. package/dist/paraglide/messages/toggle_two_page_mode.d.ts +4 -0
  27. package/dist/paraglide/messages/toggle_two_page_mode.js +33 -0
  28. package/dist/paraglide/messages/two_page_mode.d.ts +4 -0
  29. package/dist/paraglide/messages/two_page_mode.js +33 -0
  30. package/dist/paraglide/messages/viewing_mode_individuals.d.ts +4 -0
  31. package/dist/paraglide/messages/viewing_mode_individuals.js +33 -0
  32. package/dist/paraglide/messages/viewing_mode_label.d.ts +4 -0
  33. package/dist/paraglide/messages/viewing_mode_label.js +33 -0
  34. package/dist/paraglide/messages/viewing_mode_paged.d.ts +4 -0
  35. package/dist/paraglide/messages/viewing_mode_paged.js +33 -0
  36. package/dist/paraglide/messages/viewing_mode_shift_pairing.d.ts +4 -0
  37. package/dist/paraglide/messages/viewing_mode_shift_pairing.js +33 -0
  38. package/dist/plugins/annotation-editor/AnnotationEditorController.svelte +5 -3
  39. package/dist/plugins/annotation-editor/AnnotationEditorPanel.svelte +3 -3
  40. package/dist/plugins/annotation-editor/AnnotationManager.svelte.d.ts +3 -0
  41. package/dist/plugins/annotation-editor/AnnotationManager.svelte.js +19 -14
  42. package/dist/plugins/annotation-editor/loader.svelte.js +2 -2
  43. package/dist/plugins/annotation-editor.js +1228 -32159
  44. package/dist/plugins/image-manipulation/ImageManipulationController.svelte +1 -1
  45. package/dist/plugins/image-manipulation.js +3 -3
  46. package/dist/state/manifests.svelte.d.ts +2 -1
  47. package/dist/state/manifests.svelte.js +5 -9
  48. package/dist/state/manifests.test.js +52 -50
  49. package/dist/state/viewer.svelte.d.ts +20 -1
  50. package/dist/state/viewer.svelte.js +150 -16
  51. package/dist/triiiceratops-bundle.js +3107 -2584
  52. package/dist/triiiceratops-element.iife.js +26 -26
  53. package/dist/triiiceratops.css +1 -1
  54. package/dist/types/config.d.ts +33 -0
  55. package/dist/utils/annotationAdapter.js +2 -2
  56. package/dist/utils/annotationAdapter.test.js +0 -1
  57. package/package.json +12 -2
  58. package/dist/X-i_EmjXwW.js +0 -906
@@ -61,6 +61,28 @@
61
61
  };
62
62
  let galleryElement: HTMLElement | null = $state(null);
63
63
 
64
+ // Initialize position and size from config if available (only once on mount)
65
+ $effect(() => {
66
+ if (
67
+ viewerState.config.gallery?.width &&
68
+ viewerState.config.gallery?.height
69
+ ) {
70
+ viewerState.gallerySize = {
71
+ width: viewerState.config.gallery.width,
72
+ height: viewerState.config.gallery.height,
73
+ };
74
+ }
75
+ if (
76
+ viewerState.config.gallery?.x !== undefined &&
77
+ viewerState.config.gallery?.y !== undefined
78
+ ) {
79
+ viewerState.galleryPosition = {
80
+ x: viewerState.config.gallery.x,
81
+ y: viewerState.config.gallery.y,
82
+ };
83
+ }
84
+ });
85
+
64
86
  // Generate thumbnail data
65
87
  let thumbnails = $derived.by(() => {
66
88
  if (!canvases || !Array.isArray(canvases))
@@ -83,8 +105,8 @@
83
105
  : thumb.id || thumb['@id'];
84
106
  }
85
107
  }
86
- } catch (e) {
87
- console.warn('Error getting thumbnail', e);
108
+ } catch {
109
+ console.warn('Error getting thumbnail');
88
110
  }
89
111
 
90
112
  // Fallback to first image if no thumbnail service
@@ -169,7 +191,7 @@
169
191
  (pObj['@id'] as string | undefined) ||
170
192
  JSON.stringify(pObj);
171
193
  }
172
- } catch (e) {
194
+ } catch {
173
195
  // ignore
174
196
  }
175
197
 
@@ -322,7 +344,26 @@
322
344
  }
323
345
 
324
346
  function selectCanvas(canvasId: string) {
325
- viewerState.setCanvas(canvasId);
347
+ if (viewerState.viewingMode === 'paged') {
348
+ const canvasIndex = thumbnails.findIndex((t) => t.id === canvasId);
349
+ const singlePages = viewerState.pagedOffset;
350
+ // If within single pages section, select directly
351
+ if (canvasIndex < singlePages) {
352
+ viewerState.setCanvas(canvasId);
353
+ } else {
354
+ // Check if this is a left-hand page (start of a pair)
355
+ const pairPosition = (canvasIndex - singlePages) % 2;
356
+ if (pairPosition === 0) {
357
+ viewerState.setCanvas(canvasId);
358
+ } else {
359
+ // Right-hand page, select the left page of this pair
360
+ const prevCanvas = thumbnails[canvasIndex - 1];
361
+ viewerState.setCanvas(prevCanvas.id);
362
+ }
363
+ }
364
+ } else {
365
+ viewerState.setCanvas(canvasId);
366
+ }
326
367
  }
327
368
 
328
369
  // State for docking
@@ -384,6 +425,8 @@
384
425
  (dockSide === 'none' && viewerState.gallerySize.height < 320),
385
426
  );
386
427
 
428
+ let fixedHeight = $derived(viewerState.galleryFixedHeight);
429
+
387
430
  function startDrag(e: MouseEvent) {
388
431
  if (!draggable) return; // Dragging disabled in config
389
432
  if ((e.target as HTMLElement).closest('.resize-handle')) return; // Don't drag if resizing
@@ -444,6 +487,53 @@
444
487
  dockSide = 'none';
445
488
  }
446
489
  }
490
+
491
+ // Grouped thumbnail mode (for two-page mode)
492
+ const groupedThumbnailIndices = $derived.by(() => {
493
+ const indices: number[] = [];
494
+ if (viewerState.viewingMode === 'paged' && canvases) {
495
+ // Single pages at the start: pagedOffset (default 0, shifted = 1)
496
+ const singlePages = viewerState.pagedOffset;
497
+ // Add indices for single pages
498
+ for (let i = 0; i < singlePages && i < canvases.length; i++) {
499
+ indices.push(i);
500
+ }
501
+ // Add indices for paired pages (step by 2 starting from singlePages)
502
+ for (let i = singlePages; i < canvases.length; i += 2) {
503
+ indices.push(i);
504
+ }
505
+ }
506
+ return indices;
507
+ });
508
+
509
+ const groupedThumbnails = $derived.by(() => {
510
+ const groups: Array<{
511
+ id: string;
512
+ label: string;
513
+ srcs: string[];
514
+ index: number;
515
+ }> = [];
516
+ const thumbs = thumbnails;
517
+ const singlePages = viewerState.pagedOffset;
518
+ for (const i of groupedThumbnailIndices) {
519
+ const first = thumbs[i];
520
+ // Only pair if we're past the single pages section
521
+ const second = i < singlePages ? null : thumbs[i + 1];
522
+ const groupId = first.id;
523
+ const groupLabel = first.label;
524
+ const groupSrcs = [first.src];
525
+ if (second) {
526
+ groupSrcs.push(second.src);
527
+ }
528
+ groups.push({
529
+ id: groupId,
530
+ label: groupLabel,
531
+ srcs: groupSrcs,
532
+ index: i,
533
+ });
534
+ }
535
+ return groups;
536
+ });
447
537
  </script>
448
538
 
449
539
  {#if viewerState.showThumbnailGallery}
@@ -514,44 +604,145 @@
514
604
  : 'grid gap-2'}
515
605
  style={isHorizontal
516
606
  ? ''
517
- : 'grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));'}
607
+ : `grid-template-columns: repeat(auto-fill, minmax(${fixedHeight}px, 1fr));`}
518
608
  >
519
- {#each thumbnails as thumb}
520
- <button
521
- class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
522
- ? 'w-[90px]'
523
- : ''} {viewerState.canvasId === thumb.id
524
- ? 'ring-2 ring-primary bg-primary/5'
525
- : ''}"
526
- onclick={() => selectCanvas(thumb.id)}
527
- data-id={thumb.id}
528
- aria-label="Select canvas {thumb.label}"
529
- >
530
- <div
531
- class="aspect-3/4 bg-base-300 rounded overflow-hidden relative w-full flex items-center justify-center"
609
+ {#if viewerState.viewingMode === 'paged'}
610
+ <!-- grouped thumbnail display -->
611
+ {#each groupedThumbnails as thumbGroup}
612
+ <button
613
+ class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
614
+ ? 'w-auto'
615
+ : thumbGroup.srcs.length > 1
616
+ ? 'col-span-2'
617
+ : ''} {viewerState.canvasId === thumbGroup.id
618
+ ? 'ring-2 ring-primary bg-primary/5'
619
+ : ''}"
620
+ style={isHorizontal
621
+ ? `height: ${fixedHeight + 24}px`
622
+ : ''}
623
+ onclick={() => selectCanvas(thumbGroup.id)}
624
+ data-id={thumbGroup.id}
625
+ aria-label="Select canvas {thumbGroup.label}"
532
626
  >
533
- {#if thumb.src}
534
- <img
535
- src={thumb.src}
536
- alt={thumb.label}
537
- class="object-contain w-full h-full"
538
- loading="lazy"
539
- draggable="false"
540
- />
541
- {:else}
542
- <span class="opacity-20 text-4xl">?</span>
543
- {/if}
544
- </div>
545
- <div
546
- class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
627
+ <div
628
+ class="{isHorizontal
629
+ ? 'h-full w-auto flex-row'
630
+ : thumbGroup.srcs.length > 1
631
+ ? 'aspect-3/2 w-full'
632
+ : 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center gap-px"
633
+ style={isHorizontal
634
+ ? `height: ${fixedHeight}px`
635
+ : ''}
636
+ >
637
+ <div
638
+ class="flex items-center justify-center overflow-hidden {isHorizontal
639
+ ? 'h-full w-auto'
640
+ : 'h-full ' +
641
+ (thumbGroup.srcs.length > 1
642
+ ? 'w-1/2'
643
+ : 'w-full')}"
644
+ >
645
+ {#if thumbGroup.srcs[0]}
646
+ <img
647
+ src={thumbGroup.srcs[0]}
648
+ alt={thumbGroup.label}
649
+ class="object-contain {isHorizontal
650
+ ? 'h-full w-auto'
651
+ : 'w-full h-full'} {thumbGroup
652
+ .srcs.length > 1
653
+ ? 'object-right'
654
+ : 'object-center'}"
655
+ loading="lazy"
656
+ draggable="false"
657
+ />
658
+ {:else}
659
+ <span class="opacity-20 text-4xl"
660
+ >?</span
661
+ >
662
+ {/if}
663
+ </div>
664
+ {#if thumbGroup.srcs.length > 1}
665
+ <div
666
+ class="flex items-center justify-center overflow-hidden {isHorizontal
667
+ ? 'h-full w-auto'
668
+ : 'h-full w-1/2'}"
669
+ >
670
+ {#if thumbGroup.srcs[1]}
671
+ <img
672
+ src={thumbGroup.srcs[1]}
673
+ alt={thumbGroup.label}
674
+ class="object-contain {isHorizontal
675
+ ? 'h-full w-auto'
676
+ : 'w-full h-full'} object-left"
677
+ loading="lazy"
678
+ draggable="false"
679
+ />
680
+ {:else}
681
+ <span class="opacity-20 text-4xl"
682
+ >?</span
683
+ >
684
+ {/if}
685
+ </div>
686
+ {/if}
687
+ </div>
688
+ <div
689
+ class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
690
+ >
691
+ <span class="font-bold mr-1"
692
+ >{thumbGroup.index + 1}.</span
693
+ >
694
+ {thumbGroup.label}
695
+ </div>
696
+ </button>
697
+ {/each}
698
+ {:else}
699
+ {#each thumbnails as thumb}
700
+ <button
701
+ class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
702
+ ? 'w-auto'
703
+ : ''} {viewerState.canvasId === thumb.id
704
+ ? 'ring-2 ring-primary bg-primary/5'
705
+ : ''}"
706
+ style={isHorizontal
707
+ ? `height: ${fixedHeight + 24}px`
708
+ : ''}
709
+ onclick={() => selectCanvas(thumb.id)}
710
+ data-id={thumb.id}
711
+ aria-label="Select canvas {thumb.label}"
547
712
  >
548
- <span class="font-bold mr-1"
549
- >{thumb.index + 1}.</span
713
+ <div
714
+ class="{isHorizontal
715
+ ? 'h-full w-auto'
716
+ : 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center"
717
+ style={isHorizontal
718
+ ? `height: ${fixedHeight}px`
719
+ : ''}
720
+ >
721
+ {#if thumb.src}
722
+ <img
723
+ src={thumb.src}
724
+ alt={thumb.label}
725
+ class="object-contain {isHorizontal
726
+ ? 'h-full w-auto'
727
+ : 'w-full h-full'}"
728
+ loading="lazy"
729
+ draggable="false"
730
+ />
731
+ {:else}
732
+ <span class="opacity-20 text-4xl">?</span>
733
+ {/if}
734
+ </div>
735
+ <div
736
+ class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
550
737
  >
551
- {thumb.label}
552
- </div>
553
- </button>
554
- {/each}
738
+ <span class="font-bold mr-1"
739
+ >{thumb.index + 1}.</span
740
+ >
741
+ {thumb.label}
742
+ </div>
743
+ </button>
744
+ {/each}
745
+ {/if}
555
746
  </div>
556
747
  </div>
557
748
 
@@ -7,6 +7,8 @@
7
7
  import ChatCenteredText from 'phosphor-svelte/lib/ChatCenteredText';
8
8
  import Info from 'phosphor-svelte/lib/Info';
9
9
  import List from 'phosphor-svelte/lib/List';
10
+ import BookOpen from 'phosphor-svelte/lib/BookOpen';
11
+ import Scroll from 'phosphor-svelte/lib/Scroll';
10
12
  import X from 'phosphor-svelte/lib/X';
11
13
  import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
12
14
  import { m, language } from '../state/i18n.svelte';
@@ -34,10 +36,11 @@
34
36
  const showFullscreen = $derived(toolbarConfig.showFullscreen !== false);
35
37
  const showAnnotations = $derived(toolbarConfig.showAnnotations !== false);
36
38
  const showInfo = $derived(toolbarConfig.showInfo !== false);
39
+ const showViewingMode = $derived(toolbarConfig.showViewingMode !== false);
37
40
 
38
41
  // Derived list of sorted plugin buttons
39
42
  let sortedPluginButtons = $derived.by(() => {
40
- language.current;
43
+ void language.current;
41
44
  return [...viewerState.pluginMenuButtons].sort(
42
45
  (a, b) => (a.order ?? 100) - (b.order ?? 100),
43
46
  );
@@ -46,6 +49,18 @@
46
49
  function toggleOpen() {
47
50
  viewerState.toggleToolbar();
48
51
  }
52
+
53
+ let isOverflowVisible = $state(false);
54
+ $effect(() => {
55
+ if (isOpen) {
56
+ const timer = setTimeout(() => {
57
+ isOverflowVisible = true;
58
+ }, 320); // Slightly longer than 300ms to ensure transition is done
59
+ return () => clearTimeout(timer);
60
+ } else {
61
+ isOverflowVisible = false;
62
+ }
63
+ });
49
64
  </script>
50
65
 
51
66
  <div
@@ -60,7 +75,7 @@
60
75
  <!-- Collapsible Toolbar -->
61
76
  <div
62
77
  class={[
63
- 'pointer-events-auto bg-base-100/95 backdrop-blur shadow-xl transition-all duration-300 ease-in-out flex overflow-hidden',
78
+ 'pointer-events-auto bg-base-100/95 backdrop-blur shadow-xl transition-all duration-300 ease-in-out flex',
64
79
  // Layout based on position
65
80
  isTop &&
66
81
  'flex-row-reverse h-12 w-auto max-w-full rounded-b-xl border-x border-b border-base-200 origin-top',
@@ -76,6 +91,9 @@
76
91
  !isOpen && !isTop && 'w-0 opacity-0',
77
92
  !isOpen && !isTop && isLeft && '-translate-x-full',
78
93
  !isOpen && !isTop && !isLeft && 'translate-x-full',
94
+
95
+ // Overflow handling
96
+ isOverflowVisible ? 'overflow-visible' : 'overflow-hidden',
79
97
  ]}
80
98
  >
81
99
  <!-- Close Button (Inside Menu) -->
@@ -104,13 +122,19 @@
104
122
  <div class={isTop ? 'w-2' : 'h-2'}></div>
105
123
  {/if}
106
124
 
125
+ <!-- Scrollable Actions -->
107
126
  <ul
108
127
  class={[
109
128
  'menu menu-md gap-2 flex-nowrap items-center min-h-0',
129
+ isTop && 'px-2 py-1 menu-horizontal w-auto flex-row-reverse',
110
130
  isTop &&
111
- 'px-2 py-1 menu-horizontal w-auto overflow-x-auto overflow-y-hidden flex-row-reverse',
131
+ !isOverflowVisible &&
132
+ 'overflow-x-auto overflow-y-hidden',
133
+ isTop && isOverflowVisible && 'overflow-visible',
112
134
  !isTop &&
135
+ !isOverflowVisible &&
113
136
  'py-2 px-1 flex-1 overflow-y-auto overflow-x-hidden w-12',
137
+ !isTop && isOverflowVisible && 'py-2 px-1 flex-1 w-12',
114
138
  ]}
115
139
  >
116
140
  <!-- --- Standard Actions --- -->
@@ -157,6 +181,81 @@
157
181
  </li>
158
182
  {/if}
159
183
 
184
+ {#if showViewingMode}
185
+ <li
186
+ class="dropdown {isTop
187
+ ? 'dropdown-bottom'
188
+ : isLeft
189
+ ? 'dropdown-right'
190
+ : 'dropdown-left'}"
191
+ >
192
+ <div
193
+ tabindex="0"
194
+ role="button"
195
+ class="flex items-center justify-center"
196
+ use:tooltip={{
197
+ content: m.viewing_mode_label(),
198
+ position: tooltipPos,
199
+ }}
200
+ aria-label={m.viewing_mode_label()}
201
+ >
202
+ {#if viewerState.viewingMode === 'paged'}
203
+ <BookOpen size={24} weight="bold" />
204
+ {:else}
205
+ <Scroll size={24} weight="bold" />
206
+ {/if}
207
+ </div>
208
+ <ul
209
+ tabindex="-1"
210
+ class="dropdown-content z-50 menu p-2 shadow bg-base-100 rounded-box w-48 border border-base-200 font-normal {isTop
211
+ ? 'left-1/2 -translate-x-1/2'
212
+ : ''}"
213
+ >
214
+ <li>
215
+ <button
216
+ class={viewerState.viewingMode === 'individuals'
217
+ ? 'active'
218
+ : ''}
219
+ onclick={() =>
220
+ viewerState.setViewingMode('individuals')}
221
+ >
222
+ <Scroll size={16} />
223
+ {m.viewing_mode_individuals()}
224
+ </button>
225
+ </li>
226
+ <li>
227
+ <button
228
+ class={viewerState.viewingMode === 'paged'
229
+ ? 'active'
230
+ : ''}
231
+ onclick={() =>
232
+ viewerState.setViewingMode('paged')}
233
+ >
234
+ <BookOpen size={16} />
235
+ {m.viewing_mode_paged()}
236
+ </button>
237
+ </li>
238
+ {#if viewerState.viewingMode === 'paged'}
239
+ <div class="divider my-1"></div>
240
+ <li>
241
+ <label class="label cursor-pointer py-1 gap-2">
242
+ <span class="label-text text-sm"
243
+ >{m.viewing_mode_shift_pairing()}</span
244
+ >
245
+ <input
246
+ type="checkbox"
247
+ class="checkbox checkbox-sm"
248
+ checked={viewerState.pagedOffset === 1}
249
+ onchange={() =>
250
+ viewerState.togglePagedOffset()}
251
+ />
252
+ </label>
253
+ </li>
254
+ {/if}
255
+ </ul>
256
+ </li>
257
+ {/if}
258
+
160
259
  {#if showFullscreen}
161
260
  <li>
162
261
  <button
@@ -227,7 +326,7 @@
227
326
  {/if}
228
327
 
229
328
  <!-- Separator if both groups exist -->
230
- {#if (showSearch || showGallery || showFullscreen || showAnnotations || showInfo) && sortedPluginButtons.length > 0}
329
+ {#if (showSearch || showGallery || showFullscreen || showAnnotations || showInfo || showViewingMode) && sortedPluginButtons.length > 0}
231
330
  <div
232
331
  class={[
233
332
  'divider',
@@ -241,9 +340,9 @@
241
340
  {#each sortedPluginButtons as button (button.id)}
242
341
  {@const Icon = button.icon}
243
342
  {@const tooltipText =
244
- // @ts-ignore
343
+ // @ts-expect-error - m[button.tooltip] might be a function
245
344
  typeof m[button.tooltip] === 'function'
246
- ? // @ts-ignore
345
+ ? // @ts-expect-error - m[button.tooltip] is a function
247
346
  m[button.tooltip]()
248
347
  : button.tooltip}
249
348
  <li>
@@ -234,6 +234,19 @@
234
234
 
235
235
  // Use Manifesto to get images
236
236
  let images = canvas.getImages();
237
+ if (internalViewerState.viewingMode === 'paged') {
238
+ // Single pages at the start: pagedOffset (default 0, shifted = 1)
239
+ const singlePages = internalViewerState.pagedOffset;
240
+ // Only show two-page spread if we're past the single pages section
241
+ if (currentCanvasIndex >= singlePages) {
242
+ const nextIndex = currentCanvasIndex + 1;
243
+ if (nextIndex < canvases.length) {
244
+ const nextCanvas = canvases[nextIndex];
245
+ const nextImages = nextCanvas.getImages();
246
+ images = images.concat(nextImages);
247
+ }
248
+ }
249
+ }
237
250
 
238
251
  // Fallback for IIIF v3: iterate content if images is empty
239
252
  if ((!images || !images.length) && canvas.getContent) {
@@ -251,7 +264,14 @@
251
264
  return null;
252
265
  }
253
266
 
254
- const annotation = images[0];
267
+ // Map images to tile sources, in two page mode, this will get two image sources
268
+ const tileSourcesArray = images.map((annotation: any) =>
269
+ getImageService(annotation),
270
+ );
271
+ return tileSourcesArray;
272
+ });
273
+
274
+ function getImageService(annotation: any) {
255
275
  let resource = annotation.getResource ? annotation.getResource() : null;
256
276
 
257
277
  // v3 fallback: getBody
@@ -357,7 +377,7 @@
357
377
  );
358
378
  const url = resourceId;
359
379
  return { type: 'image', url };
360
- });
380
+ }
361
381
  </script>
362
382
 
363
383
  <div
@@ -385,7 +405,11 @@
385
405
 
386
406
  <!-- Gallery (when docked left) -->
387
407
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'left'}
388
- <div class="h-full w-[140px] pointer-events-auto relative">
408
+ <div
409
+ class="h-full pointer-events-auto relative"
410
+ style="width: {internalViewerState.galleryFixedHeight +
411
+ 40}px"
412
+ >
389
413
  <ThumbnailGallery {canvases} />
390
414
  </div>
391
415
  {/if}
@@ -411,7 +435,8 @@
411
435
  <!-- Top Area (Gallery) -->
412
436
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'top'}
413
437
  <div
414
- class="flex-none h-40 w-full pointer-events-auto relative z-20"
438
+ class="flex-none w-full pointer-events-auto relative z-20"
439
+ style="height: {internalViewerState.galleryFixedHeight + 50}px"
415
440
  >
416
441
  <ThumbnailGallery {canvases} />
417
442
  </div>
@@ -438,12 +463,7 @@
438
463
  {manifestData.error}
439
464
  </div>
440
465
  {:else if tileSources}
441
- {#key tileSources}
442
- <OSDViewer
443
- {tileSources}
444
- viewerState={internalViewerState}
445
- />
446
- {/key}
466
+ <OSDViewer {tileSources} viewerState={internalViewerState} />
447
467
  {:else if manifestData && !manifestData.isFetching && !tileSources}
448
468
  <div
449
469
  class="w-full h-full flex items-center justify-center text-base-content/50"
@@ -484,7 +504,8 @@
484
504
  <!-- Bottom Area (Gallery) -->
485
505
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'bottom'}
486
506
  <div
487
- class="flex-none h-40 w-full pointer-events-auto relative z-20"
507
+ class="flex-none w-full pointer-events-auto relative z-20"
508
+ style="height: {internalViewerState.galleryFixedHeight + 50}px"
488
509
  >
489
510
  <ThumbnailGallery {canvases} />
490
511
  </div>
@@ -520,7 +541,11 @@
520
541
 
521
542
  <!-- Gallery (when docked right) -->
522
543
  {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'right'}
523
- <div class="h-full w-[140px] pointer-events-auto relative">
544
+ <div
545
+ class="h-full pointer-events-auto relative"
546
+ style="width: {internalViewerState.galleryFixedHeight +
547
+ 40}px"
548
+ >
524
549
  <ThumbnailGallery {canvases} />
525
550
  </div>
526
551
  {/if}
@@ -1,3 +1,4 @@
1
+ <!-- svelte-ignore options_missing_custom_element -->
1
2
  <svelte:options
2
3
  customElement={{
3
4
  tag: 'triiiceratops-viewer',
@@ -115,7 +116,7 @@
115
116
  if (typeof config === 'string') {
116
117
  try {
117
118
  return JSON.parse(config);
118
- } catch (e) {
119
+ } catch {
119
120
  console.warn(`Invalid config JSON: "${config}". Ignoring.`);
120
121
  return undefined;
121
122
  }
@@ -124,6 +125,7 @@
124
125
  });
125
126
  </script>
126
127
 
128
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
127
129
  {@html `<style>${styles}</style>`}
128
130
 
129
131
  <div bind:this={hostElement} class="w-full h-full">
@@ -4,6 +4,7 @@
4
4
  // Plugins built as IIFE need to share the same Svelte runtime instance
5
5
  // so that getContext/setContext work correctly across bundle boundaries
6
6
  // @ts-expect-error - svelte/internal/client is not typed but exists at runtime
7
+ // eslint-disable-next-line svelte/no-svelte-internal
7
8
  import * as svelteInternal from 'svelte/internal/client';
8
9
  import * as svelte from 'svelte';
9
10
  window.__TriiiceratopsSvelteRuntime = {
@@ -1,4 +1,4 @@
1
- import { a as t } from "./X-i_EmjXwW.js";
1
+ import { a as t } from "./X-Bn7S7vUL.js";
2
2
  const s = (
3
3
  /** @type {(inputs: {}) => LocalizedString} */
4
4
  () => (
@@ -121,4 +121,13 @@ export * from "./image_filters_reset.js";
121
121
  export * from "./annotation_tool_rectangle.js";
122
122
  export * from "./annotation_tool_polygon.js";
123
123
  export * from "./annotation_tool_point.js";
124
+ export * from "./viewing_mode_label.js";
125
+ export * from "./viewing_mode_individuals.js";
126
+ export * from "./viewing_mode_paged.js";
127
+ export * from "./viewing_mode_shift_pairing.js";
128
+ export * from "./settings_toggle_show_viewing_mode.js";
129
+ export * from "./toggle_two_page_mode.js";
130
+ export * from "./toggle_single_page_mode.js";
131
+ export * from "./show_mode_toggle.js";
132
+ export * from "./two_page_mode.js";
124
133
  export type LocalizedString = import("../runtime.js").LocalizedString;