retold-remote 0.0.4 → 0.0.6

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 (63) hide show
  1. package/docs/README.md +181 -0
  2. package/docs/_cover.md +14 -0
  3. package/docs/_sidebar.md +10 -0
  4. package/docs/_topbar.md +3 -0
  5. package/docs/audio-viewer.md +133 -0
  6. package/docs/ebook-reader.md +90 -0
  7. package/docs/image-viewer.md +90 -0
  8. package/docs/server-setup.md +262 -0
  9. package/docs/video-viewer.md +134 -0
  10. package/html/docs.html +59 -0
  11. package/package.json +21 -7
  12. package/source/Pict-Application-RetoldRemote.js +143 -2
  13. package/source/RetoldRemote-ExtensionMaps.js +33 -0
  14. package/source/cli/RetoldRemote-Server-Setup.js +82 -67
  15. package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
  16. package/source/providers/Pict-Provider-CollectionManager.js +934 -0
  17. package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
  18. package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
  19. package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
  20. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
  21. package/source/providers/Pict-Provider-ToastNotification.js +96 -0
  22. package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
  23. package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
  24. package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
  25. package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
  26. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
  27. package/source/server/RetoldRemote-ArchiveService.js +2 -12
  28. package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
  29. package/source/server/RetoldRemote-CollectionService.js +684 -0
  30. package/source/server/RetoldRemote-EbookService.js +7 -16
  31. package/source/server/RetoldRemote-MediaService.js +3 -14
  32. package/source/server/RetoldRemote-ParimeCache.js +349 -0
  33. package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
  34. package/source/server/RetoldRemote-VideoFrameService.js +7 -15
  35. package/source/views/PictView-Remote-AudioExplorer.js +10 -43
  36. package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
  37. package/source/views/PictView-Remote-Gallery.js +237 -44
  38. package/source/views/PictView-Remote-ImageViewer.js +1 -34
  39. package/source/views/PictView-Remote-Layout.js +410 -20
  40. package/source/views/PictView-Remote-MediaViewer.js +338 -51
  41. package/source/views/PictView-Remote-SettingsPanel.js +155 -138
  42. package/source/views/PictView-Remote-TopBar.js +615 -14
  43. package/source/views/PictView-Remote-VLCSetup.js +766 -0
  44. package/source/views/PictView-Remote-VideoExplorer.js +20 -54
  45. package/web-application/css/docuserve.css +73 -0
  46. package/web-application/docs/README.md +181 -0
  47. package/web-application/docs/_cover.md +14 -0
  48. package/web-application/docs/_sidebar.md +10 -0
  49. package/web-application/docs/_topbar.md +3 -0
  50. package/web-application/docs/audio-viewer.md +133 -0
  51. package/web-application/docs/ebook-reader.md +90 -0
  52. package/web-application/docs/image-viewer.md +90 -0
  53. package/web-application/docs/server-setup.md +262 -0
  54. package/web-application/docs/video-viewer.md +134 -0
  55. package/web-application/docs.html +59 -0
  56. package/web-application/js/pict-docuserve.min.js +58 -0
  57. package/web-application/js/pict.min.js +2 -2
  58. package/web-application/js/pict.min.js.map +1 -1
  59. package/web-application/retold-remote.js +2558 -439
  60. package/web-application/retold-remote.js.map +1 -1
  61. package/web-application/retold-remote.min.js +41 -11
  62. package/web-application/retold-remote.min.js.map +1 -1
  63. package/server.js +0 -43
@@ -433,6 +433,69 @@ const _ViewConfiguration =
433
433
  color: var(--retold-text-dim);
434
434
  font-size: 0.85rem;
435
435
  }
436
+ /* Distraction-free toggle */
437
+ .retold-remote-df-toggle
438
+ {
439
+ position: absolute;
440
+ top: 8px;
441
+ left: 8px;
442
+ z-index: 15;
443
+ padding: 4px 8px;
444
+ border: 1px solid var(--retold-border);
445
+ border-radius: 3px;
446
+ background: rgba(0, 0, 0, 0.5);
447
+ color: var(--retold-text-muted);
448
+ font-size: 0.72rem;
449
+ cursor: pointer;
450
+ opacity: 0;
451
+ transition: opacity 0.2s ease, color 0.15s, border-color 0.15s;
452
+ font-family: inherit;
453
+ white-space: nowrap;
454
+ }
455
+ .retold-remote-viewer-body:hover .retold-remote-df-toggle,
456
+ .retold-remote-df-toggle:focus
457
+ {
458
+ opacity: 1;
459
+ }
460
+ .retold-remote-df-toggle:hover
461
+ {
462
+ color: var(--retold-text-primary);
463
+ border-color: var(--retold-accent);
464
+ }
465
+ .retold-remote-df-toggle.active
466
+ {
467
+ color: var(--retold-accent);
468
+ border-color: var(--retold-accent);
469
+ }
470
+ /* Distraction-free exit hotspot (top-left corner) */
471
+ .retold-remote-df-exit-hotspot
472
+ {
473
+ position: absolute;
474
+ top: 0;
475
+ left: 0;
476
+ width: 80px;
477
+ height: 80px;
478
+ z-index: 16;
479
+ cursor: pointer;
480
+ display: none;
481
+ }
482
+ .retold-remote-df-exit-hotspot::after
483
+ {
484
+ content: '';
485
+ position: absolute;
486
+ top: 12px;
487
+ left: 12px;
488
+ width: 8px;
489
+ height: 8px;
490
+ border-radius: 50%;
491
+ background: var(--retold-accent);
492
+ opacity: 0;
493
+ transition: opacity 0.3s ease;
494
+ }
495
+ .retold-remote-df-exit-hotspot:hover::after
496
+ {
497
+ opacity: 0.6;
498
+ }
436
499
  `
437
500
  };
438
501
 
@@ -441,6 +504,12 @@ class RetoldRemoteMediaViewerView extends libPictView
441
504
  constructor(pFable, pOptions, pServiceHash)
442
505
  {
443
506
  super(pFable, pOptions, pServiceHash);
507
+
508
+ this._swipeStartX = 0;
509
+ this._swipeStartY = 0;
510
+ this._swipeTouchCount = 0;
511
+ this._swipeHandlers = null;
512
+ this._dfExitHandlers = null;
444
513
  }
445
514
 
446
515
  /**
@@ -475,7 +544,7 @@ class RetoldRemoteMediaViewerView extends libPictView
475
544
  tmpHTML += '<div class="retold-remote-viewer-header">';
476
545
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].closeViewer()" title="Back (Esc)">&larr; Back</button>';
477
546
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">&lsaquo; Prev</button>';
478
- tmpHTML += '<div class="retold-remote-viewer-title">' + this._escapeHTML(tmpFileName) + '</div>';
547
+ tmpHTML += '<div class="retold-remote-viewer-title">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFileName) + '</div>';
479
548
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].nextFile()" title="Next (j)">Next &rsaquo;</button>';
480
549
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">&#9432;</button>';
481
550
  tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFullscreen()" title="Fullscreen (f)">&#9634;</button>';
@@ -484,6 +553,14 @@ class RetoldRemoteMediaViewerView extends libPictView
484
553
  // Body with media content
485
554
  tmpHTML += '<div class="retold-remote-viewer-body">';
486
555
 
556
+ // Distraction-free toggle (top-left corner)
557
+ let tmpDFActive = tmpRemote._distractionFreeMode ? ' active' : '';
558
+ tmpHTML += '<button class="retold-remote-df-toggle' + tmpDFActive + '" id="RetoldRemote-DF-Toggle" onclick="pict.views[\'RetoldRemote-MediaViewer\'].toggleDistractionFree()" title="Distraction-Free (d)">&#9673; DF</button>';
559
+
560
+ // Exit hotspot for distraction-free mode (double-click/tap top-left to exit)
561
+ let tmpDFHotspotDisplay = tmpRemote._distractionFreeMode ? '' : ' style="display:none"';
562
+ tmpHTML += '<div class="retold-remote-df-exit-hotspot" id="RetoldRemote-DF-ExitHotspot"' + tmpDFHotspotDisplay + '></div>';
563
+
487
564
  switch (pMediaType)
488
565
  {
489
566
  case 'image':
@@ -538,6 +615,12 @@ class RetoldRemoteMediaViewerView extends libPictView
538
615
  }
539
616
  }
540
617
 
618
+ // Set up swipe navigation for touch devices
619
+ this._setupSwipeNavigation();
620
+
621
+ // Set up distraction-free exit hotspot (double-click/tap top-left)
622
+ this._setupDFExitHotspot();
623
+
541
624
  // Update topbar
542
625
  let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
543
626
  if (tmpTopBar)
@@ -546,9 +629,224 @@ class RetoldRemoteMediaViewerView extends libPictView
546
629
  }
547
630
  }
548
631
 
632
+ /**
633
+ * Attach touch event listeners to the viewer body for swipe-based
634
+ * prev/next navigation. Only single-finger horizontal swipes are
635
+ * recognised; pinch gestures and vertical scrolling are ignored.
636
+ * Swipes are also suppressed when the image is zoomed and the
637
+ * container is scrollable, so the user can pan freely.
638
+ */
639
+ _setupSwipeNavigation()
640
+ {
641
+ // Clean up any previous listeners
642
+ this._cleanupSwipe();
643
+
644
+ let tmpBody = document.querySelector('.retold-remote-viewer-body');
645
+ if (!tmpBody)
646
+ {
647
+ return;
648
+ }
649
+
650
+ let tmpSelf = this;
651
+ let tmpSwipeThreshold = 50; // minimum px to count as a swipe
652
+
653
+ let tmpOnTouchStart = function (pEvent)
654
+ {
655
+ tmpSelf._swipeTouchCount = pEvent.touches.length;
656
+ if (pEvent.touches.length !== 1)
657
+ {
658
+ return;
659
+ }
660
+ tmpSelf._swipeStartX = pEvent.touches[0].clientX;
661
+ tmpSelf._swipeStartY = pEvent.touches[0].clientY;
662
+ };
663
+
664
+ let tmpOnTouchEnd = function (pEvent)
665
+ {
666
+ // Ignore multi-touch (pinch zoom, etc.)
667
+ if (tmpSelf._swipeTouchCount !== 1)
668
+ {
669
+ return;
670
+ }
671
+
672
+ let tmpEndX = pEvent.changedTouches[0].clientX;
673
+ let tmpEndY = pEvent.changedTouches[0].clientY;
674
+ let tmpDeltaX = tmpEndX - tmpSelf._swipeStartX;
675
+ let tmpDeltaY = tmpEndY - tmpSelf._swipeStartY;
676
+
677
+ // Must be primarily horizontal
678
+ if (Math.abs(tmpDeltaX) < tmpSwipeThreshold || Math.abs(tmpDeltaY) > Math.abs(tmpDeltaX))
679
+ {
680
+ return;
681
+ }
682
+
683
+ // If the viewer body is scrollable (zoomed image), don't
684
+ // swipe — let the user pan instead.
685
+ let tmpContainer = document.querySelector('.retold-remote-viewer-body');
686
+ if (tmpContainer && (tmpContainer.scrollWidth > tmpContainer.clientWidth + 2))
687
+ {
688
+ return;
689
+ }
690
+
691
+ let tmpNav = tmpSelf.pict.providers['RetoldRemote-GalleryNavigation'];
692
+ if (!tmpNav)
693
+ {
694
+ return;
695
+ }
696
+
697
+ if (tmpDeltaX < 0)
698
+ {
699
+ tmpNav.nextFile();
700
+ }
701
+ else
702
+ {
703
+ tmpNav.prevFile();
704
+ }
705
+ };
706
+
707
+ tmpBody.addEventListener('touchstart', tmpOnTouchStart, { passive: true });
708
+ tmpBody.addEventListener('touchend', tmpOnTouchEnd, { passive: true });
709
+
710
+ this._swipeHandlers =
711
+ {
712
+ element: tmpBody,
713
+ touchstart: tmpOnTouchStart,
714
+ touchend: tmpOnTouchEnd
715
+ };
716
+ }
717
+
718
+ /**
719
+ * Remove swipe touch listeners from the viewer body.
720
+ */
721
+ _cleanupSwipe()
722
+ {
723
+ if (this._swipeHandlers)
724
+ {
725
+ this._swipeHandlers.element.removeEventListener('touchstart', this._swipeHandlers.touchstart);
726
+ this._swipeHandlers.element.removeEventListener('touchend', this._swipeHandlers.touchend);
727
+ this._swipeHandlers = null;
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Toggle distraction-free mode from the viewer overlay button.
733
+ * Delegates to the gallery navigation provider, then updates
734
+ * the toggle button and exit hotspot state.
735
+ */
736
+ toggleDistractionFree()
737
+ {
738
+ let tmpNav = this.pict.providers['RetoldRemote-GalleryNavigation'];
739
+ if (tmpNav)
740
+ {
741
+ tmpNav._toggleDistractionFree();
742
+ }
743
+ this._updateDFControls();
744
+ }
745
+
746
+ /**
747
+ * Sync the distraction-free toggle button and exit hotspot
748
+ * with the current mode state.
749
+ */
750
+ _updateDFControls()
751
+ {
752
+ let tmpRemote = this.pict.AppData.RetoldRemote;
753
+ let tmpActive = tmpRemote._distractionFreeMode || false;
754
+
755
+ let tmpToggle = document.getElementById('RetoldRemote-DF-Toggle');
756
+ if (tmpToggle)
757
+ {
758
+ if (tmpActive)
759
+ {
760
+ tmpToggle.classList.add('active');
761
+ }
762
+ else
763
+ {
764
+ tmpToggle.classList.remove('active');
765
+ }
766
+ }
767
+
768
+ let tmpHotspot = document.getElementById('RetoldRemote-DF-ExitHotspot');
769
+ if (tmpHotspot)
770
+ {
771
+ tmpHotspot.style.display = tmpActive ? '' : 'none';
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Set up the double-click / double-tap handler on the
777
+ * exit hotspot so the user can leave distraction-free mode
778
+ * from the top-left corner of the viewer.
779
+ */
780
+ _setupDFExitHotspot()
781
+ {
782
+ this._cleanupDFExitHotspot();
783
+
784
+ let tmpHotspot = document.getElementById('RetoldRemote-DF-ExitHotspot');
785
+ if (!tmpHotspot)
786
+ {
787
+ return;
788
+ }
789
+
790
+ let tmpSelf = this;
791
+
792
+ // Double-click for mouse
793
+ let tmpOnDblClick = function ()
794
+ {
795
+ let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
796
+ if (tmpRemote._distractionFreeMode)
797
+ {
798
+ tmpSelf.toggleDistractionFree();
799
+ }
800
+ };
801
+
802
+ // Double-tap for touch: detect two taps within 300ms
803
+ let tmpLastTap = 0;
804
+ let tmpOnTouchEnd = function (pEvent)
805
+ {
806
+ let tmpNow = Date.now();
807
+ if (tmpNow - tmpLastTap < 300)
808
+ {
809
+ pEvent.preventDefault();
810
+ let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
811
+ if (tmpRemote._distractionFreeMode)
812
+ {
813
+ tmpSelf.toggleDistractionFree();
814
+ }
815
+ tmpLastTap = 0;
816
+ }
817
+ else
818
+ {
819
+ tmpLastTap = tmpNow;
820
+ }
821
+ };
822
+
823
+ tmpHotspot.addEventListener('dblclick', tmpOnDblClick);
824
+ tmpHotspot.addEventListener('touchend', tmpOnTouchEnd);
825
+
826
+ this._dfExitHandlers =
827
+ {
828
+ element: tmpHotspot,
829
+ dblclick: tmpOnDblClick,
830
+ touchend: tmpOnTouchEnd
831
+ };
832
+ }
833
+
834
+ /**
835
+ * Remove exit hotspot event listeners.
836
+ */
837
+ _cleanupDFExitHotspot()
838
+ {
839
+ if (this._dfExitHandlers)
840
+ {
841
+ this._dfExitHandlers.element.removeEventListener('dblclick', this._dfExitHandlers.dblclick);
842
+ this._dfExitHandlers.element.removeEventListener('touchend', this._dfExitHandlers.touchend);
843
+ this._dfExitHandlers = null;
844
+ }
845
+ }
846
+
549
847
  _buildImageHTML(pURL, pFileName)
550
848
  {
551
- return '<img src="' + pURL + '" alt="' + this._escapeHTML(pFileName) + '" '
849
+ return '<img src="' + pURL + '" alt="' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName) + '" '
552
850
  + 'style="max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in;" '
553
851
  + 'id="RetoldRemote-ImageViewer-Img" '
554
852
  + 'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '
@@ -563,7 +861,7 @@ class RetoldRemoteMediaViewerView extends libPictView
563
861
 
564
862
  // Build the action menu (shown by default instead of the player)
565
863
  let tmpHTML = '<div class="retold-remote-video-action-menu" id="RetoldRemote-VideoActionMenu">';
566
- tmpHTML += '<div class="retold-remote-video-action-menu-title">' + this._escapeHTML(pFileName) + '</div>';
864
+ tmpHTML += '<div class="retold-remote-video-action-menu-title">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName) + '</div>';
567
865
 
568
866
  // Frame preview container (loaded on demand via t key or automatically)
569
867
  if (tmpCapabilities.ffmpeg)
@@ -603,16 +901,13 @@ class RetoldRemoteMediaViewerView extends libPictView
603
901
  + '</button>';
604
902
  }
605
903
 
606
- // VLC option (v)
607
- if (tmpCapabilities.vlc)
608
- {
609
- tmpHTML += '<button class="retold-remote-video-action-btn" '
610
- + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
611
- + 'title="Open with VLC media player">'
612
- + '<span class="retold-remote-video-action-key">v</span>'
613
- + 'Open with VLC'
614
- + '</button>';
615
- }
904
+ // VLC streaming option (v) — always available, streams from server to client VLC
905
+ tmpHTML += '<button class="retold-remote-video-action-btn" '
906
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
907
+ + 'title="Stream to VLC on this device">'
908
+ + '<span class="retold-remote-video-action-key">v</span>'
909
+ + 'Stream with VLC'
910
+ + '</button>';
616
911
 
617
912
  tmpHTML += '</div>';
618
913
 
@@ -655,14 +950,11 @@ class RetoldRemoteMediaViewerView extends libPictView
655
950
  + '</button>';
656
951
  }
657
952
 
658
- if (tmpCapabilities.vlc)
659
- {
660
- tmpHTML += '<button class="retold-remote-vlc-btn" '
661
- + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
662
- + 'title="Open with VLC">'
663
- + 'Open with VLC'
664
- + '</button>';
665
- }
953
+ tmpHTML += '<button class="retold-remote-vlc-btn" '
954
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
955
+ + 'title="Stream to VLC on this device">'
956
+ + 'Stream with VLC'
957
+ + '</button>';
666
958
 
667
959
  tmpHTML += '</div>'; // end stats
668
960
  tmpHTML += '</div>'; // end wrap
@@ -709,7 +1001,7 @@ class RetoldRemoteMediaViewerView extends libPictView
709
1001
  let tmpFrame = pData.Frames[0];
710
1002
  let tmpFrameURL = '/api/media/video-frame/' + pData.CacheKey + '/' + tmpFrame.Filename;
711
1003
  tmpWrap.innerHTML = '<img src="' + tmpFrameURL + '" '
712
- + 'alt="' + this._escapeHTML(tmpFilePath.replace(/^.*\//, '')) + '" '
1004
+ + 'alt="' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFilePath.replace(/^.*\//, '')) + '" '
713
1005
  + 'onerror="this.parentNode.innerHTML=\'\'">';
714
1006
  }
715
1007
  else
@@ -731,25 +1023,35 @@ class RetoldRemoteMediaViewerView extends libPictView
731
1023
 
732
1024
  let tmpHTML = '<div style="text-align: center; padding: 40px;">'
733
1025
  + '<div style="margin-bottom: 24px;">' + tmpIconHTML + '</div>'
734
- + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
1026
+ + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName) + '</div>'
735
1027
  + '<audio controls' + (this.pict.AppData.RetoldRemote.AutoplayAudio ? ' autoplay' : '') + ' preload="metadata" id="RetoldRemote-AudioPlayer" style="width: 100%; max-width: 500px;">'
736
1028
  + '<source src="' + pURL + '">'
737
1029
  + 'Your browser does not support the audio tag.'
738
1030
  + '</audio>';
739
1031
 
1032
+ // Action buttons below the player
1033
+ tmpHTML += '<div style="margin-top: 20px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">';
1034
+
740
1035
  // Explore Audio button (available when ffprobe is present)
741
1036
  let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
742
1037
  if (tmpCapabilities.ffprobe || tmpCapabilities.ffmpeg)
743
1038
  {
744
- tmpHTML += '<div style="margin-top: 20px;">'
745
- + '<button class="retold-remote-explore-btn" '
1039
+ tmpHTML += '<button class="retold-remote-explore-btn" '
746
1040
  + 'onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
747
1041
  + 'title="Explore waveform and extract segments from this audio">'
748
- + '&#128202; Explore Audio'
749
- + '</button>'
750
- + '</div>';
1042
+ + 'Explore Audio'
1043
+ + '</button>';
751
1044
  }
752
1045
 
1046
+ // Stream with VLC
1047
+ tmpHTML += '<button class="retold-remote-vlc-btn" '
1048
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
1049
+ + 'title="Stream to VLC on this device (v)">'
1050
+ + 'Stream with VLC'
1051
+ + '</button>';
1052
+
1053
+ tmpHTML += '</div>';
1054
+
753
1055
  tmpHTML += '</div>';
754
1056
  return tmpHTML;
755
1057
  }
@@ -775,7 +1077,7 @@ class RetoldRemoteMediaViewerView extends libPictView
775
1077
  let tmpDocIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '&#128196;';
776
1078
  return '<div style="text-align: center; padding: 40px;">'
777
1079
  + '<div style="margin-bottom: 24px;">' + tmpDocIconHTML + '</div>'
778
- + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
1080
+ + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName) + '</div>'
779
1081
  + '<a href="' + pURL + '" target="_blank" style="color: var(--retold-accent); font-size: 0.9rem;">Open in new tab</a>'
780
1082
  + '</div>';
781
1083
  }
@@ -994,7 +1296,7 @@ class RetoldRemoteMediaViewerView extends libPictView
994
1296
  if (tmpEl)
995
1297
  {
996
1298
  tmpEl.innerHTML = '<div class="retold-remote-ebook-loading">Failed to convert: '
997
- + tmpSelf._escapeHTML(pError.message)
1299
+ + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pError.message)
998
1300
  + '<br><a href="' + pContentURL + '" target="_blank" style="color: var(--retold-accent); margin-top: 12px; display: inline-block;">Download file</a>'
999
1301
  + '</div>';
1000
1302
  }
@@ -1098,7 +1400,7 @@ class RetoldRemoteMediaViewerView extends libPictView
1098
1400
  if (tmpContentEl)
1099
1401
  {
1100
1402
  tmpContentEl.innerHTML = '<div class="retold-remote-ebook-loading">Failed to load ebook: '
1101
- + tmpSelf._escapeHTML(pError.message) + '</div>';
1403
+ + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pError.message) + '</div>';
1102
1404
  }
1103
1405
  });
1104
1406
  }
@@ -1126,9 +1428,9 @@ class RetoldRemoteMediaViewerView extends libPictView
1126
1428
  let tmpItem = pItems[i];
1127
1429
  let tmpIndentClass = pDepth > 0 ? ' indent-' + Math.min(pDepth, 2) : '';
1128
1430
  tmpHTML += '<button class="retold-remote-ebook-toc-item' + tmpIndentClass + '" '
1129
- + 'data-href="' + tmpSelf._escapeHTML(tmpItem.href) + '" '
1431
+ + 'data-href="' + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpItem.href) + '" '
1130
1432
  + 'onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookGoToChapter(this.getAttribute(\'data-href\'))">'
1131
- + tmpSelf._escapeHTML(tmpItem.label.trim())
1433
+ + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpItem.label.trim())
1132
1434
  + '</button>';
1133
1435
 
1134
1436
  if (tmpItem.subitems && tmpItem.subitems.length > 0)
@@ -1195,7 +1497,7 @@ class RetoldRemoteMediaViewerView extends libPictView
1195
1497
  let tmpFallbackIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '&#128196;';
1196
1498
  return '<div style="text-align: center; padding: 40px;">'
1197
1499
  + '<div style="margin-bottom: 24px;">' + tmpFallbackIconHTML + '</div>'
1198
- + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
1500
+ + '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName) + '</div>'
1199
1501
  + '<a href="' + pURL + '" target="_blank" style="color: var(--retold-accent); font-size: 0.9rem;">Download / Open in new tab</a>'
1200
1502
  + '</div>';
1201
1503
  }
@@ -1228,7 +1530,7 @@ class RetoldRemoteMediaViewerView extends libPictView
1228
1530
 
1229
1531
  if (pData.Size !== undefined)
1230
1532
  {
1231
- tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Size</span><span class="retold-remote-fileinfo-value">' + tmpSelf._formatFileSize(pData.Size) + '</span></div>';
1533
+ tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Size</span><span class="retold-remote-fileinfo-value">' + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].formatFileSize(pData.Size) + '</span></div>';
1232
1534
  }
1233
1535
  if (pData.Width && pData.Height)
1234
1536
  {
@@ -1300,7 +1602,7 @@ class RetoldRemoteMediaViewerView extends libPictView
1300
1602
  }
1301
1603
  if (pData.Size !== undefined)
1302
1604
  {
1303
- tmpStatsHTML += '<span><span class="retold-remote-video-stat-label">Size</span> <span class="retold-remote-video-stat-value">' + tmpSelf._formatFileSize(pData.Size) + '</span></span>';
1605
+ tmpStatsHTML += '<span><span class="retold-remote-video-stat-label">Size</span> <span class="retold-remote-video-stat-value">' + tmpSelf.pict.providers['RetoldRemote-FormattingUtilities'].formatFileSize(pData.Size) + '</span></span>';
1304
1606
  }
1305
1607
 
1306
1608
  // Preserve the Explore and VLC buttons if they exist
@@ -1314,21 +1616,6 @@ class RetoldRemoteMediaViewerView extends libPictView
1314
1616
  });
1315
1617
  }
1316
1618
 
1317
- _formatFileSize(pBytes)
1318
- {
1319
- if (!pBytes || pBytes === 0) return '0 B';
1320
- let tmpUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
1321
- let tmpIndex = Math.floor(Math.log(pBytes) / Math.log(1024));
1322
- if (tmpIndex >= tmpUnits.length) tmpIndex = tmpUnits.length - 1;
1323
- let tmpSize = pBytes / Math.pow(1024, tmpIndex);
1324
- return tmpSize.toFixed(tmpIndex === 0 ? 0 : 1) + ' ' + tmpUnits[tmpIndex];
1325
- }
1326
-
1327
- _escapeHTML(pText)
1328
- {
1329
- if (!pText) return '';
1330
- return pText.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1331
- }
1332
1619
  }
1333
1620
 
1334
1621
  RetoldRemoteMediaViewerView.default_configuration = _ViewConfiguration;