retold-remote 0.0.22 → 0.0.25

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 (47) hide show
  1. package/css/retold-remote.css +87 -20
  2. package/docs/README.md +59 -11
  3. package/docs/_sidebar.md +1 -0
  4. package/docs/collections.md +30 -0
  5. package/docs/ebook-reader.md +75 -1
  6. package/docs/image-explorer.md +27 -1
  7. package/docs/server-setup.md +28 -18
  8. package/docs/stack-launcher.md +218 -0
  9. package/docs/ultravisor-integration.md +2 -0
  10. package/package.json +10 -7
  11. package/source/Pict-Application-RetoldRemote.js +2 -0
  12. package/source/RetoldRemote-ExtensionMaps.js +1 -1
  13. package/source/cli/RetoldRemote-Server-Setup.js +240 -2
  14. package/source/cli/RetoldRemote-Stack-Launcher.js +387 -0
  15. package/source/cli/RetoldRemote-Stack-Run.js +41 -0
  16. package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
  17. package/source/providers/CollectionManager-AddItems.js +166 -0
  18. package/source/providers/Pict-Provider-GalleryNavigation.js +46 -0
  19. package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +5 -0
  20. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
  21. package/source/server/RetoldRemote-CollectionExportService.js +696 -0
  22. package/source/server/RetoldRemote-CollectionService.js +5 -0
  23. package/source/server/RetoldRemote-EbookService.js +194 -3
  24. package/source/server/RetoldRemote-SubimageService.js +530 -0
  25. package/source/server/RetoldRemote-ToolDetector.js +50 -0
  26. package/source/server/RetoldRemote-UltravisorOperations.js +6 -6
  27. package/source/views/MediaViewer-EbookViewer.js +419 -1
  28. package/source/views/MediaViewer-PdfViewer.js +963 -0
  29. package/source/views/PictView-Remote-CollectionsPanel.js +166 -0
  30. package/source/views/PictView-Remote-ImageExplorer.js +606 -1
  31. package/source/views/PictView-Remote-ImageViewer.js +2 -2
  32. package/source/views/PictView-Remote-Layout.js +12 -0
  33. package/source/views/PictView-Remote-MediaViewer.js +83 -25
  34. package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
  35. package/web-application/css/retold-remote.css +87 -20
  36. package/web-application/docs/README.md +59 -11
  37. package/web-application/docs/_sidebar.md +1 -0
  38. package/web-application/docs/collections.md +30 -0
  39. package/web-application/docs/ebook-reader.md +75 -1
  40. package/web-application/docs/image-explorer.md +27 -1
  41. package/web-application/docs/server-setup.md +28 -18
  42. package/web-application/docs/stack-launcher.md +218 -0
  43. package/web-application/docs/ultravisor-integration.md +2 -0
  44. package/web-application/retold-remote.js +399 -45
  45. package/web-application/retold-remote.js.map +1 -1
  46. package/web-application/retold-remote.min.js +13 -12
  47. package/web-application/retold-remote.min.js.map +1 -1
@@ -2,6 +2,7 @@ const libPictView = require('pict-view');
2
2
 
3
3
  const _MediaViewerEbookViewer = require('./MediaViewer-EbookViewer');
4
4
  const _MediaViewerCodeViewer = require('./MediaViewer-CodeViewer');
5
+ const _MediaViewerPdfViewer = require('./MediaViewer-PdfViewer');
5
6
 
6
7
  const _ViewConfiguration =
7
8
  {
@@ -59,6 +60,7 @@ class RetoldRemoteMediaViewerView extends libPictView
59
60
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].closeViewer()" title="Back (Esc)">&larr; Back</button>';
60
61
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">&lsaquo; Prev</button>';
61
62
  tmpHTML += '<div class="retold-remote-viewer-title">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFileName) + '</div>';
63
+ tmpHTML += '<button class="retold-remote-viewer-nav-btn" id="RetoldRemote-HeaderExploreBtn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" title="Explore image (e)" style="display:none;">&#128269; Explore</button>';
62
64
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].nextFile()" title="Next (j)">Next &rsaquo;</button>';
63
65
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">&#9432;</button>';
64
66
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].toggleDistractionFree()" title="Distraction-Free (d)">&#9634;</button>';
@@ -127,7 +129,7 @@ class RetoldRemoteMediaViewerView extends libPictView
127
129
  this._loadCodeViewer(tmpContentURL, pFilePath);
128
130
  }
129
131
 
130
- // Load ebook viewer for epub/mobi
132
+ // Load document viewers: ebook for epub/mobi, PDF for pdf, convert-then-PDF for others
131
133
  if (pMediaType === 'document')
132
134
  {
133
135
  let tmpExt = pFilePath.replace(/^.*\./, '').toLowerCase();
@@ -135,7 +137,15 @@ class RetoldRemoteMediaViewerView extends libPictView
135
137
  {
136
138
  this._loadEbookViewer(tmpContentURL, pFilePath);
137
139
  }
138
-
140
+ else if (tmpExt === 'pdf')
141
+ {
142
+ this._loadPdfViewer(tmpContentURL, pFilePath);
143
+ }
144
+ else
145
+ {
146
+ // Convertible document types — convert to PDF first, then load PDF viewer
147
+ this._loadConvertedDocumentViewer(pFilePath);
148
+ }
139
149
  }
140
150
 
141
151
  // Set up swipe navigation for touch devices
@@ -533,19 +543,12 @@ class RetoldRemoteMediaViewerView extends libPictView
533
543
  tmpImg.onclick = function () { pict.views['RetoldRemote-ImageViewer'].toggleZoom(); };
534
544
  tmpFragment.appendChild(tmpImg);
535
545
 
536
- // Explore button (always present, initially hidden unless pShowExplore)
537
- let tmpBtn = document.createElement('button');
538
- tmpBtn.className = 'retold-remote-image-explore-btn';
539
- tmpBtn.id = 'RetoldRemote-ImageExploreBtn';
540
- tmpBtn.style.display = pShowExplore ? '' : 'none';
541
- tmpBtn.title = 'Open in deep-zoom explorer (e)';
542
- tmpBtn.innerHTML = '&#128269; Explore';
543
- tmpBtn.onclick = function ()
544
- {
545
- pict.views['RetoldRemote-ImageExplorer'].showExplorer(
546
- pict.AppData.RetoldRemote.CurrentViewerFile);
547
- };
548
- tmpFragment.appendChild(tmpBtn);
546
+ // Always show the Explore button in the header nav bar for images
547
+ let tmpHeaderExploreBtn = document.getElementById('RetoldRemote-HeaderExploreBtn');
548
+ if (tmpHeaderExploreBtn)
549
+ {
550
+ tmpHeaderExploreBtn.style.display = '';
551
+ }
549
552
 
550
553
  // Dimension badge for large images
551
554
  if (pShowExplore && pOrigWidth && pOrigHeight)
@@ -568,12 +571,6 @@ class RetoldRemoteMediaViewerView extends libPictView
568
571
  + 'id="RetoldRemote-ImageViewer-Img" '
569
572
  + 'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '
570
573
  + 'onclick="pict.views[\'RetoldRemote-ImageViewer\'].toggleZoom()">';
571
- // Explore button (hidden by default, shown by ImageViewer for large images)
572
- tmpHTML += '<button class="retold-remote-image-explore-btn" id="RetoldRemote-ImageExploreBtn" '
573
- + 'style="display:none" '
574
- + 'onclick="pict.views[\'RetoldRemote-ImageExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
575
- + 'title="Open in deep-zoom explorer (e)">'
576
- + '&#128269; Explore</button>';
577
574
  return tmpHTML;
578
575
  }
579
576
 
@@ -786,9 +783,7 @@ class RetoldRemoteMediaViewerView extends libPictView
786
783
 
787
784
  if (tmpExtension === 'pdf')
788
785
  {
789
- return '<iframe src="' + pURL + '" '
790
- + 'style="width: 100%; height: 100%; border: none;">'
791
- + '</iframe>';
786
+ return this._buildPdfHTML(pURL, pFileName, pFilePath);
792
787
  }
793
788
 
794
789
  if (tmpExtension === 'epub' || tmpExtension === 'mobi')
@@ -796,7 +791,15 @@ class RetoldRemoteMediaViewerView extends libPictView
796
791
  return this._buildEbookHTML(pURL, pFileName, pFilePath);
797
792
  }
798
793
 
799
- // For other document types, show a download link
794
+ // For convertible document types (doc, docx, rtf, odt, wpd, etc.),
795
+ // show the PDF viewer shell — conversion happens async in _loadDocumentViewer
796
+ let tmpConvertibleExts = { 'doc': true, 'docx': true, 'rtf': true, 'odt': true, 'wpd': true, 'wps': true, 'pages': true, 'odp': true, 'ppt': true, 'pptx': true, 'ods': true, 'xls': true, 'xlsx': true };
797
+ if (tmpConvertibleExts[tmpExtension])
798
+ {
799
+ return this._buildPdfHTML(pURL, pFileName, pFilePath);
800
+ }
801
+
802
+ // Unknown document types: show a download link
800
803
  let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
801
804
  let tmpDocIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '&#128196;';
802
805
  return '<div style="text-align: center; padding: 40px;">'
@@ -813,6 +816,60 @@ class RetoldRemoteMediaViewerView extends libPictView
813
816
  // ebookGoToChapter, ebookPrevPage, ebookNextPage, toggleEbookTOC
814
817
  // are in MediaViewer-EbookViewer.js (mixed in below).
815
818
 
819
+ /**
820
+ * Convert a document (doc, docx, rtf, odt, wpd, etc.) to PDF and load in the PDF viewer.
821
+ * Shows a converting message in the PDF content area while waiting.
822
+ *
823
+ * @param {string} pFilePath - Relative file path
824
+ */
825
+ _loadConvertedDocumentViewer(pFilePath)
826
+ {
827
+ let tmpSelf = this;
828
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
829
+ let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(pFilePath) : encodeURIComponent(pFilePath);
830
+
831
+ // Show converting message in the PDF content area
832
+ let tmpContent = document.getElementById('RetoldRemote-PdfContent');
833
+ if (tmpContent)
834
+ {
835
+ tmpContent.innerHTML = '<div style="text-align:center;padding:40px;color:var(--retold-text-dim);">'
836
+ + '<div style="font-size:1.5rem;margin-bottom:12px;">&#9881;</div>'
837
+ + 'Converting document to PDF\u2026'
838
+ + '</div>';
839
+ }
840
+
841
+ fetch('/api/media/doc-convert?path=' + tmpPathParam)
842
+ .then((pResponse) => pResponse.json())
843
+ .then((pData) =>
844
+ {
845
+ if (!pData || !pData.Success)
846
+ {
847
+ throw new Error(pData ? pData.Error : 'Conversion failed.');
848
+ }
849
+
850
+ // Build the PDF URL from the ebook cache (same serving endpoint)
851
+ let tmpPdfURL = '/api/media/ebook/' + pData.CacheKey + '/' + pData.OutputFilename;
852
+
853
+ // Load the PDF viewer with the converted file
854
+ tmpSelf._loadPdfViewer(tmpPdfURL, pFilePath);
855
+ })
856
+ .catch((pError) =>
857
+ {
858
+ if (tmpContent)
859
+ {
860
+ let tmpFmt = tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'];
861
+ tmpContent.innerHTML = '<div style="text-align:center;padding:40px;color:var(--retold-text-dim);">'
862
+ + '<div style="margin-bottom:12px;color:#e06c75;">Conversion failed</div>'
863
+ + '<div style="font-size:0.85rem;">' + tmpFmt.escapeHTML(pError.message) + '</div>'
864
+ + '<div style="margin-top:16px;">'
865
+ + '<a href="' + (tmpProvider ? tmpProvider.getContentURL(pFilePath) : '/content/' + encodeURIComponent(pFilePath))
866
+ + '" target="_blank" style="color:var(--retold-accent);font-size:0.85rem;">Download original file</a>'
867
+ + '</div>'
868
+ + '</div>';
869
+ }
870
+ });
871
+ }
872
+
816
873
  _buildFallbackHTML(pURL, pFileName)
817
874
  {
818
875
  let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
@@ -942,6 +999,7 @@ class RetoldRemoteMediaViewerView extends libPictView
942
999
 
943
1000
  Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerEbookViewer);
944
1001
  Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerCodeViewer);
1002
+ Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerPdfViewer);
945
1003
 
946
1004
  RetoldRemoteMediaViewerView.default_configuration = _ViewConfiguration;
947
1005
 
@@ -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 += ' &mdash; ' + 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;">&#128270;</button>';
200
+ tmpHTML += '<button onclick="pict.views[\'RetoldRemote-SubimagesPanel\'].addRegionToCollection(\'' + tmpRegion.ID + '\')" title="Add to collection" style="font-size:0.7rem;padding:2px 6px;">&#10010;</button>';
201
+ tmpHTML += '<button onclick="pict.views[\'RetoldRemote-SubimagesPanel\'].deleteRegion(\'' + tmpRegion.ID + '\')" title="Delete region" style="font-size:0.7rem;padding:2px 6px;">&#128465;</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;
@@ -2263,26 +2263,7 @@ html, body
2263
2263
  {
2264
2264
  opacity: 1;
2265
2265
  }
2266
- .retold-remote-image-explore-btn
2267
- {
2268
- position: absolute;
2269
- bottom: 16px;
2270
- right: 16px;
2271
- background: rgba(40, 44, 52, 0.85);
2272
- color: #abb2bf;
2273
- border: 1px solid rgba(255, 255, 255, 0.15);
2274
- padding: 6px 14px;
2275
- border-radius: 6px;
2276
- font-size: 0.82rem;
2277
- cursor: pointer;
2278
- z-index: 20;
2279
- transition: background 0.2s, color 0.2s;
2280
- }
2281
- .retold-remote-image-explore-btn:hover
2282
- {
2283
- background: rgba(97, 175, 239, 0.3);
2284
- color: #fff;
2285
- }
2266
+ /* Explore button overlay styles removed — button moved to nav bar */
2286
2267
  .retold-remote-image-large-badge
2287
2268
  {
2288
2269
  position: absolute;
@@ -2480,6 +2461,75 @@ html, body
2480
2461
  text-align: center;
2481
2462
  color: var(--retold-text-secondary);
2482
2463
  }
2464
+ /* Subimage region overlays */
2465
+ .retold-remote-iex-region-overlay
2466
+ {
2467
+ border: 2px solid rgba(229, 192, 123, 0.85);
2468
+ background: rgba(229, 192, 123, 0.08);
2469
+ pointer-events: none;
2470
+ position: relative;
2471
+ }
2472
+ /* Subimages sidebar panel */
2473
+ .retold-remote-subimages-panel
2474
+ {
2475
+ overflow-y: auto;
2476
+ height: 100%;
2477
+ }
2478
+ .retold-remote-subimages-item
2479
+ {
2480
+ display: flex;
2481
+ align-items: center;
2482
+ justify-content: space-between;
2483
+ padding: 6px 10px;
2484
+ border-bottom: 1px solid var(--retold-border);
2485
+ cursor: default;
2486
+ transition: background 0.15s;
2487
+ }
2488
+ .retold-remote-subimages-item:hover
2489
+ {
2490
+ background: rgba(255, 255, 255, 0.04);
2491
+ }
2492
+ .retold-remote-subimages-item-info
2493
+ {
2494
+ flex: 1;
2495
+ min-width: 0;
2496
+ overflow: hidden;
2497
+ }
2498
+ .retold-remote-subimages-item-label
2499
+ {
2500
+ font-size: 0.82rem;
2501
+ color: var(--retold-text);
2502
+ white-space: nowrap;
2503
+ overflow: hidden;
2504
+ text-overflow: ellipsis;
2505
+ }
2506
+ .retold-remote-subimages-item-dims
2507
+ {
2508
+ font-size: 0.7rem;
2509
+ color: var(--retold-text-dim);
2510
+ margin-top: 1px;
2511
+ }
2512
+ .retold-remote-subimages-item-actions
2513
+ {
2514
+ display: flex;
2515
+ gap: 2px;
2516
+ margin-left: 6px;
2517
+ flex-shrink: 0;
2518
+ }
2519
+ .retold-remote-subimages-item-actions button
2520
+ {
2521
+ background: transparent;
2522
+ border: 1px solid var(--retold-border);
2523
+ border-radius: 3px;
2524
+ color: var(--retold-text-dim);
2525
+ cursor: pointer;
2526
+ transition: color 0.15s, border-color 0.15s;
2527
+ }
2528
+ .retold-remote-subimages-item-actions button:hover
2529
+ {
2530
+ color: var(--retold-text);
2531
+ border-color: var(--retold-accent);
2532
+ }
2483
2533
 
2484
2534
  /* ============================================================
2485
2535
  AudioExplorer
@@ -3515,6 +3565,23 @@ html, body
3515
3565
  border-color: var(--retold-accent);
3516
3566
  color: var(--retold-text-primary);
3517
3567
  }
3568
+ .retold-remote-collections-export-btn
3569
+ {
3570
+ padding: 3px 8px;
3571
+ border: 1px solid var(--retold-border);
3572
+ border-radius: 3px;
3573
+ background: transparent;
3574
+ color: var(--retold-text-muted);
3575
+ font-size: 0.72rem;
3576
+ cursor: pointer;
3577
+ font-family: inherit;
3578
+ margin-left: auto;
3579
+ }
3580
+ .retold-remote-collections-export-btn:hover
3581
+ {
3582
+ border-color: var(--retold-accent);
3583
+ color: var(--retold-text-primary);
3584
+ }
3518
3585
  /* ---- Item rows ---- */
3519
3586
  .retold-remote-collection-item
3520
3587
  {