retold-remote 0.0.1 → 0.0.2
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/html/index.html +2 -0
- package/package.json +20 -14
- package/source/Pict-Application-RetoldRemote.js +46 -5
- package/source/cli/RetoldRemote-CLI-Run.js +0 -0
- package/source/cli/RetoldRemote-Server-Setup.js +790 -8
- package/source/cli/commands/RetoldRemote-Command-Serve.js +34 -1
- package/source/providers/Pict-Provider-GalleryFilterSort.js +61 -9
- package/source/providers/Pict-Provider-GalleryNavigation.js +517 -18
- package/source/providers/Pict-Provider-RetoldRemote.js +11 -2
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +1 -0
- package/source/server/RetoldRemote-ArchiveService.js +830 -0
- package/source/server/RetoldRemote-AudioWaveformService.js +673 -0
- package/source/server/RetoldRemote-EbookService.js +242 -0
- package/source/server/RetoldRemote-MediaService.js +1 -1
- package/source/server/RetoldRemote-ToolDetector.js +31 -1
- package/source/server/RetoldRemote-VideoFrameService.js +486 -0
- package/source/views/PictView-Remote-AudioExplorer.js +1213 -0
- package/source/views/PictView-Remote-Gallery.js +141 -2
- package/source/views/PictView-Remote-Layout.js +18 -27
- package/source/views/PictView-Remote-MediaViewer.js +638 -39
- package/source/views/PictView-Remote-SettingsPanel.js +23 -0
- package/source/views/PictView-Remote-TopBar.js +121 -0
- package/source/views/PictView-Remote-VideoExplorer.js +1229 -0
- package/web-application/index.html +2 -0
- package/web-application/js/epub.min.js +1 -0
- package/web-application/retold-remote.js +7030 -1244
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +13 -44
- package/web-application/retold-remote.min.js.map +1 -1
- package/web-application/retold-remote.compatible.js +0 -5764
- package/web-application/retold-remote.compatible.js.map +0 -1
- package/web-application/retold-remote.compatible.min.js +0 -120
- package/web-application/retold-remote.compatible.min.js.map +0 -1
|
@@ -16,6 +16,8 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
16
16
|
this._columnsPerRow = 4;
|
|
17
17
|
this._keydownBound = false;
|
|
18
18
|
this._helpPanelVisible = false;
|
|
19
|
+
this._sidebarFocused = false;
|
|
20
|
+
this._sidebarCursorIndex = 0;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -79,6 +81,55 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
// F9 toggles sidebar focus from any mode
|
|
85
|
+
if (pEvent.key === 'F9')
|
|
86
|
+
{
|
|
87
|
+
pEvent.preventDefault();
|
|
88
|
+
if (tmpSelf._sidebarFocused)
|
|
89
|
+
{
|
|
90
|
+
tmpSelf._blurSidebar();
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
{
|
|
94
|
+
tmpSelf._focusSidebar();
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// / toggles filter bar from any context (including when search is focused)
|
|
100
|
+
if (pEvent.key === '/')
|
|
101
|
+
{
|
|
102
|
+
// If the search input is currently focused, / should hide the bar
|
|
103
|
+
let tmpSearchInput = document.getElementById('RetoldRemote-Gallery-Search');
|
|
104
|
+
if (pEvent.target === tmpSearchInput)
|
|
105
|
+
{
|
|
106
|
+
pEvent.preventDefault();
|
|
107
|
+
tmpSelf._hideFilterBar();
|
|
108
|
+
tmpSearchInput.blur();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If another input is focused, let it type normally
|
|
113
|
+
if (pEvent.target.tagName === 'INPUT' || pEvent.target.tagName === 'TEXTAREA' || pEvent.target.isContentEditable)
|
|
114
|
+
{
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Otherwise toggle the filter bar
|
|
119
|
+
pEvent.preventDefault();
|
|
120
|
+
tmpSelf._toggleFilterBar();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Escape from the search input hides the filter bar
|
|
125
|
+
if (pEvent.key === 'Escape' && pEvent.target.id === 'RetoldRemote-Gallery-Search')
|
|
126
|
+
{
|
|
127
|
+
pEvent.preventDefault();
|
|
128
|
+
pEvent.target.blur();
|
|
129
|
+
tmpSelf._hideFilterBar();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
82
133
|
// Don't capture keys when an input is focused
|
|
83
134
|
if (pEvent.target.tagName === 'INPUT' || pEvent.target.tagName === 'TEXTAREA' || pEvent.target.isContentEditable)
|
|
84
135
|
{
|
|
@@ -96,10 +147,22 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
96
147
|
let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
|
|
97
148
|
let tmpActiveMode = tmpRemote.ActiveMode;
|
|
98
149
|
|
|
99
|
-
if (tmpActiveMode === 'gallery')
|
|
150
|
+
if (tmpActiveMode === 'gallery' && tmpSelf._sidebarFocused)
|
|
151
|
+
{
|
|
152
|
+
tmpSelf._handleSidebarKey(pEvent);
|
|
153
|
+
}
|
|
154
|
+
else if (tmpActiveMode === 'gallery')
|
|
100
155
|
{
|
|
101
156
|
tmpSelf._handleGalleryKey(pEvent);
|
|
102
157
|
}
|
|
158
|
+
else if (tmpActiveMode === 'video-explorer')
|
|
159
|
+
{
|
|
160
|
+
tmpSelf._handleVideoExplorerKey(pEvent);
|
|
161
|
+
}
|
|
162
|
+
else if (tmpActiveMode === 'audio-explorer')
|
|
163
|
+
{
|
|
164
|
+
tmpSelf._handleAudioExplorerKey(pEvent);
|
|
165
|
+
}
|
|
103
166
|
else if (tmpActiveMode === 'viewer')
|
|
104
167
|
{
|
|
105
168
|
tmpSelf._handleViewerKey(pEvent);
|
|
@@ -156,9 +219,9 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
156
219
|
this._toggleViewMode();
|
|
157
220
|
break;
|
|
158
221
|
|
|
159
|
-
case '
|
|
222
|
+
case 'x':
|
|
160
223
|
pEvent.preventDefault();
|
|
161
|
-
this.
|
|
224
|
+
this._clearAllFilters();
|
|
162
225
|
break;
|
|
163
226
|
|
|
164
227
|
case 'Home':
|
|
@@ -174,6 +237,8 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
174
237
|
case 'f':
|
|
175
238
|
pEvent.preventDefault();
|
|
176
239
|
{
|
|
240
|
+
// Ensure the filter bar is visible first
|
|
241
|
+
this._showFilterBar();
|
|
177
242
|
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
178
243
|
if (tmpGalleryView)
|
|
179
244
|
{
|
|
@@ -185,11 +250,16 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
185
250
|
case 's':
|
|
186
251
|
pEvent.preventDefault();
|
|
187
252
|
{
|
|
188
|
-
|
|
189
|
-
|
|
253
|
+
// Ensure the filter bar is visible first
|
|
254
|
+
this._showFilterBar();
|
|
255
|
+
setTimeout(() =>
|
|
190
256
|
{
|
|
191
|
-
tmpSortSelect.
|
|
192
|
-
|
|
257
|
+
let tmpSortSelect = document.getElementById('RetoldRemote-Gallery-Sort');
|
|
258
|
+
if (tmpSortSelect)
|
|
259
|
+
{
|
|
260
|
+
tmpSortSelect.focus();
|
|
261
|
+
}
|
|
262
|
+
}, 50);
|
|
193
263
|
}
|
|
194
264
|
break;
|
|
195
265
|
|
|
@@ -202,6 +272,141 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
202
272
|
pEvent.preventDefault();
|
|
203
273
|
this._toggleDistractionFree();
|
|
204
274
|
break;
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Handle keyboard events when the sidebar file list has focus.
|
|
281
|
+
*/
|
|
282
|
+
_handleSidebarKey(pEvent)
|
|
283
|
+
{
|
|
284
|
+
let tmpRows = document.querySelectorAll('#Pict-FileBrowser-DetailRows .pict-fb-detail-row');
|
|
285
|
+
let tmpCount = tmpRows.length;
|
|
286
|
+
|
|
287
|
+
if (tmpCount === 0)
|
|
288
|
+
{
|
|
289
|
+
// Nothing in the sidebar, bail back to gallery
|
|
290
|
+
this._blurSidebar();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
switch (pEvent.key)
|
|
295
|
+
{
|
|
296
|
+
case 'ArrowDown':
|
|
297
|
+
pEvent.preventDefault();
|
|
298
|
+
this._moveSidebarCursor(Math.min(this._sidebarCursorIndex + 1, tmpCount - 1));
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case 'ArrowUp':
|
|
302
|
+
pEvent.preventDefault();
|
|
303
|
+
this._moveSidebarCursor(Math.max(this._sidebarCursorIndex - 1, 0));
|
|
304
|
+
break;
|
|
305
|
+
|
|
306
|
+
case 'Home':
|
|
307
|
+
pEvent.preventDefault();
|
|
308
|
+
this._moveSidebarCursor(0);
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
case 'End':
|
|
312
|
+
pEvent.preventDefault();
|
|
313
|
+
this._moveSidebarCursor(tmpCount - 1);
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'Enter':
|
|
317
|
+
pEvent.preventDefault();
|
|
318
|
+
{
|
|
319
|
+
// Click the focused row to open it (folder or file)
|
|
320
|
+
let tmpRow = tmpRows[this._sidebarCursorIndex];
|
|
321
|
+
if (tmpRow)
|
|
322
|
+
{
|
|
323
|
+
// Fire the dblclick handler which opens folders / selects files
|
|
324
|
+
let tmpDblClickHandler = tmpRow.getAttribute('ondblclick');
|
|
325
|
+
if (tmpDblClickHandler)
|
|
326
|
+
{
|
|
327
|
+
new Function(tmpDblClickHandler).call(tmpRow);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'Escape':
|
|
334
|
+
pEvent.preventDefault();
|
|
335
|
+
this._blurSidebar();
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Move focus into the sidebar file list.
|
|
342
|
+
*/
|
|
343
|
+
_focusSidebar()
|
|
344
|
+
{
|
|
345
|
+
let tmpWrap = document.querySelector('.content-editor-sidebar-wrap');
|
|
346
|
+
if (!tmpWrap || tmpWrap.classList.contains('collapsed'))
|
|
347
|
+
{
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this._sidebarFocused = true;
|
|
352
|
+
this._sidebarCursorIndex = 0;
|
|
353
|
+
|
|
354
|
+
// Apply visual focus ring on the sidebar
|
|
355
|
+
let tmpInner = document.querySelector('.content-editor-sidebar-inner');
|
|
356
|
+
if (tmpInner)
|
|
357
|
+
{
|
|
358
|
+
tmpInner.classList.add('keyboard-focused');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this._moveSidebarCursor(0);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Return focus from sidebar back to the gallery.
|
|
366
|
+
*/
|
|
367
|
+
_blurSidebar()
|
|
368
|
+
{
|
|
369
|
+
this._sidebarFocused = false;
|
|
370
|
+
|
|
371
|
+
// Remove sidebar focus ring
|
|
372
|
+
let tmpInner = document.querySelector('.content-editor-sidebar-inner');
|
|
373
|
+
if (tmpInner)
|
|
374
|
+
{
|
|
375
|
+
tmpInner.classList.remove('keyboard-focused');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Remove highlight from all rows
|
|
379
|
+
let tmpRows = document.querySelectorAll('#Pict-FileBrowser-DetailRows .pict-fb-detail-row');
|
|
380
|
+
for (let i = 0; i < tmpRows.length; i++)
|
|
381
|
+
{
|
|
382
|
+
tmpRows[i].classList.remove('sidebar-focused');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Move the sidebar cursor to a new index and highlight the row.
|
|
388
|
+
*/
|
|
389
|
+
_moveSidebarCursor(pIndex)
|
|
390
|
+
{
|
|
391
|
+
let tmpRows = document.querySelectorAll('#Pict-FileBrowser-DetailRows .pict-fb-detail-row');
|
|
392
|
+
if (tmpRows.length === 0)
|
|
393
|
+
{
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Remove old highlight
|
|
398
|
+
if (this._sidebarCursorIndex < tmpRows.length)
|
|
399
|
+
{
|
|
400
|
+
tmpRows[this._sidebarCursorIndex].classList.remove('sidebar-focused');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
this._sidebarCursorIndex = pIndex;
|
|
404
|
+
|
|
405
|
+
// Apply new highlight and scroll into view
|
|
406
|
+
if (pIndex < tmpRows.length)
|
|
407
|
+
{
|
|
408
|
+
tmpRows[pIndex].classList.add('sidebar-focused');
|
|
409
|
+
tmpRows[pIndex].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
205
410
|
}
|
|
206
411
|
}
|
|
207
412
|
|
|
@@ -265,6 +470,11 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
265
470
|
this._cycleFitMode();
|
|
266
471
|
break;
|
|
267
472
|
|
|
473
|
+
case 'Enter':
|
|
474
|
+
pEvent.preventDefault();
|
|
475
|
+
this._openWithVLC();
|
|
476
|
+
break;
|
|
477
|
+
|
|
268
478
|
case 'd':
|
|
269
479
|
pEvent.preventDefault();
|
|
270
480
|
this._toggleDistractionFree();
|
|
@@ -272,6 +482,74 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
272
482
|
}
|
|
273
483
|
}
|
|
274
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Handle keyboard events in video explorer mode.
|
|
487
|
+
*/
|
|
488
|
+
_handleVideoExplorerKey(pEvent)
|
|
489
|
+
{
|
|
490
|
+
switch (pEvent.key)
|
|
491
|
+
{
|
|
492
|
+
case 'Escape':
|
|
493
|
+
pEvent.preventDefault();
|
|
494
|
+
let tmpVEX = this.pict.views['RetoldRemote-VideoExplorer'];
|
|
495
|
+
if (tmpVEX)
|
|
496
|
+
{
|
|
497
|
+
tmpVEX.goBack();
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Handle keyboard events in audio explorer mode.
|
|
505
|
+
*/
|
|
506
|
+
_handleAudioExplorerKey(pEvent)
|
|
507
|
+
{
|
|
508
|
+
let tmpAEX = this.pict.views['RetoldRemote-AudioExplorer'];
|
|
509
|
+
if (!tmpAEX)
|
|
510
|
+
{
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
switch (pEvent.key)
|
|
515
|
+
{
|
|
516
|
+
case 'Escape':
|
|
517
|
+
pEvent.preventDefault();
|
|
518
|
+
if (tmpAEX._selectionStart >= 0)
|
|
519
|
+
{
|
|
520
|
+
tmpAEX.clearSelection();
|
|
521
|
+
}
|
|
522
|
+
else
|
|
523
|
+
{
|
|
524
|
+
tmpAEX.goBack();
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
case '+':
|
|
528
|
+
case '=':
|
|
529
|
+
pEvent.preventDefault();
|
|
530
|
+
tmpAEX.zoomIn();
|
|
531
|
+
break;
|
|
532
|
+
case '-':
|
|
533
|
+
case '_':
|
|
534
|
+
pEvent.preventDefault();
|
|
535
|
+
tmpAEX.zoomOut();
|
|
536
|
+
break;
|
|
537
|
+
case '0':
|
|
538
|
+
pEvent.preventDefault();
|
|
539
|
+
tmpAEX.zoomToFit();
|
|
540
|
+
break;
|
|
541
|
+
case 'z':
|
|
542
|
+
case 'Z':
|
|
543
|
+
pEvent.preventDefault();
|
|
544
|
+
tmpAEX.zoomToSelection();
|
|
545
|
+
break;
|
|
546
|
+
case ' ':
|
|
547
|
+
pEvent.preventDefault();
|
|
548
|
+
tmpAEX.playSelection();
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
275
553
|
/**
|
|
276
554
|
* Move the gallery cursor to a new index and update the UI.
|
|
277
555
|
*
|
|
@@ -321,9 +599,9 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
321
599
|
|
|
322
600
|
let tmpItem = tmpItems[tmpIndex];
|
|
323
601
|
|
|
324
|
-
if (tmpItem.Type === 'folder')
|
|
602
|
+
if (tmpItem.Type === 'folder' || tmpItem.Type === 'archive')
|
|
325
603
|
{
|
|
326
|
-
// Navigate into the folder
|
|
604
|
+
// Navigate into the folder or archive
|
|
327
605
|
let tmpApp = this.pict.PictApplication;
|
|
328
606
|
if (tmpApp && tmpApp.loadFileList)
|
|
329
607
|
{
|
|
@@ -353,7 +631,9 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
353
631
|
return;
|
|
354
632
|
}
|
|
355
633
|
|
|
356
|
-
let tmpParent = tmpCurrentLocation.
|
|
634
|
+
let tmpParent = tmpCurrentLocation.indexOf('/') >= 0
|
|
635
|
+
? tmpCurrentLocation.replace(/\/[^/]+\/?$/, '')
|
|
636
|
+
: '';
|
|
357
637
|
let tmpApp = this.pict.PictApplication;
|
|
358
638
|
if (tmpApp && tmpApp.loadFileList)
|
|
359
639
|
{
|
|
@@ -467,6 +747,95 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
467
747
|
}
|
|
468
748
|
}
|
|
469
749
|
|
|
750
|
+
// ──────────────────────────────────────────────
|
|
751
|
+
// Filter bar toggle
|
|
752
|
+
// ──────────────────────────────────────────────
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Toggle the filter bar visibility.
|
|
756
|
+
* If hidden, show it and focus the search input.
|
|
757
|
+
* If visible, hide it.
|
|
758
|
+
*/
|
|
759
|
+
_toggleFilterBar()
|
|
760
|
+
{
|
|
761
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
762
|
+
|
|
763
|
+
if (tmpRemote.FilterBarVisible)
|
|
764
|
+
{
|
|
765
|
+
this._hideFilterBar();
|
|
766
|
+
}
|
|
767
|
+
else
|
|
768
|
+
{
|
|
769
|
+
this._showFilterBar();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Show the filter bar and focus the search input.
|
|
775
|
+
*/
|
|
776
|
+
_showFilterBar()
|
|
777
|
+
{
|
|
778
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
779
|
+
|
|
780
|
+
if (tmpRemote.FilterBarVisible)
|
|
781
|
+
{
|
|
782
|
+
// Already visible — just focus search
|
|
783
|
+
let tmpSearch = document.getElementById('RetoldRemote-Gallery-Search');
|
|
784
|
+
if (tmpSearch)
|
|
785
|
+
{
|
|
786
|
+
tmpSearch.focus();
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
tmpRemote.FilterBarVisible = true;
|
|
792
|
+
|
|
793
|
+
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
794
|
+
if (tmpGalleryView)
|
|
795
|
+
{
|
|
796
|
+
tmpGalleryView.renderGallery();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Focus the search input after render
|
|
800
|
+
setTimeout(() =>
|
|
801
|
+
{
|
|
802
|
+
let tmpSearch = document.getElementById('RetoldRemote-Gallery-Search');
|
|
803
|
+
if (tmpSearch)
|
|
804
|
+
{
|
|
805
|
+
tmpSearch.focus();
|
|
806
|
+
}
|
|
807
|
+
}, 50);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Hide the filter bar.
|
|
812
|
+
*/
|
|
813
|
+
_hideFilterBar()
|
|
814
|
+
{
|
|
815
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
816
|
+
tmpRemote.FilterBarVisible = false;
|
|
817
|
+
|
|
818
|
+
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
819
|
+
if (tmpGalleryView)
|
|
820
|
+
{
|
|
821
|
+
tmpGalleryView.renderGallery();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Clear all active filters and update the gallery.
|
|
827
|
+
*/
|
|
828
|
+
_clearAllFilters()
|
|
829
|
+
{
|
|
830
|
+
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
831
|
+
if (tmpGalleryView)
|
|
832
|
+
{
|
|
833
|
+
tmpGalleryView.clearAllFilters();
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
this._showToast('Filters cleared');
|
|
837
|
+
}
|
|
838
|
+
|
|
470
839
|
// ──────────────────────────────────────────────
|
|
471
840
|
// Help panel
|
|
472
841
|
// ──────────────────────────────────────────────
|
|
@@ -522,11 +891,13 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
522
891
|
['← → ↑ ↓', 'Navigate tiles'],
|
|
523
892
|
['Enter', 'Open selected item'],
|
|
524
893
|
['Escape', 'Go up one folder'],
|
|
894
|
+
['F9', 'Toggle sidebar focus'],
|
|
525
895
|
['Home / End', 'Jump to first / last'],
|
|
526
896
|
['g', 'Toggle gallery / list view'],
|
|
527
|
-
['/', '
|
|
528
|
-
['f', 'Toggle filter panel'],
|
|
897
|
+
['/', 'Toggle filter bar & search'],
|
|
898
|
+
['f', 'Toggle advanced filter panel'],
|
|
529
899
|
['s', 'Focus sort dropdown'],
|
|
900
|
+
['x', 'Clear all filters'],
|
|
530
901
|
['c', 'Settings / config panel'],
|
|
531
902
|
['d', 'Distraction-free mode']
|
|
532
903
|
];
|
|
@@ -539,6 +910,26 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
539
910
|
}
|
|
540
911
|
tmpHTML += '</div>';
|
|
541
912
|
|
|
913
|
+
// Sidebar shortcuts
|
|
914
|
+
tmpHTML += '<div class="retold-remote-help-section">';
|
|
915
|
+
tmpHTML += '<div class="retold-remote-help-section-title">Sidebar (F9 to focus)</div>';
|
|
916
|
+
|
|
917
|
+
let tmpSidebarShortcuts =
|
|
918
|
+
[
|
|
919
|
+
['↑ / ↓', 'Navigate file list'],
|
|
920
|
+
['Enter', 'Open selected item'],
|
|
921
|
+
['Home / End', 'Jump to first / last'],
|
|
922
|
+
['Escape / F9', 'Return to gallery']
|
|
923
|
+
];
|
|
924
|
+
for (let i = 0; i < tmpSidebarShortcuts.length; i++)
|
|
925
|
+
{
|
|
926
|
+
tmpHTML += '<div class="retold-remote-help-row">';
|
|
927
|
+
tmpHTML += '<kbd class="retold-remote-help-key">' + tmpSidebarShortcuts[i][0] + '</kbd>';
|
|
928
|
+
tmpHTML += '<span class="retold-remote-help-desc">' + tmpSidebarShortcuts[i][1] + '</span>';
|
|
929
|
+
tmpHTML += '</div>';
|
|
930
|
+
}
|
|
931
|
+
tmpHTML += '</div>';
|
|
932
|
+
|
|
542
933
|
// Viewer shortcuts
|
|
543
934
|
tmpHTML += '<div class="retold-remote-help-section">';
|
|
544
935
|
tmpHTML += '<div class="retold-remote-help-section-title">Media Viewer</div>';
|
|
@@ -551,6 +942,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
551
942
|
['f', 'Toggle fullscreen'],
|
|
552
943
|
['i', 'Toggle file info'],
|
|
553
944
|
['Space', 'Play / pause media'],
|
|
945
|
+
['Enter', 'Open video in VLC'],
|
|
554
946
|
['z', 'Cycle fit mode'],
|
|
555
947
|
['+ / -', 'Zoom in / out'],
|
|
556
948
|
['0', 'Reset zoom'],
|
|
@@ -572,6 +964,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
572
964
|
let tmpGlobalShortcuts =
|
|
573
965
|
[
|
|
574
966
|
['F1', 'Toggle this help panel'],
|
|
967
|
+
['F9', 'Toggle sidebar focus'],
|
|
575
968
|
['Escape', 'Close help panel']
|
|
576
969
|
];
|
|
577
970
|
for (let i = 0; i < tmpGlobalShortcuts.length; i++)
|
|
@@ -585,7 +978,11 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
585
978
|
|
|
586
979
|
// Active mode indicator
|
|
587
980
|
tmpHTML += '<div class="retold-remote-help-footer">';
|
|
588
|
-
|
|
981
|
+
let tmpModeLabel = 'Gallery';
|
|
982
|
+
if (tmpActiveMode === 'viewer') tmpModeLabel = 'Media Viewer';
|
|
983
|
+
else if (tmpActiveMode === 'video-explorer') tmpModeLabel = 'Video Explorer';
|
|
984
|
+
else if (tmpActiveMode === 'audio-explorer') tmpModeLabel = 'Audio Explorer';
|
|
985
|
+
tmpHTML += 'Current mode: <strong>' + tmpModeLabel + '</strong>';
|
|
589
986
|
tmpHTML += '</div>';
|
|
590
987
|
|
|
591
988
|
tmpHTML += '</div>'; // end flyout
|
|
@@ -595,7 +992,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
595
992
|
}
|
|
596
993
|
|
|
597
994
|
/**
|
|
598
|
-
*
|
|
995
|
+
* F9 — Toggle the settings/configuration panel.
|
|
599
996
|
* Opens the sidebar if collapsed, switches to the Settings tab,
|
|
600
997
|
* or toggles back to the Files tab if Settings is already showing.
|
|
601
998
|
*/
|
|
@@ -736,14 +1133,27 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
736
1133
|
|
|
737
1134
|
_toggleFullscreen()
|
|
738
1135
|
{
|
|
739
|
-
let tmpViewer = document.getElementById('RetoldRemote-Viewer-Container');
|
|
740
|
-
if (!tmpViewer) return;
|
|
741
|
-
|
|
742
1136
|
if (document.fullscreenElement)
|
|
743
1137
|
{
|
|
744
1138
|
document.exitFullscreen();
|
|
1139
|
+
return;
|
|
745
1140
|
}
|
|
746
|
-
|
|
1141
|
+
|
|
1142
|
+
// When viewing a video, fullscreen the video element itself
|
|
1143
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1144
|
+
if (tmpRemote.CurrentViewerMediaType === 'video')
|
|
1145
|
+
{
|
|
1146
|
+
let tmpVideo = document.getElementById('RetoldRemote-VideoPlayer');
|
|
1147
|
+
if (tmpVideo)
|
|
1148
|
+
{
|
|
1149
|
+
tmpVideo.requestFullscreen();
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// For other media types, fullscreen the viewer container
|
|
1155
|
+
let tmpViewer = document.getElementById('RetoldRemote-Viewer-Container');
|
|
1156
|
+
if (tmpViewer)
|
|
747
1157
|
{
|
|
748
1158
|
tmpViewer.requestFullscreen();
|
|
749
1159
|
}
|
|
@@ -812,6 +1222,95 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
812
1222
|
tmpImageViewer.cycleFitMode();
|
|
813
1223
|
}
|
|
814
1224
|
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Open the current video file with VLC via the server endpoint.
|
|
1228
|
+
*/
|
|
1229
|
+
_openWithVLC()
|
|
1230
|
+
{
|
|
1231
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1232
|
+
|
|
1233
|
+
// Only works for video files
|
|
1234
|
+
if (tmpRemote.CurrentViewerMediaType !== 'video')
|
|
1235
|
+
{
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Check if VLC is available
|
|
1240
|
+
let tmpCapabilities = tmpRemote.ServerCapabilities || {};
|
|
1241
|
+
if (!tmpCapabilities.vlc)
|
|
1242
|
+
{
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
1247
|
+
if (!tmpFilePath)
|
|
1248
|
+
{
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Show a brief toast
|
|
1253
|
+
this._showToast('Opening in VLC...');
|
|
1254
|
+
|
|
1255
|
+
// POST to the server to open the file
|
|
1256
|
+
fetch('/api/media/open',
|
|
1257
|
+
{
|
|
1258
|
+
method: 'POST',
|
|
1259
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1260
|
+
body: JSON.stringify({ path: tmpFilePath })
|
|
1261
|
+
})
|
|
1262
|
+
.then((pResponse) =>
|
|
1263
|
+
{
|
|
1264
|
+
return pResponse.json();
|
|
1265
|
+
})
|
|
1266
|
+
.then((pData) =>
|
|
1267
|
+
{
|
|
1268
|
+
if (!pData.Success)
|
|
1269
|
+
{
|
|
1270
|
+
this._showToast('Failed to open: ' + (pData.Error || 'Unknown error'));
|
|
1271
|
+
}
|
|
1272
|
+
})
|
|
1273
|
+
.catch((pError) =>
|
|
1274
|
+
{
|
|
1275
|
+
this._showToast('Failed to open: ' + pError.message);
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Show a brief toast notification in the viewer.
|
|
1281
|
+
*
|
|
1282
|
+
* @param {string} pMessage - Text to display
|
|
1283
|
+
*/
|
|
1284
|
+
_showToast(pMessage)
|
|
1285
|
+
{
|
|
1286
|
+
let tmpIndicator = document.getElementById('RetoldRemote-FitIndicator');
|
|
1287
|
+
if (!tmpIndicator)
|
|
1288
|
+
{
|
|
1289
|
+
tmpIndicator = document.createElement('div');
|
|
1290
|
+
tmpIndicator.id = 'RetoldRemote-FitIndicator';
|
|
1291
|
+
tmpIndicator.className = 'retold-remote-fit-indicator';
|
|
1292
|
+
|
|
1293
|
+
let tmpContainer = document.querySelector('.retold-remote-viewer-body');
|
|
1294
|
+
if (tmpContainer)
|
|
1295
|
+
{
|
|
1296
|
+
tmpContainer.appendChild(tmpIndicator);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
tmpIndicator.textContent = pMessage;
|
|
1301
|
+
tmpIndicator.classList.add('visible');
|
|
1302
|
+
|
|
1303
|
+
if (this._toastTimeout)
|
|
1304
|
+
{
|
|
1305
|
+
clearTimeout(this._toastTimeout);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
let tmpSelf = this;
|
|
1309
|
+
this._toastTimeout = setTimeout(function ()
|
|
1310
|
+
{
|
|
1311
|
+
tmpIndicator.classList.remove('visible');
|
|
1312
|
+
}, 1500);
|
|
1313
|
+
}
|
|
815
1314
|
}
|
|
816
1315
|
|
|
817
1316
|
GalleryNavigationProvider.default_configuration = _DefaultProviderConfiguration;
|
|
@@ -174,8 +174,17 @@ class RetoldRemoteProvider extends libPictProvider
|
|
|
174
174
|
*/
|
|
175
175
|
getContentURL(pPath)
|
|
176
176
|
{
|
|
177
|
-
//
|
|
178
|
-
//
|
|
177
|
+
// When hashed filenames is enabled, use the /content-hashed/<hash> route
|
|
178
|
+
// so the real file path is never exposed to the browser.
|
|
179
|
+
if (this.pict.AppData.RetoldRemote.HashedFilenames)
|
|
180
|
+
{
|
|
181
|
+
let tmpHash = this.getHashForPath(pPath);
|
|
182
|
+
if (tmpHash)
|
|
183
|
+
{
|
|
184
|
+
return '/content-hashed/' + tmpHash;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Fallback: encode each path segment individually to preserve directory separators.
|
|
179
188
|
let tmpSegments = pPath.split('/').map((pSeg) => encodeURIComponent(pSeg));
|
|
180
189
|
return '/content/' + tmpSegments.join('/');
|
|
181
190
|
}
|
|
@@ -91,6 +91,7 @@ const _FallbackExtensionMap =
|
|
|
91
91
|
'.zip': 'file-archive', '.tar': 'file-archive', '.gz': 'file-archive',
|
|
92
92
|
'.rar': 'file-archive', '.7z': 'file-archive', '.bz2': 'file-archive',
|
|
93
93
|
'.xz': 'file-archive', '.tgz': 'file-archive',
|
|
94
|
+
'.cbz': 'file-archive', '.cbr': 'file-archive',
|
|
94
95
|
|
|
95
96
|
// Audio
|
|
96
97
|
'.mp3': 'file-audio', '.wav': 'file-audio', '.flac': 'file-audio',
|