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
@@ -47,6 +47,30 @@ module.exports =
47
47
  return this.addAudioSnippetToCollection(tmpTargetGUID);
48
48
  }
49
49
 
50
+ // If the image explorer is active with a selection, add as subimage
51
+ if (tmpRemote.ActiveMode === 'image-explorer')
52
+ {
53
+ let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
54
+ if (tmpIEX)
55
+ {
56
+ let tmpActiveSelection = tmpIEX.getActiveSelection();
57
+ if (tmpActiveSelection)
58
+ {
59
+ return this.addSubimageToCollection(tmpTargetGUID, tmpActiveSelection, tmpIEX._currentPath);
60
+ }
61
+ }
62
+ }
63
+
64
+ // If viewing a document (PDF/EPUB) with a pending region, add as document-region
65
+ if (tmpRemote.ActiveMode === 'viewer' && tmpRemote.CurrentViewerMediaType === 'document')
66
+ {
67
+ let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
68
+ if (tmpMediaViewer && tmpMediaViewer._pendingDocumentRegion)
69
+ {
70
+ return this.addDocumentRegionToCollection(tmpTargetGUID, tmpMediaViewer._pendingDocumentRegion, tmpRemote.CurrentViewerFile);
71
+ }
72
+ }
73
+
50
74
  let tmpCurrentItem = this._resolveCurrentItem();
51
75
 
52
76
  if (!tmpCurrentItem || !tmpCurrentItem.Path)
@@ -329,6 +353,148 @@ module.exports =
329
353
  return true;
330
354
  },
331
355
 
356
+ /**
357
+ * Add a subimage region (crop) to a collection.
358
+ *
359
+ * @param {string} pGUID - Collection GUID
360
+ * @param {object} pRegion - { X, Y, Width, Height, Label?, ID? } in image pixels
361
+ * @param {string} [pFilePath] - Explicit file path (defaults to current viewer file)
362
+ * @returns {boolean} true if the add was initiated
363
+ */
364
+ addSubimageToCollection: function addSubimageToCollection(pGUID, pRegion, pFilePath)
365
+ {
366
+ let tmpRemote = this._getRemote();
367
+ let tmpTargetGUID = pGUID || tmpRemote.LastUsedCollectionGUID;
368
+ if (!tmpTargetGUID || !pRegion)
369
+ {
370
+ return false;
371
+ }
372
+
373
+ let tmpFilePath = pFilePath || tmpRemote.CurrentViewerFile;
374
+ if (!tmpFilePath)
375
+ {
376
+ return false;
377
+ }
378
+
379
+ let tmpFileName = tmpFilePath.replace(/^.*\//, '');
380
+ let tmpLabel = pRegion.Label
381
+ ? pRegion.Label
382
+ : tmpFileName + ': ' + pRegion.Width + '\u00d7' + pRegion.Height + ' at ' + pRegion.X + ',' + pRegion.Y;
383
+
384
+ let tmpItem =
385
+ {
386
+ Type: 'image-crop',
387
+ Path: tmpFilePath,
388
+ CropRegion:
389
+ {
390
+ X: pRegion.X,
391
+ Y: pRegion.Y,
392
+ Width: pRegion.Width,
393
+ Height: pRegion.Height
394
+ },
395
+ Label: tmpLabel,
396
+ Note: ''
397
+ };
398
+
399
+ // If we have a hash for this file, include it
400
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
401
+ if (tmpProvider)
402
+ {
403
+ let tmpHash = tmpProvider.getHashForPath(tmpFilePath);
404
+ if (tmpHash)
405
+ {
406
+ tmpItem.Hash = tmpHash;
407
+ }
408
+ }
409
+
410
+ this.addItemsToCollection(tmpTargetGUID, [tmpItem]);
411
+ return true;
412
+ },
413
+
414
+ /**
415
+ * Add a document region (text selection or visual area) to a collection.
416
+ *
417
+ * @param {string} pGUID - Collection GUID
418
+ * @param {object} pRegion - Region object with Type, Label, and type-specific fields
419
+ * @param {string} [pFilePath] - Explicit file path (defaults to current viewer file)
420
+ * @returns {boolean} true if the add was initiated
421
+ */
422
+ addDocumentRegionToCollection: function addDocumentRegionToCollection(pGUID, pRegion, pFilePath)
423
+ {
424
+ let tmpRemote = this._getRemote();
425
+ let tmpTargetGUID = pGUID || tmpRemote.LastUsedCollectionGUID;
426
+ if (!tmpTargetGUID || !pRegion)
427
+ {
428
+ return false;
429
+ }
430
+
431
+ let tmpFilePath = pFilePath || tmpRemote.CurrentViewerFile;
432
+ if (!tmpFilePath)
433
+ {
434
+ return false;
435
+ }
436
+
437
+ let tmpFileName = tmpFilePath.replace(/^.*\//, '');
438
+ let tmpIsText = (pRegion.Type === 'text-selection');
439
+
440
+ // Build label
441
+ let tmpLabel = pRegion.Label || '';
442
+ if (!tmpLabel)
443
+ {
444
+ if (tmpIsText && pRegion.SelectedText)
445
+ {
446
+ tmpLabel = pRegion.SelectedText.substring(0, 50);
447
+ if (pRegion.SelectedText.length > 50) tmpLabel += '\u2026';
448
+ }
449
+ else
450
+ {
451
+ tmpLabel = tmpFileName;
452
+ }
453
+ if (pRegion.PageNumber)
454
+ {
455
+ tmpLabel = 'p.' + pRegion.PageNumber + ': ' + tmpLabel;
456
+ }
457
+ }
458
+
459
+ let tmpItem =
460
+ {
461
+ Type: 'document-region',
462
+ Path: tmpFilePath,
463
+ Label: tmpLabel,
464
+ Note: '',
465
+ DocumentRegionType: pRegion.Type || 'visual-region',
466
+ PageNumber: pRegion.PageNumber || null,
467
+ CFI: pRegion.CFI || null,
468
+ SelectedText: pRegion.SelectedText || null
469
+ };
470
+
471
+ // Include crop region for visual selections
472
+ if (!tmpIsText && pRegion.X !== undefined)
473
+ {
474
+ tmpItem.CropRegion =
475
+ {
476
+ X: pRegion.X,
477
+ Y: pRegion.Y,
478
+ Width: pRegion.Width,
479
+ Height: pRegion.Height
480
+ };
481
+ }
482
+
483
+ // File hash
484
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
485
+ if (tmpProvider)
486
+ {
487
+ let tmpHash = tmpProvider.getHashForPath(tmpFilePath);
488
+ if (tmpHash)
489
+ {
490
+ tmpItem.Hash = tmpHash;
491
+ }
492
+ }
493
+
494
+ this.addItemsToCollection(tmpTargetGUID, [tmpItem]);
495
+ return true;
496
+ },
497
+
332
498
  /**
333
499
  * Format a timestamp in seconds to a human-readable string.
334
500
  *
@@ -630,6 +630,52 @@ class GalleryNavigationProvider extends libPictProvider
630
630
  tmpAEX.showExplorer(pItem.Path, pItem.AudioStart, pItem.AudioEnd);
631
631
  }
632
632
  }
633
+ else if (pItem.Type === 'document-region')
634
+ {
635
+ // Navigate to the document, then to the specific location
636
+ let tmpApp = this.pict.PictApplication;
637
+ if (tmpApp && tmpApp.navigateToFile)
638
+ {
639
+ tmpApp.navigateToFile(pItem.Path);
640
+
641
+ // After the viewer loads, navigate to the specific page/CFI
642
+ let tmpSelf = this;
643
+ setTimeout(() =>
644
+ {
645
+ let tmpMediaViewer = tmpSelf.pict.views['RetoldRemote-MediaViewer'];
646
+ if (tmpMediaViewer)
647
+ {
648
+ if (pItem.CFI && tmpMediaViewer._activeRendition)
649
+ {
650
+ tmpMediaViewer._activeRendition.display(pItem.CFI);
651
+ }
652
+ else if (pItem.PageNumber && typeof tmpMediaViewer._renderPdfPage === 'function')
653
+ {
654
+ tmpMediaViewer._renderPdfPage(pItem.PageNumber);
655
+ }
656
+ }
657
+ }, 1000);
658
+ }
659
+ }
660
+ else if (pItem.Type === 'image-crop' && pItem.CropRegion)
661
+ {
662
+ let tmpIEX = this.pict.views['RetoldRemote-ImageExplorer'];
663
+ if (tmpIEX)
664
+ {
665
+ tmpIEX.showExplorer(pItem.Path);
666
+ // Zoom to the crop region after the viewer loads
667
+ let tmpCrop = pItem.CropRegion;
668
+ setTimeout(() =>
669
+ {
670
+ if (tmpIEX._osdViewer && tmpIEX._dziData)
671
+ {
672
+ let tmpImageRect = new OpenSeadragon.Rect(tmpCrop.X, tmpCrop.Y, tmpCrop.Width, tmpCrop.Height);
673
+ let tmpViewportRect = tmpIEX._osdViewer.viewport.imageToViewportRectangle(tmpImageRect);
674
+ tmpIEX._osdViewer.viewport.fitBounds(tmpViewportRect);
675
+ }
676
+ }, 800);
677
+ }
678
+ }
633
679
  else if (pItem.Type === 'video-frame' && pItem.FrameCacheKey && pItem.FrameFilename)
634
680
  {
635
681
  // Show the cached frame image directly in the viewer
@@ -77,6 +77,11 @@ function handleImageExplorerKey(pGalleryNav, pEvent)
77
77
  }
78
78
  }
79
79
  break;
80
+
81
+ case 's':
82
+ pEvent.preventDefault();
83
+ tmpIEX.toggleSelectionMode();
84
+ break;
80
85
  }
81
86
  }
82
87
 
@@ -166,6 +166,29 @@ function handleViewerKey(pGalleryNav, pEvent)
166
166
  pGalleryNav._cycleFitMode();
167
167
  break;
168
168
 
169
+ case 's':
170
+ pEvent.preventDefault();
171
+ {
172
+ let tmpMediaViewer = pGalleryNav.pict.views['RetoldRemote-MediaViewer'];
173
+ if (tmpMediaViewer)
174
+ {
175
+ let tmpViewerMediaType = tmpRemote.CurrentViewerMediaType;
176
+ if (tmpViewerMediaType === 'document')
177
+ {
178
+ // Toggle region selection for EPUB or PDF
179
+ if (typeof tmpMediaViewer.ebookToggleRegionSelect === 'function' && tmpMediaViewer._activeRendition)
180
+ {
181
+ tmpMediaViewer.ebookToggleRegionSelect();
182
+ }
183
+ else if (typeof tmpMediaViewer.pdfToggleRegionSelect === 'function' && tmpMediaViewer._pdfDocument)
184
+ {
185
+ tmpMediaViewer.pdfToggleRegionSelect();
186
+ }
187
+ }
188
+ }
189
+ }
190
+ break;
191
+
169
192
  case 'Enter':
170
193
  pEvent.preventDefault();
171
194
  pGalleryNav._streamWithVLC();