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,353 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _ViewConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: "RetoldRemote-SubimagesPanel",
|
|
6
|
+
DefaultRenderable: "RetoldRemote-SubimagesPanel",
|
|
7
|
+
DefaultDestinationAddress: "#RetoldRemote-Subimages-Container",
|
|
8
|
+
AutoRender: false,
|
|
9
|
+
|
|
10
|
+
CSS: ``
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Subimages Panel — sidebar tab showing labeled subimage regions
|
|
15
|
+
* for the currently viewed image file.
|
|
16
|
+
*
|
|
17
|
+
* Regions are fetched from the SubimageService API and displayed
|
|
18
|
+
* as a list with label, dimensions, and action buttons.
|
|
19
|
+
*/
|
|
20
|
+
class RetoldRemoteSubimagesPanelView extends libPictView
|
|
21
|
+
{
|
|
22
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
23
|
+
{
|
|
24
|
+
super(pFable, pOptions, pServiceHash);
|
|
25
|
+
this._regions = [];
|
|
26
|
+
this._currentPath = '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Render the subimages panel for the current file.
|
|
31
|
+
*/
|
|
32
|
+
render()
|
|
33
|
+
{
|
|
34
|
+
let tmpContainer = document.getElementById('RetoldRemote-Subimages-Container');
|
|
35
|
+
if (!tmpContainer)
|
|
36
|
+
{
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
41
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
42
|
+
let tmpMediaType = tmpRemote.CurrentViewerMediaType;
|
|
43
|
+
|
|
44
|
+
// Show for images and documents (EPUB, PDF, CBZ pages)
|
|
45
|
+
if (!tmpFilePath)
|
|
46
|
+
{
|
|
47
|
+
tmpContainer.innerHTML = '<div style="padding:12px;color:var(--retold-text-dim);font-size:0.82rem;">View a file to see its regions.</div>';
|
|
48
|
+
this._regions = [];
|
|
49
|
+
this._currentPath = '';
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If the file changed, fetch regions
|
|
54
|
+
if (tmpFilePath !== this._currentPath)
|
|
55
|
+
{
|
|
56
|
+
this._currentPath = tmpFilePath;
|
|
57
|
+
this._fetchAndRender(tmpFilePath, tmpContainer);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Re-render with cached regions (e.g. after add/delete)
|
|
62
|
+
this._renderRegionList(tmpContainer);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Force a refresh from the server for the current file.
|
|
67
|
+
*/
|
|
68
|
+
refresh()
|
|
69
|
+
{
|
|
70
|
+
this._currentPath = '';
|
|
71
|
+
this.render();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fetch regions from the server and render.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} pFilePath - Relative file path
|
|
78
|
+
* @param {HTMLElement} pContainer - The container to render into
|
|
79
|
+
*/
|
|
80
|
+
_fetchAndRender(pFilePath, pContainer)
|
|
81
|
+
{
|
|
82
|
+
let tmpSelf = this;
|
|
83
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
84
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(pFilePath) : encodeURIComponent(pFilePath);
|
|
85
|
+
|
|
86
|
+
pContainer.innerHTML = '<div style="padding:12px;color:var(--retold-text-dim);font-size:0.82rem;">Loading\u2026</div>';
|
|
87
|
+
|
|
88
|
+
fetch('/api/media/subimage-regions?path=' + tmpPathParam)
|
|
89
|
+
.then((pResponse) => pResponse.json())
|
|
90
|
+
.then((pResult) =>
|
|
91
|
+
{
|
|
92
|
+
if (pResult && pResult.Success)
|
|
93
|
+
{
|
|
94
|
+
tmpSelf._regions = pResult.Regions || [];
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
{
|
|
98
|
+
tmpSelf._regions = [];
|
|
99
|
+
}
|
|
100
|
+
tmpSelf._renderRegionList(pContainer);
|
|
101
|
+
})
|
|
102
|
+
.catch(() =>
|
|
103
|
+
{
|
|
104
|
+
tmpSelf._regions = [];
|
|
105
|
+
tmpSelf._renderRegionList(pContainer);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Render the region list into the container.
|
|
111
|
+
*
|
|
112
|
+
* @param {HTMLElement} pContainer - The container element
|
|
113
|
+
*/
|
|
114
|
+
_renderRegionList(pContainer)
|
|
115
|
+
{
|
|
116
|
+
if (!pContainer)
|
|
117
|
+
{
|
|
118
|
+
pContainer = document.getElementById('RetoldRemote-Subimages-Container');
|
|
119
|
+
}
|
|
120
|
+
if (!pContainer)
|
|
121
|
+
{
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Also sync the regions to the image explorer if it's open
|
|
126
|
+
let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
|
|
127
|
+
if (tmpIEX && tmpIEX._currentPath === this._currentPath)
|
|
128
|
+
{
|
|
129
|
+
tmpIEX._savedRegions = this._regions;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let tmpFmt = this.pict.providers['RetoldRemote-FormattingUtilities'];
|
|
133
|
+
let tmpFileName = (this._currentPath || '').replace(/^.*\//, '');
|
|
134
|
+
|
|
135
|
+
let tmpHTML = '<div class="retold-remote-subimages-panel">';
|
|
136
|
+
|
|
137
|
+
// Header
|
|
138
|
+
tmpHTML += '<div style="padding:8px 10px;font-size:0.78rem;color:var(--retold-text-dim);border-bottom:1px solid var(--retold-border);">';
|
|
139
|
+
tmpHTML += tmpFmt.escapeHTML(tmpFileName);
|
|
140
|
+
tmpHTML += ' — ' + this._regions.length + ' region' + (this._regions.length !== 1 ? 's' : '');
|
|
141
|
+
tmpHTML += '</div>';
|
|
142
|
+
|
|
143
|
+
if (this._regions.length === 0)
|
|
144
|
+
{
|
|
145
|
+
tmpHTML += '<div style="padding:16px 12px;color:var(--retold-text-dim);font-size:0.8rem;text-align:center;">';
|
|
146
|
+
tmpHTML += 'No regions yet.<br>Use selection tools in the viewer to create regions.';
|
|
147
|
+
tmpHTML += '</div>';
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
{
|
|
151
|
+
for (let i = 0; i < this._regions.length; i++)
|
|
152
|
+
{
|
|
153
|
+
let tmpRegion = this._regions[i];
|
|
154
|
+
let tmpLabel = tmpRegion.Label || '(unlabeled)';
|
|
155
|
+
let tmpIsText = (tmpRegion.Type === 'text-selection');
|
|
156
|
+
|
|
157
|
+
// Build description based on region type
|
|
158
|
+
let tmpDesc = '';
|
|
159
|
+
if (tmpIsText)
|
|
160
|
+
{
|
|
161
|
+
let tmpPreview = (tmpRegion.SelectedText || '').substring(0, 60);
|
|
162
|
+
if ((tmpRegion.SelectedText || '').length > 60) tmpPreview += '\u2026';
|
|
163
|
+
tmpDesc = tmpPreview || '(no text)';
|
|
164
|
+
if (tmpRegion.PageNumber)
|
|
165
|
+
{
|
|
166
|
+
tmpDesc = 'p.' + tmpRegion.PageNumber + ' \u2014 ' + tmpDesc;
|
|
167
|
+
}
|
|
168
|
+
else if (tmpRegion.ChapterTitle)
|
|
169
|
+
{
|
|
170
|
+
tmpDesc = tmpRegion.ChapterTitle;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
{
|
|
175
|
+
tmpDesc = (tmpRegion.Width || 0) + ' \u00d7 ' + (tmpRegion.Height || 0) + ' px';
|
|
176
|
+
if (tmpRegion.PageNumber)
|
|
177
|
+
{
|
|
178
|
+
tmpDesc = 'p.' + tmpRegion.PageNumber + ' \u2014 ' + tmpDesc;
|
|
179
|
+
}
|
|
180
|
+
else if (tmpRegion.X !== null && tmpRegion.Y !== null)
|
|
181
|
+
{
|
|
182
|
+
tmpDesc += ' at ' + tmpRegion.X + ', ' + tmpRegion.Y;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Type icon
|
|
187
|
+
let tmpTypeIcon = tmpIsText ? '\uD83D\uDCDD' : '\uD83D\uDD32';
|
|
188
|
+
|
|
189
|
+
tmpHTML += '<div class="retold-remote-subimages-item" data-region-id="' + tmpRegion.ID + '">';
|
|
190
|
+
|
|
191
|
+
// Region info
|
|
192
|
+
tmpHTML += '<div class="retold-remote-subimages-item-info">';
|
|
193
|
+
tmpHTML += '<div class="retold-remote-subimages-item-label">' + tmpTypeIcon + ' ' + tmpFmt.escapeHTML(tmpLabel) + '</div>';
|
|
194
|
+
tmpHTML += '<div class="retold-remote-subimages-item-dims">' + tmpFmt.escapeHTML(tmpDesc) + '</div>';
|
|
195
|
+
tmpHTML += '</div>';
|
|
196
|
+
|
|
197
|
+
// Actions
|
|
198
|
+
tmpHTML += '<div class="retold-remote-subimages-item-actions">';
|
|
199
|
+
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-SubimagesPanel\'].navigateToRegion(\'' + tmpRegion.ID + '\')" title="Navigate to region" style="font-size:0.7rem;padding:2px 6px;">🔎</button>';
|
|
200
|
+
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-SubimagesPanel\'].addRegionToCollection(\'' + tmpRegion.ID + '\')" title="Add to collection" style="font-size:0.7rem;padding:2px 6px;">✚</button>';
|
|
201
|
+
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-SubimagesPanel\'].deleteRegion(\'' + tmpRegion.ID + '\')" title="Delete region" style="font-size:0.7rem;padding:2px 6px;">🗑</button>';
|
|
202
|
+
tmpHTML += '</div>';
|
|
203
|
+
|
|
204
|
+
tmpHTML += '</div>';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
tmpHTML += '</div>';
|
|
209
|
+
pContainer.innerHTML = tmpHTML;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Navigate to a specific region — handles images, EPUB, and PDF.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} pRegionID - The region ID
|
|
216
|
+
*/
|
|
217
|
+
navigateToRegion(pRegionID)
|
|
218
|
+
{
|
|
219
|
+
let tmpRegion = null;
|
|
220
|
+
for (let i = 0; i < this._regions.length; i++)
|
|
221
|
+
{
|
|
222
|
+
if (this._regions[i].ID === pRegionID)
|
|
223
|
+
{
|
|
224
|
+
tmpRegion = this._regions[i];
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!tmpRegion)
|
|
229
|
+
{
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
234
|
+
let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
235
|
+
|
|
236
|
+
// EPUB: navigate to CFI location
|
|
237
|
+
if (tmpRegion.CFI && tmpMediaViewer && tmpMediaViewer._activeRendition)
|
|
238
|
+
{
|
|
239
|
+
tmpMediaViewer._activeRendition.display(tmpRegion.CFI);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// PDF: navigate to page
|
|
244
|
+
if (tmpRegion.PageNumber && tmpMediaViewer && typeof tmpMediaViewer._renderPdfPage === 'function')
|
|
245
|
+
{
|
|
246
|
+
tmpMediaViewer._renderPdfPage(tmpRegion.PageNumber);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Image: use image explorer
|
|
251
|
+
let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
|
|
252
|
+
if (tmpRemote.ActiveMode === 'image-explorer' && tmpIEX)
|
|
253
|
+
{
|
|
254
|
+
tmpIEX.zoomToRegion(pRegionID);
|
|
255
|
+
}
|
|
256
|
+
else if (tmpIEX && this._currentPath)
|
|
257
|
+
{
|
|
258
|
+
tmpIEX.showExplorer(this._currentPath);
|
|
259
|
+
setTimeout(() =>
|
|
260
|
+
{
|
|
261
|
+
tmpIEX.zoomToRegion(pRegionID);
|
|
262
|
+
}, 800);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add a subimage region to the active collection.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} pRegionID - The region ID to add
|
|
270
|
+
*/
|
|
271
|
+
addRegionToCollection(pRegionID)
|
|
272
|
+
{
|
|
273
|
+
let tmpRegion = null;
|
|
274
|
+
for (let i = 0; i < this._regions.length; i++)
|
|
275
|
+
{
|
|
276
|
+
if (this._regions[i].ID === pRegionID)
|
|
277
|
+
{
|
|
278
|
+
tmpRegion = this._regions[i];
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!tmpRegion)
|
|
284
|
+
{
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let tmpCollMgr = this.pict.providers['RetoldRemote-CollectionManager'];
|
|
289
|
+
if (tmpCollMgr)
|
|
290
|
+
{
|
|
291
|
+
let tmpGUID = tmpCollMgr.getQuickAddTargetGUID();
|
|
292
|
+
if (tmpGUID)
|
|
293
|
+
{
|
|
294
|
+
tmpCollMgr.addSubimageToCollection(tmpGUID, tmpRegion, this._currentPath);
|
|
295
|
+
}
|
|
296
|
+
else
|
|
297
|
+
{
|
|
298
|
+
let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
|
|
299
|
+
if (tmpTopBar && typeof tmpTopBar.showAddToCollectionDropdown === 'function')
|
|
300
|
+
{
|
|
301
|
+
tmpTopBar.showAddToCollectionDropdown();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Delete a subimage region.
|
|
309
|
+
*
|
|
310
|
+
* @param {string} pRegionID - The region ID to delete
|
|
311
|
+
*/
|
|
312
|
+
deleteRegion(pRegionID)
|
|
313
|
+
{
|
|
314
|
+
let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
|
|
315
|
+
if (tmpIEX && tmpIEX._currentPath === this._currentPath)
|
|
316
|
+
{
|
|
317
|
+
// Delegate to the explorer which handles the API call and overlay removal
|
|
318
|
+
tmpIEX.deleteRegion(pRegionID);
|
|
319
|
+
}
|
|
320
|
+
else
|
|
321
|
+
{
|
|
322
|
+
// Delete directly via API
|
|
323
|
+
let tmpSelf = this;
|
|
324
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
325
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(this._currentPath) : encodeURIComponent(this._currentPath);
|
|
326
|
+
|
|
327
|
+
fetch('/api/media/subimage-regions/' + encodeURIComponent(pRegionID) + '?path=' + tmpPathParam,
|
|
328
|
+
{
|
|
329
|
+
method: 'DELETE'
|
|
330
|
+
})
|
|
331
|
+
.then((pResponse) => pResponse.json())
|
|
332
|
+
.then((pResult) =>
|
|
333
|
+
{
|
|
334
|
+
if (pResult && pResult.Success)
|
|
335
|
+
{
|
|
336
|
+
tmpSelf._regions = pResult.Regions || [];
|
|
337
|
+
tmpSelf._renderRegionList();
|
|
338
|
+
|
|
339
|
+
let tmpToast = tmpSelf.pict.providers['RetoldRemote-ToastNotification'];
|
|
340
|
+
if (tmpToast)
|
|
341
|
+
{
|
|
342
|
+
tmpToast.showToast('Region deleted');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
.catch(() => {});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
RetoldRemoteSubimagesPanelView.default_configuration = _ViewConfiguration;
|
|
352
|
+
|
|
353
|
+
module.exports = RetoldRemoteSubimagesPanelView;
|
|
@@ -23,6 +23,7 @@ const _ViewConfiguration =
|
|
|
23
23
|
<div class="retold-remote-topbar-info" id="RetoldRemote-TopBar-Info"></div>
|
|
24
24
|
<div class="retold-remote-topbar-actions">
|
|
25
25
|
<button class="retold-remote-topbar-aisort-btn" id="RetoldRemote-TopBar-AISortBtn" onclick="pict.views['ContentEditor-TopBar'].triggerAISort()" title="AI Sort (generate sort plan for current folder)" style="display:none;">Ai</button>
|
|
26
|
+
<button class="retold-remote-topbar-btn retold-remote-topbar-regions-btn" id="RetoldRemote-TopBar-RegionsBtn" onclick="pict.views['RetoldRemote-RegionsBrowser'] && pict.views['RetoldRemote-RegionsBrowser'].open()" title="Browse all regions by folder">▣</button>
|
|
26
27
|
<button class="retold-remote-topbar-btn retold-remote-topbar-addcoll-btn" id="RetoldRemote-TopBar-AddToCollectionBtn" onclick="pict.views['ContentEditor-TopBar'].addToCollection(event)" title="Add to collection">★</button>
|
|
27
28
|
<button class="retold-remote-topbar-btn retold-remote-topbar-favorites-btn" id="RetoldRemote-TopBar-FavoritesBtn" onclick="pict.views['ContentEditor-TopBar'].toggleFavorite()" title="Toggle favorite (h)">♡</button>
|
|
28
29
|
<button class="retold-remote-topbar-sidebar-toggle retold-remote-topbar-collections-btn" id="RetoldRemote-TopBar-CollectionsBtn" onclick="pict.views['ContentEditor-TopBar'].toggleCollections()" title="Toggle Collections panel (b)">★</button>
|
|
@@ -203,6 +203,14 @@ class RetoldRemoteVideoExplorerView extends libPictView
|
|
|
203
203
|
tmpRemote.CurrentViewerFile = pFilePath;
|
|
204
204
|
tmpRemote.CurrentViewerMediaType = 'video';
|
|
205
205
|
this._currentPath = pFilePath;
|
|
206
|
+
|
|
207
|
+
// Notify the layout so active sidebar panels (Info, Regions, etc.)
|
|
208
|
+
// refresh to the new file instead of keeping stale content.
|
|
209
|
+
let tmpLayout = this.pict.views['ContentEditor-Layout'];
|
|
210
|
+
if (tmpLayout && typeof tmpLayout.notifyCurrentFileChanged === 'function')
|
|
211
|
+
{
|
|
212
|
+
tmpLayout.notifyCurrentFileChanged(pFilePath);
|
|
213
|
+
}
|
|
206
214
|
this._frameData = null;
|
|
207
215
|
this._selectedFrameIndex = -1;
|
|
208
216
|
this._customFrames = [];
|
|
@@ -357,16 +365,54 @@ class RetoldRemoteVideoExplorerView extends libPictView
|
|
|
357
365
|
tmpURL += '&width=1920&height=1080';
|
|
358
366
|
}
|
|
359
367
|
|
|
360
|
-
fetch(
|
|
368
|
+
// Cancel any previous in-flight frame fetch (e.g. switching videos fast)
|
|
369
|
+
this._cancelActiveOperation();
|
|
370
|
+
|
|
371
|
+
// Start a new operation entry for this extraction
|
|
372
|
+
let tmpStatus = this.pict.providers['RetoldRemote-OperationStatus'];
|
|
373
|
+
let tmpOp = tmpStatus ? tmpStatus.startOperation(
|
|
374
|
+
{
|
|
375
|
+
Label: 'Extracting video frames',
|
|
376
|
+
Phase: 'Starting…',
|
|
377
|
+
Cancelable: true
|
|
378
|
+
}) : null;
|
|
379
|
+
if (tmpOp)
|
|
380
|
+
{
|
|
381
|
+
this._activeOperationId = tmpOp.OperationId;
|
|
382
|
+
this._activeAbortController = tmpOp.AbortController;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let tmpFetchOptions = {};
|
|
386
|
+
if (tmpOp && tmpOp.AbortController)
|
|
387
|
+
{
|
|
388
|
+
tmpFetchOptions.signal = tmpOp.AbortController.signal;
|
|
389
|
+
}
|
|
390
|
+
if (tmpOp)
|
|
391
|
+
{
|
|
392
|
+
tmpFetchOptions.headers = { 'X-Op-Id': tmpOp.OperationId };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fetch(tmpURL, tmpFetchOptions)
|
|
361
396
|
.then((pResponse) => pResponse.json())
|
|
362
397
|
.then((pData) =>
|
|
363
398
|
{
|
|
364
399
|
if (!pData || !pData.Success)
|
|
365
400
|
{
|
|
401
|
+
if (tmpOp && tmpStatus)
|
|
402
|
+
{
|
|
403
|
+
tmpStatus.errorOperation(tmpOp.OperationId, { message: pData ? pData.Error : 'Unknown error' });
|
|
404
|
+
}
|
|
366
405
|
tmpSelf._showError(pData ? pData.Error : 'Unknown error');
|
|
367
406
|
return;
|
|
368
407
|
}
|
|
369
408
|
|
|
409
|
+
if (tmpOp && tmpStatus)
|
|
410
|
+
{
|
|
411
|
+
tmpStatus.completeOperation(tmpOp.OperationId);
|
|
412
|
+
}
|
|
413
|
+
tmpSelf._activeOperationId = null;
|
|
414
|
+
tmpSelf._activeAbortController = null;
|
|
415
|
+
|
|
370
416
|
tmpSelf._frameData = pData;
|
|
371
417
|
tmpSelf._renderFrames();
|
|
372
418
|
|
|
@@ -375,10 +421,38 @@ class RetoldRemoteVideoExplorerView extends libPictView
|
|
|
375
421
|
})
|
|
376
422
|
.catch((pError) =>
|
|
377
423
|
{
|
|
424
|
+
// Silent abort — user navigated away or switched videos
|
|
425
|
+
if (pError && pError.name === 'AbortError')
|
|
426
|
+
{
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (tmpOp && tmpStatus)
|
|
430
|
+
{
|
|
431
|
+
tmpStatus.errorOperation(tmpOp.OperationId, pError);
|
|
432
|
+
}
|
|
378
433
|
tmpSelf._showError(pError.message);
|
|
379
434
|
});
|
|
380
435
|
}
|
|
381
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Cancel any in-flight frame extraction for this explorer. Used on
|
|
439
|
+
* navigate-away and when starting a fresh extraction.
|
|
440
|
+
*/
|
|
441
|
+
_cancelActiveOperation()
|
|
442
|
+
{
|
|
443
|
+
if (this._activeAbortController)
|
|
444
|
+
{
|
|
445
|
+
try { this._activeAbortController.abort(); } catch (pErr) { /* ignore */ }
|
|
446
|
+
}
|
|
447
|
+
let tmpStatus = this.pict.providers['RetoldRemote-OperationStatus'];
|
|
448
|
+
if (this._activeOperationId && tmpStatus)
|
|
449
|
+
{
|
|
450
|
+
tmpStatus.cancelOperation(this._activeOperationId);
|
|
451
|
+
}
|
|
452
|
+
this._activeOperationId = null;
|
|
453
|
+
this._activeAbortController = null;
|
|
454
|
+
}
|
|
455
|
+
|
|
382
456
|
/**
|
|
383
457
|
* Render the extracted frames into the grid.
|
|
384
458
|
*/
|
|
@@ -698,6 +772,7 @@ class RetoldRemoteVideoExplorerView extends libPictView
|
|
|
698
772
|
*/
|
|
699
773
|
goBack()
|
|
700
774
|
{
|
|
775
|
+
this._cancelActiveOperation();
|
|
701
776
|
this._cleanupWindowListeners();
|
|
702
777
|
|
|
703
778
|
let tmpNav = this.pict.providers['RetoldRemote-GalleryNavigation'];
|
|
@@ -712,6 +787,7 @@ class RetoldRemoteVideoExplorerView extends libPictView
|
|
|
712
787
|
*/
|
|
713
788
|
playInBrowser()
|
|
714
789
|
{
|
|
790
|
+
this._cancelActiveOperation();
|
|
715
791
|
this._cleanupWindowListeners();
|
|
716
792
|
|
|
717
793
|
let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
|