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.
- package/dist/chunks/TriiiceratopsViewer-BGtsfUPF.js +10298 -0
- package/dist/chunks/openseadragon-BTypULhm.js +12427 -0
- 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 -4480
- 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/triiiceratops-element-image.js +1 -1
- package/dist/triiiceratops-element.js +1 -1
- 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/chunks/TriiiceratopsViewer-CyamQrMe.js +0 -22698
- package/dist/plugin-De14WKQl.js +0 -546
- package/dist/plugins/image-manipulation.js +0 -454
- 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,260 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { parseAnnotations } from '../utils/annotationAdapter';
|
|
4
|
+
import { manifestsState } from '../state/manifests.svelte';
|
|
5
|
+
import type { ViewerState } from '../state/viewer.svelte';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
tileSources,
|
|
9
|
+
viewerState,
|
|
10
|
+
}: { tileSources: string | object | null; viewerState: ViewerState } =
|
|
11
|
+
$props();
|
|
12
|
+
|
|
13
|
+
let container: HTMLElement | undefined = $state();
|
|
14
|
+
let viewer: any | undefined = $state();
|
|
15
|
+
let OSD: any | undefined = $state();
|
|
16
|
+
|
|
17
|
+
// Track OSD state changes for reactivity
|
|
18
|
+
let osdVersion = $state(0);
|
|
19
|
+
|
|
20
|
+
// Get all annotations for current canvas (manifest + search)
|
|
21
|
+
let allAnnotations = $derived.by(() => {
|
|
22
|
+
if (!viewerState.manifestId || !viewerState.canvasId) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const manifestAnnotations = manifestsState.getAnnotations(
|
|
26
|
+
viewerState.manifestId,
|
|
27
|
+
viewerState.canvasId,
|
|
28
|
+
);
|
|
29
|
+
const searchAnnotations = viewerState.currentCanvasSearchAnnotations;
|
|
30
|
+
return [...manifestAnnotations, ...searchAnnotations];
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Get search hit IDs for styling
|
|
34
|
+
let searchHitIds = $derived.by(() => {
|
|
35
|
+
const ids = new Set<string>();
|
|
36
|
+
viewerState.currentCanvasSearchAnnotations.forEach((anno: any) => {
|
|
37
|
+
const id = anno.id || anno['@id'];
|
|
38
|
+
if (id) ids.add(id);
|
|
39
|
+
});
|
|
40
|
+
return ids;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Parse annotations
|
|
44
|
+
let parsedAnnotations = $derived.by(() => {
|
|
45
|
+
return parseAnnotations(allAnnotations, searchHitIds);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Rendered annotations with pixel coordinates
|
|
49
|
+
let renderedAnnotations = $derived.by(() => {
|
|
50
|
+
// Depend on osdVersion to trigger updates
|
|
51
|
+
osdVersion;
|
|
52
|
+
|
|
53
|
+
if (!viewer || !OSD || !parsedAnnotations.length) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tiledImage = viewer.world.getItemAt(0);
|
|
58
|
+
if (!tiledImage) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const results: any[] = [];
|
|
63
|
+
|
|
64
|
+
for (const anno of parsedAnnotations) {
|
|
65
|
+
// Filter based on visibility
|
|
66
|
+
if (anno.isSearchHit) {
|
|
67
|
+
// Search hits are always visible
|
|
68
|
+
} else if (!viewerState.showAnnotations) {
|
|
69
|
+
continue;
|
|
70
|
+
} else if (!viewerState.visibleAnnotationIds.has(anno.id)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (anno.geometry.type === 'RECTANGLE') {
|
|
75
|
+
// Convert image coordinates to viewport coordinates
|
|
76
|
+
const viewportRect = tiledImage.imageToViewportRectangle(
|
|
77
|
+
anno.geometry.x,
|
|
78
|
+
anno.geometry.y,
|
|
79
|
+
anno.geometry.w,
|
|
80
|
+
anno.geometry.h,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Convert viewport to pixel coordinates
|
|
84
|
+
const pixelRect =
|
|
85
|
+
viewer.viewport.viewportToViewerElementRectangle(
|
|
86
|
+
viewportRect,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
results.push({
|
|
90
|
+
id: anno.id,
|
|
91
|
+
type: 'RECTANGLE' as const,
|
|
92
|
+
rect: {
|
|
93
|
+
x: pixelRect.x,
|
|
94
|
+
y: pixelRect.y,
|
|
95
|
+
width: pixelRect.width,
|
|
96
|
+
height: pixelRect.height,
|
|
97
|
+
},
|
|
98
|
+
isSearchHit: anno.isSearchHit,
|
|
99
|
+
tooltip: anno.body.value,
|
|
100
|
+
});
|
|
101
|
+
} else if (anno.geometry.type === 'POLYGON') {
|
|
102
|
+
// Convert each point from image to viewport to pixel
|
|
103
|
+
const pixelPoints = anno.geometry.points.map((point) => {
|
|
104
|
+
const viewportPoint = tiledImage.imageToViewportCoordinates(
|
|
105
|
+
new OSD.Point(point[0], point[1]),
|
|
106
|
+
);
|
|
107
|
+
const pixelPoint =
|
|
108
|
+
viewer.viewport.viewportToViewerElementCoordinates(
|
|
109
|
+
viewportPoint,
|
|
110
|
+
);
|
|
111
|
+
return [pixelPoint.x, pixelPoint.y];
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Calculate bounding box for SVG positioning
|
|
115
|
+
let minX = Infinity,
|
|
116
|
+
minY = Infinity,
|
|
117
|
+
maxX = -Infinity,
|
|
118
|
+
maxY = -Infinity;
|
|
119
|
+
for (const [x, y] of pixelPoints) {
|
|
120
|
+
minX = Math.min(minX, x);
|
|
121
|
+
minY = Math.min(minY, y);
|
|
122
|
+
maxX = Math.max(maxX, x);
|
|
123
|
+
maxY = Math.max(maxY, y);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Adjust points relative to bounding box
|
|
127
|
+
const relativePoints = pixelPoints.map(([x, y]) => [
|
|
128
|
+
x - minX,
|
|
129
|
+
y - minY,
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
results.push({
|
|
133
|
+
id: anno.id,
|
|
134
|
+
type: 'POLYGON' as const,
|
|
135
|
+
bounds: {
|
|
136
|
+
x: minX,
|
|
137
|
+
y: minY,
|
|
138
|
+
width: maxX - minX,
|
|
139
|
+
height: maxY - minY,
|
|
140
|
+
},
|
|
141
|
+
points: relativePoints,
|
|
142
|
+
isSearchHit: anno.isSearchHit,
|
|
143
|
+
tooltip: anno.body.value,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return results;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
onMount(() => {
|
|
152
|
+
if (!container) return;
|
|
153
|
+
|
|
154
|
+
(async () => {
|
|
155
|
+
// Dynamically import OpenSeadragon to avoid SSR issues
|
|
156
|
+
const osdModule = await import('openseadragon');
|
|
157
|
+
OSD = osdModule.default || osdModule;
|
|
158
|
+
|
|
159
|
+
// Initialize OpenSeadragon viewer
|
|
160
|
+
viewer = OSD({
|
|
161
|
+
element: container,
|
|
162
|
+
tileSources: null, // Will be set via effect
|
|
163
|
+
prefixUrl: '', // No navigation UI images needed
|
|
164
|
+
showNavigationControl: false,
|
|
165
|
+
showHomeControl: false,
|
|
166
|
+
showFullPageControl: false,
|
|
167
|
+
showSequenceControl: false,
|
|
168
|
+
showZoomControl: false,
|
|
169
|
+
showRotationControl: false,
|
|
170
|
+
animationTime: 0.5,
|
|
171
|
+
springStiffness: 7.0,
|
|
172
|
+
zoomPerClick: 2.0,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Notify plugins that OSD is ready
|
|
176
|
+
viewerState.notifyOSDReady(viewer);
|
|
177
|
+
})();
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
viewer?.destroy();
|
|
181
|
+
viewerState.osdViewer = null;
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Subscribe to OSD events for reactivity
|
|
186
|
+
$effect(() => {
|
|
187
|
+
if (!viewer) return;
|
|
188
|
+
|
|
189
|
+
const update = () => {
|
|
190
|
+
osdVersion++;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
viewer.addHandler('open', update);
|
|
194
|
+
viewer.addHandler('animation', update);
|
|
195
|
+
viewer.addHandler('resize', update);
|
|
196
|
+
viewer.addHandler('rotate', update);
|
|
197
|
+
viewer.world.addHandler('add-item', update);
|
|
198
|
+
viewer.world.addHandler('remove-item', update);
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
viewer.removeHandler('open', update);
|
|
202
|
+
viewer.removeHandler('animation', update);
|
|
203
|
+
viewer.removeHandler('resize', update);
|
|
204
|
+
viewer.removeHandler('rotate', update);
|
|
205
|
+
viewer.world.removeHandler('add-item', update);
|
|
206
|
+
viewer.world.removeHandler('remove-item', update);
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Load tile source when it changes
|
|
211
|
+
$effect(() => {
|
|
212
|
+
if (!viewer || !tileSources) return;
|
|
213
|
+
|
|
214
|
+
viewer.open(tileSources);
|
|
215
|
+
});
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<div class="w-full h-full relative">
|
|
219
|
+
<div
|
|
220
|
+
bind:this={container}
|
|
221
|
+
class="w-full h-full osd-background bg-base-100"
|
|
222
|
+
></div>
|
|
223
|
+
|
|
224
|
+
<!-- Render annotations -->
|
|
225
|
+
{#each renderedAnnotations as anno (anno.id)}
|
|
226
|
+
{#if anno.type === 'RECTANGLE'}
|
|
227
|
+
<div
|
|
228
|
+
class="absolute border-2 transition-colors cursor-pointer pointer-events-auto {anno.isSearchHit
|
|
229
|
+
? 'border-yellow-400 bg-yellow-400/40 hover:bg-yellow-400/60'
|
|
230
|
+
: 'border-red-500 bg-red-500/20 hover:bg-red-500/40'}"
|
|
231
|
+
style="
|
|
232
|
+
left: {anno.rect.x}px;
|
|
233
|
+
top: {anno.rect.y}px;
|
|
234
|
+
width: {anno.rect.width}px;
|
|
235
|
+
height: {anno.rect.height}px;
|
|
236
|
+
"
|
|
237
|
+
title={anno.tooltip}
|
|
238
|
+
></div>
|
|
239
|
+
{:else if anno.type === 'POLYGON'}
|
|
240
|
+
<svg
|
|
241
|
+
class="absolute pointer-events-auto"
|
|
242
|
+
style="
|
|
243
|
+
left: {anno.bounds.x}px;
|
|
244
|
+
top: {anno.bounds.y}px;
|
|
245
|
+
width: {anno.bounds.width}px;
|
|
246
|
+
height: {anno.bounds.height}px;
|
|
247
|
+
"
|
|
248
|
+
>
|
|
249
|
+
<title>{anno.tooltip}</title>
|
|
250
|
+
<polygon
|
|
251
|
+
points={anno.points.map((p: any) => p.join(',')).join(' ')}
|
|
252
|
+
class="cursor-pointer transition-colors {anno.isSearchHit
|
|
253
|
+
? 'fill-yellow-400/40 stroke-yellow-400 hover:fill-yellow-400/60'
|
|
254
|
+
: 'fill-red-500/20 stroke-red-500 hover:fill-red-500/40'}"
|
|
255
|
+
stroke-width="2"
|
|
256
|
+
/>
|
|
257
|
+
</svg>
|
|
258
|
+
{/if}
|
|
259
|
+
{/each}
|
|
260
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ViewerState } from '../state/viewer.svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
tileSources: string | object | null;
|
|
4
|
+
viewerState: ViewerState;
|
|
5
|
+
};
|
|
6
|
+
declare const OSDViewer: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type OSDViewer = ReturnType<typeof OSDViewer>;
|
|
8
|
+
export default OSDViewer;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, untrack } from 'svelte';
|
|
3
|
+
import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
|
|
4
|
+
import Spinner from 'phosphor-svelte/lib/Spinner';
|
|
5
|
+
import X from 'phosphor-svelte/lib/X';
|
|
6
|
+
import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
|
|
7
|
+
import { m } from '../state/i18n.svelte';
|
|
8
|
+
|
|
9
|
+
const viewerState = getContext<ViewerState>(VIEWER_STATE_KEY);
|
|
10
|
+
|
|
11
|
+
// We'll initialize from viewerState to preserve context.
|
|
12
|
+
let searchQuery = $state('');
|
|
13
|
+
let resultsContainer: HTMLElement;
|
|
14
|
+
|
|
15
|
+
let showCloseButton = $derived(
|
|
16
|
+
viewerState.config.search?.showCloseButton ?? true,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Sync local query with viewerState
|
|
20
|
+
$effect(() => {
|
|
21
|
+
if (viewerState.searchQuery !== untrack(() => searchQuery)) {
|
|
22
|
+
searchQuery = viewerState.searchQuery;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function handleSearch() {
|
|
27
|
+
viewerState.search(searchQuery);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
31
|
+
if (e.key === 'Enter') {
|
|
32
|
+
handleSearch();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function navigate(result: any) {
|
|
37
|
+
const canvas = viewerState.canvases[result.canvasIndex];
|
|
38
|
+
if (canvas) {
|
|
39
|
+
viewerState.setCanvas(canvas.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<!-- Drawer / Panel -->
|
|
45
|
+
{#if viewerState.showSearchPanel}
|
|
46
|
+
<div
|
|
47
|
+
class="h-full w-80 bg-base-200 shadow-2xl z-100 flex flex-col border-l border-base-300"
|
|
48
|
+
role="dialog"
|
|
49
|
+
aria-label={m.search_panel_title()}
|
|
50
|
+
>
|
|
51
|
+
<!-- Header -->
|
|
52
|
+
<div
|
|
53
|
+
class="flex items-center justify-between p-4 border-b border-base-300"
|
|
54
|
+
>
|
|
55
|
+
<h2 class="font-bold text-lg">{m.search()}</h2>
|
|
56
|
+
{#if showCloseButton}
|
|
57
|
+
<button
|
|
58
|
+
class="btn btn-sm btn-circle btn-ghost"
|
|
59
|
+
onclick={() => viewerState.toggleSearchPanel()}
|
|
60
|
+
aria-label={m.close_search()}
|
|
61
|
+
>
|
|
62
|
+
<X size={20} weight="bold" />
|
|
63
|
+
</button>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Search Input -->
|
|
68
|
+
<div class="p-4 border-b border-base-300 shrink-0">
|
|
69
|
+
<div class="relative w-full">
|
|
70
|
+
<input
|
|
71
|
+
type="text"
|
|
72
|
+
bind:value={searchQuery}
|
|
73
|
+
onkeydown={handleKeydown}
|
|
74
|
+
placeholder={m.search_panel_placeholder()}
|
|
75
|
+
class="input input-bordered w-full pr-12"
|
|
76
|
+
/>
|
|
77
|
+
<button
|
|
78
|
+
class="btn btn-primary absolute right-0 top-0 h-full rounded-l-none"
|
|
79
|
+
onclick={handleSearch}
|
|
80
|
+
aria-label={m.search_panel_title()}
|
|
81
|
+
>
|
|
82
|
+
{#if viewerState.isSearching}
|
|
83
|
+
<span class="loading loading-spinner loading-xs"></span>
|
|
84
|
+
{:else}
|
|
85
|
+
<MagnifyingGlass size={20} weight="bold" />
|
|
86
|
+
{/if}
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Results -->
|
|
92
|
+
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
|
93
|
+
{#if viewerState.isSearching}
|
|
94
|
+
<div class="flex justify-center p-8">
|
|
95
|
+
<span
|
|
96
|
+
class="loading loading-spinner loading-lg text-primary"
|
|
97
|
+
></span>
|
|
98
|
+
</div>
|
|
99
|
+
{:else if viewerState.searchResults.length === 0 && viewerState.searchQuery}
|
|
100
|
+
<div class="text-center opacity-50 p-4">
|
|
101
|
+
{m.search_panel_no_results({
|
|
102
|
+
query: viewerState.searchQuery,
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
{:else if viewerState.searchResults.length === 0 && !viewerState.searchQuery}
|
|
106
|
+
<div class="text-center opacity-50 p-4 text-sm">
|
|
107
|
+
{m.search_panel_instruction()}
|
|
108
|
+
</div>
|
|
109
|
+
{:else}
|
|
110
|
+
<!-- Results Header -->
|
|
111
|
+
<div
|
|
112
|
+
class="text-xs font-bold opacity-50 uppercase tracking-wider pb-2"
|
|
113
|
+
>
|
|
114
|
+
{m.search_panel_results_count({
|
|
115
|
+
count: viewerState.searchResults.length,
|
|
116
|
+
})}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{#each viewerState.searchResults as result, i}
|
|
120
|
+
<button
|
|
121
|
+
class="w-full text-left card bg-base-100 shadow hover:shadow-md transition-all p-4 text-sm group border border-transparent hover:border-primary focus:outline-none focus:ring-2 focus:ring-primary"
|
|
122
|
+
onclick={() => navigate(result)}
|
|
123
|
+
>
|
|
124
|
+
<div class="flex justify-between items-baseline mb-1">
|
|
125
|
+
<span
|
|
126
|
+
class="font-bold text-xs opacity-70 bg-base-200 px-1.5 py-0.5 rounded"
|
|
127
|
+
>{result.canvasLabel}</span
|
|
128
|
+
>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{#if result.type === 'hit'}
|
|
132
|
+
<div class="leading-relaxed">
|
|
133
|
+
<span>{@html result.before}</span>
|
|
134
|
+
<span
|
|
135
|
+
class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
|
|
136
|
+
>{@html result.match}</span
|
|
137
|
+
>
|
|
138
|
+
<span>{@html result.after}</span>
|
|
139
|
+
</div>
|
|
140
|
+
{:else}
|
|
141
|
+
<div class="leading-relaxed">
|
|
142
|
+
{result.match}
|
|
143
|
+
</div>
|
|
144
|
+
{/if}
|
|
145
|
+
</button>
|
|
146
|
+
{/each}
|
|
147
|
+
{/if}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
{/if}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CaretDown from 'phosphor-svelte/lib/CaretDown';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { m } from '../state/i18n.svelte';
|
|
5
|
+
|
|
6
|
+
let theme = $state('light');
|
|
7
|
+
|
|
8
|
+
const themes = [
|
|
9
|
+
'light',
|
|
10
|
+
'dark',
|
|
11
|
+
'cupcake',
|
|
12
|
+
'bumblebee',
|
|
13
|
+
'emerald',
|
|
14
|
+
'corporate',
|
|
15
|
+
'synthwave',
|
|
16
|
+
'retro',
|
|
17
|
+
'cyberpunk',
|
|
18
|
+
'valentine',
|
|
19
|
+
'halloween',
|
|
20
|
+
'garden',
|
|
21
|
+
'forest',
|
|
22
|
+
'aqua',
|
|
23
|
+
'lofi',
|
|
24
|
+
'pastel',
|
|
25
|
+
'fantasy',
|
|
26
|
+
'wireframe',
|
|
27
|
+
'black',
|
|
28
|
+
'luxury',
|
|
29
|
+
'dracula',
|
|
30
|
+
'cmyk',
|
|
31
|
+
'autumn',
|
|
32
|
+
'business',
|
|
33
|
+
'acid',
|
|
34
|
+
'lemonade',
|
|
35
|
+
'night',
|
|
36
|
+
'coffee',
|
|
37
|
+
'winter',
|
|
38
|
+
'dim',
|
|
39
|
+
'nord',
|
|
40
|
+
'sunset',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
onMount(() => {
|
|
44
|
+
const storedTheme = localStorage.getItem('theme');
|
|
45
|
+
if (storedTheme) {
|
|
46
|
+
theme = storedTheme;
|
|
47
|
+
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
48
|
+
theme = 'dark';
|
|
49
|
+
}
|
|
50
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function onThemeChange(newTheme: string) {
|
|
54
|
+
theme = newTheme;
|
|
55
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
56
|
+
localStorage.setItem('theme', theme);
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div title={m.change_theme_label()} class="dropdown dropdown-end">
|
|
61
|
+
<div
|
|
62
|
+
tabindex="0"
|
|
63
|
+
role="button"
|
|
64
|
+
class="btn group btn-sm gap-1.5 px-1.5 btn-ghost"
|
|
65
|
+
aria-label={m.change_theme_label()}
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
class="bg-base-100 group-hover:border-base-content/20 border-base-content/10 grid shrink-0 grid-cols-2 gap-0.5 rounded-md border p-1 transition-colors"
|
|
69
|
+
>
|
|
70
|
+
<div class="bg-base-content size-1 rounded-full"></div>
|
|
71
|
+
<div class="bg-primary size-1 rounded-full"></div>
|
|
72
|
+
<div class="bg-secondary size-1 rounded-full"></div>
|
|
73
|
+
<div class="bg-accent size-1 rounded-full"></div>
|
|
74
|
+
</div>
|
|
75
|
+
<CaretDown size={16} />
|
|
76
|
+
</div>
|
|
77
|
+
<div
|
|
78
|
+
tabindex="-1"
|
|
79
|
+
class="dropdown-content bg-base-200 text-base-content rounded-box top-px h-122 max-h-[calc(100vh-8.6rem)] overflow-y-auto border border-white/5 shadow-2xl outline outline-black/5 mt-16"
|
|
80
|
+
>
|
|
81
|
+
<ul class="menu w-56">
|
|
82
|
+
<li class="menu-title text-xs">{m.theme_menu_title()}</li>
|
|
83
|
+
{#each themes as t}
|
|
84
|
+
<li>
|
|
85
|
+
<button class="gap-3 px-2" onclick={() => onThemeChange(t)}>
|
|
86
|
+
<div
|
|
87
|
+
data-theme={t}
|
|
88
|
+
class="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded-md p-1 shadow-sm"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
class="bg-base-content size-1 rounded-full"
|
|
92
|
+
></div>
|
|
93
|
+
<div class="bg-primary size-1 rounded-full"></div>
|
|
94
|
+
<div class="bg-secondary size-1 rounded-full"></div>
|
|
95
|
+
<div class="bg-accent size-1 rounded-full"></div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="w-32 truncate">
|
|
98
|
+
{t}
|
|
99
|
+
</div>
|
|
100
|
+
<svg
|
|
101
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
102
|
+
width="16"
|
|
103
|
+
height="16"
|
|
104
|
+
viewBox="0 0 24 24"
|
|
105
|
+
fill="currentColor"
|
|
106
|
+
class={theme === t
|
|
107
|
+
? 'h-3 w-3 shrink-0'
|
|
108
|
+
: 'invisible h-3 w-3 shrink-0'}
|
|
109
|
+
><path
|
|
110
|
+
d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"
|
|
111
|
+
></path></svg
|
|
112
|
+
>
|
|
113
|
+
</button>
|
|
114
|
+
</li>
|
|
115
|
+
{/each}
|
|
116
|
+
</ul>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|