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
@@ -68,11 +68,11 @@ class RetoldRemoteImageViewerView extends libPictView
68
68
  }
69
69
 
70
70
  /**
71
- * Show the explore button for opening the deep-zoom explorer.
71
+ * Show the explore button in the header nav bar.
72
72
  */
73
73
  _showExploreButton()
74
74
  {
75
- let tmpBtn = document.getElementById('RetoldRemote-ImageExploreBtn');
75
+ let tmpBtn = document.getElementById('RetoldRemote-HeaderExploreBtn');
76
76
  if (tmpBtn)
77
77
  {
78
78
  tmpBtn.style.display = '';
@@ -24,6 +24,7 @@ const _ViewConfiguration =
24
24
  <button class="content-editor-sidebar-tab active" data-tab="files" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('files')">Files</button>
25
25
  <button class="content-editor-sidebar-tab" data-tab="favorites" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('favorites')">Favorites</button>
26
26
  <button class="content-editor-sidebar-tab" data-tab="info" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('info')">Info</button>
27
+ <button class="content-editor-sidebar-tab" data-tab="subimages" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('subimages')">Regions</button>
27
28
  <button class="content-editor-sidebar-tab" data-tab="settings" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('settings')">Settings</button>
28
29
  <button class="content-editor-sidebar-tab content-editor-sidebar-tab-collections" data-tab="collections" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('collections')" style="display:none;">Collections</button>
29
30
  </div>
@@ -32,6 +33,7 @@ const _ViewConfiguration =
32
33
  <div id="RetoldRemote-Favorites-Body"></div>
33
34
  </div>
34
35
  <div class="content-editor-sidebar-pane" data-pane="info" id="RetoldRemote-Info-Container" style="display:none"></div>
36
+ <div class="content-editor-sidebar-pane" data-pane="subimages" id="RetoldRemote-Subimages-Container" style="display:none"></div>
35
37
  <div class="content-editor-sidebar-pane" data-pane="settings" id="RetoldRemote-Settings-Container" style="display:none"></div>
36
38
  <div class="content-editor-sidebar-pane" data-pane="collections" id="RetoldRemote-Collections-MobilePane" style="display:none"></div>
37
39
  </div>
@@ -48,6 +50,7 @@ const _ViewConfiguration =
48
50
  </div>
49
51
  </div>
50
52
  </div>
53
+ <div class="retold-remote-operation-status" id="RetoldRemote-OperationStatus-Container"></div>
51
54
  `
52
55
  }
53
56
  ],
@@ -196,6 +199,16 @@ class RetoldRemoteLayoutView extends libPictView
196
199
  pEl.style.display = (pEl.getAttribute('data-pane') === pTab) ? '' : 'none';
197
200
  });
198
201
 
202
+ // Subimages tab: render the subimages panel
203
+ if (pTab === 'subimages')
204
+ {
205
+ let tmpSubimagesView = this.pict.views['RetoldRemote-SubimagesPanel'];
206
+ if (tmpSubimagesView)
207
+ {
208
+ tmpSubimagesView.render();
209
+ }
210
+ }
211
+
199
212
  // Render settings panel on demand
200
213
  if (pTab === 'settings')
201
214
  {
@@ -256,6 +269,51 @@ class RetoldRemoteLayoutView extends libPictView
256
269
 
257
270
  }
258
271
 
272
+ /**
273
+ * Notify the layout that the currently-viewed file has changed (or been
274
+ * cleared). This re-renders whichever sidebar panel is currently visible
275
+ * so stale file-scoped content (Info metadata, Regions list, etc.) gets
276
+ * refreshed without the user having to click the tab again.
277
+ *
278
+ * Call this from every viewer that mutates tmpRemote.CurrentViewerFile,
279
+ * and from the gallery clear path. Do NOT call it on intra-file navigation
280
+ * (PDF page changes, EPUB chapter changes) — those don't change the file.
281
+ *
282
+ * @param {string} pFilePath - The new current file path (or '' when clearing)
283
+ */
284
+ notifyCurrentFileChanged(pFilePath)
285
+ {
286
+ // Find the currently-active sidebar tab
287
+ let tmpActiveTab = document.querySelector('.content-editor-sidebar-tab.active');
288
+ if (!tmpActiveTab)
289
+ {
290
+ return;
291
+ }
292
+ let tmpTab = tmpActiveTab.getAttribute('data-tab');
293
+
294
+ // Re-render whichever panel owns file-scoped state. The panel's own
295
+ // render() already detects whether the file actually changed (via its
296
+ // internal _currentPath comparison) and re-fetches only when needed.
297
+ if (tmpTab === 'subimages')
298
+ {
299
+ let tmpSubimagesView = this.pict.views['RetoldRemote-SubimagesPanel'];
300
+ if (tmpSubimagesView)
301
+ {
302
+ tmpSubimagesView.render();
303
+ }
304
+ }
305
+ else if (tmpTab === 'info')
306
+ {
307
+ let tmpInfoView = this.pict.views['RetoldRemote-FileInfoPanel'];
308
+ if (tmpInfoView)
309
+ {
310
+ tmpInfoView.render();
311
+ }
312
+ }
313
+ // Other tabs (files, favorites, settings, collections) are not
314
+ // file-scoped, so there's nothing to refresh on file change.
315
+ }
316
+
259
317
  _setupResizeHandle()
260
318
  {
261
319
  let tmpSelf = this;
@@ -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
  {
@@ -40,6 +41,14 @@ class RetoldRemoteMediaViewerView extends libPictView
40
41
  tmpRemote.CurrentViewerMediaType = pMediaType;
41
42
  tmpRemote.VideoMenuActive = (pMediaType === 'video');
42
43
 
44
+ // Notify the layout so active sidebar panels (Info, Regions, etc.)
45
+ // refresh to the new file instead of keeping stale content.
46
+ let tmpLayout = this.pict.views['ContentEditor-Layout'];
47
+ if (tmpLayout && typeof tmpLayout.notifyCurrentFileChanged === 'function')
48
+ {
49
+ tmpLayout.notifyCurrentFileChanged(pFilePath);
50
+ }
51
+
43
52
  // Show viewer, hide gallery
44
53
  let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
45
54
  let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
@@ -59,6 +68,7 @@ class RetoldRemoteMediaViewerView extends libPictView
59
68
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].closeViewer()" title="Back (Esc)">&larr; Back</button>';
60
69
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">&lsaquo; Prev</button>';
61
70
  tmpHTML += '<div class="retold-remote-viewer-title">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFileName) + '</div>';
71
+ 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
72
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].nextFile()" title="Next (j)">Next &rsaquo;</button>';
63
73
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">&#9432;</button>';
64
74
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].toggleDistractionFree()" title="Distraction-Free (d)">&#9634;</button>';
@@ -127,7 +137,7 @@ class RetoldRemoteMediaViewerView extends libPictView
127
137
  this._loadCodeViewer(tmpContentURL, pFilePath);
128
138
  }
129
139
 
130
- // Load ebook viewer for epub/mobi
140
+ // Load document viewers: ebook for epub/mobi, PDF for pdf, convert-then-PDF for others
131
141
  if (pMediaType === 'document')
132
142
  {
133
143
  let tmpExt = pFilePath.replace(/^.*\./, '').toLowerCase();
@@ -135,7 +145,15 @@ class RetoldRemoteMediaViewerView extends libPictView
135
145
  {
136
146
  this._loadEbookViewer(tmpContentURL, pFilePath);
137
147
  }
138
-
148
+ else if (tmpExt === 'pdf')
149
+ {
150
+ this._loadPdfViewer(tmpContentURL, pFilePath);
151
+ }
152
+ else
153
+ {
154
+ // Convertible document types — convert to PDF first, then load PDF viewer
155
+ this._loadConvertedDocumentViewer(pFilePath);
156
+ }
139
157
  }
140
158
 
141
159
  // Set up swipe navigation for touch devices
@@ -169,6 +187,15 @@ class RetoldRemoteMediaViewerView extends libPictView
169
187
  tmpRemote.CurrentViewerMediaType = 'image';
170
188
  tmpRemote.VideoMenuActive = false;
171
189
 
190
+ // Notify the layout so active sidebar panels (Info, Regions, etc.)
191
+ // refresh. This is especially important when switching between
192
+ // collection items via the direct-image path.
193
+ let tmpLayout = this.pict.views['ContentEditor-Layout'];
194
+ if (tmpLayout && typeof tmpLayout.notifyCurrentFileChanged === 'function')
195
+ {
196
+ tmpLayout.notifyCurrentFileChanged(pFilePath || '');
197
+ }
198
+
172
199
  // Show viewer, hide gallery
173
200
  let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
174
201
  let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
@@ -533,19 +560,12 @@ class RetoldRemoteMediaViewerView extends libPictView
533
560
  tmpImg.onclick = function () { pict.views['RetoldRemote-ImageViewer'].toggleZoom(); };
534
561
  tmpFragment.appendChild(tmpImg);
535
562
 
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);
563
+ // Always show the Explore button in the header nav bar for images
564
+ let tmpHeaderExploreBtn = document.getElementById('RetoldRemote-HeaderExploreBtn');
565
+ if (tmpHeaderExploreBtn)
566
+ {
567
+ tmpHeaderExploreBtn.style.display = '';
568
+ }
549
569
 
550
570
  // Dimension badge for large images
551
571
  if (pShowExplore && pOrigWidth && pOrigHeight)
@@ -568,12 +588,6 @@ class RetoldRemoteMediaViewerView extends libPictView
568
588
  + 'id="RetoldRemote-ImageViewer-Img" '
569
589
  + 'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '
570
590
  + '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
591
  return tmpHTML;
578
592
  }
579
593
 
@@ -786,9 +800,7 @@ class RetoldRemoteMediaViewerView extends libPictView
786
800
 
787
801
  if (tmpExtension === 'pdf')
788
802
  {
789
- return '<iframe src="' + pURL + '" '
790
- + 'style="width: 100%; height: 100%; border: none;">'
791
- + '</iframe>';
803
+ return this._buildPdfHTML(pURL, pFileName, pFilePath);
792
804
  }
793
805
 
794
806
  if (tmpExtension === 'epub' || tmpExtension === 'mobi')
@@ -796,7 +808,15 @@ class RetoldRemoteMediaViewerView extends libPictView
796
808
  return this._buildEbookHTML(pURL, pFileName, pFilePath);
797
809
  }
798
810
 
799
- // For other document types, show a download link
811
+ // For convertible document types (doc, docx, rtf, odt, wpd, etc.),
812
+ // show the PDF viewer shell — conversion happens async in _loadDocumentViewer
813
+ 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 };
814
+ if (tmpConvertibleExts[tmpExtension])
815
+ {
816
+ return this._buildPdfHTML(pURL, pFileName, pFilePath);
817
+ }
818
+
819
+ // Unknown document types: show a download link
800
820
  let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
801
821
  let tmpDocIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '&#128196;';
802
822
  return '<div style="text-align: center; padding: 40px;">'
@@ -813,6 +833,60 @@ class RetoldRemoteMediaViewerView extends libPictView
813
833
  // ebookGoToChapter, ebookPrevPage, ebookNextPage, toggleEbookTOC
814
834
  // are in MediaViewer-EbookViewer.js (mixed in below).
815
835
 
836
+ /**
837
+ * Convert a document (doc, docx, rtf, odt, wpd, etc.) to PDF and load in the PDF viewer.
838
+ * Shows a converting message in the PDF content area while waiting.
839
+ *
840
+ * @param {string} pFilePath - Relative file path
841
+ */
842
+ _loadConvertedDocumentViewer(pFilePath)
843
+ {
844
+ let tmpSelf = this;
845
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
846
+ let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(pFilePath) : encodeURIComponent(pFilePath);
847
+
848
+ // Show converting message in the PDF content area
849
+ let tmpContent = document.getElementById('RetoldRemote-PdfContent');
850
+ if (tmpContent)
851
+ {
852
+ tmpContent.innerHTML = '<div style="text-align:center;padding:40px;color:var(--retold-text-dim);">'
853
+ + '<div style="font-size:1.5rem;margin-bottom:12px;">&#9881;</div>'
854
+ + 'Converting document to PDF\u2026'
855
+ + '</div>';
856
+ }
857
+
858
+ fetch('/api/media/doc-convert?path=' + tmpPathParam)
859
+ .then((pResponse) => pResponse.json())
860
+ .then((pData) =>
861
+ {
862
+ if (!pData || !pData.Success)
863
+ {
864
+ throw new Error(pData ? pData.Error : 'Conversion failed.');
865
+ }
866
+
867
+ // Build the PDF URL from the ebook cache (same serving endpoint)
868
+ let tmpPdfURL = '/api/media/ebook/' + pData.CacheKey + '/' + pData.OutputFilename;
869
+
870
+ // Load the PDF viewer with the converted file
871
+ tmpSelf._loadPdfViewer(tmpPdfURL, pFilePath);
872
+ })
873
+ .catch((pError) =>
874
+ {
875
+ if (tmpContent)
876
+ {
877
+ let tmpFmt = tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'];
878
+ tmpContent.innerHTML = '<div style="text-align:center;padding:40px;color:var(--retold-text-dim);">'
879
+ + '<div style="margin-bottom:12px;color:#e06c75;">Conversion failed</div>'
880
+ + '<div style="font-size:0.85rem;">' + tmpFmt.escapeHTML(pError.message) + '</div>'
881
+ + '<div style="margin-top:16px;">'
882
+ + '<a href="' + (tmpProvider ? tmpProvider.getContentURL(pFilePath) : '/content/' + encodeURIComponent(pFilePath))
883
+ + '" target="_blank" style="color:var(--retold-accent);font-size:0.85rem;">Download original file</a>'
884
+ + '</div>'
885
+ + '</div>';
886
+ }
887
+ });
888
+ }
889
+
816
890
  _buildFallbackHTML(pURL, pFileName)
817
891
  {
818
892
  let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
@@ -942,6 +1016,7 @@ class RetoldRemoteMediaViewerView extends libPictView
942
1016
 
943
1017
  Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerEbookViewer);
944
1018
  Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerCodeViewer);
1019
+ Object.assign(RetoldRemoteMediaViewerView.prototype, _MediaViewerPdfViewer);
945
1020
 
946
1021
  RetoldRemoteMediaViewerView.default_configuration = _ViewConfiguration;
947
1022