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
@@ -42,18 +42,15 @@
42
42
  const shouldBeVisible =
43
43
  viewerState.config.annotations?.visible ?? true;
44
44
 
45
+ viewerState.visibleAnnotationIds.clear();
45
46
  if (shouldBeVisible) {
46
- const newSet = new Set<string>();
47
47
  annotations.forEach((a: any) => {
48
48
  const id = getAnnotationId(a);
49
- if (id) newSet.add(id);
49
+ if (id) viewerState.visibleAnnotationIds.add(id);
50
50
  });
51
- viewerState.visibleAnnotationIds = newSet;
52
- } else {
53
- viewerState.visibleAnnotationIds = new Set();
54
51
  }
55
52
  } else {
56
- viewerState.visibleAnnotationIds = new Set();
53
+ viewerState.visibleAnnotationIds.clear();
57
54
  }
58
55
  });
59
56
 
@@ -72,24 +69,19 @@
72
69
  } else {
73
70
  viewerState.visibleAnnotationIds.add(id);
74
71
  }
75
- // Reassign to trigger reactivity
76
- viewerState.visibleAnnotationIds = new Set(
77
- viewerState.visibleAnnotationIds,
78
- );
79
72
  }
80
73
 
81
74
  function toggleAllAnnotations() {
82
75
  if (isAllVisible) {
83
76
  // Hide all
84
- viewerState.visibleAnnotationIds = new Set();
77
+ viewerState.visibleAnnotationIds.clear();
85
78
  } else {
86
79
  // Show all
87
- const newSet = new Set<string>();
80
+ viewerState.visibleAnnotationIds.clear();
88
81
  annotations.forEach((a: any) => {
89
82
  const id = getAnnotationId(a);
90
- if (id) newSet.add(id);
83
+ if (id) viewerState.visibleAnnotationIds.add(id);
91
84
  });
92
- viewerState.visibleAnnotationIds = newSet;
93
85
  }
94
86
  }
95
87
 
@@ -98,7 +90,7 @@
98
90
  let toolbarContainer: HTMLElement | undefined = $state();
99
91
 
100
92
  // Calculate coordinates for connecting line
101
- let connectingLine = $derived.by(() => {
93
+ let _connectingLine = $derived.by(() => {
102
94
  if (!hoveredAnnotationId) return null;
103
95
  return null;
104
96
  });
@@ -254,7 +246,7 @@
254
246
  <div
255
247
  class="absolute right-0 mt-2 w-96 bg-base-200/95 backdrop-blur shadow-xl rounded-box p-0 max-h-[60vh] overflow-y-auto border border-base-300 flex flex-col divide-y divide-base-300"
256
248
  >
257
- {#each renderedAnnotations as anno, i}
249
+ {#each renderedAnnotations as anno, i (anno.id)}
258
250
  {@const isVisible = viewerState.visibleAnnotationIds.has(
259
251
  anno.id,
260
252
  )}
@@ -315,7 +307,7 @@
315
307
  ? ''
316
308
  : 'opacity-50'} space-y-2"
317
309
  >
318
- {#each anno.bodies as body}
310
+ {#each anno.bodies as body, i (i)}
319
311
  <div
320
312
  class="flex flex-wrap gap-2 pointer-events-auto"
321
313
  >
@@ -354,6 +346,7 @@
354
346
  <!-- Commenting / Default -->
355
347
  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
356
348
  {#if body.isHtml}
349
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
357
350
  {@html body.value}
358
351
  {:else}
359
352
  {body.value || '(No content)'}
@@ -79,7 +79,7 @@
79
79
  }
80
80
  }
81
81
  }
82
- } catch (e) {
82
+ } catch {
83
83
  /* ignore */
84
84
  }
85
85
  return `Canvas ${index + 1}`;
@@ -213,7 +213,7 @@
213
213
  onchange={(e) => setLocale(e.currentTarget.value as any)}
214
214
  aria-label={m.language_select_label()}
215
215
  >
216
- {#each locales as lang}
216
+ {#each locales as lang (lang)}
217
217
  <option value={lang}>{languageNames[lang] || lang}</option>
218
218
  {/each}
219
219
  </select>
@@ -230,7 +230,6 @@
230
230
  >
231
231
  <Gear size={20} />
232
232
  </div>
233
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
234
233
  <ul
235
234
  tabindex="-1"
236
235
  class="dropdown-content z-20 menu bg-base-100 rounded-box w-80 p-2 shadow border border-base-300 max-h-[80vh] overflow-y-auto block invisible pointer-events-none group-focus-within:visible group-focus-within:pointer-events-auto"
@@ -435,6 +434,49 @@
435
434
  />
436
435
  </label>
437
436
  </li>
437
+ <li>
438
+ <label class="label cursor-pointer py-1">
439
+ <span class="label-text"
440
+ >{m.settings_toggle_show_viewing_mode()}</span
441
+ >
442
+ <input
443
+ type="checkbox"
444
+ class="checkbox checkbox-xs"
445
+ checked={config.toolbar
446
+ ?.showViewingMode ?? true}
447
+ onchange={(e) => {
448
+ if (!config.toolbar)
449
+ config.toolbar = {};
450
+ config.toolbar.showViewingMode =
451
+ e.currentTarget.checked;
452
+ }}
453
+ />
454
+ </label>
455
+ </li>
456
+ <li>
457
+ <label class="label cursor-pointer py-1">
458
+ <span class="label-text"
459
+ >{m.viewing_mode_label()}</span
460
+ >
461
+ <select
462
+ class="select select-bordered select-xs"
463
+ value={config.viewingMode ??
464
+ 'individuals'}
465
+ onchange={(e) => {
466
+ config.viewingMode = (
467
+ e.currentTarget as HTMLSelectElement
468
+ ).value as 'individuals' | 'paged';
469
+ }}
470
+ >
471
+ <option value="individuals"
472
+ >{m.viewing_mode_individuals()}</option
473
+ >
474
+ <option value="paged"
475
+ >{m.viewing_mode_paged()}</option
476
+ >
477
+ </select>
478
+ </label>
479
+ </li>
438
480
  </ul>
439
481
  </details>
440
482
  </li>
@@ -535,6 +577,32 @@
535
577
  </select>
536
578
  </label>
537
579
  </li>
580
+ <li>
581
+ <label class="label cursor-pointer py-1 gap-2">
582
+ <span class="label-text"
583
+ >Thumbnail Height</span
584
+ >
585
+ <input
586
+ type="range"
587
+ min="50"
588
+ max="300"
589
+ value={config.gallery?.fixedHeight ??
590
+ 120}
591
+ class="range range-xs range-primary w-24"
592
+ oninput={(e) => {
593
+ if (!config.gallery)
594
+ config.gallery = {};
595
+ config.gallery.fixedHeight =
596
+ parseInt(e.currentTarget.value);
597
+ }}
598
+ />
599
+ <span
600
+ class="text-xs opacity-50 w-8 text-right"
601
+ >{config.gallery?.fixedHeight ??
602
+ 120}px</span
603
+ >
604
+ </label>
605
+ </li>
538
606
  </ul>
539
607
  </details>
540
608
  </li>
@@ -731,7 +799,7 @@
731
799
  value={isCustom ? 'custom' : manifestUrl}
732
800
  onchange={handleSelectChange}
733
801
  >
734
- {#each SUGGESTED_MANIFESTS as manifest}
802
+ {#each SUGGESTED_MANIFESTS as manifest (manifest.url)}
735
803
  <option value={manifest.url}>{manifest.label}</option>
736
804
  {/each}
737
805
  <option value="custom">{m.try_your_own()}</option>
@@ -770,7 +838,7 @@
770
838
  {#if canvases.length === 0}
771
839
  <option value="" disabled>{m.no_canvases_loaded()}</option>
772
840
  {:else}
773
- {#each canvases as canvas, i}
841
+ {#each canvases as canvas, i (canvas.id)}
774
842
  <option value={canvas.id}>
775
843
  {getCanvasLabel(canvas, i)}
776
844
  </option>
@@ -98,6 +98,7 @@
98
98
  <div class="py-4 overflow-y-auto max-h-[70vh]">
99
99
  {#if description}
100
100
  <div class="mb-6 prose">
101
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
101
102
  <p>{@html description}</p>
102
103
  </div>
103
104
  {/if}
@@ -107,6 +108,7 @@
107
108
  <dt class="font-bold text-lg opacity-70 mt-6">
108
109
  {m.attribution()}
109
110
  </dt>
111
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
110
112
  <dd class="text-sm ps-2">{@html attribution}</dd>
111
113
  {/if}
112
114
 
@@ -124,10 +126,11 @@
124
126
  </dd>
125
127
  {/if}
126
128
 
127
- {#each metadata as item}
129
+ {#each metadata as item, i (i)}
128
130
  <dt class="font-bold text-lg opacity-70 mt-6">
129
131
  {item.label}
130
132
  </dt>
133
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
131
134
  <dd class="text-sm ps-2">{@html item.value}</dd>
132
135
  {/each}
133
136
  </dl>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
+ import { SvelteSet } from 'svelte/reactivity';
3
4
  import { parseAnnotations } from '../utils/annotationAdapter';
4
5
  import { manifestsState } from '../state/manifests.svelte';
5
6
  import type { ViewerState } from '../state/viewer.svelte';
@@ -16,6 +17,8 @@
16
17
 
17
18
  // Track OSD state changes for reactivity
18
19
  let osdVersion = $state(0);
20
+ // Track last opened tile source to prevent unnecessary resets
21
+ let lastTileSourceStr = '';
19
22
 
20
23
  // Get all annotations for current canvas (manifest + search)
21
24
  let allAnnotations = $derived.by(() => {
@@ -32,7 +35,7 @@
32
35
 
33
36
  // Get search hit IDs for styling
34
37
  let searchHitIds = $derived.by(() => {
35
- const ids = new Set<string>();
38
+ const ids = new SvelteSet<string>();
36
39
  viewerState.currentCanvasSearchAnnotations.forEach((anno: any) => {
37
40
  const id = anno.id || anno['@id'];
38
41
  if (id) ids.add(id);
@@ -48,7 +51,7 @@
48
51
  // Rendered annotations with pixel coordinates
49
52
  let renderedAnnotations = $derived.by(() => {
50
53
  // Depend on osdVersion to trigger updates
51
- osdVersion;
54
+ void osdVersion;
52
55
 
53
56
  if (!viewer || !OSD || !parsedAnnotations.length) {
54
57
  return [];
@@ -160,9 +163,13 @@
160
163
  onMount(() => {
161
164
  if (!container) return;
162
165
 
166
+ let mounted = true;
167
+
163
168
  (async () => {
164
169
  // Dynamically import OpenSeadragon to avoid SSR issues
165
170
  const osdModule = await import('openseadragon');
171
+ if (!mounted) return;
172
+
166
173
  OSD = osdModule.default || osdModule;
167
174
 
168
175
  // Initialize OpenSeadragon viewer
@@ -191,6 +198,7 @@
191
198
  })();
192
199
 
193
200
  return () => {
201
+ mounted = false;
194
202
  viewer?.destroy();
195
203
  viewerState.osdViewer = null;
196
204
  };
@@ -225,7 +233,35 @@
225
233
  $effect(() => {
226
234
  if (!viewer || !tileSources) return;
227
235
 
228
- viewer.open(tileSources);
236
+ // Check if source actually changed to avoid resetting zoom
237
+ const currentStr = JSON.stringify(tileSources);
238
+ if (currentStr === lastTileSourceStr) return;
239
+ lastTileSourceStr = currentStr;
240
+
241
+ if (
242
+ viewerState.viewingMode === 'paged' &&
243
+ tileSources instanceof Array &&
244
+ tileSources.length === 2
245
+ ) {
246
+ const secondPageLocation = 1.025;
247
+ const twoPageSpread = [
248
+ {
249
+ tileSource: tileSources[0],
250
+ x: 0,
251
+ y: 0,
252
+ width: 1.0,
253
+ },
254
+ {
255
+ tileSource: tileSources[1],
256
+ x: secondPageLocation, // small gap between pages
257
+ y: 0,
258
+ width: 1.0,
259
+ },
260
+ ];
261
+ viewer.open(twoPageSpread);
262
+ } else {
263
+ viewer.open(tileSources);
264
+ }
229
265
  });
230
266
  </script>
231
267
 
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getContext, untrack } from 'svelte';
3
3
  import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
4
- import Spinner from 'phosphor-svelte/lib/Spinner';
5
4
  import X from 'phosphor-svelte/lib/X';
6
5
  import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
7
6
  import { m } from '../state/i18n.svelte';
@@ -10,7 +9,6 @@
10
9
 
11
10
  // We'll initialize from viewerState to preserve context.
12
11
  let searchQuery = $state('');
13
- let resultsContainer: HTMLElement;
14
12
 
15
13
  let showCloseButton = $derived(
16
14
  viewerState.config.search?.showCloseButton ?? true,
@@ -124,7 +122,7 @@
124
122
  })}
125
123
  </div>
126
124
 
127
- {#each viewerState.searchResults as group}
125
+ {#each viewerState.searchResults as group (group.canvasIndex)}
128
126
  <button
129
127
  class="w-full text-left bg-base-100 shadow-sm border border-base-200 rounded-box cursor-pointer hover:shadow-md transition-all block p-0 select-none {viewerState.currentCanvasIndex ===
130
128
  group.canvasIndex
@@ -144,21 +142,26 @@
144
142
  >
145
143
  </div>
146
144
  <div class="p-0">
147
- {#each group.hits.slice(0, 1) as result}
145
+ {#each group.hits.slice(0, 1) as result, i (i)}
148
146
  <div
149
147
  class="p-3 text-sm border-b border-base-200 last:border-none hover:bg-base-200/30 transition-colors"
150
148
  >
151
149
  {#if result.type === 'hit'}
152
150
  <div class="leading-relaxed">
151
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
153
152
  <span>{@html result.before}</span>
154
153
  <span
155
154
  class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
156
- >{@html result.match}</span
157
155
  >
156
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
157
+ {@html result.match}
158
+ </span>
159
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
158
160
  <span>{@html result.after}</span>
159
161
  </div>
160
162
  {:else}
161
163
  <div class="leading-relaxed">
164
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
162
165
  {@html result.match}
163
166
  </div>
164
167
  {/if}
@@ -80,7 +80,7 @@
80
80
  >
81
81
  <ul class="menu w-56">
82
82
  <li class="menu-title text-xs">{m.theme_menu_title()}</li>
83
- {#each themes as t}
83
+ {#each themes as t (t)}
84
84
  <li>
85
85
  <button class="gap-3 px-2" onclick={() => onThemeChange(t)}>
86
86
  <div