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.
- package/docs/README.md +181 -0
- package/docs/_cover.md +14 -0
- package/docs/_sidebar.md +10 -0
- package/docs/_topbar.md +3 -0
- package/docs/audio-viewer.md +133 -0
- package/docs/ebook-reader.md +90 -0
- package/docs/image-viewer.md +90 -0
- package/docs/server-setup.md +262 -0
- package/docs/video-viewer.md +134 -0
- package/html/docs.html +59 -0
- package/package.json +21 -7
- package/source/Pict-Application-RetoldRemote.js +143 -2
- package/source/RetoldRemote-ExtensionMaps.js +33 -0
- package/source/cli/RetoldRemote-Server-Setup.js +82 -67
- package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
- package/source/providers/Pict-Provider-CollectionManager.js +934 -0
- package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
- package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
- package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
- package/source/providers/Pict-Provider-ToastNotification.js +96 -0
- package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
- package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
- package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
- package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
- package/source/server/RetoldRemote-ArchiveService.js +2 -12
- package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
- package/source/server/RetoldRemote-CollectionService.js +684 -0
- package/source/server/RetoldRemote-EbookService.js +7 -16
- package/source/server/RetoldRemote-MediaService.js +3 -14
- package/source/server/RetoldRemote-ParimeCache.js +349 -0
- package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
- package/source/server/RetoldRemote-VideoFrameService.js +7 -15
- package/source/views/PictView-Remote-AudioExplorer.js +10 -43
- package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
- package/source/views/PictView-Remote-Gallery.js +237 -44
- package/source/views/PictView-Remote-ImageViewer.js +1 -34
- package/source/views/PictView-Remote-Layout.js +410 -20
- package/source/views/PictView-Remote-MediaViewer.js +338 -51
- package/source/views/PictView-Remote-SettingsPanel.js +155 -138
- package/source/views/PictView-Remote-TopBar.js +615 -14
- package/source/views/PictView-Remote-VLCSetup.js +766 -0
- package/source/views/PictView-Remote-VideoExplorer.js +20 -54
- package/web-application/css/docuserve.css +73 -0
- package/web-application/docs/README.md +181 -0
- package/web-application/docs/_cover.md +14 -0
- package/web-application/docs/_sidebar.md +10 -0
- package/web-application/docs/_topbar.md +3 -0
- package/web-application/docs/audio-viewer.md +133 -0
- package/web-application/docs/ebook-reader.md +90 -0
- package/web-application/docs/image-viewer.md +90 -0
- package/web-application/docs/server-setup.md +262 -0
- package/web-application/docs/video-viewer.md +134 -0
- package/web-application/docs.html +59 -0
- package/web-application/js/pict-docuserve.min.js +58 -0
- 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 +2558 -439
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +41 -11
- package/web-application/retold-remote.min.js.map +1 -1
- 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)">← Back</button>';
|
|
477
546
|
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">‹ Prev</button>';
|
|
478
|
-
tmpHTML += '<div class="retold-remote-viewer-title">' + this.
|
|
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 ›</button>';
|
|
480
549
|
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">ⓘ</button>';
|
|
481
550
|
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFullscreen()" title="Fullscreen (f)">▢</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)">◉ 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.
|
|
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.
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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.
|
|
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.
|
|
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 += '<
|
|
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
|
-
+ '
|
|
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>' : '📄';
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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>' : '📄';
|
|
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.
|
|
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.
|
|
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.
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1331
|
-
}
|
|
1332
1619
|
}
|
|
1333
1620
|
|
|
1334
1621
|
RetoldRemoteMediaViewerView.default_configuration = _ViewConfiguration;
|