retold-remote 0.0.23 → 0.0.26

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.
Files changed (79) hide show
  1. package/css/retold-remote.css +343 -20
  2. package/docs/.nojekyll +0 -0
  3. package/docs/README.md +64 -12
  4. package/docs/_cover.md +6 -6
  5. package/docs/_sidebar.md +2 -0
  6. package/docs/_topbar.md +1 -1
  7. package/docs/_version.json +7 -0
  8. package/docs/collections.md +30 -0
  9. package/docs/css/docuserve.css +327 -0
  10. package/docs/ebook-reader.md +75 -1
  11. package/docs/image-explorer.md +62 -2
  12. package/docs/index.html +39 -0
  13. package/docs/retold-catalog.json +254 -0
  14. package/docs/retold-keyword-index.json +31216 -0
  15. package/docs/server-setup.md +122 -91
  16. package/docs/stack-launcher.md +218 -0
  17. package/docs/synology.md +585 -0
  18. package/docs/ultravisor-configuration.md +5 -5
  19. package/docs/ultravisor-integration.md +4 -2
  20. package/package.json +20 -14
  21. package/source/Pict-Application-RetoldRemote.js +22 -0
  22. package/source/RetoldRemote-ExtensionMaps.js +1 -1
  23. package/source/cli/RetoldRemote-Server-Setup.js +460 -7
  24. package/source/cli/RetoldRemote-Stack-Launcher.js +563 -0
  25. package/source/cli/RetoldRemote-Stack-Run.js +41 -0
  26. package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
  27. package/source/providers/CollectionManager-AddItems.js +166 -0
  28. package/source/providers/Pict-Provider-GalleryNavigation.js +55 -0
  29. package/source/providers/Pict-Provider-OperationStatus.js +597 -0
  30. package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +20 -1
  31. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
  32. package/source/server/RetoldRemote-AudioWaveformService.js +49 -3
  33. package/source/server/RetoldRemote-CollectionExportService.js +763 -0
  34. package/source/server/RetoldRemote-CollectionService.js +5 -0
  35. package/source/server/RetoldRemote-EbookService.js +218 -3
  36. package/source/server/RetoldRemote-ImageService.js +221 -46
  37. package/source/server/RetoldRemote-MediaService.js +63 -4
  38. package/source/server/RetoldRemote-MetadataCache.js +25 -5
  39. package/source/server/RetoldRemote-OperationBroadcaster.js +363 -0
  40. package/source/server/RetoldRemote-SubimageService.js +680 -0
  41. package/source/server/RetoldRemote-ToolDetector.js +50 -0
  42. package/source/server/RetoldRemote-UltravisorBeacon.js +18 -3
  43. package/source/server/RetoldRemote-UltravisorDispatcher.js +65 -491
  44. package/source/server/RetoldRemote-UltravisorOperations.js +133 -20
  45. package/source/server/RetoldRemote-VideoFrameService.js +302 -9
  46. package/source/views/MediaViewer-EbookViewer.js +419 -1
  47. package/source/views/MediaViewer-PdfViewer.js +1050 -0
  48. package/source/views/PictView-Remote-AudioExplorer.js +77 -1
  49. package/source/views/PictView-Remote-CollectionsPanel.js +213 -0
  50. package/source/views/PictView-Remote-Gallery.js +365 -64
  51. package/source/views/PictView-Remote-ImageExplorer.js +1529 -44
  52. package/source/views/PictView-Remote-ImageViewer.js +2 -2
  53. package/source/views/PictView-Remote-Layout.js +58 -0
  54. package/source/views/PictView-Remote-MediaViewer.js +100 -25
  55. package/source/views/PictView-Remote-RegionsBrowser.js +554 -0
  56. package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
  57. package/source/views/PictView-Remote-TopBar.js +1 -0
  58. package/source/views/PictView-Remote-VideoExplorer.js +77 -1
  59. package/web-application/css/docuserve.css +277 -23
  60. package/web-application/css/retold-remote.css +343 -20
  61. package/web-application/docs/README.md +64 -12
  62. package/web-application/docs/_cover.md +6 -6
  63. package/web-application/docs/_sidebar.md +2 -0
  64. package/web-application/docs/_topbar.md +1 -1
  65. package/web-application/docs/collections.md +30 -0
  66. package/web-application/docs/ebook-reader.md +75 -1
  67. package/web-application/docs/image-explorer.md +62 -2
  68. package/web-application/docs/server-setup.md +122 -91
  69. package/web-application/docs/stack-launcher.md +218 -0
  70. package/web-application/docs/synology.md +585 -0
  71. package/web-application/docs/ultravisor-configuration.md +5 -5
  72. package/web-application/docs/ultravisor-integration.md +4 -2
  73. package/web-application/js/pict-docuserve.min.js +12 -12
  74. package/web-application/js/pict.min.js +2 -2
  75. package/web-application/js/pict.min.js.map +1 -1
  76. package/web-application/retold-remote.js +6596 -1784
  77. package/web-application/retold-remote.js.map +1 -1
  78. package/web-application/retold-remote.min.js +75 -23
  79. package/web-application/retold-remote.min.js.map +1 -1
@@ -0,0 +1,554 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ /**
4
+ * Regions Browser — folder-scoped listing of saved subimage regions
5
+ * across all files.
6
+ *
7
+ * Opened from the topbar ▣ button. Shows a folder tree on the left
8
+ * and a list of files with their regions on the right. Click a region
9
+ * to close the browser and jump to it.
10
+ *
11
+ * Backing data: GET /api/media/subimage-regions?folder=<prefix>
12
+ * Returns { Success, Folder, Files: [{ Path, Regions }] }
13
+ *
14
+ * Scales via a server-side in-memory cache keyed on a full enumeration
15
+ * of the Bibliograph subimage-regions source. The folder prefix is
16
+ * applied as a client-visible filter; the cache is invalidated on every
17
+ * mutation so updates are near-instantaneous in the typical case.
18
+ */
19
+ const _ViewConfiguration =
20
+ {
21
+ ViewIdentifier: "RetoldRemote-RegionsBrowser",
22
+ DefaultRenderable: "RetoldRemote-RegionsBrowser",
23
+ DefaultDestinationAddress: "#RetoldRemote-RegionsBrowser-Overlay",
24
+ AutoRender: false,
25
+
26
+ CSS: `
27
+ #RetoldRemote-RegionsBrowser-Overlay
28
+ {
29
+ position: fixed;
30
+ top: 0; left: 0; right: 0; bottom: 0;
31
+ background: rgba(0, 0, 0, 0.85);
32
+ z-index: 10000;
33
+ display: none;
34
+ flex-direction: column;
35
+ }
36
+ #RetoldRemote-RegionsBrowser-Overlay.active { display: flex; }
37
+ .rrrb-header
38
+ {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 12px;
42
+ padding: 8px 14px;
43
+ background: var(--retold-bg-panel, #282c34);
44
+ border-bottom: 1px solid var(--retold-border, #3e4451);
45
+ color: var(--retold-text, #abb2bf);
46
+ }
47
+ .rrrb-header-title
48
+ {
49
+ font-size: 0.95rem;
50
+ font-weight: 600;
51
+ flex: 1;
52
+ }
53
+ .rrrb-header button
54
+ {
55
+ background: var(--retold-bg-input, #1e1e1e);
56
+ color: var(--retold-text, #abb2bf);
57
+ border: 1px solid var(--retold-border, #3e4451);
58
+ border-radius: 4px;
59
+ padding: 4px 10px;
60
+ font-size: 0.78rem;
61
+ cursor: pointer;
62
+ }
63
+ .rrrb-body
64
+ {
65
+ flex: 1;
66
+ display: flex;
67
+ overflow: hidden;
68
+ }
69
+ .rrrb-tree
70
+ {
71
+ width: 260px;
72
+ overflow-y: auto;
73
+ background: var(--retold-bg-panel, #21252b);
74
+ border-right: 1px solid var(--retold-border, #3e4451);
75
+ padding: 8px 0;
76
+ }
77
+ .rrrb-tree-node
78
+ {
79
+ padding: 5px 14px;
80
+ color: var(--retold-text, #abb2bf);
81
+ cursor: pointer;
82
+ font-size: 0.82rem;
83
+ white-space: nowrap;
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ }
87
+ .rrrb-tree-node:hover { background: var(--retold-bg-hover, rgba(255,255,255,0.05)); }
88
+ .rrrb-tree-node.active
89
+ {
90
+ background: var(--retold-accent, rgba(97,175,239,0.15));
91
+ color: var(--retold-text-bright, #e0e0e0);
92
+ font-weight: 600;
93
+ }
94
+ .rrrb-tree-count
95
+ {
96
+ color: var(--retold-text-dim, #707880);
97
+ font-size: 0.72rem;
98
+ margin-left: 4px;
99
+ }
100
+ .rrrb-list
101
+ {
102
+ flex: 1;
103
+ overflow-y: auto;
104
+ background: var(--retold-bg, #181a1f);
105
+ padding: 8px 14px;
106
+ }
107
+ .rrrb-list-empty
108
+ {
109
+ color: var(--retold-text-dim, #707880);
110
+ text-align: center;
111
+ padding: 40px 20px;
112
+ font-size: 0.9rem;
113
+ }
114
+ .rrrb-file-group
115
+ {
116
+ margin-bottom: 16px;
117
+ border: 1px solid var(--retold-border, #3e4451);
118
+ border-radius: 6px;
119
+ background: var(--retold-bg-panel, #21252b);
120
+ overflow: hidden;
121
+ }
122
+ .rrrb-file-header
123
+ {
124
+ padding: 8px 12px;
125
+ background: var(--retold-bg-panel-alt, #282c34);
126
+ color: var(--retold-text, #abb2bf);
127
+ font-size: 0.82rem;
128
+ font-weight: 600;
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ }
133
+ .rrrb-file-name
134
+ {
135
+ flex: 1;
136
+ overflow: hidden;
137
+ text-overflow: ellipsis;
138
+ white-space: nowrap;
139
+ }
140
+ .rrrb-file-count
141
+ {
142
+ color: var(--retold-text-dim, #707880);
143
+ font-size: 0.72rem;
144
+ }
145
+ .rrrb-regions
146
+ {
147
+ padding: 6px 12px 10px 12px;
148
+ display: flex;
149
+ flex-wrap: wrap;
150
+ gap: 6px;
151
+ }
152
+ .rrrb-region
153
+ {
154
+ background: var(--retold-bg-input, #1e1e1e);
155
+ border: 1px solid var(--retold-border, #3e4451);
156
+ border-radius: 4px;
157
+ padding: 5px 10px;
158
+ font-size: 0.75rem;
159
+ color: var(--retold-text, #abb2bf);
160
+ cursor: pointer;
161
+ display: inline-flex;
162
+ align-items: center;
163
+ gap: 6px;
164
+ }
165
+ .rrrb-region:hover { background: var(--retold-bg-hover, rgba(255,255,255,0.08)); border-color: var(--retold-accent, #61afef); }
166
+ .rrrb-region-label { font-weight: 600; }
167
+ .rrrb-region-dims { color: var(--retold-text-dim, #707880); font-size: 0.68rem; }
168
+ `,
169
+
170
+ Templates:
171
+ [
172
+ {
173
+ Hash: "RetoldRemote-RegionsBrowser",
174
+ Template: /*html*/`
175
+ <div id="RetoldRemote-RegionsBrowser-Overlay">
176
+ <div class="rrrb-header">
177
+ <div class="rrrb-header-title" id="RetoldRemote-RegionsBrowser-Title">Regions Browser</div>
178
+ <button onclick="pict.views['RetoldRemote-RegionsBrowser'].refresh()" title="Reload">&#8635; Reload</button>
179
+ <button onclick="pict.views['RetoldRemote-RegionsBrowser'].close()" title="Close (Esc)">&#10005; Close</button>
180
+ </div>
181
+ <div class="rrrb-body">
182
+ <div class="rrrb-tree" id="RetoldRemote-RegionsBrowser-Tree"></div>
183
+ <div class="rrrb-list" id="RetoldRemote-RegionsBrowser-List"></div>
184
+ </div>
185
+ </div>
186
+ `
187
+ }
188
+ ],
189
+
190
+ Renderables:
191
+ [
192
+ {
193
+ RenderableHash: "RetoldRemote-RegionsBrowser",
194
+ TemplateHash: "RetoldRemote-RegionsBrowser",
195
+ DestinationAddress: "#RetoldRemote-RegionsBrowser-Container"
196
+ }
197
+ ]
198
+ };
199
+
200
+ class RetoldRemoteRegionsBrowserView extends libPictView
201
+ {
202
+ constructor(pFable, pOptions, pServiceHash)
203
+ {
204
+ super(pFable, pOptions, pServiceHash);
205
+
206
+ // Server response: [{ Path, Regions: [...] }, ...]
207
+ this._allFiles = [];
208
+
209
+ // Currently-highlighted folder in the tree. '' = root (all files).
210
+ this._selectedFolder = '';
211
+
212
+ // Keydown handler ref for add/remove
213
+ this._keyHandler = null;
214
+ }
215
+
216
+ /**
217
+ * Open the browser overlay, render the UI, and fetch the full list
218
+ * of regions from the server.
219
+ */
220
+ open()
221
+ {
222
+ // Ensure the overlay container exists in the DOM
223
+ this._ensureOverlayContainer();
224
+
225
+ // Render the shell
226
+ this.render();
227
+
228
+ // Make the overlay visible
229
+ let tmpOverlay = document.getElementById('RetoldRemote-RegionsBrowser-Overlay');
230
+ if (tmpOverlay) tmpOverlay.classList.add('active');
231
+
232
+ // Seed the selected folder from the gallery's current location
233
+ let tmpRemote = this.pict.AppData.RetoldRemote;
234
+ let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
235
+ this._selectedFolder = tmpCurrentLocation.replace(/\/+$/, '').replace(/^\/+/, '');
236
+
237
+ // Install Escape-to-close
238
+ let tmpSelf = this;
239
+ this._keyHandler = function (pEvent)
240
+ {
241
+ if (pEvent.key === 'Escape')
242
+ {
243
+ pEvent.preventDefault();
244
+ pEvent.stopPropagation();
245
+ tmpSelf.close();
246
+ }
247
+ };
248
+ document.addEventListener('keydown', this._keyHandler, true);
249
+
250
+ // Fetch regions from the server
251
+ this._fetchRegions();
252
+ }
253
+
254
+ /**
255
+ * Close the browser overlay.
256
+ */
257
+ close()
258
+ {
259
+ let tmpOverlay = document.getElementById('RetoldRemote-RegionsBrowser-Overlay');
260
+ if (tmpOverlay) tmpOverlay.classList.remove('active');
261
+
262
+ if (this._keyHandler)
263
+ {
264
+ document.removeEventListener('keydown', this._keyHandler, true);
265
+ this._keyHandler = null;
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Re-fetch the full regions list from the server (bypassing the
271
+ * in-memory cache on the server is not possible, but this will
272
+ * return the current cached values).
273
+ */
274
+ refresh()
275
+ {
276
+ this._fetchRegions();
277
+ }
278
+
279
+ /**
280
+ * Ensure the overlay destination container exists in the DOM tree.
281
+ * Appends it to the body on first call.
282
+ */
283
+ _ensureOverlayContainer()
284
+ {
285
+ let tmpContainer = document.getElementById('RetoldRemote-RegionsBrowser-Container');
286
+ if (!tmpContainer)
287
+ {
288
+ tmpContainer = document.createElement('div');
289
+ tmpContainer.id = 'RetoldRemote-RegionsBrowser-Container';
290
+ document.body.appendChild(tmpContainer);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Fetch all regions from the server (empty folder prefix returns
296
+ * everything, which is cheap thanks to the server-side cache).
297
+ */
298
+ _fetchRegions()
299
+ {
300
+ let tmpSelf = this;
301
+ let tmpListEl = document.getElementById('RetoldRemote-RegionsBrowser-List');
302
+ let tmpTreeEl = document.getElementById('RetoldRemote-RegionsBrowser-Tree');
303
+ if (tmpListEl) tmpListEl.innerHTML = '<div class="rrrb-list-empty">Loading\u2026</div>';
304
+ if (tmpTreeEl) tmpTreeEl.innerHTML = '';
305
+
306
+ fetch('/api/media/subimage-regions?folder=')
307
+ .then((pResponse) => pResponse.json())
308
+ .then((pResult) =>
309
+ {
310
+ if (!pResult || !pResult.Success || !Array.isArray(pResult.Files))
311
+ {
312
+ tmpSelf._allFiles = [];
313
+ tmpSelf._renderTree();
314
+ tmpSelf._renderList();
315
+ return;
316
+ }
317
+ tmpSelf._allFiles = pResult.Files;
318
+ tmpSelf._renderTree();
319
+ tmpSelf._renderList();
320
+ })
321
+ .catch((pError) =>
322
+ {
323
+ if (tmpListEl)
324
+ {
325
+ tmpListEl.innerHTML = '<div class="rrrb-list-empty">Failed to load: '
326
+ + (pError && pError.message ? pError.message : 'unknown error')
327
+ + '</div>';
328
+ }
329
+ });
330
+ }
331
+
332
+ /**
333
+ * Build the folder tree from the list of files and render it.
334
+ * Folders are derived from the parent directories of each file path.
335
+ */
336
+ _renderTree()
337
+ {
338
+ let tmpTreeEl = document.getElementById('RetoldRemote-RegionsBrowser-Tree');
339
+ if (!tmpTreeEl) return;
340
+
341
+ // Build a map of folder → total region count
342
+ let tmpFolderCounts = {};
343
+ tmpFolderCounts[''] = 0; // root
344
+ for (let i = 0; i < this._allFiles.length; i++)
345
+ {
346
+ let tmpEntry = this._allFiles[i];
347
+ let tmpRegionCount = Array.isArray(tmpEntry.Regions) ? tmpEntry.Regions.length : 0;
348
+ tmpFolderCounts[''] += tmpRegionCount;
349
+
350
+ let tmpParts = (tmpEntry.Path || '').split('/');
351
+ tmpParts.pop(); // remove file name
352
+ let tmpAcc = '';
353
+ for (let j = 0; j < tmpParts.length; j++)
354
+ {
355
+ tmpAcc = tmpAcc ? (tmpAcc + '/' + tmpParts[j]) : tmpParts[j];
356
+ tmpFolderCounts[tmpAcc] = (tmpFolderCounts[tmpAcc] || 0) + tmpRegionCount;
357
+ }
358
+ }
359
+
360
+ // Sort folder keys alphabetically, root first
361
+ let tmpFolderKeys = Object.keys(tmpFolderCounts);
362
+ tmpFolderKeys.sort();
363
+
364
+ let tmpFmt = this.pict.providers['RetoldRemote-FormattingUtilities'];
365
+ let tmpHTML = '';
366
+
367
+ // Always show root at the top
368
+ let tmpRootActive = (this._selectedFolder === '') ? ' active' : '';
369
+ tmpHTML += '<div class="rrrb-tree-node' + tmpRootActive + '" onclick="pict.views[\'RetoldRemote-RegionsBrowser\'].selectFolder(\'\')">';
370
+ tmpHTML += '&#128193; <em>All folders</em>';
371
+ tmpHTML += '<span class="rrrb-tree-count">(' + tmpFolderCounts[''] + ')</span>';
372
+ tmpHTML += '</div>';
373
+
374
+ for (let i = 0; i < tmpFolderKeys.length; i++)
375
+ {
376
+ let tmpKey = tmpFolderKeys[i];
377
+ if (tmpKey === '') continue;
378
+ let tmpDepth = tmpKey.split('/').length;
379
+ let tmpName = tmpKey.split('/').pop();
380
+ let tmpActive = (this._selectedFolder === tmpKey) ? ' active' : '';
381
+ let tmpIndent = 'padding-left:' + (14 + (tmpDepth - 1) * 14) + 'px;';
382
+ tmpHTML += '<div class="rrrb-tree-node' + tmpActive + '" style="' + tmpIndent + '"'
383
+ + ' onclick="pict.views[\'RetoldRemote-RegionsBrowser\'].selectFolder(\''
384
+ + tmpKey.replace(/'/g, "\\'") + '\')">';
385
+ tmpHTML += '&#128193; ' + tmpFmt.escapeHTML(tmpName);
386
+ tmpHTML += '<span class="rrrb-tree-count">(' + tmpFolderCounts[tmpKey] + ')</span>';
387
+ tmpHTML += '</div>';
388
+ }
389
+
390
+ tmpTreeEl.innerHTML = tmpHTML;
391
+ }
392
+
393
+ /**
394
+ * Render the file/region list for the currently-selected folder.
395
+ */
396
+ _renderList()
397
+ {
398
+ let tmpListEl = document.getElementById('RetoldRemote-RegionsBrowser-List');
399
+ if (!tmpListEl) return;
400
+
401
+ let tmpFmt = this.pict.providers['RetoldRemote-FormattingUtilities'];
402
+ let tmpFolder = this._selectedFolder || '';
403
+
404
+ // Filter files by the selected folder prefix
405
+ let tmpFilteredFiles = [];
406
+ for (let i = 0; i < this._allFiles.length; i++)
407
+ {
408
+ let tmpEntry = this._allFiles[i];
409
+ if (!tmpEntry || !tmpEntry.Path) continue;
410
+ if (tmpFolder === ''
411
+ || tmpEntry.Path === tmpFolder
412
+ || tmpEntry.Path.indexOf(tmpFolder + '/') === 0)
413
+ {
414
+ tmpFilteredFiles.push(tmpEntry);
415
+ }
416
+ }
417
+
418
+ // Update the header title
419
+ let tmpTitleEl = document.getElementById('RetoldRemote-RegionsBrowser-Title');
420
+ if (tmpTitleEl)
421
+ {
422
+ let tmpLabel = tmpFolder || 'All folders';
423
+ let tmpTotal = 0;
424
+ for (let i = 0; i < tmpFilteredFiles.length; i++)
425
+ {
426
+ tmpTotal += (tmpFilteredFiles[i].Regions || []).length;
427
+ }
428
+ tmpTitleEl.textContent = 'Regions Browser — ' + tmpLabel
429
+ + ' (' + tmpTotal + ' region' + (tmpTotal === 1 ? '' : 's') + ' in '
430
+ + tmpFilteredFiles.length + ' file' + (tmpFilteredFiles.length === 1 ? '' : 's') + ')';
431
+ }
432
+
433
+ if (tmpFilteredFiles.length === 0)
434
+ {
435
+ tmpListEl.innerHTML = '<div class="rrrb-list-empty">No regions in this folder yet.</div>';
436
+ return;
437
+ }
438
+
439
+ let tmpHTML = '';
440
+ for (let i = 0; i < tmpFilteredFiles.length; i++)
441
+ {
442
+ let tmpEntry = tmpFilteredFiles[i];
443
+ let tmpFileName = (tmpEntry.Path || '').replace(/^.*\//, '');
444
+ let tmpRegions = tmpEntry.Regions || [];
445
+
446
+ tmpHTML += '<div class="rrrb-file-group">';
447
+ tmpHTML += '<div class="rrrb-file-header">';
448
+ tmpHTML += '<span class="rrrb-file-name" title="' + tmpFmt.escapeHTML(tmpEntry.Path) + '">' + tmpFmt.escapeHTML(tmpFileName) + '</span>';
449
+ tmpHTML += '<span class="rrrb-file-count">' + tmpRegions.length + ' region' + (tmpRegions.length === 1 ? '' : 's') + '</span>';
450
+ tmpHTML += '</div>';
451
+ tmpHTML += '<div class="rrrb-regions">';
452
+
453
+ for (let j = 0; j < tmpRegions.length; j++)
454
+ {
455
+ let tmpRegion = tmpRegions[j];
456
+ let tmpLabel = tmpRegion.Label || '(unlabeled)';
457
+ let tmpDims = '';
458
+ if (typeof tmpRegion.Width === 'number' && typeof tmpRegion.Height === 'number')
459
+ {
460
+ tmpDims = tmpRegion.Width + '×' + tmpRegion.Height;
461
+ }
462
+ else if (tmpRegion.PageNumber)
463
+ {
464
+ tmpDims = 'p.' + tmpRegion.PageNumber;
465
+ }
466
+
467
+ tmpHTML += '<div class="rrrb-region" onclick="pict.views[\'RetoldRemote-RegionsBrowser\'].navigateTo('
468
+ + '\'' + tmpEntry.Path.replace(/'/g, "\\'") + '\','
469
+ + '\'' + tmpRegion.ID + '\')">';
470
+ tmpHTML += '<span class="rrrb-region-label">' + tmpFmt.escapeHTML(tmpLabel) + '</span>';
471
+ if (tmpDims)
472
+ {
473
+ tmpHTML += '<span class="rrrb-region-dims">' + tmpFmt.escapeHTML(tmpDims) + '</span>';
474
+ }
475
+ tmpHTML += '</div>';
476
+ }
477
+
478
+ tmpHTML += '</div>';
479
+ tmpHTML += '</div>';
480
+ }
481
+
482
+ tmpListEl.innerHTML = tmpHTML;
483
+ }
484
+
485
+ /**
486
+ * User clicked a folder in the tree — update the selection and
487
+ * re-render the list pane.
488
+ *
489
+ * @param {string} pFolder - Folder prefix ('' for root)
490
+ */
491
+ selectFolder(pFolder)
492
+ {
493
+ this._selectedFolder = pFolder || '';
494
+ this._renderTree();
495
+ this._renderList();
496
+ }
497
+
498
+ /**
499
+ * User clicked a region — close the browser, navigate to the file,
500
+ * and zoom to the region. Handles images (via ImageExplorer) and
501
+ * documents (via MediaViewer).
502
+ *
503
+ * @param {string} pFilePath - Full relative path of the file
504
+ * @param {string} pRegionID - ID of the region to zoom to
505
+ */
506
+ navigateTo(pFilePath, pRegionID)
507
+ {
508
+ this.close();
509
+
510
+ let tmpSelf = this;
511
+ let tmpExt = pFilePath.split('.').pop().toLowerCase();
512
+ let tmpIsImage = ['jpg','jpeg','png','gif','bmp','webp','tiff','tif','svg'].indexOf(tmpExt) >= 0;
513
+
514
+ // Use the image explorer for images, media viewer for everything else
515
+ if (tmpIsImage)
516
+ {
517
+ let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
518
+ if (tmpIEX)
519
+ {
520
+ tmpIEX.showExplorer(pFilePath);
521
+ // Wait for the explorer and its regions to load before zooming
522
+ setTimeout(function ()
523
+ {
524
+ let tmpIEX2 = tmpSelf.pict.views['RetoldRemote-ImageExplorer'];
525
+ if (tmpIEX2 && typeof tmpIEX2.zoomToRegion === 'function')
526
+ {
527
+ tmpIEX2.zoomToRegion(pRegionID);
528
+ }
529
+ }, 900);
530
+ return;
531
+ }
532
+ }
533
+
534
+ // Fallback: open in the media viewer
535
+ let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
536
+ if (tmpMediaViewer)
537
+ {
538
+ tmpMediaViewer.showMedia(pFilePath, tmpIsImage ? 'image' : 'document');
539
+ // Defer the jump-to-region call to after the viewer settles
540
+ setTimeout(function ()
541
+ {
542
+ let tmpSubPanel = tmpSelf.pict.views['RetoldRemote-SubimagesPanel'];
543
+ if (tmpSubPanel && typeof tmpSubPanel.navigateToRegion === 'function')
544
+ {
545
+ tmpSubPanel.navigateToRegion(pRegionID);
546
+ }
547
+ }, 900);
548
+ }
549
+ }
550
+ }
551
+
552
+ RetoldRemoteRegionsBrowserView.default_configuration = _ViewConfiguration;
553
+
554
+ module.exports = RetoldRemoteRegionsBrowserView;