triiiceratops 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AnnotationOverlay.svelte +288 -0
- package/dist/components/AnnotationOverlay.svelte.d.ts +3 -0
- package/dist/components/CanvasNavigation.svelte +32 -0
- package/dist/components/CanvasNavigation.svelte.d.ts +11 -0
- package/dist/components/DemoHeader.svelte +703 -0
- package/dist/components/DemoHeader.svelte.d.ts +9 -0
- package/dist/components/FloatingMenu.svelte +208 -0
- package/dist/components/FloatingMenu.svelte.d.ts +3 -0
- package/dist/components/LeftFab.svelte +69 -0
- package/dist/components/LeftFab.svelte.d.ts +3 -0
- package/dist/components/MetadataDialog.svelte +151 -0
- package/dist/components/MetadataDialog.svelte.d.ts +3 -0
- package/dist/components/OSDViewer.svelte +260 -0
- package/dist/components/OSDViewer.svelte.d.ts +8 -0
- package/dist/components/SearchPanel.svelte +150 -0
- package/dist/components/SearchPanel.svelte.d.ts +3 -0
- package/dist/components/ThemeToggle.svelte +118 -0
- package/dist/components/ThemeToggle.svelte.d.ts +3 -0
- package/dist/components/ThumbnailGallery.svelte +601 -0
- package/dist/components/ThumbnailGallery.svelte.d.ts +36 -0
- package/dist/components/TriiiceratopsViewer.svelte +434 -0
- package/dist/components/TriiiceratopsViewer.svelte.d.ts +20 -0
- package/dist/components/TriiiceratopsViewerElement.svelte +139 -0
- package/dist/components/TriiiceratopsViewerElement.svelte.d.ts +27 -0
- package/dist/components/TriiiceratopsViewerElementImage.svelte +143 -0
- package/dist/components/TriiiceratopsViewerElementImage.svelte.d.ts +27 -0
- package/dist/custom-element-image.d.ts +1 -0
- package/dist/custom-element-image.js +2 -0
- package/dist/custom-element.d.ts +1 -0
- package/dist/custom-element.js +3 -0
- package/dist/index.js +9 -4153
- package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte +134 -0
- package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +10 -0
- package/dist/{src/lib/plugins → plugins}/image-manipulation/ImageManipulationPlugin.svelte.d.ts +2 -2
- package/dist/plugins/image-manipulation/ImageManipulationPlugin.svelte.js +122 -0
- package/dist/{src/lib/plugins → plugins}/image-manipulation/filters.d.ts +1 -1
- package/dist/plugins/image-manipulation/filters.js +48 -0
- package/dist/plugins/image-manipulation/index.js +2 -0
- package/dist/plugins/image-manipulation/types.js +7 -0
- package/dist/state/i18n.svelte.d.ts +4 -0
- package/dist/state/i18n.svelte.js +18 -0
- package/dist/state/manifests.svelte.js +210 -0
- package/dist/state/manifests.test.d.ts +1 -0
- package/dist/state/manifests.test.js +242 -0
- package/dist/{src/lib/state → state}/viewer.svelte.d.ts +4 -4
- package/dist/state/viewer.svelte.js +693 -0
- package/dist/theme/colorUtils.js +196 -0
- package/dist/theme/colorUtils.test.d.ts +1 -0
- package/dist/theme/colorUtils.test.js +90 -0
- package/dist/theme/index.js +52 -0
- package/dist/{src/lib/theme → theme}/themeManager.d.ts +4 -1
- package/dist/theme/themeManager.js +177 -0
- package/dist/theme/types.js +40 -0
- package/dist/triiiceratops-bundle.js +4676 -0
- package/dist/types/config.js +1 -0
- package/dist/{src/lib/types → types}/plugin.d.ts +3 -3
- package/dist/types/plugin.js +36 -0
- package/dist/utils/annotationAdapter.js +354 -0
- package/dist/utils/annotationAdapter.test.d.ts +1 -0
- package/dist/utils/annotationAdapter.test.js +91 -0
- package/package.json +6 -5
- package/dist/plugin-CHYleMsW.js +0 -538
- package/dist/plugins/image-manipulation.js +0 -411
- package/dist/src/lib/components/AnnotationOverlay.svelte.d.ts +0 -1
- package/dist/src/lib/components/CanvasNavigation.svelte.d.ts +0 -1
- package/dist/src/lib/components/FloatingMenu.svelte.d.ts +0 -1
- package/dist/src/lib/components/LeftFab.svelte.d.ts +0 -1
- package/dist/src/lib/components/MetadataDialog.svelte.d.ts +0 -1
- package/dist/src/lib/components/OSDViewer.svelte.d.ts +0 -1
- package/dist/src/lib/components/SearchPanel.svelte.d.ts +0 -1
- package/dist/src/lib/components/ThumbnailGallery.svelte.d.ts +0 -1
- package/dist/src/lib/components/TriiiceratopsViewer.svelte.d.ts +0 -1
- package/dist/src/lib/custom-element-image.d.ts +0 -0
- package/dist/src/lib/custom-element.d.ts +0 -0
- package/dist/src/lib/paraglide/messages/de.d.ts +0 -96
- package/dist/src/lib/paraglide/messages/en.d.ts +0 -96
- package/dist/src/lib/paraglide/messages.d.ts +0 -272
- package/dist/src/lib/paraglide/runtime.d.ts +0 -52
- package/dist/src/lib/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +0 -1
- package/dist/src/lib/state/i18n.svelte.d.ts +0 -5
- package/dist/triiiceratops.css +0 -1
- /package/dist/{src/lib/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/lib/plugins → plugins}/image-manipulation/index.d.ts +0 -0
- /package/dist/{src/lib/plugins → plugins}/image-manipulation/types.d.ts +0 -0
- /package/dist/{src/lib/state → state}/manifests.svelte.d.ts +0 -0
- /package/dist/{src/lib/theme → theme}/colorUtils.d.ts +0 -0
- /package/dist/{src/lib/theme → theme}/index.d.ts +0 -0
- /package/dist/{src/lib/theme → theme}/types.d.ts +0 -0
- /package/dist/{src/lib/types → types}/config.d.ts +0 -0
- /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;
|