retold-remote 0.0.2 → 0.0.4
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/package.json +4 -4
- package/source/Pict-Application-RetoldRemote.js +3 -1
- package/source/providers/Pict-Provider-GalleryFilterSort.js +12 -1
- package/source/providers/Pict-Provider-GalleryNavigation.js +68 -0
- package/source/server/RetoldRemote-MediaService.js +1 -1
- package/source/server/RetoldRemote-VideoFrameService.js +2 -1
- package/source/views/PictView-Remote-MediaViewer.js +216 -9
- package/source/views/PictView-Remote-SettingsPanel.js +123 -18
- 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 +3070 -2837
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +4 -4
- package/web-application/retold-remote.min.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-remote",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Retold Remote - NAS media browser with gallery views and keyboard navigation",
|
|
5
5
|
"main": "source/Pict-RetoldRemote-Bundle.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
"fable-serviceproviderbase": "^3.0.19",
|
|
31
31
|
"orator": "^6.0.3",
|
|
32
32
|
"orator-serviceserver-restify": "^2.0.9",
|
|
33
|
-
"pict": "^1.0.
|
|
33
|
+
"pict": "^1.0.356",
|
|
34
34
|
"pict-application": "^1.0.33",
|
|
35
35
|
"pict-provider": "^1.0.12",
|
|
36
36
|
"pict-section-code": "^1.0.3",
|
|
37
37
|
"pict-section-filebrowser": "^0.0.2",
|
|
38
38
|
"pict-service-commandlineutility": "^1.0.19",
|
|
39
39
|
"pict-view": "^1.0.67",
|
|
40
|
-
"retold-content-system": "^1.0.
|
|
40
|
+
"retold-content-system": "^1.0.4",
|
|
41
41
|
"yauzl": "^3.2.0"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"sharp": "^0.
|
|
44
|
+
"sharp": "^0.34.5"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"quackage": "^1.0.59"
|
|
@@ -78,6 +78,7 @@ class RetoldRemoteApplication extends libContentEditorApplication
|
|
|
78
78
|
RawFileList: [], // Unfiltered server response
|
|
79
79
|
GalleryItems: [], // Filtered+sorted file list (single source of truth)
|
|
80
80
|
GalleryCursorIndex: 0, // Currently highlighted item
|
|
81
|
+
FolderCursorHistory: {}, // Map of folder path -> last cursor index
|
|
81
82
|
GalleryFilter: 'all', // 'all', 'images', 'video', 'audio', 'documents'
|
|
82
83
|
SearchQuery: '',
|
|
83
84
|
SearchCaseSensitive: false,
|
|
@@ -467,7 +468,8 @@ class RetoldRemoteApplication extends libContentEditorApplication
|
|
|
467
468
|
{
|
|
468
469
|
// Fallback if provider not ready
|
|
469
470
|
tmpRemote.GalleryItems = pFileList || [];
|
|
470
|
-
tmpRemote.
|
|
471
|
+
let tmpSavedIndex = tmpRemote.FolderCursorHistory && tmpRemote.FolderCursorHistory[tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation || ''];
|
|
472
|
+
tmpRemote.GalleryCursorIndex = (typeof tmpSavedIndex === 'number' && tmpSavedIndex < (pFileList || []).length) ? tmpSavedIndex : 0;
|
|
471
473
|
let tmpGalleryView = tmpSelf.pict.views['RetoldRemote-Gallery'];
|
|
472
474
|
if (tmpGalleryView)
|
|
473
475
|
{
|
|
@@ -60,7 +60,18 @@ class GalleryFilterSortProvider extends libPictProvider
|
|
|
60
60
|
|
|
61
61
|
// Write result
|
|
62
62
|
tmpRemote.GalleryItems = tmpItems;
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
// Restore cursor position if we have a saved one for this folder
|
|
65
|
+
let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
|
|
66
|
+
let tmpSavedIndex = tmpRemote.FolderCursorHistory && tmpRemote.FolderCursorHistory[tmpCurrentLocation];
|
|
67
|
+
if (typeof tmpSavedIndex === 'number' && tmpSavedIndex < tmpItems.length)
|
|
68
|
+
{
|
|
69
|
+
tmpRemote.GalleryCursorIndex = tmpSavedIndex;
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
tmpRemote.GalleryCursorIndex = 0;
|
|
74
|
+
}
|
|
64
75
|
|
|
65
76
|
// Re-render gallery
|
|
66
77
|
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
@@ -415,6 +415,66 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
415
415
|
*/
|
|
416
416
|
_handleViewerKey(pEvent)
|
|
417
417
|
{
|
|
418
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
419
|
+
|
|
420
|
+
// Video action menu mode — intercept keys for menu options
|
|
421
|
+
if (tmpRemote.VideoMenuActive && tmpRemote.CurrentViewerMediaType === 'video')
|
|
422
|
+
{
|
|
423
|
+
switch (pEvent.key)
|
|
424
|
+
{
|
|
425
|
+
case 'Escape':
|
|
426
|
+
pEvent.preventDefault();
|
|
427
|
+
this.closeViewer();
|
|
428
|
+
return;
|
|
429
|
+
|
|
430
|
+
case 'ArrowRight':
|
|
431
|
+
case 'j':
|
|
432
|
+
pEvent.preventDefault();
|
|
433
|
+
this.nextFile();
|
|
434
|
+
return;
|
|
435
|
+
|
|
436
|
+
case 'ArrowLeft':
|
|
437
|
+
case 'k':
|
|
438
|
+
pEvent.preventDefault();
|
|
439
|
+
this.prevFile();
|
|
440
|
+
return;
|
|
441
|
+
|
|
442
|
+
case 'e':
|
|
443
|
+
pEvent.preventDefault();
|
|
444
|
+
let tmpVEX = this.pict.views['RetoldRemote-VideoExplorer'];
|
|
445
|
+
if (tmpVEX)
|
|
446
|
+
{
|
|
447
|
+
tmpVEX.showExplorer(tmpRemote.CurrentViewerFile);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
|
|
451
|
+
case ' ':
|
|
452
|
+
case 'Enter':
|
|
453
|
+
pEvent.preventDefault();
|
|
454
|
+
let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
455
|
+
if (tmpViewer)
|
|
456
|
+
{
|
|
457
|
+
tmpViewer.playVideo();
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
|
|
461
|
+
case 't':
|
|
462
|
+
pEvent.preventDefault();
|
|
463
|
+
let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
464
|
+
if (tmpMediaViewer)
|
|
465
|
+
{
|
|
466
|
+
tmpMediaViewer.loadVideoMenuFrame();
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
|
|
470
|
+
case 'v':
|
|
471
|
+
pEvent.preventDefault();
|
|
472
|
+
this._openWithVLC();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
418
478
|
switch (pEvent.key)
|
|
419
479
|
{
|
|
420
480
|
case 'Escape':
|
|
@@ -601,6 +661,10 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
601
661
|
|
|
602
662
|
if (tmpItem.Type === 'folder' || tmpItem.Type === 'archive')
|
|
603
663
|
{
|
|
664
|
+
// Remember cursor position in the current folder before navigating away
|
|
665
|
+
let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
|
|
666
|
+
tmpRemote.FolderCursorHistory[tmpCurrentLocation] = tmpIndex;
|
|
667
|
+
|
|
604
668
|
// Navigate into the folder or archive
|
|
605
669
|
let tmpApp = this.pict.PictApplication;
|
|
606
670
|
if (tmpApp && tmpApp.loadFileList)
|
|
@@ -631,6 +695,10 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
631
695
|
return;
|
|
632
696
|
}
|
|
633
697
|
|
|
698
|
+
// Remember cursor position in the current folder before navigating away
|
|
699
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
700
|
+
tmpRemote.FolderCursorHistory[tmpCurrentLocation] = tmpRemote.GalleryCursorIndex || 0;
|
|
701
|
+
|
|
634
702
|
let tmpParent = tmpCurrentLocation.indexOf('/') >= 0
|
|
635
703
|
? tmpCurrentLocation.replace(/\/[^/]+\/?$/, '')
|
|
636
704
|
: '';
|
|
@@ -475,7 +475,7 @@ class RetoldRemoteMediaService extends libFableServiceProviderBase
|
|
|
475
475
|
{
|
|
476
476
|
let tmpOutputFormat = pFormat === 'webp' ? 'webp' : 'mjpeg';
|
|
477
477
|
// Extract a frame at 10% into the video
|
|
478
|
-
let tmpCmd = `ffmpeg -ss 00:00:02 -i "${pFullPath}" -vframes 1 -vf "scale=${pWidth}:${pHeight}:force_original_aspect_ratio=decrease" -f
|
|
478
|
+
let tmpCmd = `ffmpeg -ss 00:00:02 -i "${pFullPath}" -vframes 1 -vf "scale=${pWidth}:${pHeight}:force_original_aspect_ratio=decrease" -f ${tmpOutputFormat} pipe:1`;
|
|
479
479
|
let tmpBuffer = libChildProcess.execSync(tmpCmd, { maxBuffer: 10 * 1024 * 1024, timeout: 30000 });
|
|
480
480
|
return fCallback(null, tmpBuffer);
|
|
481
481
|
}
|
|
@@ -147,7 +147,8 @@ class RetoldRemoteVideoFrameService extends libFableServiceProviderBase
|
|
|
147
147
|
|
|
148
148
|
let tmpCodec = (pFormat === 'png') ? 'png' : (pFormat === 'webp') ? 'webp' : 'mjpeg';
|
|
149
149
|
|
|
150
|
-
let
|
|
150
|
+
let tmpMuxer = (pFormat === 'png') ? 'image2' : (pFormat === 'webp') ? 'webp' : 'mjpeg';
|
|
151
|
+
let tmpCmd = `ffmpeg -ss ${tmpTimeStr} -i "${pAbsPath}" -vframes 1 -vf "scale=${pWidth}:${pHeight}:force_original_aspect_ratio=decrease" -f ${tmpMuxer} -y "${pOutputPath}"`;
|
|
151
152
|
libChildProcess.execSync(tmpCmd, { stdio: 'ignore', timeout: 30000 });
|
|
152
153
|
return libFs.existsSync(pOutputPath);
|
|
153
154
|
}
|
|
@@ -232,6 +232,85 @@ const _ViewConfiguration =
|
|
|
232
232
|
background: var(--retold-accent);
|
|
233
233
|
color: var(--retold-bg-primary);
|
|
234
234
|
}
|
|
235
|
+
/* Video action menu */
|
|
236
|
+
.retold-remote-video-action-menu
|
|
237
|
+
{
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: center;
|
|
242
|
+
gap: 12px;
|
|
243
|
+
width: 100%;
|
|
244
|
+
height: 100%;
|
|
245
|
+
}
|
|
246
|
+
.retold-remote-video-action-menu-title
|
|
247
|
+
{
|
|
248
|
+
font-size: 0.85rem;
|
|
249
|
+
color: var(--retold-text-secondary);
|
|
250
|
+
margin-bottom: 4px;
|
|
251
|
+
text-align: center;
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
text-overflow: ellipsis;
|
|
254
|
+
max-width: 80%;
|
|
255
|
+
}
|
|
256
|
+
.retold-remote-video-action-thumb-wrap
|
|
257
|
+
{
|
|
258
|
+
margin-bottom: 4px;
|
|
259
|
+
text-align: center;
|
|
260
|
+
}
|
|
261
|
+
.retold-remote-video-action-thumb-wrap img
|
|
262
|
+
{
|
|
263
|
+
max-width: 640px;
|
|
264
|
+
max-height: 360px;
|
|
265
|
+
border-radius: 6px;
|
|
266
|
+
border: 1px solid var(--retold-border);
|
|
267
|
+
object-fit: contain;
|
|
268
|
+
background: var(--retold-bg-primary);
|
|
269
|
+
}
|
|
270
|
+
.retold-remote-video-action-thumb-wrap .retold-remote-video-action-thumb-loading
|
|
271
|
+
{
|
|
272
|
+
color: var(--retold-text-dim);
|
|
273
|
+
font-size: 0.78rem;
|
|
274
|
+
font-style: italic;
|
|
275
|
+
padding: 8px;
|
|
276
|
+
}
|
|
277
|
+
.retold-remote-video-action-btn
|
|
278
|
+
{
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: 12px;
|
|
282
|
+
padding: 12px 24px;
|
|
283
|
+
min-width: 280px;
|
|
284
|
+
border: 1px solid var(--retold-border);
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
background: var(--retold-bg-secondary);
|
|
287
|
+
color: var(--retold-text-secondary);
|
|
288
|
+
font-size: 0.85rem;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
291
|
+
font-family: inherit;
|
|
292
|
+
text-align: left;
|
|
293
|
+
}
|
|
294
|
+
.retold-remote-video-action-btn:hover,
|
|
295
|
+
.retold-remote-video-action-btn.selected
|
|
296
|
+
{
|
|
297
|
+
border-color: var(--retold-accent);
|
|
298
|
+
color: var(--retold-text-primary);
|
|
299
|
+
background: var(--retold-bg-tertiary);
|
|
300
|
+
}
|
|
301
|
+
.retold-remote-video-action-key
|
|
302
|
+
{
|
|
303
|
+
display: inline-block;
|
|
304
|
+
padding: 2px 8px;
|
|
305
|
+
border: 1px solid var(--retold-border);
|
|
306
|
+
border-radius: 3px;
|
|
307
|
+
background: var(--retold-bg-primary);
|
|
308
|
+
color: var(--retold-text-dim);
|
|
309
|
+
font-size: 0.72rem;
|
|
310
|
+
font-family: var(--retold-font-mono, monospace);
|
|
311
|
+
min-width: 24px;
|
|
312
|
+
text-align: center;
|
|
313
|
+
}
|
|
235
314
|
/* Ebook reader */
|
|
236
315
|
.retold-remote-ebook-wrap
|
|
237
316
|
{
|
|
@@ -376,6 +455,7 @@ class RetoldRemoteMediaViewerView extends libPictView
|
|
|
376
455
|
tmpRemote.ActiveMode = 'viewer';
|
|
377
456
|
tmpRemote.CurrentViewerFile = pFilePath;
|
|
378
457
|
tmpRemote.CurrentViewerMediaType = pMediaType;
|
|
458
|
+
tmpRemote.VideoMenuActive = (pMediaType === 'video');
|
|
379
459
|
|
|
380
460
|
// Show viewer, hide gallery
|
|
381
461
|
let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
|
|
@@ -477,12 +557,88 @@ class RetoldRemoteMediaViewerView extends libPictView
|
|
|
477
557
|
|
|
478
558
|
_buildVideoHTML(pURL, pFileName)
|
|
479
559
|
{
|
|
560
|
+
let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
|
|
561
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
562
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
563
|
+
|
|
564
|
+
// Build the action menu (shown by default instead of the player)
|
|
565
|
+
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>';
|
|
567
|
+
|
|
568
|
+
// Frame preview container (loaded on demand via t key or automatically)
|
|
569
|
+
if (tmpCapabilities.ffmpeg)
|
|
570
|
+
{
|
|
571
|
+
tmpHTML += '<div id="RetoldRemote-VideoActionThumb" class="retold-remote-video-action-thumb-wrap"></div>';
|
|
572
|
+
// Kick off frame extraction automatically
|
|
573
|
+
setTimeout(() => { this.loadVideoMenuFrame(); }, 0);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Explore option (e)
|
|
577
|
+
if (tmpCapabilities.ffmpeg)
|
|
578
|
+
{
|
|
579
|
+
tmpHTML += '<button class="retold-remote-video-action-btn" '
|
|
580
|
+
+ 'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
|
|
581
|
+
+ 'title="Explore frames from this video">'
|
|
582
|
+
+ '<span class="retold-remote-video-action-key">e</span>'
|
|
583
|
+
+ 'Explore Video Frames'
|
|
584
|
+
+ '</button>';
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Play option (space/enter)
|
|
588
|
+
tmpHTML += '<button class="retold-remote-video-action-btn selected" '
|
|
589
|
+
+ 'onclick="pict.views[\'RetoldRemote-MediaViewer\'].playVideo()" '
|
|
590
|
+
+ 'title="Play video in browser">'
|
|
591
|
+
+ '<span class="retold-remote-video-action-key">Space</span>'
|
|
592
|
+
+ 'Play in Browser'
|
|
593
|
+
+ '</button>';
|
|
594
|
+
|
|
595
|
+
// Thumbnail option (t)
|
|
596
|
+
if (tmpCapabilities.ffmpeg)
|
|
597
|
+
{
|
|
598
|
+
tmpHTML += '<button class="retold-remote-video-action-btn" '
|
|
599
|
+
+ 'onclick="pict.views[\'RetoldRemote-MediaViewer\'].loadVideoMenuFrame()" '
|
|
600
|
+
+ 'title="Extract a frame from the midpoint of this video">'
|
|
601
|
+
+ '<span class="retold-remote-video-action-key">t</span>'
|
|
602
|
+
+ 'Thumbnail'
|
|
603
|
+
+ '</button>';
|
|
604
|
+
}
|
|
605
|
+
|
|
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
|
+
}
|
|
616
|
+
|
|
617
|
+
tmpHTML += '</div>';
|
|
618
|
+
|
|
619
|
+
return tmpHTML;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Launch the in-browser video player (from the video action menu).
|
|
624
|
+
*/
|
|
625
|
+
playVideo()
|
|
626
|
+
{
|
|
627
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
628
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
629
|
+
if (!tmpFilePath) return;
|
|
630
|
+
|
|
631
|
+
let tmpFileName = tmpFilePath.replace(/^.*\//, '');
|
|
632
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
633
|
+
let tmpContentURL = tmpProvider ? tmpProvider.getContentURL(tmpFilePath) : ('/content/' + encodeURIComponent(tmpFilePath));
|
|
634
|
+
let tmpCapabilities = tmpRemote.ServerCapabilities || {};
|
|
635
|
+
|
|
480
636
|
let tmpHTML = '<div class="retold-remote-video-wrap">';
|
|
481
637
|
|
|
482
|
-
let tmpAutoplayVideo =
|
|
638
|
+
let tmpAutoplayVideo = tmpRemote.AutoplayVideo ? ' autoplay' : '';
|
|
483
639
|
tmpHTML += '<video controls' + tmpAutoplayVideo + ' preload="metadata" '
|
|
484
640
|
+ 'id="RetoldRemote-VideoPlayer">'
|
|
485
|
-
+ '<source src="' +
|
|
641
|
+
+ '<source src="' + tmpContentURL + '">'
|
|
486
642
|
+ 'Your browser does not support the video tag.'
|
|
487
643
|
+ '</video>';
|
|
488
644
|
|
|
@@ -490,31 +646,82 @@ class RetoldRemoteMediaViewerView extends libPictView
|
|
|
490
646
|
tmpHTML += '<div class="retold-remote-video-stats" id="RetoldRemote-VideoStats">';
|
|
491
647
|
tmpHTML += '<span class="retold-remote-video-stat-label">Loading info...</span>';
|
|
492
648
|
|
|
493
|
-
// Explore Video button (only when ffmpeg is available)
|
|
494
|
-
let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
|
|
495
649
|
if (tmpCapabilities.ffmpeg)
|
|
496
650
|
{
|
|
497
651
|
tmpHTML += '<button class="retold-remote-explore-btn" '
|
|
498
652
|
+ 'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
|
|
499
653
|
+ 'title="Explore frames from this video">'
|
|
500
|
-
+ '
|
|
654
|
+
+ 'Explore Video'
|
|
501
655
|
+ '</button>';
|
|
502
656
|
}
|
|
503
657
|
|
|
504
|
-
// VLC button (only shown when VLC capability is available)
|
|
505
658
|
if (tmpCapabilities.vlc)
|
|
506
659
|
{
|
|
507
660
|
tmpHTML += '<button class="retold-remote-vlc-btn" '
|
|
508
661
|
+ 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
|
|
509
|
-
+ 'title="Open with VLC
|
|
510
|
-
+ '
|
|
662
|
+
+ 'title="Open with VLC">'
|
|
663
|
+
+ 'Open with VLC'
|
|
511
664
|
+ '</button>';
|
|
512
665
|
}
|
|
513
666
|
|
|
514
667
|
tmpHTML += '</div>'; // end stats
|
|
515
668
|
tmpHTML += '</div>'; // end wrap
|
|
516
669
|
|
|
517
|
-
|
|
670
|
+
// Replace the action menu with the player
|
|
671
|
+
let tmpMenu = document.getElementById('RetoldRemote-VideoActionMenu');
|
|
672
|
+
if (tmpMenu)
|
|
673
|
+
{
|
|
674
|
+
tmpMenu.outerHTML = tmpHTML;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Mark that we are now in player mode (not menu mode)
|
|
678
|
+
tmpRemote.VideoMenuActive = false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Extract and display a single full-resolution frame from the midpoint of the current video.
|
|
683
|
+
*/
|
|
684
|
+
loadVideoMenuFrame()
|
|
685
|
+
{
|
|
686
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
687
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
688
|
+
if (!tmpFilePath) return;
|
|
689
|
+
|
|
690
|
+
let tmpThumbWrap = document.getElementById('RetoldRemote-VideoActionThumb');
|
|
691
|
+
if (!tmpThumbWrap) return;
|
|
692
|
+
|
|
693
|
+
tmpThumbWrap.innerHTML = '<div class="retold-remote-video-action-thumb-loading">Extracting frame...</div>';
|
|
694
|
+
|
|
695
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
696
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(tmpFilePath) : encodeURIComponent(tmpFilePath);
|
|
697
|
+
|
|
698
|
+
fetch('/api/media/video-frames?path=' + tmpPathParam + '&count=1')
|
|
699
|
+
.then((pResponse) => pResponse.json())
|
|
700
|
+
.then((pData) =>
|
|
701
|
+
{
|
|
702
|
+
// Verify we are still on the same file and still in the menu
|
|
703
|
+
if (tmpRemote.CurrentViewerFile !== tmpFilePath) return;
|
|
704
|
+
let tmpWrap = document.getElementById('RetoldRemote-VideoActionThumb');
|
|
705
|
+
if (!tmpWrap) return;
|
|
706
|
+
|
|
707
|
+
if (pData && pData.Frames && pData.Frames.length > 0)
|
|
708
|
+
{
|
|
709
|
+
let tmpFrame = pData.Frames[0];
|
|
710
|
+
let tmpFrameURL = '/api/media/video-frame/' + pData.CacheKey + '/' + tmpFrame.Filename;
|
|
711
|
+
tmpWrap.innerHTML = '<img src="' + tmpFrameURL + '" '
|
|
712
|
+
+ 'alt="' + this._escapeHTML(tmpFilePath.replace(/^.*\//, '')) + '" '
|
|
713
|
+
+ 'onerror="this.parentNode.innerHTML=\'\'">';
|
|
714
|
+
}
|
|
715
|
+
else
|
|
716
|
+
{
|
|
717
|
+
tmpWrap.innerHTML = '';
|
|
718
|
+
}
|
|
719
|
+
})
|
|
720
|
+
.catch(() =>
|
|
721
|
+
{
|
|
722
|
+
let tmpWrap = document.getElementById('RetoldRemote-VideoActionThumb');
|
|
723
|
+
if (tmpWrap) tmpWrap.innerHTML = '';
|
|
724
|
+
});
|
|
518
725
|
}
|
|
519
726
|
|
|
520
727
|
_buildAudioHTML(pURL, pFileName)
|
|
@@ -77,6 +77,44 @@ const _ViewConfiguration =
|
|
|
77
77
|
{
|
|
78
78
|
color: var(--retold-danger-muted);
|
|
79
79
|
}
|
|
80
|
+
.retold-remote-settings-shortcut-group
|
|
81
|
+
{
|
|
82
|
+
margin-bottom: 10px;
|
|
83
|
+
}
|
|
84
|
+
.retold-remote-settings-shortcut-group-title
|
|
85
|
+
{
|
|
86
|
+
font-size: 0.68rem;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
color: var(--retold-text-muted);
|
|
89
|
+
margin-bottom: 4px;
|
|
90
|
+
padding-bottom: 2px;
|
|
91
|
+
border-bottom: 1px solid var(--retold-border);
|
|
92
|
+
}
|
|
93
|
+
.retold-remote-settings-shortcut-row
|
|
94
|
+
{
|
|
95
|
+
display: flex;
|
|
96
|
+
justify-content: space-between;
|
|
97
|
+
align-items: center;
|
|
98
|
+
padding: 2px 0;
|
|
99
|
+
}
|
|
100
|
+
.retold-remote-settings-shortcut-desc
|
|
101
|
+
{
|
|
102
|
+
color: var(--retold-text-dim);
|
|
103
|
+
font-size: 0.72rem;
|
|
104
|
+
}
|
|
105
|
+
.retold-remote-settings-shortcut-key
|
|
106
|
+
{
|
|
107
|
+
display: inline-block;
|
|
108
|
+
padding: 1px 6px;
|
|
109
|
+
border: 1px solid var(--retold-border);
|
|
110
|
+
border-radius: 3px;
|
|
111
|
+
background: var(--retold-bg-primary);
|
|
112
|
+
color: var(--retold-accent);
|
|
113
|
+
font-size: 0.68rem;
|
|
114
|
+
font-family: var(--retold-font-mono, monospace);
|
|
115
|
+
min-width: 18px;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
80
118
|
`
|
|
81
119
|
};
|
|
82
120
|
|
|
@@ -241,27 +279,77 @@ class RetoldRemoteSettingsPanelView extends libPictView
|
|
|
241
279
|
tmpHTML += '<div class="retold-remote-settings-section">';
|
|
242
280
|
tmpHTML += '<div class="retold-remote-settings-section-title">Keyboard Shortcuts</div>';
|
|
243
281
|
|
|
244
|
-
|
|
245
|
-
|
|
282
|
+
tmpHTML += this._buildShortcutGroup('Global',
|
|
283
|
+
[
|
|
284
|
+
{ key: 'F1', desc: 'Help panel' },
|
|
285
|
+
{ key: 'F9', desc: 'Focus sidebar' },
|
|
286
|
+
{ key: '/', desc: 'Search / filter bar' },
|
|
287
|
+
{ key: 'Esc', desc: 'Close overlay / back' }
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
tmpHTML += this._buildShortcutGroup('Gallery',
|
|
291
|
+
[
|
|
292
|
+
{ key: '\u2190 \u2191 \u2192 \u2193', desc: 'Navigate items' },
|
|
246
293
|
{ key: 'Enter', desc: 'Open item' },
|
|
247
|
-
{ key: '
|
|
248
|
-
{ key: '
|
|
249
|
-
{ key: '
|
|
250
|
-
{ key: '
|
|
294
|
+
{ key: 'Esc', desc: 'Go up one folder' },
|
|
295
|
+
{ key: 'Home', desc: 'Jump to first item' },
|
|
296
|
+
{ key: 'End', desc: 'Jump to last item' },
|
|
297
|
+
{ key: 'g', desc: 'Toggle grid / list' },
|
|
298
|
+
{ key: 'f', desc: 'Advanced filter panel' },
|
|
299
|
+
{ key: 's', desc: 'Focus sort dropdown' },
|
|
300
|
+
{ key: 'x', desc: 'Clear all filters' },
|
|
301
|
+
{ key: 'c', desc: 'Settings panel' },
|
|
302
|
+
{ key: 'd', desc: 'Distraction-free mode' }
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
tmpHTML += this._buildShortcutGroup('Sidebar (F9)',
|
|
306
|
+
[
|
|
307
|
+
{ key: '\u2191 \u2193', desc: 'Navigate file list' },
|
|
308
|
+
{ key: 'Home', desc: 'Jump to first' },
|
|
309
|
+
{ key: 'End', desc: 'Jump to last' },
|
|
310
|
+
{ key: 'Enter', desc: 'Open item' },
|
|
311
|
+
{ key: 'Esc', desc: 'Return to gallery' }
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
tmpHTML += this._buildShortcutGroup('Media Viewer',
|
|
315
|
+
[
|
|
316
|
+
{ key: 'Esc', desc: 'Back to gallery' },
|
|
317
|
+
{ key: '\u2192 j', desc: 'Next file' },
|
|
318
|
+
{ key: '\u2190 k', desc: 'Previous file' },
|
|
251
319
|
{ key: 'Space', desc: 'Play / pause' },
|
|
252
|
-
{ key: '
|
|
320
|
+
{ key: 'f', desc: 'Fullscreen' },
|
|
321
|
+
{ key: 'i', desc: 'File info overlay' },
|
|
322
|
+
{ key: '+ -', desc: 'Zoom in / out' },
|
|
253
323
|
{ key: '0', desc: 'Reset zoom' },
|
|
254
|
-
{ key: '
|
|
255
|
-
{ key: '
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
324
|
+
{ key: 'z', desc: 'Cycle fit mode' },
|
|
325
|
+
{ key: 'd', desc: 'Distraction-free mode' }
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
tmpHTML += this._buildShortcutGroup('Video Menu',
|
|
329
|
+
[
|
|
330
|
+
{ key: 'Space', desc: 'Play in browser' },
|
|
331
|
+
{ key: 'Enter', desc: 'Play in browser' },
|
|
332
|
+
{ key: 'e', desc: 'Explore video frames' },
|
|
333
|
+
{ key: 't', desc: 'Extract thumbnail' },
|
|
334
|
+
{ key: 'v', desc: 'Open with VLC' },
|
|
335
|
+
{ key: '\u2192 j', desc: 'Next file' },
|
|
336
|
+
{ key: '\u2190 k', desc: 'Previous file' },
|
|
337
|
+
{ key: 'Esc', desc: 'Back to gallery' }
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
tmpHTML += this._buildShortcutGroup('Video Explorer',
|
|
341
|
+
[
|
|
342
|
+
{ key: 'Esc', desc: 'Back' }
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
tmpHTML += this._buildShortcutGroup('Audio Explorer',
|
|
346
|
+
[
|
|
347
|
+
{ key: 'Space', desc: 'Play selection' },
|
|
348
|
+
{ key: '+ -', desc: 'Zoom in / out' },
|
|
349
|
+
{ key: '0', desc: 'Zoom to fit' },
|
|
350
|
+
{ key: 'z', desc: 'Zoom to selection' },
|
|
351
|
+
{ key: 'Esc', desc: 'Clear selection / back' }
|
|
352
|
+
]);
|
|
265
353
|
|
|
266
354
|
tmpHTML += '</div>'; // end shortcuts section
|
|
267
355
|
|
|
@@ -270,6 +358,23 @@ class RetoldRemoteSettingsPanelView extends libPictView
|
|
|
270
358
|
tmpContainer.innerHTML = tmpHTML;
|
|
271
359
|
}
|
|
272
360
|
|
|
361
|
+
_buildShortcutGroup(pTitle, pShortcuts)
|
|
362
|
+
{
|
|
363
|
+
let tmpHTML = '<div class="retold-remote-settings-shortcut-group">';
|
|
364
|
+
tmpHTML += '<div class="retold-remote-settings-shortcut-group-title">' + pTitle + '</div>';
|
|
365
|
+
|
|
366
|
+
for (let i = 0; i < pShortcuts.length; i++)
|
|
367
|
+
{
|
|
368
|
+
tmpHTML += '<div class="retold-remote-settings-shortcut-row">';
|
|
369
|
+
tmpHTML += '<span class="retold-remote-settings-shortcut-desc">' + pShortcuts[i].desc + '</span>';
|
|
370
|
+
tmpHTML += '<span class="retold-remote-settings-shortcut-key">' + pShortcuts[i].key + '</span>';
|
|
371
|
+
tmpHTML += '</div>';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
tmpHTML += '</div>';
|
|
375
|
+
return tmpHTML;
|
|
376
|
+
}
|
|
377
|
+
|
|
273
378
|
changeTheme(pThemeKey)
|
|
274
379
|
{
|
|
275
380
|
let tmpThemeProvider = this.pict.providers['RetoldRemote-Theme'];
|