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,601 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import X from 'phosphor-svelte/lib/X';
4
+ import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
5
+
6
+ // Minimal canvas/annotation types covering methods used here
7
+ type ManifestService = {
8
+ id?: string;
9
+ ['@id']?: string;
10
+ profile?: unknown;
11
+ getProfile?: () => unknown;
12
+ };
13
+
14
+ type ManifestResource =
15
+ | {
16
+ id?: string;
17
+ ['@id']?: string;
18
+ __jsonld?: any;
19
+ getServices?: () => ManifestService[];
20
+ }
21
+ | any;
22
+
23
+ type ManifestAnnotation =
24
+ | {
25
+ __jsonld?: any;
26
+ getResource?: () => ManifestResource | null;
27
+ getBody?: () => ManifestResource | ManifestResource[] | null;
28
+ body?: ManifestResource | ManifestResource[];
29
+ }
30
+ | any;
31
+
32
+ type ManifestCanvas =
33
+ | {
34
+ id: string;
35
+ getLabel: () => { value: string }[];
36
+ getThumbnail?: () =>
37
+ | string
38
+ | { id?: string; ['@id']?: string }
39
+ | null;
40
+ getImages?: () => ManifestAnnotation[];
41
+ getContent?: () => ManifestAnnotation[];
42
+ }
43
+ | any;
44
+
45
+ const viewerState = getContext<ViewerState>(VIEWER_STATE_KEY);
46
+
47
+ // Config shorthands
48
+ let draggable = $derived(viewerState.config.gallery?.draggable ?? true);
49
+ let showCloseButton = $derived(
50
+ viewerState.config.gallery?.showCloseButton ?? true,
51
+ );
52
+
53
+ let { canvases } = $props<{ canvases?: ManifestCanvas[] }>();
54
+
55
+ let isResizing = $state(false);
56
+ let resizeStart: { x: number; y: number; w: number; h: number } = {
57
+ x: 0,
58
+ y: 0,
59
+ w: 0,
60
+ h: 0,
61
+ };
62
+ let galleryElement: HTMLElement | null = $state(null);
63
+
64
+ // Generate thumbnail data
65
+ let thumbnails = $derived.by(() => {
66
+ if (!canvases || !Array.isArray(canvases))
67
+ return [] as Array<{
68
+ id: string;
69
+ label: string;
70
+ src: string;
71
+ index: number;
72
+ }>;
73
+ return canvases.map((canvas: ManifestCanvas, index: number) => {
74
+ // Manifesto getThumbnail logic
75
+ let src = '';
76
+ try {
77
+ if (canvas.getThumbnail) {
78
+ const thumb = canvas.getThumbnail();
79
+ if (thumb) {
80
+ src =
81
+ typeof thumb === 'string'
82
+ ? thumb
83
+ : thumb.id || thumb['@id'];
84
+ }
85
+ }
86
+ } catch (e) {
87
+ console.warn('Error getting thumbnail', e);
88
+ }
89
+
90
+ // Fallback to first image if no thumbnail service
91
+ if (!src) {
92
+ // Use Manifesto to get images
93
+ let images = canvas.getImages();
94
+
95
+ // Fallback for IIIF v3: iterate content if images is empty
96
+ if ((!images || !images.length) && canvas.getContent) {
97
+ images = canvas.getContent();
98
+ }
99
+
100
+ if (images && images.length > 0) {
101
+ const annotation: ManifestAnnotation = images[0];
102
+ let resource = annotation.getResource
103
+ ? annotation.getResource()
104
+ : null;
105
+
106
+ // v3 fallback: getBody
107
+ if (!resource && annotation.getBody) {
108
+ const body = annotation.getBody();
109
+ if (Array.isArray(body) && body.length > 0)
110
+ resource = body[0];
111
+ else if (body) resource = body;
112
+ }
113
+
114
+ if (
115
+ resource &&
116
+ !resource.id &&
117
+ !resource.__jsonld &&
118
+ (!resource.getServices ||
119
+ resource.getServices().length === 0)
120
+ ) {
121
+ resource = null;
122
+ }
123
+
124
+ if (!resource) {
125
+ // raw json fallback
126
+ const json = annotation.__jsonld || annotation;
127
+ if (json.body) {
128
+ resource = Array.isArray(json.body)
129
+ ? json.body[0]
130
+ : json.body;
131
+ }
132
+ }
133
+
134
+ if (resource) {
135
+ const getServices = () => {
136
+ let s: ManifestService[] = [];
137
+ if (resource.getServices) {
138
+ s = resource.getServices();
139
+ }
140
+ if (!s || s.length === 0) {
141
+ const rJson = resource.__jsonld || resource;
142
+ if (rJson.service) {
143
+ s = Array.isArray(rJson.service)
144
+ ? rJson.service
145
+ : [rJson.service];
146
+ }
147
+ }
148
+ return s;
149
+ };
150
+
151
+ const services = getServices();
152
+ let isLevel0 = false;
153
+ if (services.length > 0) {
154
+ const service = services[0];
155
+ let profile: unknown = '';
156
+ try {
157
+ profile = service.getProfile
158
+ ? service.getProfile()
159
+ : (service.profile as unknown) || '';
160
+ // Handle Manifesto profile object
161
+ if (typeof profile === 'object' && profile) {
162
+ const pObj = profile as Record<
163
+ string,
164
+ unknown
165
+ >;
166
+ profile =
167
+ (pObj.value as string | undefined) ||
168
+ (pObj.id as string | undefined) ||
169
+ (pObj['@id'] as string | undefined) ||
170
+ JSON.stringify(pObj);
171
+ }
172
+ } catch (e) {
173
+ // ignore
174
+ }
175
+
176
+ const pStr = String(profile ?? '').toLowerCase();
177
+ if (
178
+ pStr.includes('level0') ||
179
+ pStr.includes('level-0')
180
+ ) {
181
+ isLevel0 = true;
182
+ }
183
+
184
+ const serviceId = service.id || service['@id'];
185
+
186
+ if (!isLevel0) {
187
+ src = `${serviceId}/full/200,/0/default.jpg`;
188
+ }
189
+ }
190
+
191
+ if (!src) {
192
+ src = resource.id || resource['@id'];
193
+
194
+ // Fallback: if resource was reconstructed but ID is missing (common with Manifesto v3), check raw annotation
195
+ if (!src) {
196
+ let rawBody: any = null;
197
+ // Try to find the original raw body from which resource was derived
198
+ if (
199
+ annotation.__jsonld &&
200
+ annotation.__jsonld.body
201
+ ) {
202
+ rawBody = annotation.__jsonld.body;
203
+ } else if (annotation.body) {
204
+ rawBody = annotation.body;
205
+ }
206
+
207
+ if (rawBody) {
208
+ const bodyObj = Array.isArray(rawBody)
209
+ ? rawBody[0]
210
+ : rawBody;
211
+ src = bodyObj.id || bodyObj['@id'];
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ return {
220
+ id: canvas.id,
221
+ label: canvas.getLabel().length
222
+ ? canvas.getLabel()[0].value
223
+ : `Canvas ${index + 1}`,
224
+ src,
225
+ index,
226
+ };
227
+ });
228
+ });
229
+
230
+ function onDrag(e: MouseEvent) {
231
+ if (!viewerState.isGalleryDragging) return;
232
+
233
+ // Simple fixed positioning logic
234
+ let newX = e.clientX - viewerState.galleryDragOffset.x;
235
+ let newY = e.clientY - viewerState.galleryDragOffset.y;
236
+
237
+ // Constrain to Window (Viewport)
238
+ const maxX = Math.max(
239
+ 0,
240
+ window.innerWidth - viewerState.gallerySize.width,
241
+ );
242
+ const maxY = Math.max(
243
+ 0,
244
+ window.innerHeight - viewerState.gallerySize.height,
245
+ );
246
+ newX = Math.max(0, Math.min(newX, maxX));
247
+ newY = Math.max(0, Math.min(newY, maxY));
248
+
249
+ viewerState.galleryPosition = { x: newX, y: newY };
250
+
251
+ // Use the stored center panel rect (captured at drag start, works with shadow DOM)
252
+ const rect = viewerState.galleryCenterPanelRect;
253
+ if (!rect) {
254
+ console.warn('[Gallery] No center panel rect available');
255
+ return;
256
+ }
257
+
258
+ const x = e.clientX;
259
+ const y = e.clientY;
260
+
261
+ // Threshold for docking detection (pixels)
262
+ const THRESHOLD = 60;
263
+
264
+ // Reset
265
+ viewerState.dragOverSide = null;
266
+
267
+ // Check boundaries
268
+ if (x >= rect.left && x <= rect.left + THRESHOLD) {
269
+ viewerState.dragOverSide = 'left';
270
+ } else if (x <= rect.right && x >= rect.right - THRESHOLD) {
271
+ viewerState.dragOverSide = 'right';
272
+ } else if (y >= rect.top && y <= rect.top + THRESHOLD) {
273
+ viewerState.dragOverSide = 'top';
274
+ } else if (y <= rect.bottom && y >= rect.bottom - THRESHOLD) {
275
+ viewerState.dragOverSide = 'bottom';
276
+ }
277
+ }
278
+
279
+ function stopDrag() {
280
+ // If we were dragging towards a dock zone
281
+ const dropTarget = viewerState.dragOverSide;
282
+ console.log('[Gallery] stopDrag. dropTarget:', dropTarget);
283
+
284
+ viewerState.isGalleryDragging = false;
285
+ viewerState.dragOverSide = null;
286
+ window.removeEventListener('mousemove', onDrag);
287
+ window.removeEventListener('mouseup', stopDrag);
288
+
289
+ // Commit drop
290
+ if (dropTarget) {
291
+ viewerState.dockSide = dropTarget;
292
+ }
293
+ }
294
+
295
+ function startResize(e: MouseEvent) {
296
+ e.stopPropagation(); // Prevent drag
297
+ isResizing = true;
298
+ resizeStart = {
299
+ x: e.clientX,
300
+ y: e.clientY,
301
+ w: viewerState.gallerySize.width,
302
+ h: viewerState.gallerySize.height,
303
+ };
304
+ window.addEventListener('mousemove', onResize);
305
+ window.addEventListener('mouseup', stopResize);
306
+ }
307
+
308
+ function onResize(e: MouseEvent) {
309
+ if (!isResizing) return;
310
+ const dx = e.clientX - resizeStart.x;
311
+ const dy = e.clientY - resizeStart.y;
312
+ viewerState.gallerySize = {
313
+ width: Math.max(200, resizeStart.w + dx),
314
+ height: Math.max(200, resizeStart.h + dy),
315
+ };
316
+ }
317
+
318
+ function stopResize() {
319
+ isResizing = false;
320
+ window.removeEventListener('mousemove', onResize);
321
+ window.removeEventListener('mouseup', stopResize);
322
+ }
323
+
324
+ function selectCanvas(canvasId: string) {
325
+ viewerState.setCanvas(canvasId);
326
+ }
327
+
328
+ // State for docking
329
+ // We default to bottom, but we should sync with viewerState immediately?
330
+ // Actually dockSide *is* viewerState.dockSide essentially.
331
+ // We can just use viewerState.dockSide and provide a local setter?
332
+ // Using a local proxy to sync back and forth:
333
+ let dockSide: 'none' | 'top' | 'bottom' | 'left' | 'right' = $state(
334
+ viewerState.dockSide as 'none' | 'top' | 'bottom' | 'left' | 'right',
335
+ );
336
+
337
+ // Sync external changes
338
+ $effect(() => {
339
+ const ds = viewerState.dockSide as string;
340
+ dockSide =
341
+ ds === 'none' ||
342
+ ds === 'top' ||
343
+ ds === 'bottom' ||
344
+ ds === 'left' ||
345
+ ds === 'right'
346
+ ? (ds as 'none' | 'top' | 'bottom' | 'left' | 'right')
347
+ : 'none';
348
+ });
349
+
350
+ // Sync internal changes
351
+ $effect(() => {
352
+ if (viewerState.dockSide !== dockSide) {
353
+ viewerState.dockSide = dockSide;
354
+ viewerState.isGalleryDockedBottom = dockSide === 'bottom';
355
+ viewerState.isGalleryDockedRight = dockSide === 'right';
356
+ }
357
+ });
358
+
359
+ // Switch to horizontal layout if height is small or docked to top/bottom
360
+ let isHorizontal = $derived(
361
+ dockSide === 'top' ||
362
+ dockSide === 'bottom' ||
363
+ (dockSide === 'none' && viewerState.gallerySize.height < 320),
364
+ );
365
+
366
+ function startDrag(e: MouseEvent) {
367
+ if (!draggable) return; // Dragging disabled in config
368
+ if ((e.target as HTMLElement).closest('.resize-handle')) return; // Don't drag if resizing
369
+
370
+ const wasDocked = dockSide !== 'none';
371
+
372
+ // Calculate position and offset first (no state changes yet)
373
+ if (wasDocked) {
374
+ // Center on mouse logic is still good for UX
375
+ let centeredX = Math.max(0, e.clientX - 150);
376
+ let centeredY = Math.max(0, e.clientY - 20);
377
+
378
+ // Constrain initial position so it doesn't jump off-screen if undocking near edges
379
+ const maxInitialX = Math.max(0, window.innerWidth - 300);
380
+ const maxInitialY = Math.max(0, window.innerHeight - 400);
381
+
382
+ centeredX = Math.min(centeredX, maxInitialX);
383
+ centeredY = Math.min(centeredY, maxInitialY);
384
+
385
+ viewerState.galleryPosition = { x: centeredX, y: centeredY };
386
+ viewerState.galleryDragOffset = {
387
+ x: e.clientX - centeredX,
388
+ y: e.clientY - centeredY,
389
+ };
390
+ } else {
391
+ // Already floating
392
+ viewerState.galleryDragOffset = {
393
+ x: e.clientX - viewerState.galleryPosition.x,
394
+ y: e.clientY - viewerState.galleryPosition.y,
395
+ };
396
+ }
397
+
398
+ // CRITICAL: Capture center panel rect BEFORE undocking
399
+ // Use getRootNode() to work inside shadow DOM
400
+ const root = galleryElement?.getRootNode() as Document | ShadowRoot;
401
+ const centerPanel =
402
+ root?.getElementById?.('triiiceratops-center-panel') ??
403
+ document.getElementById('triiiceratops-center-panel');
404
+ if (centerPanel) {
405
+ viewerState.galleryCenterPanelRect =
406
+ centerPanel.getBoundingClientRect();
407
+ console.log(
408
+ '[Gallery] Captured center panel rect:',
409
+ viewerState.galleryCenterPanelRect,
410
+ );
411
+ } else {
412
+ console.warn('[Gallery] Could not find center panel in startDrag');
413
+ }
414
+
415
+ // CRITICAL: Set dragging state and attach listeners BEFORE changing dockSide
416
+ // This ensures listeners persist even if component unmounts
417
+ viewerState.isGalleryDragging = true;
418
+ window.addEventListener('mousemove', onDrag);
419
+ window.addEventListener('mouseup', stopDrag);
420
+
421
+ // NOW undock - this may cause component remount, but listeners are already attached
422
+ if (wasDocked) {
423
+ dockSide = 'none';
424
+ }
425
+ }
426
+ </script>
427
+
428
+ {#if viewerState.showThumbnailGallery}
429
+ <!-- Floating Window -->
430
+ <div
431
+ bind:this={galleryElement}
432
+ class={(dockSide !== 'none'
433
+ ? `relative z-50 bg-base-100 shadow-xl border-base-300 flex transition-all duration-200 select-none w-full h-full
434
+ ${dockSide === 'bottom' || dockSide === 'top' ? 'flex-row border-t' : ''}
435
+ ${dockSide === 'left' || dockSide === 'right' ? 'flex-col border-x' : ''}`
436
+ : 'fixed z-900 bg-base-100 shadow-2xl rounded-lg flex flex-col border border-base-300 overflow-hidden select-none') +
437
+ (viewerState.isGalleryDragging
438
+ ? ' pointer-events-none opacity-80'
439
+ : '')}
440
+ style={dockSide !== 'none'
441
+ ? ''
442
+ : `left: ${viewerState.galleryPosition.x}px; top: ${viewerState.galleryPosition.y}px; width: ${viewerState.gallerySize.width}px; height: ${viewerState.gallerySize.height}px;`}
443
+ >
444
+ <!-- Close Button (show when enabled in config, regardless of dock state) -->
445
+ {#if showCloseButton}
446
+ <button
447
+ class="absolute top-1 right-1 btn btn-error btn-xs btn-circle z-20"
448
+ onclick={() => viewerState.toggleThumbnailGallery()}
449
+ aria-label="Close Gallery"
450
+ >
451
+ <X size={16} weight="bold" />
452
+ </button>
453
+ {/if}
454
+
455
+ <!-- Header Area (only show drag handle when draggable OR when floating) -->
456
+ {#if draggable || dockSide === 'none'}
457
+ <div
458
+ class={'bg-base-100 flex shrink-0 select-none relative ' +
459
+ (dockSide === 'bottom' || dockSide === 'top'
460
+ ? 'flex-row h-full items-center border-r border-base-200'
461
+ : 'flex-col w-full border-b border-base-200')}
462
+ >
463
+ <!-- Drag Handle -->
464
+ <div
465
+ class={'cursor-move flex items-center justify-center hover:bg-base-200/50 active:bg-base-200 transition-colors ' +
466
+ (dockSide === 'bottom' || dockSide === 'top'
467
+ ? 'w-8 h-full'
468
+ : 'h-6 w-full')}
469
+ onmousedown={startDrag}
470
+ role="button"
471
+ tabindex="0"
472
+ aria-label="Drag Gallery"
473
+ >
474
+ <div
475
+ class={'bg-base-300 rounded-full ' +
476
+ (dockSide === 'bottom' || dockSide === 'top'
477
+ ? 'w-1.5 h-12'
478
+ : 'w-12 h-1.5')}
479
+ ></div>
480
+ </div>
481
+ </div>
482
+ {/if}
483
+
484
+ <!-- Content (Grid or Horizontal Scroll) -->
485
+ <div
486
+ class="flex-1 p-1 bg-base-100 {isHorizontal
487
+ ? 'overflow-x-auto overflow-y-hidden h-full'
488
+ : 'overflow-y-auto overflow-x-hidden'}"
489
+ >
490
+ <div
491
+ class={isHorizontal
492
+ ? 'flex flex-row gap-2 h-full items-center'
493
+ : 'grid gap-2'}
494
+ style={isHorizontal
495
+ ? ''
496
+ : 'grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));'}
497
+ >
498
+ {#each thumbnails as thumb}
499
+ <button
500
+ class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
501
+ ? 'w-[140px]'
502
+ : ''} {viewerState.canvasId === thumb.id
503
+ ? 'ring-2 ring-primary bg-primary/5'
504
+ : ''}"
505
+ onclick={() => selectCanvas(thumb.id)}
506
+ aria-label="Select canvas {thumb.label}"
507
+ >
508
+ <div
509
+ class="aspect-4/3 bg-base-300 rounded overflow-hidden relative w-full flex items-center justify-center"
510
+ >
511
+ {#if thumb.src}
512
+ <img
513
+ src={thumb.src}
514
+ alt={thumb.label}
515
+ class="object-contain w-full h-full"
516
+ loading="lazy"
517
+ draggable="false"
518
+ />
519
+ {:else}
520
+ <span class="opacity-20 text-4xl">?</span>
521
+ {/if}
522
+ </div>
523
+ <div
524
+ class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
525
+ >
526
+ <span class="font-bold mr-1"
527
+ >{thumb.index + 1}.</span
528
+ >
529
+ {thumb.label}
530
+ </div>
531
+ </button>
532
+ {/each}
533
+ </div>
534
+ </div>
535
+
536
+ <!-- Resize Handle -->
537
+ {#if dockSide === 'none'}
538
+ <div
539
+ class="absolute bottom-0 right-0 w-6 h-6 cursor-se-resize resize-handle bg-accent hover:bg-accent-focus transition-colors z-50"
540
+ style="clip-path: polygon(100% 0, 0 100%, 100% 100%);"
541
+ onmousedown={startResize}
542
+ role="button"
543
+ tabindex="0"
544
+ aria-label="Resize"
545
+ ></div>
546
+ {/if}
547
+ </div>
548
+
549
+ {#if viewerState.isGalleryDragging}
550
+ <!-- Drop Zones -->
551
+ <!-- Top -->
552
+ <div
553
+ 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 ===
554
+ 'top'
555
+ ? 'bg-primary/20 scale-105'
556
+ : 'bg-base-100/50'}"
557
+ role="group"
558
+ >
559
+ <span class="font-bold text-primary opacity-50">Dock Top</span>
560
+ </div>
561
+
562
+ <!-- Bottom -->
563
+ <div
564
+ 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 ===
565
+ 'bottom'
566
+ ? 'bg-primary/20 scale-105'
567
+ : 'bg-base-100/50'}"
568
+ role="group"
569
+ >
570
+ <span class="font-bold text-primary opacity-50">Dock Bottom</span>
571
+ </div>
572
+
573
+ <!-- Left -->
574
+ <div
575
+ 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 ===
576
+ 'left'
577
+ ? 'bg-primary/20 scale-105'
578
+ : 'bg-base-100/50'}"
579
+ role="group"
580
+ >
581
+ <span
582
+ class="font-bold text-primary opacity-50 vertical-rl rotate-180"
583
+ style="writing-mode: vertical-rl;">Dock Left</span
584
+ >
585
+ </div>
586
+
587
+ <!-- Right -->
588
+ <div
589
+ 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 ===
590
+ 'right'
591
+ ? 'bg-primary/20 scale-105'
592
+ : 'bg-base-100/50'}"
593
+ role="group"
594
+ >
595
+ <span
596
+ class="font-bold text-primary opacity-50 vertical-rl rotate-180"
597
+ style="writing-mode: vertical-rl;">Dock Right</span
598
+ >
599
+ </div>
600
+ {/if}
601
+ {/if}
@@ -0,0 +1,36 @@
1
+ type ManifestService = {
2
+ id?: string;
3
+ ['@id']?: string;
4
+ profile?: unknown;
5
+ getProfile?: () => unknown;
6
+ };
7
+ type ManifestResource = {
8
+ id?: string;
9
+ ['@id']?: string;
10
+ __jsonld?: any;
11
+ getServices?: () => ManifestService[];
12
+ } | any;
13
+ type ManifestAnnotation = {
14
+ __jsonld?: any;
15
+ getResource?: () => ManifestResource | null;
16
+ getBody?: () => ManifestResource | ManifestResource[] | null;
17
+ body?: ManifestResource | ManifestResource[];
18
+ } | any;
19
+ type ManifestCanvas = {
20
+ id: string;
21
+ getLabel: () => {
22
+ value: string;
23
+ }[];
24
+ getThumbnail?: () => string | {
25
+ id?: string;
26
+ ['@id']?: string;
27
+ } | null;
28
+ getImages?: () => ManifestAnnotation[];
29
+ getContent?: () => ManifestAnnotation[];
30
+ } | any;
31
+ type $$ComponentProps = {
32
+ canvases?: ManifestCanvas[];
33
+ };
34
+ declare const ThumbnailGallery: import("svelte").Component<$$ComponentProps, {}, "">;
35
+ type ThumbnailGallery = ReturnType<typeof ThumbnailGallery>;
36
+ export default ThumbnailGallery;