triiiceratops 0.10.0 → 0.10.2
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/{ArrowCounterClockwise-aFffCOKw.js → ArrowCounterClockwise-CvTUOlYp.js} +1 -1
- package/dist/{X-DZEgXrJ8.js → X-Dgb3I7Ob.js} +305 -301
- package/dist/{annotation_tool_point-CZKsj4Nk.js → annotation_tool_point-CKkqbVDq.js} +1 -1
- package/dist/components/SearchPanel.svelte +35 -23
- package/dist/components/ThumbnailGallery.svelte +25 -3
- package/dist/components/TriiiceratopsViewer.svelte +71 -6
- package/dist/{image_filters_reset-BEIf-_QA.js → image_filters_reset-DZrbHhqM.js} +1 -1
- package/dist/plugins/annotation-editor.js +3 -3
- package/dist/plugins/image-manipulation.js +3 -3
- package/dist/state/manifests.svelte.d.ts +2 -2
- package/dist/state/manifests.svelte.js +3 -0
- package/dist/state/viewer.svelte.d.ts +2 -0
- package/dist/state/viewer.svelte.js +114 -92
- package/dist/triiiceratops-bundle.js +1406 -1371
- package/dist/triiiceratops-element.css +1 -0
- package/dist/triiiceratops-element.iife.js +27 -26
- package/dist/triiiceratops.css +1 -1
- package/package.json +1 -1
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function navigate(
|
|
37
|
-
const canvas = viewerState.canvases[
|
|
36
|
+
function navigate(canvasIndex: number) {
|
|
37
|
+
const canvas = viewerState.canvases[canvasIndex];
|
|
38
38
|
if (canvas) {
|
|
39
39
|
viewerState.setCanvas(canvas.id);
|
|
40
40
|
}
|
|
@@ -121,32 +121,44 @@
|
|
|
121
121
|
})}
|
|
122
122
|
</div>
|
|
123
123
|
|
|
124
|
-
{#each viewerState.searchResults as
|
|
124
|
+
{#each viewerState.searchResults as group}
|
|
125
125
|
<button
|
|
126
|
-
class="w-full text-left
|
|
127
|
-
onclick={() => navigate(
|
|
126
|
+
class="w-full text-left bg-base-100 shadow-sm border border-base-200 rounded-box cursor-pointer hover:shadow-md transition-all block p-0 select-none"
|
|
127
|
+
onclick={() => navigate(group.canvasIndex)}
|
|
128
128
|
>
|
|
129
|
-
<div
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
<div
|
|
130
|
+
class="text-sm font-bold opacity-80 bg-base-200/50 flex items-center justify-between py-2 px-3 border-b border-base-200"
|
|
131
|
+
>
|
|
132
|
+
<span>{group.canvasLabel}</span>
|
|
133
|
+
<span class="badge badge-sm badge-ghost"
|
|
134
|
+
>{group.hits.length}
|
|
135
|
+
{group.hits.length === 1
|
|
136
|
+
? 'match'
|
|
137
|
+
: 'matches'}</span
|
|
133
138
|
>
|
|
134
139
|
</div>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<span
|
|
140
|
-
class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
|
|
141
|
-
>{@html result.match}</span
|
|
140
|
+
<div class="p-0">
|
|
141
|
+
{#each group.hits.slice(0, 1) as result}
|
|
142
|
+
<div
|
|
143
|
+
class="p-3 text-sm border-b border-base-200 last:border-none hover:bg-base-200/30 transition-colors"
|
|
142
144
|
>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
{#if result.type === 'hit'}
|
|
146
|
+
<div class="leading-relaxed">
|
|
147
|
+
<span>{@html result.before}</span>
|
|
148
|
+
<span
|
|
149
|
+
class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
|
|
150
|
+
>{@html result.match}</span
|
|
151
|
+
>
|
|
152
|
+
<span>{@html result.after}</span>
|
|
153
|
+
</div>
|
|
154
|
+
{:else}
|
|
155
|
+
<div class="leading-relaxed">
|
|
156
|
+
{@html result.match}
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
159
|
+
</div>
|
|
160
|
+
{/each}
|
|
161
|
+
</div>
|
|
150
162
|
</button>
|
|
151
163
|
{/each}
|
|
152
164
|
{/if}
|
|
@@ -356,6 +356,27 @@
|
|
|
356
356
|
}
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
+
// Auto-scroll active thumbnail into view
|
|
360
|
+
$effect(() => {
|
|
361
|
+
if (!galleryElement || !viewerState.canvasId) return;
|
|
362
|
+
// Use a slight timeout to ensure DOM is ready/layout is stable if needed,
|
|
363
|
+
// though usually effect runs after render.
|
|
364
|
+
const id = viewerState.canvasId;
|
|
365
|
+
|
|
366
|
+
// requestAnimationFrame to ensure we are in a good painting frame?
|
|
367
|
+
// Or just direct. Svelte 5 effects are post-dom-update.
|
|
368
|
+
const activeEl = galleryElement.querySelector(
|
|
369
|
+
`[data-id="${CSS.escape(id)}"]`,
|
|
370
|
+
);
|
|
371
|
+
if (activeEl) {
|
|
372
|
+
activeEl.scrollIntoView({
|
|
373
|
+
behavior: 'smooth',
|
|
374
|
+
block: 'nearest',
|
|
375
|
+
inline: 'center',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
359
380
|
// Switch to horizontal layout if height is small or docked to top/bottom
|
|
360
381
|
let isHorizontal = $derived(
|
|
361
382
|
dockSide === 'top' ||
|
|
@@ -493,20 +514,21 @@
|
|
|
493
514
|
: 'grid gap-2'}
|
|
494
515
|
style={isHorizontal
|
|
495
516
|
? ''
|
|
496
|
-
: 'grid-template-columns: repeat(auto-fill, minmax(
|
|
517
|
+
: 'grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));'}
|
|
497
518
|
>
|
|
498
519
|
{#each thumbnails as thumb}
|
|
499
520
|
<button
|
|
500
521
|
class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
|
|
501
|
-
? 'w-[
|
|
522
|
+
? 'w-[90px]'
|
|
502
523
|
: ''} {viewerState.canvasId === thumb.id
|
|
503
524
|
? 'ring-2 ring-primary bg-primary/5'
|
|
504
525
|
: ''}"
|
|
505
526
|
onclick={() => selectCanvas(thumb.id)}
|
|
527
|
+
data-id={thumb.id}
|
|
506
528
|
aria-label="Select canvas {thumb.label}"
|
|
507
529
|
>
|
|
508
530
|
<div
|
|
509
|
-
class="aspect-4
|
|
531
|
+
class="aspect-3/4 bg-base-300 rounded overflow-hidden relative w-full flex items-center justify-center"
|
|
510
532
|
>
|
|
511
533
|
{#if thumb.src}
|
|
512
534
|
<img
|
|
@@ -176,10 +176,45 @@
|
|
|
176
176
|
),
|
|
177
177
|
);
|
|
178
178
|
|
|
179
|
-
let manifestData = $derived(internalViewerState.
|
|
179
|
+
let manifestData = $derived(internalViewerState.manifestEntry);
|
|
180
180
|
let canvases = $derived(internalViewerState.canvases);
|
|
181
181
|
let currentCanvasIndex = $derived(internalViewerState.currentCanvasIndex);
|
|
182
182
|
|
|
183
|
+
// Effect to trigger deferred search once manifest is loaded
|
|
184
|
+
$effect(() => {
|
|
185
|
+
if (
|
|
186
|
+
internalViewerState.pendingSearchQuery &&
|
|
187
|
+
manifestData &&
|
|
188
|
+
!manifestData.isFetching &&
|
|
189
|
+
!manifestData.error &&
|
|
190
|
+
manifestData.manifesto
|
|
191
|
+
) {
|
|
192
|
+
const query = internalViewerState.pendingSearchQuery;
|
|
193
|
+
internalViewerState.pendingSearchQuery = null;
|
|
194
|
+
console.log(
|
|
195
|
+
'[Viewer] Manifest loaded, triggering deferred search:',
|
|
196
|
+
query,
|
|
197
|
+
);
|
|
198
|
+
internalViewerState.search(query);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Auto-select first canvas if none selected
|
|
203
|
+
$effect(() => {
|
|
204
|
+
if (
|
|
205
|
+
canvases &&
|
|
206
|
+
canvases.length > 0 &&
|
|
207
|
+
!internalViewerState.canvasId &&
|
|
208
|
+
!manifestData?.isFetching
|
|
209
|
+
) {
|
|
210
|
+
console.log(
|
|
211
|
+
'[Viewer] Auto-selecting first canvas:',
|
|
212
|
+
canvases[0].id,
|
|
213
|
+
);
|
|
214
|
+
internalViewerState.setCanvas(canvases[0].id);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
183
218
|
let tileSources = $derived.by(() => {
|
|
184
219
|
if (
|
|
185
220
|
!canvases ||
|
|
@@ -340,7 +375,7 @@
|
|
|
340
375
|
>
|
|
341
376
|
<!-- Gallery (when docked left) -->
|
|
342
377
|
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'left'}
|
|
343
|
-
<div class="h-full w-[
|
|
378
|
+
<div class="h-full w-[140px] pointer-events-auto relative">
|
|
344
379
|
<ThumbnailGallery {canvases} />
|
|
345
380
|
</div>
|
|
346
381
|
{/if}
|
|
@@ -366,7 +401,7 @@
|
|
|
366
401
|
<!-- Top Area (Gallery) -->
|
|
367
402
|
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'top'}
|
|
368
403
|
<div
|
|
369
|
-
class="flex-none h-[
|
|
404
|
+
class="flex-none h-[160px] w-full pointer-events-auto relative z-20"
|
|
370
405
|
>
|
|
371
406
|
<ThumbnailGallery {canvases} />
|
|
372
407
|
</div>
|
|
@@ -399,7 +434,7 @@
|
|
|
399
434
|
viewerState={internalViewerState}
|
|
400
435
|
/>
|
|
401
436
|
{/key}
|
|
402
|
-
{:else}
|
|
437
|
+
{:else if manifestData && !manifestData.isFetching && !tileSources}
|
|
403
438
|
<div
|
|
404
439
|
class="w-full h-full flex items-center justify-center text-base-content/50"
|
|
405
440
|
>
|
|
@@ -444,7 +479,7 @@
|
|
|
444
479
|
<!-- Bottom Area (Gallery) -->
|
|
445
480
|
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'bottom'}
|
|
446
481
|
<div
|
|
447
|
-
class="flex-none h-[
|
|
482
|
+
class="flex-none h-[160px] w-full pointer-events-auto relative z-20"
|
|
448
483
|
>
|
|
449
484
|
<ThumbnailGallery {canvases} />
|
|
450
485
|
</div>
|
|
@@ -480,7 +515,7 @@
|
|
|
480
515
|
|
|
481
516
|
<!-- Gallery (when docked right) -->
|
|
482
517
|
{#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'right'}
|
|
483
|
-
<div class="h-full w-[
|
|
518
|
+
<div class="h-full w-[140px] pointer-events-auto relative">
|
|
484
519
|
<ThumbnailGallery {canvases} />
|
|
485
520
|
</div>
|
|
486
521
|
{/if}
|
|
@@ -499,3 +534,33 @@
|
|
|
499
534
|
</div>
|
|
500
535
|
{/if}
|
|
501
536
|
</div>
|
|
537
|
+
|
|
538
|
+
<style>
|
|
539
|
+
/* Scoped scrollbar styles for the viewer */
|
|
540
|
+
:global(#triiiceratops-viewer *) {
|
|
541
|
+
scrollbar-width: thin;
|
|
542
|
+
scrollbar-color: var(--fallback-bc, oklch(var(--bc) / 0.2)) transparent;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
:global(#triiiceratops-viewer ::-webkit-scrollbar) {
|
|
546
|
+
width: 4px;
|
|
547
|
+
height: 4px;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
:global(#triiiceratops-viewer ::-webkit-scrollbar-track) {
|
|
551
|
+
background: transparent;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
:global(#triiiceratops-viewer ::-webkit-scrollbar-thumb) {
|
|
555
|
+
background-color: var(--fallback-bc, oklch(var(--bc) / 0.2));
|
|
556
|
+
border-radius: 9999px;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
:global(#triiiceratops-viewer ::-webkit-scrollbar-thumb:hover) {
|
|
560
|
+
background-color: var(--fallback-bc, oklch(var(--bc) / 0.4));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
:global(#triiiceratops-viewer ::-webkit-scrollbar-corner) {
|
|
564
|
+
background: transparent;
|
|
565
|
+
}
|
|
566
|
+
</style>
|
|
@@ -4,10 +4,10 @@ var nt = (s, e, t) => Mf(s, typeof e != "symbol" ? e + "" : e, t);
|
|
|
4
4
|
import "svelte/internal/disclose-version";
|
|
5
5
|
import * as d from "svelte/internal/client";
|
|
6
6
|
import { getContext as Rf, onMount as If, onDestroy as Cf } from "svelte";
|
|
7
|
-
import { m as xl, g as as, l as Pf, s as Df, X as bl, c as Lf, V as Of } from "../X-
|
|
7
|
+
import { m as xl, g as as, l as Pf, s as Df, X as bl, c as Lf, V as Of } from "../X-Dgb3I7Ob.js";
|
|
8
8
|
import Hs from "openseadragon";
|
|
9
|
-
import { A as Bf } from "../ArrowCounterClockwise-
|
|
10
|
-
import { q as Nf, h as Ff, c as kf, j as Uf, k as $f, t as Gf, u as Hf, v as Vf, r as Xf, s as zf, m as jf, i as Yf, g as Wf, a as Zf, n as qf, f as Kf, e as Jf, b as Qf, d as ep, o as tp, l as sp, p as ip } from "../annotation_tool_point-
|
|
9
|
+
import { A as Bf } from "../ArrowCounterClockwise-CvTUOlYp.js";
|
|
10
|
+
import { q as Nf, h as Ff, c as kf, j as Uf, k as $f, t as Gf, u as Hf, v as Vf, r as Xf, s as zf, m as jf, i as Yf, g as Wf, a as Zf, n as qf, f as Kf, e as Jf, b as Qf, d as ep, o as tp, l as sp, p as ip } from "../annotation_tool_point-CKkqbVDq.js";
|
|
11
11
|
import "manifesto.js";
|
|
12
12
|
var np = Object.defineProperty, rp = (s, e, t) => e in s ? np(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, El = (s, e, t) => rp(s, typeof e != "symbol" ? e + "" : e, t), wl = Object.prototype.hasOwnProperty;
|
|
13
13
|
function Vs(s, e) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "svelte/internal/disclose-version";
|
|
2
2
|
import * as e from "svelte/internal/client";
|
|
3
3
|
import { getContext as s0 } from "svelte";
|
|
4
|
-
import { l as l0, s as i0, X as n0, c as o0, V as c0, g as v0 } from "../X-
|
|
5
|
-
import { A as d0 } from "../ArrowCounterClockwise-
|
|
6
|
-
import { i as _0, a as g0, b as f0, g as u0, c as h0, e as m0, d as p0, f as b0 } from "../image_filters_reset-
|
|
4
|
+
import { l as l0, s as i0, X as n0, c as o0, V as c0, g as v0 } from "../X-Dgb3I7Ob.js";
|
|
5
|
+
import { A as d0 } from "../ArrowCounterClockwise-CvTUOlYp.js";
|
|
6
|
+
import { i as _0, a as g0, b as f0, g as u0, c as h0, e as m0, d as p0, f as b0 } from "../image_filters_reset-DZrbHhqM.js";
|
|
7
7
|
const G = {
|
|
8
8
|
brightness: 100,
|
|
9
9
|
contrast: 100,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface ManifestEntry {
|
|
1
|
+
export interface ManifestEntry {
|
|
2
2
|
json?: any;
|
|
3
3
|
manifesto?: any;
|
|
4
4
|
error?: any;
|
|
@@ -14,10 +14,10 @@ export declare class ManifestsState {
|
|
|
14
14
|
getUserAnnotations(manifestId: string, canvasId: string): any[];
|
|
15
15
|
fetchManifest(manifestId: string): Promise<void>;
|
|
16
16
|
getManifest(manifestId: string): any;
|
|
17
|
+
getManifestEntry(manifestId: string): ManifestEntry | undefined;
|
|
17
18
|
fetchAnnotationList(url: string): Promise<void>;
|
|
18
19
|
getCanvases(manifestId: string): any;
|
|
19
20
|
getAnnotations(manifestId: string, canvasId: string): any[];
|
|
20
21
|
manualGetAnnotations(manifestId: string, canvasId: string): any[];
|
|
21
22
|
}
|
|
22
23
|
export declare const manifestsState: ManifestsState;
|
|
23
|
-
export {};
|
|
@@ -57,6 +57,9 @@ export class ManifestsState {
|
|
|
57
57
|
const entry = this.manifests[manifestId];
|
|
58
58
|
return entry?.manifesto;
|
|
59
59
|
}
|
|
60
|
+
getManifestEntry(manifestId) {
|
|
61
|
+
return this.manifests[manifestId];
|
|
62
|
+
}
|
|
60
63
|
async fetchAnnotationList(url) {
|
|
61
64
|
if (this.manifests[url])
|
|
62
65
|
return; // Already fetched or fetching
|
|
@@ -73,6 +73,7 @@ export declare class ViewerState {
|
|
|
73
73
|
private dispatchStateChange;
|
|
74
74
|
constructor(initialManifestId?: string | null, initialCanvasId?: string | null, initialPlugins?: PluginDef[]);
|
|
75
75
|
get manifest(): any;
|
|
76
|
+
get manifestEntry(): import("./manifests.svelte.js").ManifestEntry | null | undefined;
|
|
76
77
|
get canvases(): any;
|
|
77
78
|
get currentCanvasIndex(): any;
|
|
78
79
|
get hasNext(): boolean;
|
|
@@ -87,6 +88,7 @@ export declare class ViewerState {
|
|
|
87
88
|
toggleFullScreen(): void;
|
|
88
89
|
toggleMetadataDialog(): void;
|
|
89
90
|
searchQuery: string;
|
|
91
|
+
pendingSearchQuery: string | null;
|
|
90
92
|
searchResults: any[];
|
|
91
93
|
isSearching: boolean;
|
|
92
94
|
showSearchPanel: boolean;
|
|
@@ -111,19 +111,16 @@ export class ViewerState {
|
|
|
111
111
|
return null;
|
|
112
112
|
return manifestsState.getManifest(this.manifestId);
|
|
113
113
|
}
|
|
114
|
+
get manifestEntry() {
|
|
115
|
+
if (!this.manifestId)
|
|
116
|
+
return null;
|
|
117
|
+
return manifestsState.getManifestEntry(this.manifestId);
|
|
118
|
+
}
|
|
114
119
|
get canvases() {
|
|
115
120
|
if (!this.manifestId)
|
|
116
121
|
return [];
|
|
117
122
|
const canvases = manifestsState.getCanvases(this.manifestId);
|
|
118
123
|
// Auto-initialize canvasId to first canvas if not set
|
|
119
|
-
if (canvases.length > 0 && !this.canvasId) {
|
|
120
|
-
// Use setTimeout to avoid updating state during a derived computation
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
if (!this.canvasId && canvases.length > 0) {
|
|
123
|
-
this.canvasId = canvases[0].id;
|
|
124
|
-
}
|
|
125
|
-
}, 0);
|
|
126
|
-
}
|
|
127
124
|
return canvases;
|
|
128
125
|
}
|
|
129
126
|
get currentCanvasIndex() {
|
|
@@ -232,6 +229,7 @@ export class ViewerState {
|
|
|
232
229
|
this.showMetadataDialog = !this.showMetadataDialog;
|
|
233
230
|
}
|
|
234
231
|
searchQuery = $state('');
|
|
232
|
+
pendingSearchQuery = $state(null);
|
|
235
233
|
searchResults = $state([]);
|
|
236
234
|
isSearching = $state(false);
|
|
237
235
|
showSearchPanel = $state(false);
|
|
@@ -262,8 +260,12 @@ export class ViewerState {
|
|
|
262
260
|
this.searchResults = [];
|
|
263
261
|
try {
|
|
264
262
|
const manifest = this.manifest;
|
|
265
|
-
if (!manifest)
|
|
266
|
-
|
|
263
|
+
if (!manifest) {
|
|
264
|
+
// Defer search until manifest is loaded
|
|
265
|
+
console.log('[ViewerState] Manifest not loaded, deferring search:', query);
|
|
266
|
+
this.pendingSearchQuery = query;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
267
269
|
let service = manifest.getService('http://iiif.io/api/search/1/search') ||
|
|
268
270
|
manifest.getService('http://iiif.io/api/search/0/search');
|
|
269
271
|
if (!service) {
|
|
@@ -290,7 +292,8 @@ export class ViewerState {
|
|
|
290
292
|
throw new Error('Search request failed');
|
|
291
293
|
const data = await response.json();
|
|
292
294
|
const resources = data.resources || [];
|
|
293
|
-
|
|
295
|
+
// Group results by canvas index
|
|
296
|
+
const resultsByCanvas = new Map();
|
|
294
297
|
// Helper to parse xywh
|
|
295
298
|
const parseSelector = (onVal) => {
|
|
296
299
|
const val = typeof onVal === 'string'
|
|
@@ -306,73 +309,84 @@ export class ViewerState {
|
|
|
306
309
|
return coords; // [x, y, w, h]
|
|
307
310
|
return null;
|
|
308
311
|
};
|
|
312
|
+
// Helper to unescape mark tags
|
|
313
|
+
const decodeMark = (str) => {
|
|
314
|
+
if (!str)
|
|
315
|
+
return '';
|
|
316
|
+
return str
|
|
317
|
+
.replace(/<mark>/g, '<mark>')
|
|
318
|
+
.replace(/<\/mark>/g, '</mark>');
|
|
319
|
+
};
|
|
309
320
|
if (data.hits) {
|
|
310
321
|
for (const hit of data.hits) {
|
|
311
322
|
// hits have property 'annotations' which is array of ids
|
|
312
|
-
// Collapse all annotations for this hit into a single result per canvas
|
|
313
323
|
const annotations = hit.annotations || [];
|
|
314
|
-
|
|
324
|
+
// We need to determine which canvas this hit belongs to.
|
|
325
|
+
// A hit might technically span annotations on multiple canvases (unlikely for IIIF Content Search),
|
|
326
|
+
// but usually it's associated with specific annotations on one canvas.
|
|
327
|
+
// We will take the first valid canvas we find for the annotations.
|
|
328
|
+
let canvasIndex = -1;
|
|
329
|
+
let bounds = null;
|
|
330
|
+
let allBounds = [];
|
|
315
331
|
for (const annoId of annotations) {
|
|
316
332
|
const annotation = resources.find((r) => r['@id'] === annoId || r.id === annoId);
|
|
317
333
|
if (annotation && annotation.on) {
|
|
318
|
-
// annotation.on can be "canvas-id" or "canvas-id#xywh=..."
|
|
319
334
|
const onVal = typeof annotation.on === 'string'
|
|
320
335
|
? annotation.on
|
|
321
336
|
: annotation.on['@id'] || annotation.on.id;
|
|
322
337
|
const cleanOn = onVal.split('#')[0];
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
let label = 'Canvas ' + (canvasIndex + 1);
|
|
330
|
-
try {
|
|
331
|
-
if (canvas.getLabel) {
|
|
332
|
-
const l = canvas.getLabel();
|
|
333
|
-
if (Array.isArray(l) &&
|
|
334
|
-
l.length > 0)
|
|
335
|
-
label = l[0].value;
|
|
336
|
-
else if (typeof l === 'string')
|
|
337
|
-
label = l;
|
|
338
|
-
}
|
|
339
|
-
else if (canvas.label) {
|
|
340
|
-
// Fallback if raw object
|
|
341
|
-
if (typeof canvas.label === 'string')
|
|
342
|
-
label = canvas.label;
|
|
343
|
-
else if (Array.isArray(canvas.label))
|
|
344
|
-
label = canvas.label[0]?.value;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
catch (e) {
|
|
348
|
-
/* ignore */
|
|
349
|
-
}
|
|
350
|
-
hitBoundsByCanvas.set(canvasIndex, {
|
|
351
|
-
label: String(label),
|
|
352
|
-
bounds: [],
|
|
353
|
-
});
|
|
338
|
+
const b = parseSelector(onVal);
|
|
339
|
+
const cIndex = this.canvases.findIndex((c) => c.id === cleanOn);
|
|
340
|
+
if (cIndex >= 0) {
|
|
341
|
+
// If we haven't set a canvas yet, set it
|
|
342
|
+
if (canvasIndex === -1) {
|
|
343
|
+
canvasIndex = cIndex;
|
|
354
344
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
345
|
+
// If we found bounds, add them
|
|
346
|
+
if (b) {
|
|
347
|
+
allBounds.push(b);
|
|
348
|
+
if (!bounds)
|
|
349
|
+
bounds = b;
|
|
359
350
|
}
|
|
360
351
|
}
|
|
361
352
|
}
|
|
362
353
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
354
|
+
if (canvasIndex >= 0) {
|
|
355
|
+
if (!resultsByCanvas.has(canvasIndex)) {
|
|
356
|
+
const canvas = this.canvases[canvasIndex];
|
|
357
|
+
let label = 'Canvas ' + (canvasIndex + 1);
|
|
358
|
+
try {
|
|
359
|
+
if (canvas.getLabel) {
|
|
360
|
+
const l = canvas.getLabel();
|
|
361
|
+
if (Array.isArray(l) && l.length > 0)
|
|
362
|
+
label = l[0].value;
|
|
363
|
+
else if (typeof l === 'string')
|
|
364
|
+
label = l;
|
|
365
|
+
}
|
|
366
|
+
else if (canvas.label) {
|
|
367
|
+
// Fallback if raw object
|
|
368
|
+
if (typeof canvas.label === 'string')
|
|
369
|
+
label = canvas.label;
|
|
370
|
+
else if (Array.isArray(canvas.label))
|
|
371
|
+
label = canvas.label[0]?.value;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
/* ignore */
|
|
376
|
+
}
|
|
377
|
+
resultsByCanvas.set(canvasIndex, {
|
|
378
|
+
canvasIndex,
|
|
379
|
+
canvasLabel: String(label),
|
|
380
|
+
hits: [],
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
resultsByCanvas.get(canvasIndex).hits.push({
|
|
366
384
|
type: 'hit',
|
|
367
|
-
before: hit.before,
|
|
368
|
-
match: hit.match,
|
|
369
|
-
after: hit.after,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// Store all bounds for this hit on this canvas
|
|
373
|
-
allBounds: data.bounds,
|
|
374
|
-
// Keep first bounds for backwards compatibility
|
|
375
|
-
bounds: data.bounds.length > 0 ? data.bounds[0] : null,
|
|
385
|
+
before: decodeMark(hit.before),
|
|
386
|
+
match: decodeMark(hit.match),
|
|
387
|
+
after: decodeMark(hit.after),
|
|
388
|
+
bounds,
|
|
389
|
+
allBounds,
|
|
376
390
|
});
|
|
377
391
|
}
|
|
378
392
|
}
|
|
@@ -408,46 +422,54 @@ export class ViewerState {
|
|
|
408
422
|
catch (e) {
|
|
409
423
|
/* ignore */
|
|
410
424
|
}
|
|
411
|
-
|
|
425
|
+
if (!resultsByCanvas.has(canvasIndex)) {
|
|
426
|
+
resultsByCanvas.set(canvasIndex, {
|
|
427
|
+
canvasIndex,
|
|
428
|
+
canvasLabel: String(label),
|
|
429
|
+
hits: [],
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
resultsByCanvas.get(canvasIndex).hits.push({
|
|
412
433
|
type: 'resource',
|
|
413
|
-
match: res.resource && res.resource.chars
|
|
434
|
+
match: decodeMark(res.resource && res.resource.chars
|
|
414
435
|
? res.resource.chars
|
|
415
|
-
: res.chars || '',
|
|
416
|
-
canvasIndex,
|
|
417
|
-
canvasLabel: String(label),
|
|
436
|
+
: res.chars || ''),
|
|
418
437
|
bounds,
|
|
438
|
+
allBounds: bounds ? [bounds] : [],
|
|
419
439
|
});
|
|
420
440
|
}
|
|
421
441
|
}
|
|
422
442
|
}
|
|
423
|
-
|
|
443
|
+
// Convert Map to Array and Sort
|
|
444
|
+
this.searchResults = Array.from(resultsByCanvas.values()).sort((a, b) => a.canvasIndex - b.canvasIndex);
|
|
424
445
|
// Generate ephemeral search annotations
|
|
425
|
-
//
|
|
446
|
+
// We need to flatten our grouped structure to generate the annotations list
|
|
426
447
|
let annotationIndex = 0;
|
|
427
|
-
this.searchAnnotations =
|
|
428
|
-
const canvas = this.canvases[
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
448
|
+
this.searchAnnotations = this.searchResults.flatMap((group) => {
|
|
449
|
+
const canvas = this.canvases[group.canvasIndex];
|
|
450
|
+
return group.hits.flatMap((hit) => {
|
|
451
|
+
const boundsArray = hit.allBounds && hit.allBounds.length > 0
|
|
452
|
+
? hit.allBounds
|
|
453
|
+
: hit.bounds
|
|
454
|
+
? [hit.bounds]
|
|
455
|
+
: [];
|
|
456
|
+
return boundsArray.map((bounds) => {
|
|
457
|
+
const on = `${canvas.id}#xywh=${bounds.join(',')}`;
|
|
458
|
+
return {
|
|
459
|
+
'@id': `urn:search-hit:${annotationIndex++}`,
|
|
460
|
+
'@type': 'oa:Annotation',
|
|
461
|
+
motivation: 'sc:painting',
|
|
462
|
+
on: on,
|
|
463
|
+
canvasId: canvas.id,
|
|
464
|
+
resource: {
|
|
465
|
+
'@type': 'cnt:ContentAsText',
|
|
466
|
+
chars: hit.match,
|
|
467
|
+
},
|
|
468
|
+
// Flag to identify styling in Overlay?
|
|
469
|
+
// Or just standard rendering.
|
|
470
|
+
isSearchHit: true,
|
|
471
|
+
};
|
|
472
|
+
});
|
|
451
473
|
});
|
|
452
474
|
});
|
|
453
475
|
}
|