triiiceratops 0.16.5 → 0.16.7
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/OSDViewer.svelte +20 -5
- package/dist/components/osdReveal.d.ts +14 -0
- package/dist/components/osdReveal.js +37 -0
- package/dist/components/osdReveal.test.d.ts +1 -0
- package/dist/components/osdReveal.test.js +112 -0
- package/dist/state/viewer.search.test.js +6 -2
- package/dist/triiiceratops-bundle.js +2501 -2471
- package/dist/triiiceratops-element.iife.js +12 -12
- package/dist/types/config.d.ts +0 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import { SvelteSet } from 'svelte/reactivity';
|
|
4
|
+
import { createRevealSession } from './osdReveal';
|
|
4
5
|
import { parseAnnotations } from '../utils/annotationAdapter';
|
|
5
6
|
import { manifestsState } from '../state/manifests.svelte';
|
|
6
7
|
import type { ViewerState } from '../state/viewer.svelte';
|
|
@@ -393,15 +394,23 @@
|
|
|
393
394
|
// Load tile source when it changes
|
|
394
395
|
$effect(() => {
|
|
395
396
|
if (!viewer) return;
|
|
397
|
+
let cleanupRevealSession: (() => void) | null = null;
|
|
398
|
+
|
|
399
|
+
const clearRevealSession = () => {
|
|
400
|
+
if (!cleanupRevealSession) return;
|
|
401
|
+
cleanupRevealSession();
|
|
402
|
+
cleanupRevealSession = null;
|
|
403
|
+
};
|
|
396
404
|
|
|
397
405
|
// If sources are cleared/absent during a canvas/world switch, clear
|
|
398
406
|
// stale tiles immediately and allow the same source to reopen later.
|
|
399
407
|
if (!tileSources) {
|
|
408
|
+
clearRevealSession();
|
|
400
409
|
setViewerImageVisible(false);
|
|
401
410
|
viewer.close();
|
|
402
411
|
viewerState.tileSourceError = null;
|
|
403
412
|
lastTileSourceStr = '';
|
|
404
|
-
return;
|
|
413
|
+
return clearRevealSession;
|
|
405
414
|
}
|
|
406
415
|
|
|
407
416
|
const mode = viewerState.viewingMode;
|
|
@@ -431,11 +440,12 @@
|
|
|
431
440
|
: [];
|
|
432
441
|
|
|
433
442
|
if (sources.length === 0) {
|
|
443
|
+
clearRevealSession();
|
|
434
444
|
setViewerImageVisible(false);
|
|
435
445
|
viewer.close();
|
|
436
446
|
viewerState.tileSourceError = null;
|
|
437
447
|
lastTileSourceStr = '';
|
|
438
|
-
return;
|
|
448
|
+
return clearRevealSession;
|
|
439
449
|
}
|
|
440
450
|
|
|
441
451
|
// Capture stateKey for staleness guard
|
|
@@ -443,10 +453,13 @@
|
|
|
443
453
|
const overrides = viewerState.config?.openSeadragonConfig ?? {};
|
|
444
454
|
|
|
445
455
|
// Hide the previous image immediately; reveal once new tiles are drawn.
|
|
456
|
+
clearRevealSession();
|
|
446
457
|
setViewerImageVisible(false);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
458
|
+
cleanupRevealSession = createRevealSession({
|
|
459
|
+
viewer,
|
|
460
|
+
capturedKey,
|
|
461
|
+
getCurrentKey: () => lastTileSourceStr,
|
|
462
|
+
setViewerImageVisible,
|
|
450
463
|
});
|
|
451
464
|
|
|
452
465
|
if (mode === 'continuous') {
|
|
@@ -637,6 +650,8 @@
|
|
|
637
650
|
// Clear any previous error
|
|
638
651
|
viewerState.tileSourceError = null;
|
|
639
652
|
});
|
|
653
|
+
|
|
654
|
+
return clearRevealSession;
|
|
640
655
|
});
|
|
641
656
|
|
|
642
657
|
// Handle navigation in continuous mode
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const REVEAL_FALLBACK_MS = 1500;
|
|
2
|
+
type OSDLikeViewer = {
|
|
3
|
+
addHandler: (eventName: string, handler: () => void) => void;
|
|
4
|
+
removeHandler: (eventName: string, handler: () => void) => void;
|
|
5
|
+
};
|
|
6
|
+
type RevealSessionParams = {
|
|
7
|
+
viewer: OSDLikeViewer;
|
|
8
|
+
capturedKey: string;
|
|
9
|
+
getCurrentKey: () => string;
|
|
10
|
+
setViewerImageVisible: (isVisible: boolean) => void;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function createRevealSession({ viewer, capturedKey, getCurrentKey, setViewerImageVisible, timeoutMs, }: RevealSessionParams): () => void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const REVEAL_FALLBACK_MS = 1500;
|
|
2
|
+
const REVEAL_EVENTS = ['open', 'tile-drawn', 'animation', 'update-viewport'];
|
|
3
|
+
export function createRevealSession({ viewer, capturedKey, getCurrentKey, setViewerImageVisible, timeoutMs = REVEAL_FALLBACK_MS, }) {
|
|
4
|
+
let disposed = false;
|
|
5
|
+
let revealed = false;
|
|
6
|
+
const handlers = new Map();
|
|
7
|
+
let timeoutId = null;
|
|
8
|
+
const cleanup = () => {
|
|
9
|
+
if (disposed)
|
|
10
|
+
return;
|
|
11
|
+
disposed = true;
|
|
12
|
+
if (timeoutId !== null) {
|
|
13
|
+
clearTimeout(timeoutId);
|
|
14
|
+
timeoutId = null;
|
|
15
|
+
}
|
|
16
|
+
for (const [eventName, handler] of handlers.entries()) {
|
|
17
|
+
viewer.removeHandler(eventName, handler);
|
|
18
|
+
}
|
|
19
|
+
handlers.clear();
|
|
20
|
+
};
|
|
21
|
+
const revealIfCurrent = () => {
|
|
22
|
+
if (revealed || disposed)
|
|
23
|
+
return;
|
|
24
|
+
revealed = true;
|
|
25
|
+
cleanup();
|
|
26
|
+
if (capturedKey !== getCurrentKey())
|
|
27
|
+
return;
|
|
28
|
+
setViewerImageVisible(true);
|
|
29
|
+
};
|
|
30
|
+
for (const eventName of REVEAL_EVENTS) {
|
|
31
|
+
const handler = () => revealIfCurrent();
|
|
32
|
+
handlers.set(eventName, handler);
|
|
33
|
+
viewer.addHandler(eventName, handler);
|
|
34
|
+
}
|
|
35
|
+
timeoutId = setTimeout(revealIfCurrent, timeoutMs);
|
|
36
|
+
return cleanup;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createRevealSession } from './osdReveal';
|
|
3
|
+
class MockViewer {
|
|
4
|
+
handlers = new Map();
|
|
5
|
+
addHandler(eventName, handler) {
|
|
6
|
+
const set = this.handlers.get(eventName) ?? new Set();
|
|
7
|
+
set.add(handler);
|
|
8
|
+
this.handlers.set(eventName, set);
|
|
9
|
+
}
|
|
10
|
+
removeHandler(eventName, handler) {
|
|
11
|
+
const set = this.handlers.get(eventName);
|
|
12
|
+
if (!set)
|
|
13
|
+
return;
|
|
14
|
+
set.delete(handler);
|
|
15
|
+
if (set.size === 0)
|
|
16
|
+
this.handlers.delete(eventName);
|
|
17
|
+
}
|
|
18
|
+
emit(eventName) {
|
|
19
|
+
const set = this.handlers.get(eventName);
|
|
20
|
+
if (!set)
|
|
21
|
+
return;
|
|
22
|
+
for (const handler of [...set]) {
|
|
23
|
+
handler();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
getHandlerCount(eventName) {
|
|
27
|
+
return this.handlers.get(eventName)?.size ?? 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
describe('createRevealSession', () => {
|
|
31
|
+
it('reveals when open fires', () => {
|
|
32
|
+
const viewer = new MockViewer();
|
|
33
|
+
let currentKey = 'abc';
|
|
34
|
+
const setVisible = vi.fn();
|
|
35
|
+
createRevealSession({
|
|
36
|
+
viewer,
|
|
37
|
+
capturedKey: 'abc',
|
|
38
|
+
getCurrentKey: () => currentKey,
|
|
39
|
+
setViewerImageVisible: setVisible,
|
|
40
|
+
});
|
|
41
|
+
viewer.emit('open');
|
|
42
|
+
expect(setVisible).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(setVisible).toHaveBeenCalledWith(true);
|
|
44
|
+
});
|
|
45
|
+
it('reveals when tile-drawn fires', () => {
|
|
46
|
+
const viewer = new MockViewer();
|
|
47
|
+
const setVisible = vi.fn();
|
|
48
|
+
createRevealSession({
|
|
49
|
+
viewer,
|
|
50
|
+
capturedKey: 'abc',
|
|
51
|
+
getCurrentKey: () => 'abc',
|
|
52
|
+
setViewerImageVisible: setVisible,
|
|
53
|
+
});
|
|
54
|
+
viewer.emit('tile-drawn');
|
|
55
|
+
expect(setVisible).toHaveBeenCalledTimes(1);
|
|
56
|
+
expect(setVisible).toHaveBeenCalledWith(true);
|
|
57
|
+
});
|
|
58
|
+
it('reveals on timeout when no events fire', () => {
|
|
59
|
+
vi.useFakeTimers();
|
|
60
|
+
const viewer = new MockViewer();
|
|
61
|
+
const setVisible = vi.fn();
|
|
62
|
+
createRevealSession({
|
|
63
|
+
viewer,
|
|
64
|
+
capturedKey: 'abc',
|
|
65
|
+
getCurrentKey: () => 'abc',
|
|
66
|
+
setViewerImageVisible: setVisible,
|
|
67
|
+
timeoutMs: 25,
|
|
68
|
+
});
|
|
69
|
+
vi.advanceTimersByTime(25);
|
|
70
|
+
expect(setVisible).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(setVisible).toHaveBeenCalledWith(true);
|
|
72
|
+
vi.useRealTimers();
|
|
73
|
+
});
|
|
74
|
+
it('does not reveal when key is stale', () => {
|
|
75
|
+
const viewer = new MockViewer();
|
|
76
|
+
let currentKey = 'abc';
|
|
77
|
+
const setVisible = vi.fn();
|
|
78
|
+
createRevealSession({
|
|
79
|
+
viewer,
|
|
80
|
+
capturedKey: 'abc',
|
|
81
|
+
getCurrentKey: () => currentKey,
|
|
82
|
+
setViewerImageVisible: setVisible,
|
|
83
|
+
});
|
|
84
|
+
currentKey = 'new-key';
|
|
85
|
+
viewer.emit('open');
|
|
86
|
+
expect(setVisible).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
it('cleans up handlers and timer on cleanup', () => {
|
|
89
|
+
vi.useFakeTimers();
|
|
90
|
+
const viewer = new MockViewer();
|
|
91
|
+
const setVisible = vi.fn();
|
|
92
|
+
const cleanup = createRevealSession({
|
|
93
|
+
viewer,
|
|
94
|
+
capturedKey: 'abc',
|
|
95
|
+
getCurrentKey: () => 'abc',
|
|
96
|
+
setViewerImageVisible: setVisible,
|
|
97
|
+
timeoutMs: 25,
|
|
98
|
+
});
|
|
99
|
+
expect(viewer.getHandlerCount('open')).toBe(1);
|
|
100
|
+
expect(viewer.getHandlerCount('tile-drawn')).toBe(1);
|
|
101
|
+
expect(viewer.getHandlerCount('animation')).toBe(1);
|
|
102
|
+
expect(viewer.getHandlerCount('update-viewport')).toBe(1);
|
|
103
|
+
cleanup();
|
|
104
|
+
expect(viewer.getHandlerCount('open')).toBe(0);
|
|
105
|
+
expect(viewer.getHandlerCount('tile-drawn')).toBe(0);
|
|
106
|
+
expect(viewer.getHandlerCount('animation')).toBe(0);
|
|
107
|
+
expect(viewer.getHandlerCount('update-viewport')).toBe(0);
|
|
108
|
+
vi.advanceTimersByTime(30);
|
|
109
|
+
expect(setVisible).not.toHaveBeenCalled();
|
|
110
|
+
vi.useRealTimers();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -77,8 +77,12 @@ describe('ViewerState - IIIF Search', () => {
|
|
|
77
77
|
* Helper to setup a mock manifest with canvases
|
|
78
78
|
*/
|
|
79
79
|
function setupMockManifest(manifestJson) {
|
|
80
|
-
const
|
|
81
|
-
|
|
80
|
+
const parsed = manifesto.parseManifest(manifestJson);
|
|
81
|
+
if (!parsed) {
|
|
82
|
+
throw new Error('Failed to parse mock manifest');
|
|
83
|
+
}
|
|
84
|
+
const mockManifest = parsed;
|
|
85
|
+
const mockCanvases = mockManifest.getSequences()[0]?.getCanvases() ?? [];
|
|
82
86
|
vi.mocked(manifestsState.getManifest).mockReturnValue(mockManifest);
|
|
83
87
|
vi.mocked(manifestsState.getCanvases).mockReturnValue(mockCanvases);
|
|
84
88
|
return { mockManifest, mockCanvases };
|