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.
- package/css/retold-remote.css +343 -20
- package/docs/.nojekyll +0 -0
- package/docs/README.md +64 -12
- package/docs/_cover.md +6 -6
- package/docs/_sidebar.md +2 -0
- package/docs/_topbar.md +1 -1
- package/docs/_version.json +7 -0
- package/docs/collections.md +30 -0
- package/docs/css/docuserve.css +327 -0
- package/docs/ebook-reader.md +75 -1
- package/docs/image-explorer.md +62 -2
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +254 -0
- package/docs/retold-keyword-index.json +31216 -0
- package/docs/server-setup.md +122 -91
- package/docs/stack-launcher.md +218 -0
- package/docs/synology.md +585 -0
- package/docs/ultravisor-configuration.md +5 -5
- package/docs/ultravisor-integration.md +4 -2
- package/package.json +20 -14
- package/source/Pict-Application-RetoldRemote.js +22 -0
- package/source/RetoldRemote-ExtensionMaps.js +1 -1
- package/source/cli/RetoldRemote-Server-Setup.js +460 -7
- package/source/cli/RetoldRemote-Stack-Launcher.js +563 -0
- package/source/cli/RetoldRemote-Stack-Run.js +41 -0
- package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
- package/source/providers/CollectionManager-AddItems.js +166 -0
- package/source/providers/Pict-Provider-GalleryNavigation.js +55 -0
- package/source/providers/Pict-Provider-OperationStatus.js +597 -0
- package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +20 -1
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
- package/source/server/RetoldRemote-AudioWaveformService.js +49 -3
- package/source/server/RetoldRemote-CollectionExportService.js +763 -0
- package/source/server/RetoldRemote-CollectionService.js +5 -0
- package/source/server/RetoldRemote-EbookService.js +218 -3
- package/source/server/RetoldRemote-ImageService.js +221 -46
- package/source/server/RetoldRemote-MediaService.js +63 -4
- package/source/server/RetoldRemote-MetadataCache.js +25 -5
- package/source/server/RetoldRemote-OperationBroadcaster.js +363 -0
- package/source/server/RetoldRemote-SubimageService.js +680 -0
- package/source/server/RetoldRemote-ToolDetector.js +50 -0
- package/source/server/RetoldRemote-UltravisorBeacon.js +18 -3
- package/source/server/RetoldRemote-UltravisorDispatcher.js +65 -491
- package/source/server/RetoldRemote-UltravisorOperations.js +133 -20
- package/source/server/RetoldRemote-VideoFrameService.js +302 -9
- package/source/views/MediaViewer-EbookViewer.js +419 -1
- package/source/views/MediaViewer-PdfViewer.js +1050 -0
- package/source/views/PictView-Remote-AudioExplorer.js +77 -1
- package/source/views/PictView-Remote-CollectionsPanel.js +213 -0
- package/source/views/PictView-Remote-Gallery.js +365 -64
- package/source/views/PictView-Remote-ImageExplorer.js +1529 -44
- package/source/views/PictView-Remote-ImageViewer.js +2 -2
- package/source/views/PictView-Remote-Layout.js +58 -0
- package/source/views/PictView-Remote-MediaViewer.js +100 -25
- package/source/views/PictView-Remote-RegionsBrowser.js +554 -0
- package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
- package/source/views/PictView-Remote-TopBar.js +1 -0
- package/source/views/PictView-Remote-VideoExplorer.js +77 -1
- package/web-application/css/docuserve.css +277 -23
- package/web-application/css/retold-remote.css +343 -20
- package/web-application/docs/README.md +64 -12
- package/web-application/docs/_cover.md +6 -6
- package/web-application/docs/_sidebar.md +2 -0
- package/web-application/docs/_topbar.md +1 -1
- package/web-application/docs/collections.md +30 -0
- package/web-application/docs/ebook-reader.md +75 -1
- package/web-application/docs/image-explorer.md +62 -2
- package/web-application/docs/server-setup.md +122 -91
- package/web-application/docs/stack-launcher.md +218 -0
- package/web-application/docs/synology.md +585 -0
- package/web-application/docs/ultravisor-configuration.md +5 -5
- package/web-application/docs/ultravisor-integration.md +4 -2
- package/web-application/js/pict-docuserve.min.js +12 -12
- package/web-application/js/pict.min.js +2 -2
- package/web-application/js/pict.min.js.map +1 -1
- package/web-application/retold-remote.js +6596 -1784
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +75 -23
- 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">↻ Reload</button>
|
|
179
|
+
<button onclick="pict.views['RetoldRemote-RegionsBrowser'].close()" title="Close (Esc)">✕ 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 += '📁 <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 += '📁 ' + 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;
|