triiiceratops 0.8.1 → 0.9.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 (94) hide show
  1. package/dist/chunks/TriiiceratopsViewer-BGtsfUPF.js +10298 -0
  2. package/dist/chunks/openseadragon-BTypULhm.js +12427 -0
  3. package/dist/components/AnnotationOverlay.svelte +288 -0
  4. package/dist/components/AnnotationOverlay.svelte.d.ts +3 -0
  5. package/dist/components/CanvasNavigation.svelte +32 -0
  6. package/dist/components/CanvasNavigation.svelte.d.ts +11 -0
  7. package/dist/components/DemoHeader.svelte +703 -0
  8. package/dist/components/DemoHeader.svelte.d.ts +9 -0
  9. package/dist/components/FloatingMenu.svelte +208 -0
  10. package/dist/components/FloatingMenu.svelte.d.ts +3 -0
  11. package/dist/components/LeftFab.svelte +69 -0
  12. package/dist/components/LeftFab.svelte.d.ts +3 -0
  13. package/dist/components/MetadataDialog.svelte +151 -0
  14. package/dist/components/MetadataDialog.svelte.d.ts +3 -0
  15. package/dist/components/OSDViewer.svelte +260 -0
  16. package/dist/components/OSDViewer.svelte.d.ts +8 -0
  17. package/dist/components/SearchPanel.svelte +150 -0
  18. package/dist/components/SearchPanel.svelte.d.ts +3 -0
  19. package/dist/components/ThemeToggle.svelte +118 -0
  20. package/dist/components/ThemeToggle.svelte.d.ts +3 -0
  21. package/dist/components/ThumbnailGallery.svelte +601 -0
  22. package/dist/components/ThumbnailGallery.svelte.d.ts +36 -0
  23. package/dist/components/TriiiceratopsViewer.svelte +434 -0
  24. package/dist/components/TriiiceratopsViewer.svelte.d.ts +20 -0
  25. package/dist/components/TriiiceratopsViewerElement.svelte +139 -0
  26. package/dist/components/TriiiceratopsViewerElement.svelte.d.ts +27 -0
  27. package/dist/components/TriiiceratopsViewerElementImage.svelte +143 -0
  28. package/dist/components/TriiiceratopsViewerElementImage.svelte.d.ts +27 -0
  29. package/dist/custom-element-image.d.ts +1 -0
  30. package/dist/custom-element-image.js +2 -0
  31. package/dist/custom-element.d.ts +1 -0
  32. package/dist/custom-element.js +3 -0
  33. package/dist/{src/lib/index.d.ts → index.d.ts} +1 -0
  34. package/dist/index.js +10 -4480
  35. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte +134 -0
  36. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +10 -0
  37. package/dist/{src/lib/plugins → plugins}/image-manipulation/ImageManipulationPlugin.svelte.d.ts +2 -2
  38. package/dist/plugins/image-manipulation/ImageManipulationPlugin.svelte.js +122 -0
  39. package/dist/{src/lib/plugins → plugins}/image-manipulation/filters.d.ts +1 -1
  40. package/dist/plugins/image-manipulation/filters.js +48 -0
  41. package/dist/plugins/image-manipulation/index.js +2 -0
  42. package/dist/plugins/image-manipulation/types.js +7 -0
  43. package/dist/state/i18n.svelte.d.ts +4 -0
  44. package/dist/state/i18n.svelte.js +18 -0
  45. package/dist/state/manifests.svelte.js +210 -0
  46. package/dist/state/manifests.test.d.ts +1 -0
  47. package/dist/state/manifests.test.js +242 -0
  48. package/dist/{src/lib/state → state}/viewer.svelte.d.ts +4 -4
  49. package/dist/state/viewer.svelte.js +693 -0
  50. package/dist/theme/colorUtils.js +196 -0
  51. package/dist/theme/colorUtils.test.d.ts +1 -0
  52. package/dist/theme/colorUtils.test.js +90 -0
  53. package/dist/theme/index.js +52 -0
  54. package/dist/{src/lib/theme → theme}/themeManager.d.ts +4 -1
  55. package/dist/theme/themeManager.js +177 -0
  56. package/dist/theme/types.js +40 -0
  57. package/dist/triiiceratops-bundle.js +4676 -0
  58. package/dist/triiiceratops-element-image.js +1 -1
  59. package/dist/triiiceratops-element.js +1 -1
  60. package/dist/types/config.js +1 -0
  61. package/dist/{src/lib/types → types}/plugin.d.ts +3 -3
  62. package/dist/types/plugin.js +36 -0
  63. package/dist/utils/annotationAdapter.js +354 -0
  64. package/dist/utils/annotationAdapter.test.d.ts +1 -0
  65. package/dist/utils/annotationAdapter.test.js +91 -0
  66. package/package.json +6 -5
  67. package/dist/chunks/TriiiceratopsViewer-CyamQrMe.js +0 -22698
  68. package/dist/plugin-De14WKQl.js +0 -546
  69. package/dist/plugins/image-manipulation.js +0 -454
  70. package/dist/src/lib/components/AnnotationOverlay.svelte.d.ts +0 -1
  71. package/dist/src/lib/components/CanvasNavigation.svelte.d.ts +0 -1
  72. package/dist/src/lib/components/FloatingMenu.svelte.d.ts +0 -1
  73. package/dist/src/lib/components/LeftFab.svelte.d.ts +0 -1
  74. package/dist/src/lib/components/MetadataDialog.svelte.d.ts +0 -1
  75. package/dist/src/lib/components/OSDViewer.svelte.d.ts +0 -1
  76. package/dist/src/lib/components/SearchPanel.svelte.d.ts +0 -1
  77. package/dist/src/lib/components/ThumbnailGallery.svelte.d.ts +0 -1
  78. package/dist/src/lib/components/TriiiceratopsViewer.svelte.d.ts +0 -1
  79. package/dist/src/lib/custom-element-image.d.ts +0 -0
  80. package/dist/src/lib/custom-element.d.ts +0 -0
  81. package/dist/src/lib/paraglide/messages/de.d.ts +0 -96
  82. package/dist/src/lib/paraglide/messages/en.d.ts +0 -96
  83. package/dist/src/lib/paraglide/messages.d.ts +0 -272
  84. package/dist/src/lib/paraglide/runtime.d.ts +0 -52
  85. package/dist/src/lib/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +0 -1
  86. package/dist/src/lib/state/i18n.svelte.d.ts +0 -5
  87. /package/dist/{src/lib/plugins → plugins}/image-manipulation/index.d.ts +0 -0
  88. /package/dist/{src/lib/plugins → plugins}/image-manipulation/types.d.ts +0 -0
  89. /package/dist/{src/lib/state → state}/manifests.svelte.d.ts +0 -0
  90. /package/dist/{src/lib/theme → theme}/colorUtils.d.ts +0 -0
  91. /package/dist/{src/lib/theme → theme}/index.d.ts +0 -0
  92. /package/dist/{src/lib/theme → theme}/types.d.ts +0 -0
  93. /package/dist/{src/lib/types → types}/config.d.ts +0 -0
  94. /package/dist/{src/lib/utils → utils}/annotationAdapter.d.ts +0 -0
@@ -0,0 +1,703 @@
1
+ <script lang="ts">
2
+ import GithubLogo from 'phosphor-svelte/lib/GithubLogo';
3
+ import Gear from 'phosphor-svelte/lib/Gear';
4
+ import Copy from 'phosphor-svelte/lib/Copy';
5
+ import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
6
+ import ThemeToggle from './ThemeToggle.svelte';
7
+
8
+ import { m, language } from '../state/i18n.svelte';
9
+ import { manifestsState } from '../state/manifests.svelte';
10
+ import {
11
+ availableLanguageTags,
12
+ setLanguageTag,
13
+ } from '../paraglide/runtime.js';
14
+
15
+ import { onMount } from 'svelte';
16
+
17
+ const isDev = import.meta.env.DEV;
18
+
19
+ const SUGGESTED_MANIFESTS = [
20
+ {
21
+ label: 'Wellcome Collection (b18035723)',
22
+ url: 'https://iiif.wellcomecollection.org/presentation/v2/b18035723',
23
+ },
24
+ {
25
+ label: 'Self-Portrait Dedicated to Paul Gauguin',
26
+ url: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
27
+ },
28
+ {
29
+ label: 'CSNTM (MNTGRCP40)',
30
+ url: 'https://collections.csntm.org/image-service/iiif/artifacts/MNTGRCP40/default/manifest/',
31
+ },
32
+ {
33
+ label: 'Bodleian Library MS. Ind. Inst. Misc. 22',
34
+ url: 'https://iiif.bodleian.ox.ac.uk/iiif/manifest/e32a277e-91e2-4a6d-8ba6-cc4bad230410.json',
35
+ },
36
+ {
37
+ label: 'Yugoslavia',
38
+ url: 'https://zavicajna.digitalna.rs/iiif/api/presentation/3/96571949-03d6-478e-ab44-a2d5ad68f935%252F00000001%252Fostalo01%252F00000071/manifest',
39
+ },
40
+ ];
41
+
42
+ let {
43
+ manifestUrl = $bindable(),
44
+ onLoad,
45
+ viewerMode = $bindable('core'),
46
+ canvasId = $bindable(''),
47
+ config = $bindable({}),
48
+ } = $props();
49
+
50
+ onMount(() => {
51
+ if (!manifestUrl) {
52
+ manifestUrl = SUGGESTED_MANIFESTS[0].url;
53
+ onLoad();
54
+ }
55
+ });
56
+
57
+ let isCustom = $derived(
58
+ !SUGGESTED_MANIFESTS.some((m) => m.url === manifestUrl),
59
+ );
60
+
61
+ let canvases = $derived(
62
+ manifestUrl ? manifestsState.getCanvases(manifestUrl) : [],
63
+ );
64
+
65
+ function getCanvasLabel(canvas: any, index: number) {
66
+ try {
67
+ if (canvas.getLabel) {
68
+ const l = canvas.getLabel();
69
+ if (Array.isArray(l) && l.length > 0) return l[0].value;
70
+ if (typeof l === 'string') return l;
71
+ } else if (canvas.label) {
72
+ if (typeof canvas.label === 'string') return canvas.label;
73
+ if (
74
+ typeof canvas.label === 'object' &&
75
+ !Array.isArray(canvas.label)
76
+ ) {
77
+ const keys = Object.keys(canvas.label);
78
+ if (keys.length > 0) {
79
+ const val = canvas.label[keys[0]];
80
+ if (Array.isArray(val)) return val[0];
81
+ return val;
82
+ }
83
+ }
84
+ }
85
+ } catch (e) {
86
+ /* ignore */
87
+ }
88
+ return `Canvas ${index + 1}`;
89
+ }
90
+
91
+ function handleSelectChange(e: Event) {
92
+ const value = (e.currentTarget as HTMLSelectElement).value;
93
+ if (value !== 'custom') {
94
+ manifestUrl = value;
95
+ onLoad();
96
+ } else {
97
+ manifestUrl = '';
98
+ }
99
+ }
100
+
101
+ const languageNames: Record<string, string> = {
102
+ en: 'English',
103
+ de: 'Deutsch',
104
+ };
105
+
106
+ function handleKeydown(e: KeyboardEvent) {
107
+ if (e.key === 'Enter') {
108
+ onLoad();
109
+ }
110
+ }
111
+ import Check from 'phosphor-svelte/lib/Check';
112
+
113
+ let copied = $state(false);
114
+
115
+ function copyConfig() {
116
+ navigator.clipboard.writeText(JSON.stringify(config, null, 2));
117
+ copied = true;
118
+ setTimeout(() => {
119
+ copied = false;
120
+ }, 2000);
121
+ }
122
+
123
+ // Initialize from config
124
+ let activeSearchTerm = $state(config.search?.query || '');
125
+ let searchInitialized = false;
126
+
127
+ $effect(() => {
128
+ // Only update if not yet initialized and config has a value (e.g. from URL load)
129
+ if (!searchInitialized && config.search?.query) {
130
+ activeSearchTerm = config.search.query;
131
+ searchInitialized = true;
132
+ }
133
+ });
134
+
135
+ function handleSearchKeydown(e: KeyboardEvent) {
136
+ if (e.key === 'Enter' && config.search) {
137
+ config.search.query = activeSearchTerm;
138
+ }
139
+ }
140
+ </script>
141
+
142
+ <header
143
+ class="flex flex-col bg-base-200 shrink-0 border-b border-base-300 relative z-800"
144
+ >
145
+ <!-- Top Row: Branding & Global Settings -->
146
+ <div class="flex gap-4 items-center p-2 px-4 border-b border-base-300/50">
147
+ <a href="/triiiceratops/" class="btn btn-sm btn-ghost font-bold text-lg"
148
+ >Triiiceratops</a
149
+ >
150
+ <a href="/triiiceratops/" class="btn btn-sm btn-outline btn-primary"
151
+ >{m.docs()}</a
152
+ >
153
+
154
+ <div class="flex-1"></div>
155
+
156
+ <div class="join">
157
+ <div
158
+ class="tooltip tooltip-bottom"
159
+ data-tip={m.viewer_variant_tooltip_core()}
160
+ >
161
+ <input
162
+ class="join-item btn btn-sm"
163
+ type="radio"
164
+ name="viewerMode"
165
+ aria-label={m.viewer_variant_core()}
166
+ value="core"
167
+ bind:group={viewerMode}
168
+ />
169
+ </div>
170
+ <div
171
+ class="tooltip tooltip-bottom"
172
+ data-tip={m.viewer_variant_tooltip_full()}
173
+ >
174
+ <input
175
+ class="join-item btn btn-sm"
176
+ type="radio"
177
+ name="viewerMode"
178
+ aria-label={m.viewer_variant_full()}
179
+ value="image"
180
+ bind:group={viewerMode}
181
+ />
182
+ </div>
183
+ {#if isDev}
184
+ <div
185
+ class="tooltip tooltip-bottom"
186
+ data-tip={m.viewer_variant_tooltip_custom_theme()}
187
+ >
188
+ <input
189
+ class="join-item btn btn-sm"
190
+ type="radio"
191
+ name="viewerMode"
192
+ aria-label={m.viewer_variant_custom_theme()}
193
+ value="custom-theme"
194
+ bind:group={viewerMode}
195
+ />
196
+ </div>
197
+ <div
198
+ class="tooltip tooltip-bottom"
199
+ data-tip={m.viewer_variant_svelte_component_tooltip()}
200
+ >
201
+ <input
202
+ class="join-item btn btn-sm"
203
+ type="radio"
204
+ name="viewerMode"
205
+ aria-label={m.viewer_variant_svelte()}
206
+ value="svelte"
207
+ bind:group={viewerMode}
208
+ />
209
+ </div>
210
+ {/if}
211
+ </div>
212
+
213
+ <select
214
+ class="select select-bordered select-sm w-auto"
215
+ value={language.current}
216
+ onchange={(e) => setLanguageTag(e.currentTarget.value as any)}
217
+ aria-label={m.language_select_label()}
218
+ >
219
+ {#each availableLanguageTags as lang}
220
+ <option value={lang}>{languageNames[lang] || lang}</option>
221
+ {/each}
222
+ </select>
223
+
224
+ <ThemeToggle />
225
+
226
+ <!-- Settings Dropdown -->
227
+ <div class="dropdown dropdown-end group">
228
+ <div
229
+ tabindex="0"
230
+ role="button"
231
+ class="btn btn-ghost btn-sm"
232
+ aria-label={m.settings_label()}
233
+ >
234
+ <Gear size={20} />
235
+ </div>
236
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
237
+ <ul
238
+ tabindex="-1"
239
+ 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"
240
+ >
241
+ <li class="menu-title px-4 py-2">
242
+ {m.settings_category_general()}
243
+ </li>
244
+ <li>
245
+ <label class="label cursor-pointer py-1">
246
+ <span class="label-text"
247
+ >{m.settings_toggle_left_menu()}</span
248
+ >
249
+ <input
250
+ type="checkbox"
251
+ class="toggle toggle-sm"
252
+ bind:checked={config.showLeftMenu}
253
+ />
254
+ </label>
255
+ </li>
256
+ <li>
257
+ <label class="label cursor-pointer py-1">
258
+ <span class="label-text"
259
+ >{m.settings_toggle_right_menu()}</span
260
+ >
261
+ <input
262
+ type="checkbox"
263
+ class="toggle toggle-sm"
264
+ bind:checked={config.showRightMenu}
265
+ />
266
+ </label>
267
+ </li>
268
+ <li>
269
+ <label class="label cursor-pointer py-1">
270
+ <span class="label-text"
271
+ >{m.settings_toggle_canvas_nav()}</span
272
+ >
273
+ <input
274
+ type="checkbox"
275
+ class="toggle toggle-sm"
276
+ bind:checked={config.showCanvasNav}
277
+ />
278
+ </label>
279
+ </li>
280
+
281
+ <div class="divider my-1"></div>
282
+
283
+ <li class="menu-title px-4 py-2">
284
+ {m.settings_category_configuration()}
285
+ </li>
286
+
287
+ <li>
288
+ <details>
289
+ <summary
290
+ >{m.settings_submenu_right_menu_items()}</summary
291
+ >
292
+ <ul>
293
+ <li>
294
+ <label class="label cursor-pointer py-1">
295
+ <span class="label-text"
296
+ >{m.settings_toggle_show_search()}</span
297
+ >
298
+ <input
299
+ type="checkbox"
300
+ class="checkbox checkbox-xs"
301
+ checked={config.rightMenu?.showSearch ??
302
+ true}
303
+ onchange={(e) => {
304
+ if (!config.rightMenu)
305
+ config.rightMenu = {};
306
+ config.rightMenu.showSearch =
307
+ e.currentTarget.checked;
308
+ }}
309
+ />
310
+ </label>
311
+ </li>
312
+ <li>
313
+ <label class="label cursor-pointer py-1">
314
+ <span class="label-text"
315
+ >{m.settings_toggle_show_gallery()}</span
316
+ >
317
+ <input
318
+ type="checkbox"
319
+ class="checkbox checkbox-xs"
320
+ checked={config.rightMenu
321
+ ?.showGallery ?? true}
322
+ onchange={(e) => {
323
+ if (!config.rightMenu)
324
+ config.rightMenu = {};
325
+ config.rightMenu.showGallery =
326
+ e.currentTarget.checked;
327
+ }}
328
+ />
329
+ </label>
330
+ </li>
331
+ <li>
332
+ <label class="label cursor-pointer py-1">
333
+ <span class="label-text"
334
+ >{m.settings_toggle_show_annotations()}</span
335
+ >
336
+ <input
337
+ type="checkbox"
338
+ class="checkbox checkbox-xs"
339
+ checked={config.rightMenu
340
+ ?.showAnnotations ?? true}
341
+ onchange={(e) => {
342
+ if (!config.rightMenu)
343
+ config.rightMenu = {};
344
+ config.rightMenu.showAnnotations =
345
+ e.currentTarget.checked;
346
+ }}
347
+ />
348
+ </label>
349
+ </li>
350
+ <li>
351
+ <label class="label cursor-pointer py-1">
352
+ <span class="label-text"
353
+ >{m.settings_toggle_show_fullscreen()}</span
354
+ >
355
+ <input
356
+ type="checkbox"
357
+ class="checkbox checkbox-xs"
358
+ checked={config.rightMenu
359
+ ?.showFullscreen ?? true}
360
+ onchange={(e) => {
361
+ if (!config.rightMenu)
362
+ config.rightMenu = {};
363
+ config.rightMenu.showFullscreen =
364
+ e.currentTarget.checked;
365
+ }}
366
+ />
367
+ </label>
368
+ </li>
369
+ <li>
370
+ <label class="label cursor-pointer py-1">
371
+ <span class="label-text"
372
+ >{m.settings_toggle_show_info()}</span
373
+ >
374
+ <input
375
+ type="checkbox"
376
+ class="checkbox checkbox-xs"
377
+ checked={config.rightMenu?.showInfo ??
378
+ true}
379
+ onchange={(e) => {
380
+ if (!config.rightMenu)
381
+ config.rightMenu = {};
382
+ config.rightMenu.showInfo =
383
+ e.currentTarget.checked;
384
+ }}
385
+ />
386
+ </label>
387
+ </li>
388
+ </ul>
389
+ </details>
390
+ </li>
391
+
392
+ <li>
393
+ <details>
394
+ <summary>{m.settings_submenu_gallery()}</summary>
395
+ <ul>
396
+ <li>
397
+ <label class="label cursor-pointer py-1">
398
+ <span class="label-text"
399
+ >{m.settings_toggle_open()}</span
400
+ >
401
+ <input
402
+ type="checkbox"
403
+ class="toggle toggle-xs"
404
+ checked={config.gallery?.open ?? false}
405
+ onchange={(e) => {
406
+ if (!config.gallery)
407
+ config.gallery = {};
408
+ config.gallery.open =
409
+ e.currentTarget.checked;
410
+ }}
411
+ />
412
+ </label>
413
+ </li>
414
+ <li>
415
+ <label class="label cursor-pointer py-1">
416
+ <span class="label-text"
417
+ >{m.settings_toggle_draggable()}</span
418
+ >
419
+ <input
420
+ type="checkbox"
421
+ class="checkbox checkbox-xs"
422
+ checked={config.gallery?.draggable ??
423
+ true}
424
+ onchange={(e) => {
425
+ if (!config.gallery)
426
+ config.gallery = {};
427
+ config.gallery.draggable =
428
+ e.currentTarget.checked;
429
+ }}
430
+ />
431
+ </label>
432
+ </li>
433
+ <li>
434
+ <label class="label cursor-pointer py-1">
435
+ <span class="label-text"
436
+ >{m.settings_toggle_close_button()}</span
437
+ >
438
+ <input
439
+ type="checkbox"
440
+ class="checkbox checkbox-xs"
441
+ checked={config.gallery
442
+ ?.showCloseButton ?? true}
443
+ onchange={(e) => {
444
+ if (!config.gallery)
445
+ config.gallery = {};
446
+ config.gallery.showCloseButton =
447
+ e.currentTarget.checked;
448
+ }}
449
+ />
450
+ </label>
451
+ </li>
452
+ <li>
453
+ <label class="label cursor-pointer py-1 gap-2">
454
+ <span class="label-text"
455
+ >{m.settings_select_dock_position()}</span
456
+ >
457
+ <select
458
+ class="select select-bordered select-xs w-24"
459
+ value={config.gallery?.dockPosition ??
460
+ 'bottom'}
461
+ onchange={(e) => {
462
+ if (!config.gallery)
463
+ config.gallery = {};
464
+ config.gallery.dockPosition = (
465
+ e.currentTarget as HTMLSelectElement
466
+ ).value;
467
+ }}
468
+ onclick={(e) => e.stopPropagation()}
469
+ >
470
+ <option value="bottom"
471
+ >{m.settings_position_bottom()}</option
472
+ >
473
+ <option value="top"
474
+ >{m.settings_position_top()}</option
475
+ >
476
+ <option value="left"
477
+ >{m.settings_position_left()}</option
478
+ >
479
+ <option value="right"
480
+ >{m.settings_position_right()}</option
481
+ >
482
+ <option value="none"
483
+ >{m.settings_position_floating()}</option
484
+ >
485
+ </select>
486
+ </label>
487
+ </li>
488
+ </ul>
489
+ </details>
490
+ </li>
491
+
492
+ <li>
493
+ <details>
494
+ <summary>{m.settings_submenu_search()}</summary>
495
+ <ul>
496
+ <li>
497
+ <label class="label cursor-pointer py-1">
498
+ <span class="label-text"
499
+ >{m.settings_toggle_open()}</span
500
+ >
501
+ <input
502
+ type="checkbox"
503
+ class="toggle toggle-xs"
504
+ checked={config.search?.open ?? false}
505
+ onchange={(e) => {
506
+ if (!config.search)
507
+ config.search = {};
508
+ config.search.open =
509
+ e.currentTarget.checked;
510
+ }}
511
+ />
512
+ </label>
513
+ </li>
514
+ <li>
515
+ <label class="label cursor-pointer py-1">
516
+ <span class="label-text"
517
+ >{m.settings_toggle_close_button()}</span
518
+ >
519
+ <input
520
+ type="checkbox"
521
+ class="checkbox checkbox-xs"
522
+ checked={config.search
523
+ ?.showCloseButton ?? true}
524
+ onchange={(e) => {
525
+ if (!config.search)
526
+ config.search = {};
527
+ config.search.showCloseButton =
528
+ e.currentTarget.checked;
529
+ }}
530
+ />
531
+ </label>
532
+ </li>
533
+ </ul>
534
+ </details>
535
+ </li>
536
+
537
+ <li>
538
+ <details>
539
+ <summary>{m.settings_submenu_annotations()}</summary>
540
+ <ul>
541
+ <li>
542
+ <label class="label cursor-pointer py-1">
543
+ <span class="label-text"
544
+ >{m.settings_toggle_panel_open()}</span
545
+ >
546
+ <input
547
+ type="checkbox"
548
+ class="toggle toggle-xs"
549
+ checked={config.annotations?.open ??
550
+ false}
551
+ onchange={(e) => {
552
+ if (!config.annotations)
553
+ config.annotations = {};
554
+ config.annotations.open =
555
+ e.currentTarget.checked;
556
+ }}
557
+ />
558
+ </label>
559
+ </li>
560
+ <li>
561
+ <label class="label cursor-pointer py-1">
562
+ <span class="label-text"
563
+ >{m.settings_toggle_visible_by_default()}</span
564
+ >
565
+ <input
566
+ type="checkbox"
567
+ class="checkbox checkbox-xs"
568
+ checked={config.annotations?.visible ??
569
+ true}
570
+ onchange={(e) => {
571
+ if (!config.annotations)
572
+ config.annotations = {};
573
+ config.annotations.visible =
574
+ e.currentTarget.checked;
575
+ }}
576
+ />
577
+ </label>
578
+ </li>
579
+ </ul>
580
+ </details>
581
+ </li>
582
+ <div class="divider my-1"></div>
583
+ <li>
584
+ <button
585
+ class="btn btn-sm btn-ghost w-full justify-start gap-2"
586
+ class:text-success={copied}
587
+ onclick={copyConfig}
588
+ >
589
+ {#if copied}
590
+ <Check size={16} />
591
+ {m.copied()}
592
+ {:else}
593
+ <Copy size={16} />
594
+ {m.copy_config()}
595
+ {/if}
596
+ </button>
597
+ </li>
598
+ </ul>
599
+ </div>
600
+
601
+ <div class="tooltip tooltip-bottom" data-tip={m.github()}>
602
+ <a
603
+ href="https://github.com/d-flood/triiiceratops"
604
+ class="btn btn-ghost btn-sm"
605
+ >
606
+ <GithubLogo size={20} />
607
+ </a>
608
+ </div>
609
+ </div>
610
+
611
+ <!-- Bottom Row: External Controls -->
612
+ <div class="flex gap-4 items-center p-2 px-4 bg-base-300/30">
613
+ <span class="text-xs font-bold uppercase tracking-wider opacity-70"
614
+ >{m.demo_header_external_controls()}</span
615
+ >
616
+
617
+ <!-- Manifest Selector -->
618
+ <div class="flex gap-2 items-center">
619
+ <label
620
+ for="manifest-select"
621
+ class="text-base-content text-sm whitespace-nowrap sr-only"
622
+ >
623
+ {m.iiif_manifest_label()}
624
+ </label>
625
+ <div class="flex gap-2 items-center">
626
+ <select
627
+ id="manifest-select"
628
+ class="select select-bordered select-xs max-w-xs"
629
+ value={isCustom ? 'custom' : manifestUrl}
630
+ onchange={handleSelectChange}
631
+ >
632
+ {#each SUGGESTED_MANIFESTS as manifest}
633
+ <option value={manifest.url}>{manifest.label}</option>
634
+ {/each}
635
+ <option value="custom">{m.try_your_own()}</option>
636
+ </select>
637
+
638
+ {#if isCustom}
639
+ <input
640
+ id="manifest-input"
641
+ type="text"
642
+ bind:value={manifestUrl}
643
+ onkeydown={handleKeydown}
644
+ placeholder={m.manifest_placeholder()}
645
+ class="input input-bordered input-xs w-[300px] fade-in"
646
+ autocomplete="off"
647
+ />
648
+ <button onclick={onLoad} class="btn btn-primary btn-xs">
649
+ {m.load()}
650
+ </button>
651
+ {/if}
652
+ </div>
653
+ </div>
654
+
655
+ <div class="w-px h-4 bg-base-content/20 mx-2"></div>
656
+
657
+ <!-- Canvas Selector -->
658
+ <div class="flex gap-2 items-center">
659
+ <label class="text-xs opacity-70" for="canvas-id-select">
660
+ {m.demo_header_active_canvas()}
661
+ </label>
662
+ <select
663
+ id="canvas-id-select"
664
+ bind:value={canvasId}
665
+ class="select select-bordered select-xs w-[200px]"
666
+ disabled={canvases.length === 0}
667
+ >
668
+ {#if canvases.length === 0}
669
+ <option value="" disabled>{m.no_canvases_loaded()}</option>
670
+ {:else}
671
+ {#each canvases as canvas, i}
672
+ <option value={canvas.id}>
673
+ {getCanvasLabel(canvas, i)}
674
+ </option>
675
+ {/each}
676
+ {/if}
677
+ </select>
678
+ </div>
679
+
680
+ <div class="w-px h-4 bg-base-content/20 mx-2"></div>
681
+
682
+ <!-- Search Input -->
683
+ <div class="flex gap-2 items-center">
684
+ <label
685
+ class="text-xs opacity-70 flex items-center gap-1"
686
+ for="external-search-input"
687
+ >
688
+ <MagnifyingGlass size={14} />
689
+ <span class="sr-only">{m.search()}</span>
690
+ </label>
691
+ {#if config.search}
692
+ <input
693
+ id="external-search-input"
694
+ type="text"
695
+ placeholder={m.search_panel_placeholder()}
696
+ class="input input-bordered input-xs w-[150px]"
697
+ bind:value={activeSearchTerm}
698
+ onkeydown={handleSearchKeydown}
699
+ />
700
+ {/if}
701
+ </div>
702
+ </div>
703
+ </header>