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.
@@ -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
- viewer.addOnceHandler('tile-drawn', () => {
448
- if (capturedKey !== lastTileSourceStr) return;
449
- setViewerImageVisible(true);
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 mockManifest = manifesto.parseManifest(manifestJson);
81
- const mockCanvases = mockManifest.getSequences()[0].getCanvases();
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 };