triiiceratops 0.8.2 → 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.
- 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/{src/lib/index.d.ts → index.d.ts} +1 -0
- package/dist/index.js +10 -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/{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,434 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, onDestroy } from 'svelte';
|
|
3
|
+
import { ViewerState, VIEWER_STATE_KEY } from '../state/viewer.svelte';
|
|
4
|
+
import type { TriiiceratopsPlugin } from '../types/plugin';
|
|
5
|
+
import type { DaisyUITheme, ThemeConfig } from '../theme/types';
|
|
6
|
+
import type { ViewerConfig } from '../types/config';
|
|
7
|
+
import { applyTheme } from '../theme/themeManager';
|
|
8
|
+
import OSDViewer from './OSDViewer.svelte';
|
|
9
|
+
import CanvasNavigation from './CanvasNavigation.svelte';
|
|
10
|
+
import AnnotationOverlay from './AnnotationOverlay.svelte';
|
|
11
|
+
import ThumbnailGallery from './ThumbnailGallery.svelte';
|
|
12
|
+
import FloatingMenu from './FloatingMenu.svelte';
|
|
13
|
+
import LeftFab from './LeftFab.svelte';
|
|
14
|
+
import MetadataDialog from './MetadataDialog.svelte';
|
|
15
|
+
import SearchPanel from './SearchPanel.svelte';
|
|
16
|
+
import { m } from '../state/i18n.svelte';
|
|
17
|
+
|
|
18
|
+
// SSR-safe browser detection for library consumers
|
|
19
|
+
const browser = typeof window !== 'undefined';
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
manifestId,
|
|
23
|
+
canvasId,
|
|
24
|
+
plugins = [],
|
|
25
|
+
theme,
|
|
26
|
+
themeConfig,
|
|
27
|
+
config = {},
|
|
28
|
+
viewerState = $bindable(),
|
|
29
|
+
}: {
|
|
30
|
+
manifestId?: string;
|
|
31
|
+
canvasId?: string;
|
|
32
|
+
plugins?: TriiiceratopsPlugin[];
|
|
33
|
+
/** Built-in DaisyUI theme name. Defaults to 'light' or 'dark' based on prefers-color-scheme. */
|
|
34
|
+
theme?: DaisyUITheme;
|
|
35
|
+
/** Custom theme configuration to override the base theme's values. */
|
|
36
|
+
themeConfig?: ThemeConfig;
|
|
37
|
+
/** Configuration options for the viewer UI */
|
|
38
|
+
config?: ViewerConfig;
|
|
39
|
+
/** Bindable viewer state instance for external access (Svelte consumers) */
|
|
40
|
+
viewerState?: ViewerState;
|
|
41
|
+
} = $props();
|
|
42
|
+
|
|
43
|
+
// Reference to root element for applying theme
|
|
44
|
+
let rootElement: HTMLElement | undefined = $state();
|
|
45
|
+
|
|
46
|
+
// Reactively apply theme when element is available or theme/themeConfig changes
|
|
47
|
+
$effect(() => {
|
|
48
|
+
if (rootElement) {
|
|
49
|
+
applyTheme(rootElement, theme, themeConfig);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Create per-instance viewer state
|
|
54
|
+
// Note: We pass empty initial values and use $effect blocks below to set
|
|
55
|
+
// manifestId, canvasId, and plugins reactively, avoiding Svelte's
|
|
56
|
+
// "state_referenced_locally" warning about capturing initial prop values.
|
|
57
|
+
const internalViewerState = new ViewerState(null, undefined, []);
|
|
58
|
+
viewerState = internalViewerState; // Expose via bindable prop
|
|
59
|
+
setContext(VIEWER_STATE_KEY, internalViewerState);
|
|
60
|
+
|
|
61
|
+
onDestroy(() => {
|
|
62
|
+
internalViewerState.destroyAllPlugins();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (manifestId && manifestId !== internalViewerState.manifestId) {
|
|
67
|
+
internalViewerState.setManifest(manifestId);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Track last applied canvasId PROP value to prevent reverting internal navigation
|
|
72
|
+
let lastAppliedCanvasId = '';
|
|
73
|
+
|
|
74
|
+
$effect(() => {
|
|
75
|
+
// Only sync from prop to internal state when PROP actually changes
|
|
76
|
+
// This prevents internal navigation from being reverted when the effect
|
|
77
|
+
// runs due to internal state changes
|
|
78
|
+
if (canvasId && canvasId !== lastAppliedCanvasId) {
|
|
79
|
+
lastAppliedCanvasId = canvasId;
|
|
80
|
+
// Only apply if different from current internal state
|
|
81
|
+
if (canvasId !== internalViewerState.canvasId) {
|
|
82
|
+
internalViewerState.setCanvas(canvasId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Track last applied config to prevent redundant updates and loops
|
|
88
|
+
let lastConfigStr = '';
|
|
89
|
+
|
|
90
|
+
$effect(() => {
|
|
91
|
+
if (config) {
|
|
92
|
+
const str = JSON.stringify(config);
|
|
93
|
+
if (str !== lastConfigStr) {
|
|
94
|
+
lastConfigStr = str;
|
|
95
|
+
console.log(
|
|
96
|
+
'[Viewer] updateConfig called with new config:',
|
|
97
|
+
config,
|
|
98
|
+
);
|
|
99
|
+
internalViewerState.updateConfig(config);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Register plugins reactively
|
|
105
|
+
$effect(() => {
|
|
106
|
+
for (const plugin of plugins) {
|
|
107
|
+
internalViewerState.registerPlugin(plugin);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (!browser) return;
|
|
113
|
+
|
|
114
|
+
const handleFullScreenChange = () => {
|
|
115
|
+
internalViewerState.isFullScreen = !!document.fullscreenElement;
|
|
116
|
+
};
|
|
117
|
+
document.addEventListener('fullscreenchange', handleFullScreenChange);
|
|
118
|
+
return () => {
|
|
119
|
+
document.removeEventListener(
|
|
120
|
+
'fullscreenchange',
|
|
121
|
+
handleFullScreenChange,
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let isLeftSidebarVisible = $derived(
|
|
127
|
+
(internalViewerState.showThumbnailGallery &&
|
|
128
|
+
internalViewerState.dockSide === 'left') ||
|
|
129
|
+
internalViewerState.pluginPanels.some(
|
|
130
|
+
(p) => p.position === 'left' && p.isVisible(),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
let isRightSidebarVisible = $derived(
|
|
135
|
+
internalViewerState.showSearchPanel ||
|
|
136
|
+
(internalViewerState.showThumbnailGallery &&
|
|
137
|
+
internalViewerState.dockSide === 'right') ||
|
|
138
|
+
internalViewerState.pluginPanels.some(
|
|
139
|
+
(p) => p.position === 'right' && p.isVisible(),
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
let manifestData = $derived(internalViewerState.manifest);
|
|
144
|
+
let canvases = $derived(internalViewerState.canvases);
|
|
145
|
+
let currentCanvasIndex = $derived(internalViewerState.currentCanvasIndex);
|
|
146
|
+
|
|
147
|
+
let tileSources = $derived.by(() => {
|
|
148
|
+
if (
|
|
149
|
+
!canvases ||
|
|
150
|
+
currentCanvasIndex === -1 ||
|
|
151
|
+
!canvases[currentCanvasIndex]
|
|
152
|
+
) {
|
|
153
|
+
if (!manifestData?.isFetching) {
|
|
154
|
+
console.log('TriiiceratopsViewer: No canvas found');
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const canvas = canvases[currentCanvasIndex];
|
|
160
|
+
|
|
161
|
+
// Use Manifesto to get images
|
|
162
|
+
let images = canvas.getImages();
|
|
163
|
+
|
|
164
|
+
// Fallback for IIIF v3: iterate content if images is empty
|
|
165
|
+
if ((!images || !images.length) && canvas.getContent) {
|
|
166
|
+
images = canvas.getContent();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!images || !images.length) {
|
|
170
|
+
// Check for raw v3 items
|
|
171
|
+
if (canvas.__jsonld && canvas.__jsonld.items) {
|
|
172
|
+
// Try to locate annotation pages -> annotations
|
|
173
|
+
}
|
|
174
|
+
if (!manifestData?.isFetching) {
|
|
175
|
+
console.log('TriiiceratopsViewer: No images/content in canvas');
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const annotation = images[0];
|
|
181
|
+
let resource = annotation.getResource ? annotation.getResource() : null;
|
|
182
|
+
|
|
183
|
+
// v3 fallback: getBody
|
|
184
|
+
if (!resource && annotation.getBody) {
|
|
185
|
+
const body = annotation.getBody();
|
|
186
|
+
if (Array.isArray(body) && body.length > 0) resource = body[0];
|
|
187
|
+
else if (body) resource = body;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if resource is valid (Manifesto sometimes returns empty objects for v3 bodies)
|
|
191
|
+
if (
|
|
192
|
+
resource &&
|
|
193
|
+
!resource.id &&
|
|
194
|
+
!resource.__jsonld &&
|
|
195
|
+
(!resource.getServices || resource.getServices().length === 0)
|
|
196
|
+
) {
|
|
197
|
+
resource = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!resource) {
|
|
201
|
+
// raw json fallback
|
|
202
|
+
const json = annotation.__jsonld || annotation;
|
|
203
|
+
if (json.body) {
|
|
204
|
+
resource = Array.isArray(json.body) ? json.body[0] : json.body;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!resource) {
|
|
209
|
+
// console.log('TriiiceratopsViewer: No resource in annotation');
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Helper to normalize ID
|
|
214
|
+
const getId = (thing: any) => thing.id || thing['@id'];
|
|
215
|
+
|
|
216
|
+
// Start of service detection logic
|
|
217
|
+
let services = [];
|
|
218
|
+
if (resource.getServices) {
|
|
219
|
+
services = resource.getServices();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Fallback: check raw json service
|
|
223
|
+
if (!services.length) {
|
|
224
|
+
const rJson = resource.__jsonld || resource;
|
|
225
|
+
// console.log('Checking raw resource for services:', rJson);
|
|
226
|
+
if (rJson.service) {
|
|
227
|
+
services = Array.isArray(rJson.service)
|
|
228
|
+
? rJson.service
|
|
229
|
+
: [rJson.service];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// console.log('Found services:', services);
|
|
234
|
+
|
|
235
|
+
if (services.length > 0) {
|
|
236
|
+
// Find a valid image service
|
|
237
|
+
const service = services.find((s: any) => {
|
|
238
|
+
const type = s.getType ? s.getType() : s.type || '';
|
|
239
|
+
const profile = s.getProfile ? s.getProfile() : s.profile || '';
|
|
240
|
+
return (
|
|
241
|
+
type === 'ImageService1' ||
|
|
242
|
+
type === 'ImageService2' ||
|
|
243
|
+
type === 'ImageService3' ||
|
|
244
|
+
(typeof profile === 'string' &&
|
|
245
|
+
profile.includes('http://iiif.io/api/image')) ||
|
|
246
|
+
(typeof profile === 'string' && profile === 'level0')
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (service) {
|
|
251
|
+
let id = getId(service);
|
|
252
|
+
if (id && !id.endsWith('/info.json')) {
|
|
253
|
+
id = `${id}/info.json`;
|
|
254
|
+
}
|
|
255
|
+
return id;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Fallback: Heuristic from Image ID (if it looks like IIIF)
|
|
260
|
+
const resourceId = getId(resource);
|
|
261
|
+
if (resourceId && resourceId.includes('/iiif/')) {
|
|
262
|
+
// Try to strip standard IIIF parameters to find base
|
|
263
|
+
// IIIF URLs often look like .../identifier/region/size/rotation/quality.format
|
|
264
|
+
// We look for the part before the region (often 'full')
|
|
265
|
+
const parts = resourceId.split('/');
|
|
266
|
+
// find index of 'full' or region
|
|
267
|
+
const regionIndex = parts.findIndex(
|
|
268
|
+
(p: string) => p === 'full' || p.match(/^\d+,\d+,\d+,\d+$/),
|
|
269
|
+
);
|
|
270
|
+
if (regionIndex > 0) {
|
|
271
|
+
const base = parts.slice(0, regionIndex).join('/');
|
|
272
|
+
return `${base}/info.json`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
console.log(
|
|
277
|
+
'TriiiceratopsViewer: No service or ID found, returning raw URL',
|
|
278
|
+
);
|
|
279
|
+
const url = resourceId;
|
|
280
|
+
return { type: 'image', url };
|
|
281
|
+
});
|
|
282
|
+
</script>
|
|
283
|
+
|
|
284
|
+
<div
|
|
285
|
+
bind:this={rootElement}
|
|
286
|
+
id="triiiceratops-viewer"
|
|
287
|
+
class="flex w-full h-full relative bg-base-100 overflow-hidden"
|
|
288
|
+
>
|
|
289
|
+
<!-- Left Column -->
|
|
290
|
+
{#if isLeftSidebarVisible}
|
|
291
|
+
<div
|
|
292
|
+
class="flex-none flex flex-row z-20 bg-base-200 border-r border-base-300 transition-all"
|
|
293
|
+
>
|
|
294
|
+
<!-- Gallery (when docked left) -->
|
|
295
|
+
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'left'}
|
|
296
|
+
<div class="h-full w-[200px] pointer-events-auto relative">
|
|
297
|
+
<ThumbnailGallery {canvases} />
|
|
298
|
+
</div>
|
|
299
|
+
{/if}
|
|
300
|
+
|
|
301
|
+
{#each internalViewerState.pluginPanels as panel (panel.id)}
|
|
302
|
+
{#if panel.isVisible() && panel.position === 'left'}
|
|
303
|
+
<div class="h-full relative pointer-events-auto">
|
|
304
|
+
<panel.component {...panel.props ?? {}} />
|
|
305
|
+
</div>
|
|
306
|
+
{/if}
|
|
307
|
+
{/each}
|
|
308
|
+
</div>
|
|
309
|
+
{/if}
|
|
310
|
+
|
|
311
|
+
<!-- Center Column -->
|
|
312
|
+
<div
|
|
313
|
+
id="triiiceratops-center-panel"
|
|
314
|
+
class="flex-1 relative min-w-0 flex flex-col"
|
|
315
|
+
>
|
|
316
|
+
<!-- Top Area (Gallery) -->
|
|
317
|
+
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'top'}
|
|
318
|
+
<div
|
|
319
|
+
class="flex-none h-[140px] w-full pointer-events-auto relative z-20"
|
|
320
|
+
>
|
|
321
|
+
<ThumbnailGallery {canvases} />
|
|
322
|
+
</div>
|
|
323
|
+
{/if}
|
|
324
|
+
|
|
325
|
+
<!-- Main Viewer Area -->
|
|
326
|
+
<div class="flex-1 relative min-h-0 w-full h-full bg-base-100">
|
|
327
|
+
{#if manifestData?.isFetching}
|
|
328
|
+
<div class="w-full h-full flex items-center justify-center">
|
|
329
|
+
<span
|
|
330
|
+
class="loading loading-spinner loading-lg text-primary"
|
|
331
|
+
></span>
|
|
332
|
+
</div>
|
|
333
|
+
{:else if manifestData?.error}
|
|
334
|
+
<div
|
|
335
|
+
class="w-full h-full flex items-center justify-center text-error"
|
|
336
|
+
>
|
|
337
|
+
{m.error_prefix()}
|
|
338
|
+
{manifestData.error}
|
|
339
|
+
</div>
|
|
340
|
+
{:else if tileSources}
|
|
341
|
+
{#key tileSources}
|
|
342
|
+
<OSDViewer
|
|
343
|
+
{tileSources}
|
|
344
|
+
viewerState={internalViewerState}
|
|
345
|
+
/>
|
|
346
|
+
{/key}
|
|
347
|
+
{:else}
|
|
348
|
+
<div
|
|
349
|
+
class="w-full h-full flex items-center justify-center text-base-content/50"
|
|
350
|
+
>
|
|
351
|
+
{m.no_image_found()}
|
|
352
|
+
</div>
|
|
353
|
+
{/if}
|
|
354
|
+
|
|
355
|
+
<AnnotationOverlay />
|
|
356
|
+
<MetadataDialog />
|
|
357
|
+
|
|
358
|
+
<!-- Floating Menu / FABs -->
|
|
359
|
+
{#if internalViewerState.showRightMenu}
|
|
360
|
+
<FloatingMenu />
|
|
361
|
+
{/if}
|
|
362
|
+
{#if internalViewerState.showLeftMenu}
|
|
363
|
+
<LeftFab />
|
|
364
|
+
{/if}
|
|
365
|
+
|
|
366
|
+
<!-- Overlay Plugin Panels -->
|
|
367
|
+
{#each internalViewerState.pluginPanels as panel (panel.id)}
|
|
368
|
+
{#if panel.isVisible() && panel.position === 'overlay'}
|
|
369
|
+
<div class="absolute inset-0 z-40 pointer-events-none">
|
|
370
|
+
<panel.component {...panel.props ?? {}} />
|
|
371
|
+
</div>
|
|
372
|
+
{/if}
|
|
373
|
+
{/each}
|
|
374
|
+
|
|
375
|
+
<!-- Canvas Nav (Absolute positioned inside center, or floating?) currently assumes absolute -->
|
|
376
|
+
{#if canvases.length > 1 && internalViewerState.showCanvasNav}
|
|
377
|
+
<CanvasNavigation viewerState={internalViewerState} />
|
|
378
|
+
{/if}
|
|
379
|
+
|
|
380
|
+
<!-- Float-mode Gallery -->
|
|
381
|
+
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'none'}
|
|
382
|
+
<ThumbnailGallery {canvases} />
|
|
383
|
+
{/if}
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<!-- Bottom Area (Gallery) -->
|
|
387
|
+
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'bottom'}
|
|
388
|
+
<div
|
|
389
|
+
class="flex-none h-[140px] w-full pointer-events-auto relative z-20"
|
|
390
|
+
>
|
|
391
|
+
<ThumbnailGallery {canvases} />
|
|
392
|
+
</div>
|
|
393
|
+
{/if}
|
|
394
|
+
|
|
395
|
+
<!-- Bottom Area (Plugin Panels) -->
|
|
396
|
+
{#each internalViewerState.pluginPanels as panel (panel.id)}
|
|
397
|
+
{#if panel.isVisible() && panel.position === 'bottom'}
|
|
398
|
+
<div class="relative w-full z-40 pointer-events-auto">
|
|
399
|
+
<panel.component {...panel.props ?? {}} />
|
|
400
|
+
</div>
|
|
401
|
+
{/if}
|
|
402
|
+
{/each}
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<!-- Right Column -->
|
|
406
|
+
{#if isRightSidebarVisible}
|
|
407
|
+
<div
|
|
408
|
+
class="flex-none flex flex-row z-20 bg-base-200 border-l border-base-300 transition-all"
|
|
409
|
+
>
|
|
410
|
+
<!-- Search Panel -->
|
|
411
|
+
{#if internalViewerState.showSearchPanel}
|
|
412
|
+
<div class="h-full relative pointer-events-auto">
|
|
413
|
+
<SearchPanel />
|
|
414
|
+
</div>
|
|
415
|
+
{/if}
|
|
416
|
+
|
|
417
|
+
<!-- Gallery (when docked right) -->
|
|
418
|
+
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'right'}
|
|
419
|
+
<div class="h-full w-[200px] pointer-events-auto relative">
|
|
420
|
+
<ThumbnailGallery {canvases} />
|
|
421
|
+
</div>
|
|
422
|
+
{/if}
|
|
423
|
+
|
|
424
|
+
<!-- Right Plugin Panels -->
|
|
425
|
+
{#each internalViewerState.pluginPanels as panel (panel.id)}
|
|
426
|
+
{#if panel.isVisible() && panel.position === 'right'}
|
|
427
|
+
<div class="h-full relative pointer-events-auto">
|
|
428
|
+
<panel.component {...panel.props ?? {}} />
|
|
429
|
+
</div>
|
|
430
|
+
{/if}
|
|
431
|
+
{/each}
|
|
432
|
+
</div>
|
|
433
|
+
{/if}
|
|
434
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ViewerState } from '../state/viewer.svelte';
|
|
2
|
+
import type { TriiiceratopsPlugin } from '../types/plugin';
|
|
3
|
+
import type { DaisyUITheme, ThemeConfig } from '../theme/types';
|
|
4
|
+
import type { ViewerConfig } from '../types/config';
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
manifestId?: string;
|
|
7
|
+
canvasId?: string;
|
|
8
|
+
plugins?: TriiiceratopsPlugin[];
|
|
9
|
+
/** Built-in DaisyUI theme name. Defaults to 'light' or 'dark' based on prefers-color-scheme. */
|
|
10
|
+
theme?: DaisyUITheme;
|
|
11
|
+
/** Custom theme configuration to override the base theme's values. */
|
|
12
|
+
themeConfig?: ThemeConfig;
|
|
13
|
+
/** Configuration options for the viewer UI */
|
|
14
|
+
config?: ViewerConfig;
|
|
15
|
+
/** Bindable viewer state instance for external access (Svelte consumers) */
|
|
16
|
+
viewerState?: ViewerState;
|
|
17
|
+
};
|
|
18
|
+
declare const TriiiceratopsViewer: import("svelte").Component<$$ComponentProps, {}, "viewerState">;
|
|
19
|
+
type TriiiceratopsViewer = ReturnType<typeof TriiiceratopsViewer>;
|
|
20
|
+
export default TriiiceratopsViewer;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<svelte:options
|
|
2
|
+
customElement={{
|
|
3
|
+
tag: 'triiiceratops-viewer',
|
|
4
|
+
shadow: 'open',
|
|
5
|
+
props: {
|
|
6
|
+
manifestId: {
|
|
7
|
+
attribute: 'manifest-id',
|
|
8
|
+
type: 'String',
|
|
9
|
+
reflect: true,
|
|
10
|
+
},
|
|
11
|
+
canvasId: {
|
|
12
|
+
attribute: 'canvas-id',
|
|
13
|
+
type: 'String',
|
|
14
|
+
reflect: true,
|
|
15
|
+
},
|
|
16
|
+
theme: {
|
|
17
|
+
attribute: 'theme',
|
|
18
|
+
type: 'String',
|
|
19
|
+
reflect: true,
|
|
20
|
+
},
|
|
21
|
+
themeConfig: {
|
|
22
|
+
attribute: 'theme-config',
|
|
23
|
+
type: 'String',
|
|
24
|
+
reflect: false,
|
|
25
|
+
},
|
|
26
|
+
config: {
|
|
27
|
+
attribute: 'config',
|
|
28
|
+
type: 'String',
|
|
29
|
+
reflect: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import styles from '../../app.css?inline';
|
|
37
|
+
import TriiiceratopsViewer from './TriiiceratopsViewer.svelte';
|
|
38
|
+
import type { TriiiceratopsPlugin } from '../types/plugin';
|
|
39
|
+
import type { DaisyUITheme, ThemeConfig } from '../theme/types';
|
|
40
|
+
import type { ViewerConfig } from '../types/config';
|
|
41
|
+
import { isBuiltInTheme, parseThemeConfig } from '../theme/themeManager';
|
|
42
|
+
import type { ViewerState } from '../state/viewer.svelte';
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
manifestId = '',
|
|
46
|
+
canvasId = '',
|
|
47
|
+
plugins = [],
|
|
48
|
+
theme = undefined as string | undefined,
|
|
49
|
+
themeConfig = undefined as string | ThemeConfig | undefined,
|
|
50
|
+
config = undefined as string | ViewerConfig | undefined,
|
|
51
|
+
}: {
|
|
52
|
+
manifestId?: string;
|
|
53
|
+
canvasId?: string;
|
|
54
|
+
plugins?: TriiiceratopsPlugin[];
|
|
55
|
+
/**
|
|
56
|
+
* Built-in DaisyUI theme name (e.g., 'light', 'dark', 'cupcake').
|
|
57
|
+
* When not specified, inherits the theme from the parent context.
|
|
58
|
+
*/
|
|
59
|
+
theme?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Custom theme configuration to override the base theme.
|
|
62
|
+
* Can be a JSON string (for HTML attribute) or ThemeConfig object (for JS property).
|
|
63
|
+
* @example HTML: theme-config='{"primary":"#3b82f6","radiusBox":"0.5rem"}'
|
|
64
|
+
* @example JS: element.themeConfig = { primary: '#3b82f6', radiusBox: '0.5rem' }
|
|
65
|
+
*/
|
|
66
|
+
themeConfig?: string | ThemeConfig;
|
|
67
|
+
/**
|
|
68
|
+
* Configuration options for the viewer UI.
|
|
69
|
+
*/
|
|
70
|
+
config?: string | ViewerConfig;
|
|
71
|
+
} = $props();
|
|
72
|
+
|
|
73
|
+
// Reference to host element for event dispatch
|
|
74
|
+
let hostElement: HTMLElement;
|
|
75
|
+
|
|
76
|
+
// ViewerState from the inner component (via bindable prop)
|
|
77
|
+
let internalViewerState: ViewerState | undefined = $state();
|
|
78
|
+
|
|
79
|
+
// Track if we've already wired up the event target (only do once)
|
|
80
|
+
let eventTargetSet = false;
|
|
81
|
+
|
|
82
|
+
// Wire up eventTarget when viewerState is available - only once
|
|
83
|
+
$effect(() => {
|
|
84
|
+
if (internalViewerState && hostElement && !eventTargetSet) {
|
|
85
|
+
eventTargetSet = true;
|
|
86
|
+
internalViewerState.setEventTarget(hostElement);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Validate and convert theme string to DaisyUITheme type
|
|
91
|
+
let validatedTheme = $derived.by((): DaisyUITheme | undefined => {
|
|
92
|
+
if (!theme) return undefined;
|
|
93
|
+
if (isBuiltInTheme(theme)) return theme;
|
|
94
|
+
console.warn(`Invalid theme "${theme}". Using inherited theme.`);
|
|
95
|
+
return undefined;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Parse themeConfig if it's a JSON string, pass through if it's already an object
|
|
99
|
+
let parsedThemeConfig = $derived.by((): ThemeConfig | undefined => {
|
|
100
|
+
if (!themeConfig) return undefined;
|
|
101
|
+
if (typeof themeConfig === 'string') {
|
|
102
|
+
const parsed = parseThemeConfig(themeConfig);
|
|
103
|
+
if (!parsed) {
|
|
104
|
+
console.warn(
|
|
105
|
+
`Invalid theme-config JSON: "${themeConfig}". Ignoring.`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return parsed ?? undefined;
|
|
109
|
+
}
|
|
110
|
+
return themeConfig;
|
|
111
|
+
});
|
|
112
|
+
// Parse config if it's a JSON string, pass through if it's already an object
|
|
113
|
+
let parsedConfig = $derived.by((): ViewerConfig | undefined => {
|
|
114
|
+
if (!config) return undefined;
|
|
115
|
+
if (typeof config === 'string') {
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(config);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.warn(`Invalid config JSON: "${config}". Ignoring.`);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return config;
|
|
124
|
+
});
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
{@html `<style>${styles}</style>`}
|
|
128
|
+
|
|
129
|
+
<div bind:this={hostElement} class="w-full h-full">
|
|
130
|
+
<TriiiceratopsViewer
|
|
131
|
+
{manifestId}
|
|
132
|
+
{canvasId}
|
|
133
|
+
{plugins}
|
|
134
|
+
theme={validatedTheme}
|
|
135
|
+
themeConfig={parsedThemeConfig}
|
|
136
|
+
config={parsedConfig}
|
|
137
|
+
bind:viewerState={internalViewerState}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TriiiceratopsPlugin } from '../types/plugin';
|
|
2
|
+
import type { ThemeConfig } from '../theme/types';
|
|
3
|
+
import type { ViewerConfig } from '../types/config';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
manifestId?: string;
|
|
6
|
+
canvasId?: string;
|
|
7
|
+
plugins?: TriiiceratopsPlugin[];
|
|
8
|
+
/**
|
|
9
|
+
* Built-in DaisyUI theme name (e.g., 'light', 'dark', 'cupcake').
|
|
10
|
+
* When not specified, inherits the theme from the parent context.
|
|
11
|
+
*/
|
|
12
|
+
theme?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Custom theme configuration to override the base theme.
|
|
15
|
+
* Can be a JSON string (for HTML attribute) or ThemeConfig object (for JS property).
|
|
16
|
+
* @example HTML: theme-config='{"primary":"#3b82f6","radiusBox":"0.5rem"}'
|
|
17
|
+
* @example JS: element.themeConfig = { primary: '#3b82f6', radiusBox: '0.5rem' }
|
|
18
|
+
*/
|
|
19
|
+
themeConfig?: string | ThemeConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for the viewer UI.
|
|
22
|
+
*/
|
|
23
|
+
config?: string | ViewerConfig;
|
|
24
|
+
};
|
|
25
|
+
declare const TriiiceratopsViewerElement: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
26
|
+
type TriiiceratopsViewerElement = ReturnType<typeof TriiiceratopsViewerElement>;
|
|
27
|
+
export default TriiiceratopsViewerElement;
|