retold-remote 0.0.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-remote",
3
- "version": "0.0.3",
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": {
@@ -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.GalleryCursorIndex = 0;
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
- tmpRemote.GalleryCursorIndex = 0;
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 image2 -c:v ${tmpOutputFormat} pipe:1`;
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 tmpCmd = `ffmpeg -ss ${tmpTimeStr} -i "${pAbsPath}" -vframes 1 -vf "scale=${pWidth}:${pHeight}:force_original_aspect_ratio=decrease" -c:v ${tmpCodec} -y "${pOutputPath}"`;
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 = this.pict.AppData.RetoldRemote.AutoplayVideo ? ' autoplay' : '';
638
+ let tmpAutoplayVideo = tmpRemote.AutoplayVideo ? ' autoplay' : '';
483
639
  tmpHTML += '<video controls' + tmpAutoplayVideo + ' preload="metadata" '
484
640
  + 'id="RetoldRemote-VideoPlayer">'
485
- + '<source src="' + pURL + '">'
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
- + '&#128270; Explore Video'
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 (Enter)">'
510
- + '&#9654; Open ' + this._escapeHTML(pFileName) + ' with VLC'
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
- return tmpHTML;
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
- let tmpShortcuts = [
245
- { key: 'Arrow keys', desc: 'Navigate gallery' },
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: 'Escape', desc: 'Back / close' },
248
- { key: 'j / k', desc: 'Next / prev in viewer' },
249
- { key: 'f', desc: 'Fullscreen' },
250
- { key: 'i', desc: 'File info' },
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: '+ / -', desc: 'Zoom in / out' },
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: 'g', desc: 'Toggle grid / list' },
255
- { key: '/', desc: 'Focus search' }
256
- ];
257
-
258
- for (let i = 0; i < tmpShortcuts.length; i++)
259
- {
260
- tmpHTML += '<div class="retold-remote-settings-cap-row">';
261
- tmpHTML += '<span class="retold-remote-settings-cap-label">' + tmpShortcuts[i].desc + '</span>';
262
- tmpHTML += '<span style="color: var(--retold-accent); font-family: var(--retold-font-mono, monospace);">' + tmpShortcuts[i].key + '</span>';
263
- tmpHTML += '</div>';
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'];