triiiceratops 0.9.1 → 0.9.3

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,153 +1,60 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- var __generator = (this && this.__generator) || function (thisArg, body) {
11
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
- function verb(n) { return function (v) { return step([n, v]); }; }
14
- function step(op) {
15
- if (f) throw new TypeError("Generator is already executing.");
16
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
- if (y = 0, t) op = [op[0] & 2, t.value];
19
- switch (op[0]) {
20
- case 0: case 1: t = op; break;
21
- case 4: _.label++; return { value: op[1], done: false };
22
- case 5: _.label++; y = op[1]; op = [0]; continue;
23
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
- default:
25
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
- if (t[2]) _.ops.pop();
30
- _.trys.pop(); continue;
31
- }
32
- op = body.call(thisArg, _);
33
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
1
+ import { manifestsState } from './manifests.svelte.js';
2
+ export class ViewerState {
3
+ manifestId = $state(null);
4
+ canvasId = $state(null);
5
+ showAnnotations = $state(false);
6
+ showThumbnailGallery = $state(false);
7
+ isGalleryDockedBottom = $state(false);
8
+ isGalleryDockedRight = $state(false);
9
+ isFullScreen = $state(false);
10
+ showMetadataDialog = $state(false);
11
+ dockSide = $state('bottom');
12
+ visibleAnnotationIds = $state(new Set());
13
+ // UI Configuration
14
+ config = $state({});
15
+ // Derived configuration specific getters
16
+ get showRightMenu() {
17
+ return this.config.showRightMenu ?? true;
35
18
  }
36
- };
37
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
38
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
39
- if (ar || !(i in from)) {
40
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
41
- ar[i] = from[i];
42
- }
19
+ get showLeftMenu() {
20
+ return this.config.showLeftMenu ?? true;
43
21
  }
44
- return to.concat(ar || Array.prototype.slice.call(from));
45
- };
46
- import { manifestsState } from './manifests.svelte.js';
47
- var ViewerState = /** @class */ (function () {
48
- function ViewerState(initialManifestId, initialCanvasId, initialPlugins) {
49
- if (initialManifestId === void 0) { initialManifestId = null; }
50
- if (initialCanvasId === void 0) { initialCanvasId = null; }
51
- if (initialPlugins === void 0) { initialPlugins = []; }
52
- this.manifestId = $state(null);
53
- this.canvasId = $state(null);
54
- this.showAnnotations = $state(false);
55
- this.showThumbnailGallery = $state(false);
56
- this.isGalleryDockedBottom = $state(false);
57
- this.isGalleryDockedRight = $state(false);
58
- this.isFullScreen = $state(false);
59
- this.showMetadataDialog = $state(false);
60
- this.dockSide = $state('bottom');
61
- this.visibleAnnotationIds = $state(new Set());
62
- // UI Configuration
63
- this.config = $state({});
64
- // Gallery State (Lifted for persistence during re-docking)
65
- this.galleryPosition = $state({ x: 20, y: 100 });
66
- this.gallerySize = $state({ width: 300, height: 400 });
67
- this.isGalleryDragging = $state(false);
68
- this.galleryDragOffset = $state({ x: 0, y: 0 });
69
- this.dragOverSide = $state(null);
70
- this.galleryCenterPanelRect = $state(null);
71
- // ==================== EVENT DISPATCH (Web Component Only) ====================
72
- /**
73
- * Event target for dispatching CustomEvents.
74
- * Only set by TriiiceratopsViewerElement (web component build).
75
- * Remains null for Svelte component usage → no events dispatched.
76
- */
77
- this.eventTarget = null;
78
- this.searchQuery = $state('');
79
- this.searchResults = $state([]);
80
- this.isSearching = $state(false);
81
- this.showSearchPanel = $state(false);
82
- this.searchAnnotations = $state([]);
83
- // ==================== PLUGIN STATE ====================
84
- /** Registered plugins */
85
- this.plugins = $state([]);
86
- /** Plugin-registered menu buttons */
87
- this.pluginMenuButtons = $state([]);
88
- /** Plugin-registered panels */
89
- this.pluginPanels = $state([]);
90
- /** OpenSeadragon viewer instance (set by OSDViewer) */
91
- this.osdViewer = $state(null);
92
- /** Event handlers for inter-plugin communication */
93
- this.pluginEventHandlers = new Map();
94
- this.manifestId = initialManifestId || null;
95
- this.canvasId = initialCanvasId || null;
96
- // Fetch manifest immediately
97
- if (this.manifestId) {
98
- manifestsState.fetchManifest(this.manifestId);
99
- }
100
- // Register initial plugins
101
- for (var _i = 0, initialPlugins_1 = initialPlugins; _i < initialPlugins_1.length; _i++) {
102
- var initialPlugin = initialPlugins_1[_i];
103
- this.registerPlugin(initialPlugin);
104
- }
22
+ get showCanvasNav() {
23
+ return this.config.showCanvasNav ?? true;
105
24
  }
106
- Object.defineProperty(ViewerState.prototype, "showRightMenu", {
107
- // Derived configuration specific getters
108
- get: function () {
109
- var _a;
110
- return (_a = this.config.showRightMenu) !== null && _a !== void 0 ? _a : true;
111
- },
112
- enumerable: false,
113
- configurable: true
114
- });
115
- Object.defineProperty(ViewerState.prototype, "showLeftMenu", {
116
- get: function () {
117
- var _a;
118
- return (_a = this.config.showLeftMenu) !== null && _a !== void 0 ? _a : true;
119
- },
120
- enumerable: false,
121
- configurable: true
122
- });
123
- Object.defineProperty(ViewerState.prototype, "showCanvasNav", {
124
- get: function () {
125
- var _a;
126
- return (_a = this.config.showCanvasNav) !== null && _a !== void 0 ? _a : true;
127
- },
128
- enumerable: false,
129
- configurable: true
130
- });
25
+ // Gallery State (Lifted for persistence during re-docking)
26
+ galleryPosition = $state({ x: 20, y: 100 });
27
+ gallerySize = $state({ width: 300, height: 400 });
28
+ isGalleryDragging = $state(false);
29
+ galleryDragOffset = $state({ x: 0, y: 0 });
30
+ dragOverSide = $state(null);
31
+ galleryCenterPanelRect = $state(null);
32
+ // ==================== EVENT DISPATCH (Web Component Only) ====================
33
+ /**
34
+ * Event target for dispatching CustomEvents.
35
+ * Only set by TriiiceratopsViewerElement (web component build).
36
+ * Remains null for Svelte component usage → no events dispatched.
37
+ */
38
+ eventTarget = null;
131
39
  /**
132
40
  * Set the event target for dispatching state change events.
133
41
  * Called by TriiiceratopsViewerElement to enable event-driven API.
134
42
  */
135
- ViewerState.prototype.setEventTarget = function (target) {
43
+ setEventTarget(target) {
136
44
  this.eventTarget = target;
137
- };
45
+ }
138
46
  /**
139
47
  * Get current state as a plain object snapshot.
140
48
  * Safe to use outside Svelte's reactive system.
141
49
  * NOTE: We calculate currentCanvasIndex inline to avoid triggering the canvases getter
142
50
  * which can cause infinite loops when it auto-sets canvasId.
143
51
  */
144
- ViewerState.prototype.getSnapshot = function () {
145
- var _this = this;
52
+ getSnapshot() {
146
53
  // Calculate canvas index without triggering reactive side effects
147
- var canvasIndex = -1;
54
+ let canvasIndex = -1;
148
55
  if (this.manifestId && this.canvasId) {
149
- var canvases = manifestsState.getCanvases(this.manifestId);
150
- canvasIndex = canvases.findIndex(function (c) { return c.id === _this.canvasId; });
56
+ const canvases = manifestsState.getCanvases(this.manifestId);
57
+ canvasIndex = canvases.findIndex((c) => c.id === this.canvasId);
151
58
  }
152
59
  return {
153
60
  manifestId: this.manifestId,
@@ -160,7 +67,7 @@ var ViewerState = /** @class */ (function () {
160
67
  isFullScreen: this.isFullScreen,
161
68
  dockSide: this.dockSide,
162
69
  };
163
- };
70
+ }
164
71
  /**
165
72
  * Dispatch a state change event to the web component.
166
73
  * No-op if eventTarget is null (Svelte component usage).
@@ -168,106 +75,92 @@ var ViewerState = /** @class */ (function () {
168
75
  * Uses queueMicrotask to dispatch asynchronously AFTER the current
169
76
  * reactive cycle completes, preventing infinite update loops.
170
77
  */
171
- ViewerState.prototype.dispatchStateChange = function (eventName) {
172
- var _this = this;
173
- if (eventName === void 0) { eventName = 'statechange'; }
174
- console.log("[ViewerState] Dispatching ".concat(eventName), JSON.stringify(this.getSnapshot()));
78
+ dispatchStateChange(eventName = 'statechange') {
79
+ console.log(`[ViewerState] Dispatching ${eventName}`, JSON.stringify(this.getSnapshot()));
175
80
  if (!this.eventTarget)
176
81
  return;
177
82
  // Dispatch asynchronously to break reactive loops
178
- queueMicrotask(function () {
179
- var _a;
180
- (_a = _this.eventTarget) === null || _a === void 0 ? void 0 : _a.dispatchEvent(new CustomEvent(eventName, {
181
- detail: _this.getSnapshot(),
83
+ queueMicrotask(() => {
84
+ this.eventTarget?.dispatchEvent(new CustomEvent(eventName, {
85
+ detail: this.getSnapshot(),
182
86
  bubbles: true,
183
87
  composed: true,
184
88
  }));
185
89
  });
186
- };
187
- Object.defineProperty(ViewerState.prototype, "manifest", {
188
- get: function () {
189
- if (!this.manifestId)
190
- return null;
191
- return manifestsState.getManifest(this.manifestId);
192
- },
193
- enumerable: false,
194
- configurable: true
195
- });
196
- Object.defineProperty(ViewerState.prototype, "canvases", {
197
- get: function () {
198
- var _this = this;
199
- if (!this.manifestId)
200
- return [];
201
- var canvases = manifestsState.getCanvases(this.manifestId);
202
- // Auto-initialize canvasId to first canvas if not set
203
- if (canvases.length > 0 && !this.canvasId) {
204
- // Use setTimeout to avoid updating state during a derived computation
205
- setTimeout(function () {
206
- if (!_this.canvasId && canvases.length > 0) {
207
- _this.canvasId = canvases[0].id;
208
- }
209
- }, 0);
210
- }
211
- return canvases;
212
- },
213
- enumerable: false,
214
- configurable: true
215
- });
216
- Object.defineProperty(ViewerState.prototype, "currentCanvasIndex", {
217
- get: function () {
218
- var _this = this;
219
- if (!this.canvasId) {
220
- if (this.canvases.length > 0)
221
- return 0;
222
- return -1;
223
- }
224
- // Manifesto canvases have an id property
225
- return this.canvases.findIndex(function (c) { return c.id === _this.canvasId; });
226
- },
227
- enumerable: false,
228
- configurable: true
229
- });
230
- Object.defineProperty(ViewerState.prototype, "hasNext", {
231
- get: function () {
232
- return this.currentCanvasIndex < this.canvases.length - 1;
233
- },
234
- enumerable: false,
235
- configurable: true
236
- });
237
- Object.defineProperty(ViewerState.prototype, "hasPrevious", {
238
- get: function () {
239
- return this.currentCanvasIndex > 0;
240
- },
241
- enumerable: false,
242
- configurable: true
243
- });
244
- ViewerState.prototype.nextCanvas = function () {
90
+ }
91
+ constructor(initialManifestId = null, initialCanvasId = null, initialPlugins = []) {
92
+ this.manifestId = initialManifestId || null;
93
+ this.canvasId = initialCanvasId || null;
94
+ // Fetch manifest immediately
95
+ if (this.manifestId) {
96
+ manifestsState.fetchManifest(this.manifestId);
97
+ }
98
+ // Register initial plugins
99
+ for (const initialPlugin of initialPlugins) {
100
+ this.registerPlugin(initialPlugin);
101
+ }
102
+ }
103
+ get manifest() {
104
+ if (!this.manifestId)
105
+ return null;
106
+ return manifestsState.getManifest(this.manifestId);
107
+ }
108
+ get canvases() {
109
+ if (!this.manifestId)
110
+ return [];
111
+ const canvases = manifestsState.getCanvases(this.manifestId);
112
+ // Auto-initialize canvasId to first canvas if not set
113
+ if (canvases.length > 0 && !this.canvasId) {
114
+ // Use setTimeout to avoid updating state during a derived computation
115
+ setTimeout(() => {
116
+ if (!this.canvasId && canvases.length > 0) {
117
+ this.canvasId = canvases[0].id;
118
+ }
119
+ }, 0);
120
+ }
121
+ return canvases;
122
+ }
123
+ get currentCanvasIndex() {
124
+ if (!this.canvasId) {
125
+ if (this.canvases.length > 0)
126
+ return 0;
127
+ return -1;
128
+ }
129
+ // Manifesto canvases have an id property
130
+ return this.canvases.findIndex((c) => c.id === this.canvasId);
131
+ }
132
+ get hasNext() {
133
+ return this.currentCanvasIndex < this.canvases.length - 1;
134
+ }
135
+ get hasPrevious() {
136
+ return this.currentCanvasIndex > 0;
137
+ }
138
+ nextCanvas() {
245
139
  if (this.hasNext) {
246
- var nextIndex = this.currentCanvasIndex + 1;
247
- var canvas = this.canvases[nextIndex];
140
+ const nextIndex = this.currentCanvasIndex + 1;
141
+ const canvas = this.canvases[nextIndex];
248
142
  this.setCanvas(canvas.id);
249
143
  }
250
- };
251
- ViewerState.prototype.previousCanvas = function () {
144
+ }
145
+ previousCanvas() {
252
146
  if (this.hasPrevious) {
253
- var prevIndex = this.currentCanvasIndex - 1;
254
- var canvas = this.canvases[prevIndex];
147
+ const prevIndex = this.currentCanvasIndex - 1;
148
+ const canvas = this.canvases[prevIndex];
255
149
  this.setCanvas(canvas.id);
256
150
  }
257
- };
258
- ViewerState.prototype.setManifest = function (manifestId) {
151
+ }
152
+ setManifest(manifestId) {
259
153
  this.manifestId = manifestId;
260
154
  this.canvasId = null;
261
155
  manifestsState.fetchManifest(manifestId);
262
156
  this.dispatchStateChange('manifestchange');
263
- };
264
- ViewerState.prototype.setCanvas = function (canvasId) {
157
+ }
158
+ setCanvas(canvasId) {
265
159
  this.canvasId = canvasId;
266
160
  this.dispatchStateChange('canvaschange');
267
- };
268
- ViewerState.prototype.updateConfig = function (newConfig) {
269
- var _a;
270
- var oldConfig = this.config;
161
+ }
162
+ updateConfig(newConfig) {
163
+ const oldConfig = this.config;
271
164
  this.config = newConfig;
272
165
  // Sync state from config
273
166
  if (newConfig.gallery) {
@@ -285,8 +178,8 @@ var ViewerState = /** @class */ (function () {
285
178
  // Only search if the CONFIG has changed its query requirement.
286
179
  // This prevents stale config updates (e.g. from other property changes)
287
180
  // from overwriting a newer internal search state.
288
- var newQuery = newConfig.search.query;
289
- var oldQuery = (_a = oldConfig.search) === null || _a === void 0 ? void 0 : _a.query;
181
+ const newQuery = newConfig.search.query;
182
+ const oldQuery = oldConfig.search?.query;
290
183
  if (newQuery !== undefined &&
291
184
  newQuery !== oldQuery &&
292
185
  newQuery !== this.searchQuery) {
@@ -301,20 +194,20 @@ var ViewerState = /** @class */ (function () {
301
194
  // NOTE: We intentionally do NOT dispatch events here.
302
195
  // Config updates are external configuration, not user-initiated state changes.
303
196
  // Dispatching here would cause infinite loops when the consumer re-renders.
304
- };
305
- ViewerState.prototype.toggleAnnotations = function () {
197
+ }
198
+ toggleAnnotations() {
306
199
  this.showAnnotations = !this.showAnnotations;
307
200
  this.dispatchStateChange();
308
- };
309
- ViewerState.prototype.toggleThumbnailGallery = function () {
201
+ }
202
+ toggleThumbnailGallery() {
310
203
  this.showThumbnailGallery = !this.showThumbnailGallery;
311
204
  this.dispatchStateChange();
312
- };
313
- ViewerState.prototype.toggleFullScreen = function () {
205
+ }
206
+ toggleFullScreen() {
314
207
  if (!document.fullscreenElement) {
315
- var el = document.getElementById('triiiceratops-viewer');
208
+ const el = document.getElementById('triiiceratops-viewer');
316
209
  if (el) {
317
- el.requestFullscreen().catch(function (e) {
210
+ el.requestFullscreen().catch((e) => {
318
211
  console.warn('Fullscreen request failed', e);
319
212
  });
320
213
  }
@@ -322,199 +215,111 @@ var ViewerState = /** @class */ (function () {
322
215
  else {
323
216
  document.exitFullscreen();
324
217
  }
325
- };
326
- ViewerState.prototype.toggleMetadataDialog = function () {
218
+ }
219
+ toggleMetadataDialog() {
327
220
  this.showMetadataDialog = !this.showMetadataDialog;
328
- };
329
- ViewerState.prototype.toggleSearchPanel = function () {
221
+ }
222
+ searchQuery = $state('');
223
+ searchResults = $state([]);
224
+ isSearching = $state(false);
225
+ showSearchPanel = $state(false);
226
+ toggleSearchPanel() {
330
227
  this.showSearchPanel = !this.showSearchPanel;
331
228
  if (!this.showSearchPanel) {
332
229
  // Clear ephemeral annotations when closing search
333
230
  this.searchAnnotations = [];
334
231
  }
335
232
  this.dispatchStateChange();
336
- };
337
- Object.defineProperty(ViewerState.prototype, "currentCanvasSearchAnnotations", {
338
- get: function () {
339
- var _this = this;
340
- if (!this.canvasId)
341
- return [];
342
- return this.searchAnnotations.filter(function (a) { return a.canvasId === _this.canvasId; });
343
- },
344
- enumerable: false,
345
- configurable: true
346
- });
347
- ViewerState.prototype.search = function (query) {
348
- return __awaiter(this, void 0, void 0, function () {
349
- return __generator(this, function (_a) {
350
- switch (_a.label) {
351
- case 0:
352
- this.dispatchStateChange();
353
- return [4 /*yield*/, this._performSearch(query)];
354
- case 1:
355
- _a.sent();
356
- this.dispatchStateChange();
357
- return [2 /*return*/];
233
+ }
234
+ searchAnnotations = $state([]);
235
+ get currentCanvasSearchAnnotations() {
236
+ if (!this.canvasId)
237
+ return [];
238
+ return this.searchAnnotations.filter((a) => a.canvasId === this.canvasId);
239
+ }
240
+ async search(query) {
241
+ this.dispatchStateChange();
242
+ await this._performSearch(query);
243
+ this.dispatchStateChange();
244
+ }
245
+ async _performSearch(query) {
246
+ if (!query.trim())
247
+ return;
248
+ this.isSearching = true;
249
+ this.searchQuery = query;
250
+ this.searchResults = [];
251
+ try {
252
+ const manifest = this.manifest;
253
+ if (!manifest)
254
+ throw new Error('No manifest loaded');
255
+ let service = manifest.getService('http://iiif.io/api/search/1/search') ||
256
+ manifest.getService('http://iiif.io/api/search/0/search');
257
+ if (!service) {
258
+ // Fallback: check json directly if manifesto fails me
259
+ if (manifest.__jsonld && manifest.__jsonld.service) {
260
+ const services = Array.isArray(manifest.__jsonld.service)
261
+ ? manifest.__jsonld.service
262
+ : [manifest.__jsonld.service];
263
+ service = services.find((s) => s.profile ===
264
+ 'http://iiif.io/api/search/1/search' ||
265
+ s.profile === 'http://iiif.io/api/search/0/search');
358
266
  }
359
- });
360
- });
361
- };
362
- ViewerState.prototype._performSearch = function (query) {
363
- return __awaiter(this, void 0, void 0, function () {
364
- var manifest, service, services, serviceId, searchUrl, response, data, resources, processedResults, parseSelector, _i, _a, hit, annotations, hitBoundsByCanvas, _loop_1, this_1, _b, annotations_1, annoId, _c, hitBoundsByCanvas_1, _d, canvasIndex, data_1, _loop_2, this_2, _e, resources_1, res, annotationIndex_1, e_1;
365
- var _this = this;
366
- var _f, _g;
367
- return __generator(this, function (_h) {
368
- switch (_h.label) {
369
- case 0:
370
- if (!query.trim())
371
- return [2 /*return*/];
372
- this.isSearching = true;
373
- this.searchQuery = query;
374
- this.searchResults = [];
375
- _h.label = 1;
376
- case 1:
377
- _h.trys.push([1, 4, 5, 6]);
378
- manifest = this.manifest;
379
- if (!manifest)
380
- throw new Error('No manifest loaded');
381
- service = manifest.getService('http://iiif.io/api/search/1/search') ||
382
- manifest.getService('http://iiif.io/api/search/0/search');
383
- if (!service) {
384
- // Fallback: check json directly if manifesto fails me
385
- if (manifest.__jsonld && manifest.__jsonld.service) {
386
- services = Array.isArray(manifest.__jsonld.service)
387
- ? manifest.__jsonld.service
388
- : [manifest.__jsonld.service];
389
- service = services.find(function (s) {
390
- return s.profile ===
391
- 'http://iiif.io/api/search/1/search' ||
392
- s.profile === 'http://iiif.io/api/search/0/search';
393
- });
394
- }
395
- }
396
- if (!service) {
397
- console.warn('No IIIF search service found in manifest');
398
- this.isSearching = false;
399
- return [2 /*return*/];
400
- }
401
- serviceId = service.id || service['@id'];
402
- searchUrl = "".concat(serviceId, "?q=").concat(encodeURIComponent(query));
403
- return [4 /*yield*/, fetch(searchUrl)];
404
- case 2:
405
- response = _h.sent();
406
- if (!response.ok)
407
- throw new Error('Search request failed');
408
- return [4 /*yield*/, response.json()];
409
- case 3:
410
- data = _h.sent();
411
- resources = data.resources || [];
412
- processedResults = [];
413
- parseSelector = function (onVal) {
414
- var val = typeof onVal === 'string'
415
- ? onVal
416
- : onVal['@id'] || onVal.id;
417
- if (!val)
418
- return null;
419
- var parts = val.split('#xywh=');
420
- if (parts.length < 2)
421
- return null;
422
- var coords = parts[1].split(',').map(Number);
423
- if (coords.length === 4)
424
- return coords; // [x, y, w, h]
425
- return null;
426
- };
427
- if (data.hits) {
428
- for (_i = 0, _a = data.hits; _i < _a.length; _i++) {
429
- hit = _a[_i];
430
- annotations = hit.annotations || [];
431
- hitBoundsByCanvas = new Map();
432
- _loop_1 = function (annoId) {
433
- var annotation = resources.find(function (r) { return r['@id'] === annoId || r.id === annoId; });
434
- if (annotation && annotation.on) {
435
- // annotation.on can be "canvas-id" or "canvas-id#xywh=..."
436
- var onVal = typeof annotation.on === 'string'
437
- ? annotation.on
438
- : annotation.on['@id'] || annotation.on.id;
439
- var cleanOn_1 = onVal.split('#')[0];
440
- var bounds = parseSelector(onVal);
441
- var canvasIndex = this_1.canvases.findIndex(function (c) { return c.id === cleanOn_1; });
442
- if (canvasIndex >= 0) {
443
- if (!hitBoundsByCanvas.has(canvasIndex)) {
444
- var canvas = this_1.canvases[canvasIndex];
445
- // Try to get a label
446
- var label = 'Canvas ' + (canvasIndex + 1);
447
- try {
448
- if (canvas.getLabel) {
449
- var l = canvas.getLabel();
450
- if (Array.isArray(l) &&
451
- l.length > 0)
452
- label = l[0].value;
453
- else if (typeof l === 'string')
454
- label = l;
455
- }
456
- else if (canvas.label) {
457
- // Fallback if raw object
458
- if (typeof canvas.label === 'string')
459
- label = canvas.label;
460
- else if (Array.isArray(canvas.label))
461
- label = (_f = canvas.label[0]) === null || _f === void 0 ? void 0 : _f.value;
462
- }
463
- }
464
- catch (e) {
465
- /* ignore */
466
- }
467
- hitBoundsByCanvas.set(canvasIndex, {
468
- label: String(label),
469
- bounds: [],
470
- });
471
- }
472
- if (bounds) {
473
- hitBoundsByCanvas
474
- .get(canvasIndex)
475
- .bounds.push(bounds);
476
- }
477
- }
478
- }
479
- };
480
- this_1 = this;
481
- for (_b = 0, annotations_1 = annotations; _b < annotations_1.length; _b++) {
482
- annoId = annotations_1[_b];
483
- _loop_1(annoId);
484
- }
485
- // Create one result per canvas for this hit
486
- for (_c = 0, hitBoundsByCanvas_1 = hitBoundsByCanvas; _c < hitBoundsByCanvas_1.length; _c++) {
487
- _d = hitBoundsByCanvas_1[_c], canvasIndex = _d[0], data_1 = _d[1];
488
- processedResults.push({
489
- type: 'hit',
490
- before: hit.before,
491
- match: hit.match,
492
- after: hit.after,
493
- canvasIndex: canvasIndex,
494
- canvasLabel: data_1.label,
495
- // Store all bounds for this hit on this canvas
496
- allBounds: data_1.bounds,
497
- // Keep first bounds for backwards compatibility
498
- bounds: data_1.bounds.length > 0 ? data_1.bounds[0] : null,
499
- });
500
- }
501
- }
502
- }
503
- else if (resources.length > 0) {
504
- _loop_2 = function (res) {
505
- var onVal = typeof res.on === 'string'
506
- ? res.on
507
- : res.on['@id'] || res.on.id;
508
- var cleanOn = onVal.split('#')[0];
509
- var bounds = parseSelector(onVal);
510
- var canvasIndex = this_2.canvases.findIndex(function (c) { return c.id === cleanOn; });
511
- if (canvasIndex >= 0) {
512
- var canvas = this_2.canvases[canvasIndex];
513
- var label = 'Canvas ' + (canvasIndex + 1);
267
+ }
268
+ if (!service) {
269
+ console.warn('No IIIF search service found in manifest');
270
+ this.isSearching = false;
271
+ return;
272
+ }
273
+ // Handle service object which might be a manifesto object or raw json
274
+ const serviceId = service.id || service['@id'];
275
+ const searchUrl = `${serviceId}?q=${encodeURIComponent(query)}`;
276
+ const response = await fetch(searchUrl);
277
+ if (!response.ok)
278
+ throw new Error('Search request failed');
279
+ const data = await response.json();
280
+ const resources = data.resources || [];
281
+ const processedResults = [];
282
+ // Helper to parse xywh
283
+ const parseSelector = (onVal) => {
284
+ const val = typeof onVal === 'string'
285
+ ? onVal
286
+ : onVal['@id'] || onVal.id;
287
+ if (!val)
288
+ return null;
289
+ const parts = val.split('#xywh=');
290
+ if (parts.length < 2)
291
+ return null;
292
+ const coords = parts[1].split(',').map(Number);
293
+ if (coords.length === 4)
294
+ return coords; // [x, y, w, h]
295
+ return null;
296
+ };
297
+ if (data.hits) {
298
+ for (const hit of data.hits) {
299
+ // hits have property 'annotations' which is array of ids
300
+ // Collapse all annotations for this hit into a single result per canvas
301
+ const annotations = hit.annotations || [];
302
+ const hitBoundsByCanvas = new Map();
303
+ for (const annoId of annotations) {
304
+ const annotation = resources.find((r) => r['@id'] === annoId || r.id === annoId);
305
+ if (annotation && annotation.on) {
306
+ // annotation.on can be "canvas-id" or "canvas-id#xywh=..."
307
+ const onVal = typeof annotation.on === 'string'
308
+ ? annotation.on
309
+ : annotation.on['@id'] || annotation.on.id;
310
+ const cleanOn = onVal.split('#')[0];
311
+ const bounds = parseSelector(onVal);
312
+ const canvasIndex = this.canvases.findIndex((c) => c.id === cleanOn);
313
+ if (canvasIndex >= 0) {
314
+ if (!hitBoundsByCanvas.has(canvasIndex)) {
315
+ const canvas = this.canvases[canvasIndex];
316
+ // Try to get a label
317
+ let label = 'Canvas ' + (canvasIndex + 1);
514
318
  try {
515
319
  if (canvas.getLabel) {
516
- var l = canvas.getLabel();
517
- if (Array.isArray(l) && l.length > 0)
320
+ const l = canvas.getLabel();
321
+ if (Array.isArray(l) &&
322
+ l.length > 0)
518
323
  label = l[0].value;
519
324
  else if (typeof l === 'string')
520
325
  label = l;
@@ -524,170 +329,226 @@ var ViewerState = /** @class */ (function () {
524
329
  if (typeof canvas.label === 'string')
525
330
  label = canvas.label;
526
331
  else if (Array.isArray(canvas.label))
527
- label = (_g = canvas.label[0]) === null || _g === void 0 ? void 0 : _g.value;
332
+ label = canvas.label[0]?.value;
528
333
  }
529
334
  }
530
335
  catch (e) {
531
336
  /* ignore */
532
337
  }
533
- processedResults.push({
534
- type: 'resource',
535
- match: res.resource && res.resource.chars
536
- ? res.resource.chars
537
- : res.chars || '',
538
- canvasIndex: canvasIndex,
539
- canvasLabel: String(label),
540
- bounds: bounds,
338
+ hitBoundsByCanvas.set(canvasIndex, {
339
+ label: String(label),
340
+ bounds: [],
541
341
  });
542
342
  }
543
- };
544
- this_2 = this;
545
- // No hits (Basic level?), just annotations
546
- for (_e = 0, resources_1 = resources; _e < resources_1.length; _e++) {
547
- res = resources_1[_e];
548
- _loop_2(res);
343
+ if (bounds) {
344
+ hitBoundsByCanvas
345
+ .get(canvasIndex)
346
+ .bounds.push(bounds);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ // Create one result per canvas for this hit
352
+ for (const [canvasIndex, data] of hitBoundsByCanvas) {
353
+ processedResults.push({
354
+ type: 'hit',
355
+ before: hit.before,
356
+ match: hit.match,
357
+ after: hit.after,
358
+ canvasIndex,
359
+ canvasLabel: data.label,
360
+ // Store all bounds for this hit on this canvas
361
+ allBounds: data.bounds,
362
+ // Keep first bounds for backwards compatibility
363
+ bounds: data.bounds.length > 0 ? data.bounds[0] : null,
364
+ });
365
+ }
366
+ }
367
+ }
368
+ else if (resources.length > 0) {
369
+ // No hits (Basic level?), just annotations
370
+ for (const res of resources) {
371
+ const onVal = typeof res.on === 'string'
372
+ ? res.on
373
+ : res.on['@id'] || res.on.id;
374
+ const cleanOn = onVal.split('#')[0];
375
+ const bounds = parseSelector(onVal);
376
+ const canvasIndex = this.canvases.findIndex((c) => c.id === cleanOn);
377
+ if (canvasIndex >= 0) {
378
+ const canvas = this.canvases[canvasIndex];
379
+ let label = 'Canvas ' + (canvasIndex + 1);
380
+ try {
381
+ if (canvas.getLabel) {
382
+ const l = canvas.getLabel();
383
+ if (Array.isArray(l) && l.length > 0)
384
+ label = l[0].value;
385
+ else if (typeof l === 'string')
386
+ label = l;
387
+ }
388
+ else if (canvas.label) {
389
+ // Fallback if raw object
390
+ if (typeof canvas.label === 'string')
391
+ label = canvas.label;
392
+ else if (Array.isArray(canvas.label))
393
+ label = canvas.label[0]?.value;
549
394
  }
550
395
  }
551
- this.searchResults = processedResults;
552
- annotationIndex_1 = 0;
553
- this.searchAnnotations = processedResults.flatMap(function (r) {
554
- var canvas = _this.canvases[r.canvasIndex];
555
- // Use allBounds if available, otherwise fall back to single bounds
556
- var boundsArray = r.allBounds && r.allBounds.length > 0
557
- ? r.allBounds
558
- : r.bounds
559
- ? [r.bounds]
560
- : [];
561
- return boundsArray.map(function (bounds) {
562
- var on = "".concat(canvas.id, "#xywh=").concat(bounds.join(','));
563
- return {
564
- '@id': "urn:search-hit:".concat(annotationIndex_1++),
565
- '@type': 'oa:Annotation',
566
- motivation: 'sc:painting',
567
- on: on,
568
- canvasId: canvas.id,
569
- resource: {
570
- '@type': 'cnt:ContentAsText',
571
- chars: r.match,
572
- },
573
- // Flag to identify styling in Overlay?
574
- // Or just standard rendering.
575
- isSearchHit: true,
576
- };
577
- });
396
+ catch (e) {
397
+ /* ignore */
398
+ }
399
+ processedResults.push({
400
+ type: 'resource',
401
+ match: res.resource && res.resource.chars
402
+ ? res.resource.chars
403
+ : res.chars || '',
404
+ canvasIndex,
405
+ canvasLabel: String(label),
406
+ bounds,
578
407
  });
579
- return [3 /*break*/, 6];
580
- case 4:
581
- e_1 = _h.sent();
582
- console.error('Search error:', e_1);
583
- return [3 /*break*/, 6];
584
- case 5:
585
- this.isSearching = false;
586
- return [7 /*endfinally*/];
587
- case 6: return [2 /*return*/];
408
+ }
588
409
  }
410
+ }
411
+ this.searchResults = processedResults;
412
+ // Generate ephemeral search annotations
413
+ // Create one annotation per bound location (flatten allBounds)
414
+ let annotationIndex = 0;
415
+ this.searchAnnotations = processedResults.flatMap((r) => {
416
+ const canvas = this.canvases[r.canvasIndex];
417
+ // Use allBounds if available, otherwise fall back to single bounds
418
+ const boundsArray = r.allBounds && r.allBounds.length > 0
419
+ ? r.allBounds
420
+ : r.bounds
421
+ ? [r.bounds]
422
+ : [];
423
+ return boundsArray.map((bounds) => {
424
+ const on = `${canvas.id}#xywh=${bounds.join(',')}`;
425
+ return {
426
+ '@id': `urn:search-hit:${annotationIndex++}`,
427
+ '@type': 'oa:Annotation',
428
+ motivation: 'sc:painting',
429
+ on: on,
430
+ canvasId: canvas.id,
431
+ resource: {
432
+ '@type': 'cnt:ContentAsText',
433
+ chars: r.match,
434
+ },
435
+ // Flag to identify styling in Overlay?
436
+ // Or just standard rendering.
437
+ isSearchHit: true,
438
+ };
439
+ });
589
440
  });
590
- });
591
- };
441
+ }
442
+ catch (e) {
443
+ console.error('Search error:', e);
444
+ }
445
+ finally {
446
+ this.isSearching = false;
447
+ }
448
+ }
449
+ // ==================== PLUGIN STATE ====================
450
+ /** Registered plugins */
451
+ plugins = $state([]);
452
+ /** Plugin-registered menu buttons */
453
+ pluginMenuButtons = $state([]);
454
+ /** Plugin-registered panels */
455
+ pluginPanels = $state([]);
456
+ /** OpenSeadragon viewer instance (set by OSDViewer) */
457
+ osdViewer = $state(null);
458
+ /** Event handlers for inter-plugin communication */
459
+ pluginEventHandlers = new Map();
592
460
  // ==================== PLUGIN METHODS ====================
593
461
  /**
594
462
  * Create plugin context - the stable API surface for plugins.
595
463
  */
596
- ViewerState.prototype.createPluginContext = function () {
597
- var self = this;
464
+ createPluginContext() {
465
+ const self = this;
598
466
  return {
599
467
  viewerState: self,
600
- getOSDViewer: function () { return self.osdViewer; },
601
- registerMenuButton: function (button) {
602
- if (!self.pluginMenuButtons.find(function (b) { return b.id === button.id; })) {
603
- self.pluginMenuButtons = __spreadArray(__spreadArray([], self.pluginMenuButtons, true), [
468
+ getOSDViewer: () => self.osdViewer,
469
+ registerMenuButton(button) {
470
+ if (!self.pluginMenuButtons.find((b) => b.id === button.id)) {
471
+ self.pluginMenuButtons = [
472
+ ...self.pluginMenuButtons,
604
473
  button,
605
- ], false);
474
+ ];
606
475
  }
607
476
  },
608
- unregisterMenuButton: function (buttonId) {
609
- self.pluginMenuButtons = self.pluginMenuButtons.filter(function (b) { return b.id !== buttonId; });
477
+ unregisterMenuButton(buttonId) {
478
+ self.pluginMenuButtons = self.pluginMenuButtons.filter((b) => b.id !== buttonId);
610
479
  },
611
- registerPanel: function (panel) {
612
- if (!self.pluginPanels.find(function (p) { return p.id === panel.id; })) {
613
- self.pluginPanels = __spreadArray(__spreadArray([], self.pluginPanels, true), [panel], false);
480
+ registerPanel(panel) {
481
+ if (!self.pluginPanels.find((p) => p.id === panel.id)) {
482
+ self.pluginPanels = [...self.pluginPanels, panel];
614
483
  }
615
484
  },
616
- unregisterPanel: function (panelId) {
617
- self.pluginPanels = self.pluginPanels.filter(function (p) { return p.id !== panelId; });
485
+ unregisterPanel(panelId) {
486
+ self.pluginPanels = self.pluginPanels.filter((p) => p.id !== panelId);
618
487
  },
619
- emit: function (eventName, data) {
620
- var handlers = self.pluginEventHandlers.get(eventName);
621
- handlers === null || handlers === void 0 ? void 0 : handlers.forEach(function (handler) { return handler(data); });
488
+ emit(eventName, data) {
489
+ const handlers = self.pluginEventHandlers.get(eventName);
490
+ handlers?.forEach((handler) => handler(data));
622
491
  },
623
- on: function (eventName, handler) {
492
+ on(eventName, handler) {
624
493
  if (!self.pluginEventHandlers.has(eventName)) {
625
494
  self.pluginEventHandlers.set(eventName, new Set());
626
495
  }
627
496
  self.pluginEventHandlers.get(eventName).add(handler);
628
- return function () {
629
- var _a;
630
- (_a = self.pluginEventHandlers.get(eventName)) === null || _a === void 0 ? void 0 : _a.delete(handler);
497
+ return () => {
498
+ self.pluginEventHandlers.get(eventName)?.delete(handler);
631
499
  };
632
500
  },
633
501
  };
634
- };
502
+ }
635
503
  /**
636
504
  * Register a plugin with this viewer instance.
637
505
  */
638
- ViewerState.prototype.registerPlugin = function (plugin) {
639
- if (this.plugins.find(function (p) { return p.id === plugin.id; })) {
640
- console.warn("[Triiiceratops] Plugin \"".concat(plugin.id, "\" already registered"));
506
+ registerPlugin(plugin) {
507
+ if (this.plugins.find((p) => p.id === plugin.id)) {
508
+ console.warn(`[Triiiceratops] Plugin "${plugin.id}" already registered`);
641
509
  return;
642
510
  }
643
- this.plugins = __spreadArray(__spreadArray([], this.plugins, true), [plugin], false);
511
+ this.plugins = [...this.plugins, plugin];
644
512
  plugin.onRegister(this.createPluginContext());
645
513
  // If OSD already ready, notify immediately
646
514
  if (this.osdViewer && plugin.onViewerReady) {
647
515
  plugin.onViewerReady(this.osdViewer);
648
516
  }
649
- };
517
+ }
650
518
  /**
651
519
  * Unregister a plugin by ID.
652
520
  */
653
- ViewerState.prototype.unregisterPlugin = function (pluginId) {
654
- var _a;
655
- var plugin = this.plugins.find(function (p) { return p.id === pluginId; });
521
+ unregisterPlugin(pluginId) {
522
+ const plugin = this.plugins.find((p) => p.id === pluginId);
656
523
  if (plugin) {
657
- (_a = plugin.onDestroy) === null || _a === void 0 ? void 0 : _a.call(plugin);
658
- this.plugins = this.plugins.filter(function (p) { return p.id !== pluginId; });
524
+ plugin.onDestroy?.();
525
+ this.plugins = this.plugins.filter((p) => p.id !== pluginId);
659
526
  // Remove plugin's UI registrations
660
- this.pluginMenuButtons = this.pluginMenuButtons.filter(function (b) { return !b.id.startsWith("".concat(pluginId, ":")); });
661
- this.pluginPanels = this.pluginPanels.filter(function (p) { return !p.id.startsWith("".concat(pluginId, ":")); });
527
+ this.pluginMenuButtons = this.pluginMenuButtons.filter((b) => !b.id.startsWith(`${pluginId}:`));
528
+ this.pluginPanels = this.pluginPanels.filter((p) => !p.id.startsWith(`${pluginId}:`));
662
529
  }
663
- };
530
+ }
664
531
  /**
665
532
  * Called by OSDViewer when OpenSeadragon is ready.
666
533
  */
667
- ViewerState.prototype.notifyOSDReady = function (viewer) {
668
- var _a;
534
+ notifyOSDReady(viewer) {
669
535
  this.osdViewer = viewer;
670
- for (var _i = 0, _b = this.plugins; _i < _b.length; _i++) {
671
- var plugin = _b[_i];
672
- (_a = plugin.onViewerReady) === null || _a === void 0 ? void 0 : _a.call(plugin, viewer);
536
+ for (const plugin of this.plugins) {
537
+ plugin.onViewerReady?.(viewer);
673
538
  }
674
- };
539
+ }
675
540
  /**
676
541
  * Destroy all plugins (called on viewer unmount).
677
542
  */
678
- ViewerState.prototype.destroyAllPlugins = function () {
679
- var _a;
680
- for (var _i = 0, _b = this.plugins; _i < _b.length; _i++) {
681
- var plugin = _b[_i];
682
- (_a = plugin.onDestroy) === null || _a === void 0 ? void 0 : _a.call(plugin);
543
+ destroyAllPlugins() {
544
+ for (const plugin of this.plugins) {
545
+ plugin.onDestroy?.();
683
546
  }
684
547
  this.plugins = [];
685
548
  this.pluginMenuButtons = [];
686
549
  this.pluginPanels = [];
687
550
  this.pluginEventHandlers.clear();
688
- };
689
- return ViewerState;
690
- }());
691
- export { ViewerState };
551
+ }
552
+ }
692
553
  // Context key for providing/injecting ViewerState in components
693
- export var VIEWER_STATE_KEY = 'triiiceratops:viewerState';
554
+ export const VIEWER_STATE_KEY = 'triiiceratops:viewerState';