retold-remote 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/css/retold-remote.css +83 -0
  3. package/html/codejar.js +511 -0
  4. package/html/index.html +23 -0
  5. package/package.json +68 -0
  6. package/server.js +43 -0
  7. package/source/Pict-Application-RetoldRemote-Configuration.json +7 -0
  8. package/source/Pict-Application-RetoldRemote.js +622 -0
  9. package/source/Pict-RetoldRemote-Bundle.js +14 -0
  10. package/source/cli/RetoldRemote-CLI-Program.js +15 -0
  11. package/source/cli/RetoldRemote-CLI-Run.js +3 -0
  12. package/source/cli/RetoldRemote-Server-Setup.js +257 -0
  13. package/source/cli/commands/RetoldRemote-Command-Serve.js +87 -0
  14. package/source/providers/Pict-Provider-GalleryFilterSort.js +597 -0
  15. package/source/providers/Pict-Provider-GalleryNavigation.js +819 -0
  16. package/source/providers/Pict-Provider-RetoldRemote.js +273 -0
  17. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +640 -0
  18. package/source/providers/Pict-Provider-RetoldRemoteTheme.js +879 -0
  19. package/source/server/RetoldRemote-MediaService.js +536 -0
  20. package/source/server/RetoldRemote-PathRegistry.js +121 -0
  21. package/source/server/RetoldRemote-ThumbnailCache.js +89 -0
  22. package/source/server/RetoldRemote-ToolDetector.js +78 -0
  23. package/source/views/PictView-Remote-Gallery.js +1437 -0
  24. package/source/views/PictView-Remote-ImageViewer.js +363 -0
  25. package/source/views/PictView-Remote-Layout.js +420 -0
  26. package/source/views/PictView-Remote-MediaViewer.js +530 -0
  27. package/source/views/PictView-Remote-SettingsPanel.js +318 -0
  28. package/source/views/PictView-Remote-TopBar.js +206 -0
  29. package/web-application/codejar.js +511 -0
  30. package/web-application/css/retold-remote.css +83 -0
  31. package/web-application/index.html +23 -0
  32. package/web-application/js/pict.min.js +12 -0
  33. package/web-application/js/pict.min.js.map +1 -0
  34. package/web-application/retold-remote.compatible.js +5764 -0
  35. package/web-application/retold-remote.compatible.js.map +1 -0
  36. package/web-application/retold-remote.compatible.min.js +120 -0
  37. package/web-application/retold-remote.compatible.min.js.map +1 -0
  38. package/web-application/retold-remote.js +5763 -0
  39. package/web-application/retold-remote.js.map +1 -0
  40. package/web-application/retold-remote.min.js +120 -0
  41. package/web-application/retold-remote.min.js.map +1 -0
@@ -0,0 +1,819 @@
1
+ const libPictProvider = require('pict-provider');
2
+
3
+ const _DefaultProviderConfiguration =
4
+ {
5
+ ProviderIdentifier: 'RetoldRemote-GalleryNavigation',
6
+ AutoInitialize: true,
7
+ AutoSolveWithApp: false
8
+ };
9
+
10
+ class GalleryNavigationProvider extends libPictProvider
11
+ {
12
+ constructor(pFable, pOptions, pServiceHash)
13
+ {
14
+ super(pFable, pOptions, pServiceHash);
15
+
16
+ this._columnsPerRow = 4;
17
+ this._keydownBound = false;
18
+ this._helpPanelVisible = false;
19
+ }
20
+
21
+ /**
22
+ * Calculate how many columns are in the current gallery grid by
23
+ * inspecting the rendered DOM. In list mode this is always 1.
24
+ */
25
+ recalculateColumns()
26
+ {
27
+ let tmpRemote = this.pict.AppData.RetoldRemote;
28
+
29
+ // List mode is always a single column
30
+ if (tmpRemote.ViewMode === 'list')
31
+ {
32
+ this._columnsPerRow = 1;
33
+ return;
34
+ }
35
+
36
+ // Count tiles that share the same offsetTop as the first tile
37
+ let tmpTiles = document.querySelectorAll('.retold-remote-tile');
38
+ if (tmpTiles.length < 2)
39
+ {
40
+ this._columnsPerRow = Math.max(1, tmpTiles.length);
41
+ return;
42
+ }
43
+
44
+ let tmpFirstTop = tmpTiles[0].offsetTop;
45
+ let tmpCols = 1;
46
+ for (let i = 1; i < tmpTiles.length; i++)
47
+ {
48
+ if (tmpTiles[i].offsetTop === tmpFirstTop)
49
+ {
50
+ tmpCols++;
51
+ }
52
+ else
53
+ {
54
+ break;
55
+ }
56
+ }
57
+ this._columnsPerRow = tmpCols;
58
+ }
59
+
60
+ /**
61
+ * Bind the global keydown handler for gallery and viewer navigation.
62
+ */
63
+ bindKeyboardNavigation()
64
+ {
65
+ if (this._keydownBound)
66
+ {
67
+ return;
68
+ }
69
+
70
+ let tmpSelf = this;
71
+
72
+ this._keydownHandler = function (pEvent)
73
+ {
74
+ // F1 toggles help in any mode, even when an input is focused
75
+ if (pEvent.key === 'F1')
76
+ {
77
+ pEvent.preventDefault();
78
+ tmpSelf._toggleHelpPanel();
79
+ return;
80
+ }
81
+
82
+ // Don't capture keys when an input is focused
83
+ if (pEvent.target.tagName === 'INPUT' || pEvent.target.tagName === 'TEXTAREA' || pEvent.target.isContentEditable)
84
+ {
85
+ return;
86
+ }
87
+
88
+ // If the help panel is visible, Escape closes it
89
+ if (tmpSelf._helpPanelVisible && pEvent.key === 'Escape')
90
+ {
91
+ pEvent.preventDefault();
92
+ tmpSelf._toggleHelpPanel();
93
+ return;
94
+ }
95
+
96
+ let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
97
+ let tmpActiveMode = tmpRemote.ActiveMode;
98
+
99
+ if (tmpActiveMode === 'gallery')
100
+ {
101
+ tmpSelf._handleGalleryKey(pEvent);
102
+ }
103
+ else if (tmpActiveMode === 'viewer')
104
+ {
105
+ tmpSelf._handleViewerKey(pEvent);
106
+ }
107
+ };
108
+
109
+ document.addEventListener('keydown', this._keydownHandler);
110
+ this._keydownBound = true;
111
+ }
112
+
113
+ /**
114
+ * Handle keyboard events in gallery mode.
115
+ */
116
+ _handleGalleryKey(pEvent)
117
+ {
118
+ let tmpRemote = this.pict.AppData.RetoldRemote;
119
+ let tmpItems = tmpRemote.GalleryItems || [];
120
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
121
+
122
+ switch (pEvent.key)
123
+ {
124
+ case 'ArrowRight':
125
+ pEvent.preventDefault();
126
+ this.moveCursor(Math.min(tmpIndex + 1, tmpItems.length - 1));
127
+ break;
128
+
129
+ case 'ArrowLeft':
130
+ pEvent.preventDefault();
131
+ this.moveCursor(Math.max(tmpIndex - 1, 0));
132
+ break;
133
+
134
+ case 'ArrowDown':
135
+ pEvent.preventDefault();
136
+ this.moveCursor(Math.min(tmpIndex + this._columnsPerRow, tmpItems.length - 1));
137
+ break;
138
+
139
+ case 'ArrowUp':
140
+ pEvent.preventDefault();
141
+ this.moveCursor(Math.max(tmpIndex - this._columnsPerRow, 0));
142
+ break;
143
+
144
+ case 'Enter':
145
+ pEvent.preventDefault();
146
+ this.openCurrent();
147
+ break;
148
+
149
+ case 'Escape':
150
+ pEvent.preventDefault();
151
+ this.navigateUp();
152
+ break;
153
+
154
+ case 'g':
155
+ pEvent.preventDefault();
156
+ this._toggleViewMode();
157
+ break;
158
+
159
+ case '/':
160
+ pEvent.preventDefault();
161
+ this._focusSearch();
162
+ break;
163
+
164
+ case 'Home':
165
+ pEvent.preventDefault();
166
+ this.moveCursor(0);
167
+ break;
168
+
169
+ case 'End':
170
+ pEvent.preventDefault();
171
+ this.moveCursor(tmpItems.length - 1);
172
+ break;
173
+
174
+ case 'f':
175
+ pEvent.preventDefault();
176
+ {
177
+ let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
178
+ if (tmpGalleryView)
179
+ {
180
+ tmpGalleryView.toggleFilterPanel();
181
+ }
182
+ }
183
+ break;
184
+
185
+ case 's':
186
+ pEvent.preventDefault();
187
+ {
188
+ let tmpSortSelect = document.getElementById('RetoldRemote-Gallery-Sort');
189
+ if (tmpSortSelect)
190
+ {
191
+ tmpSortSelect.focus();
192
+ }
193
+ }
194
+ break;
195
+
196
+ case 'c':
197
+ pEvent.preventDefault();
198
+ this._toggleSettingsPanel();
199
+ break;
200
+
201
+ case 'd':
202
+ pEvent.preventDefault();
203
+ this._toggleDistractionFree();
204
+ break;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Handle keyboard events in viewer mode.
210
+ */
211
+ _handleViewerKey(pEvent)
212
+ {
213
+ switch (pEvent.key)
214
+ {
215
+ case 'Escape':
216
+ pEvent.preventDefault();
217
+ this.closeViewer();
218
+ break;
219
+
220
+ case 'ArrowRight':
221
+ case 'j':
222
+ pEvent.preventDefault();
223
+ this.nextFile();
224
+ break;
225
+
226
+ case 'ArrowLeft':
227
+ case 'k':
228
+ pEvent.preventDefault();
229
+ this.prevFile();
230
+ break;
231
+
232
+ case 'f':
233
+ pEvent.preventDefault();
234
+ this._toggleFullscreen();
235
+ break;
236
+
237
+ case 'i':
238
+ pEvent.preventDefault();
239
+ this._toggleFileInfo();
240
+ break;
241
+
242
+ case ' ':
243
+ pEvent.preventDefault();
244
+ this._togglePlayPause();
245
+ break;
246
+
247
+ case '+':
248
+ case '=':
249
+ pEvent.preventDefault();
250
+ this._zoomIn();
251
+ break;
252
+
253
+ case '-':
254
+ pEvent.preventDefault();
255
+ this._zoomOut();
256
+ break;
257
+
258
+ case '0':
259
+ pEvent.preventDefault();
260
+ this._zoomReset();
261
+ break;
262
+
263
+ case 'z':
264
+ pEvent.preventDefault();
265
+ this._cycleFitMode();
266
+ break;
267
+
268
+ case 'd':
269
+ pEvent.preventDefault();
270
+ this._toggleDistractionFree();
271
+ break;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Move the gallery cursor to a new index and update the UI.
277
+ *
278
+ * @param {number} pNewIndex - The new cursor position
279
+ */
280
+ moveCursor(pNewIndex)
281
+ {
282
+ let tmpRemote = this.pict.AppData.RetoldRemote;
283
+ let tmpOldIndex = tmpRemote.GalleryCursorIndex || 0;
284
+
285
+ if (pNewIndex === tmpOldIndex)
286
+ {
287
+ return;
288
+ }
289
+
290
+ tmpRemote.GalleryCursorIndex = pNewIndex;
291
+
292
+ // Update CSS classes on the affected elements (tiles in grid mode, rows in list mode)
293
+ let tmpOldTile = document.querySelector(`.retold-remote-tile[data-index="${tmpOldIndex}"], .retold-remote-list-row[data-index="${tmpOldIndex}"]`);
294
+ let tmpNewTile = document.querySelector(`.retold-remote-tile[data-index="${pNewIndex}"], .retold-remote-list-row[data-index="${pNewIndex}"]`);
295
+
296
+ if (tmpOldTile)
297
+ {
298
+ tmpOldTile.classList.remove('selected');
299
+ }
300
+ if (tmpNewTile)
301
+ {
302
+ tmpNewTile.classList.add('selected');
303
+ // Scroll the tile into view if needed
304
+ tmpNewTile.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Open the currently selected gallery item.
310
+ */
311
+ openCurrent()
312
+ {
313
+ let tmpRemote = this.pict.AppData.RetoldRemote;
314
+ let tmpItems = tmpRemote.GalleryItems || [];
315
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
316
+
317
+ if (tmpIndex >= tmpItems.length)
318
+ {
319
+ return;
320
+ }
321
+
322
+ let tmpItem = tmpItems[tmpIndex];
323
+
324
+ if (tmpItem.Type === 'folder')
325
+ {
326
+ // Navigate into the folder
327
+ let tmpApp = this.pict.PictApplication;
328
+ if (tmpApp && tmpApp.loadFileList)
329
+ {
330
+ tmpApp.loadFileList(tmpItem.Path);
331
+ }
332
+ }
333
+ else
334
+ {
335
+ // Open the file in the viewer
336
+ let tmpApp = this.pict.PictApplication;
337
+ if (tmpApp && tmpApp.navigateToFile)
338
+ {
339
+ tmpApp.navigateToFile(tmpItem.Path);
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Navigate up one directory level.
346
+ */
347
+ navigateUp()
348
+ {
349
+ let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
350
+
351
+ if (!tmpCurrentLocation)
352
+ {
353
+ return;
354
+ }
355
+
356
+ let tmpParent = tmpCurrentLocation.replace(/\/[^/]+\/?$/, '') || '';
357
+ let tmpApp = this.pict.PictApplication;
358
+ if (tmpApp && tmpApp.loadFileList)
359
+ {
360
+ tmpApp.loadFileList(tmpParent);
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Close the viewer and return to gallery mode.
366
+ */
367
+ closeViewer()
368
+ {
369
+ let tmpRemote = this.pict.AppData.RetoldRemote;
370
+ tmpRemote.ActiveMode = 'gallery';
371
+
372
+ let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
373
+ let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
374
+
375
+ if (tmpGalleryContainer) tmpGalleryContainer.style.display = '';
376
+ if (tmpViewerContainer) tmpViewerContainer.style.display = 'none';
377
+
378
+ // Restore the hash to the browse route (use hashed identifier when available)
379
+ let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
380
+ let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
381
+ let tmpFragId = (tmpFragProvider && tmpCurrentLocation) ? tmpFragProvider.getFragmentIdentifier(tmpCurrentLocation) : tmpCurrentLocation;
382
+ window.location.hash = tmpFragId ? '#/browse/' + tmpFragId : '#/browse/';
383
+
384
+ // Re-render gallery to ensure cursor is visible
385
+ let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
386
+ if (tmpGalleryView)
387
+ {
388
+ tmpGalleryView.renderGallery();
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Navigate to the next file in the gallery list.
394
+ */
395
+ nextFile()
396
+ {
397
+ let tmpRemote = this.pict.AppData.RetoldRemote;
398
+ let tmpItems = tmpRemote.GalleryItems || [];
399
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
400
+
401
+ // Find the next file (skip folders)
402
+ for (let i = tmpIndex + 1; i < tmpItems.length; i++)
403
+ {
404
+ if (tmpItems[i].Type === 'file')
405
+ {
406
+ tmpRemote.GalleryCursorIndex = i;
407
+ let tmpApp = this.pict.PictApplication;
408
+ if (tmpApp && tmpApp.navigateToFile)
409
+ {
410
+ tmpApp.navigateToFile(tmpItems[i].Path);
411
+ }
412
+ return;
413
+ }
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Navigate to the previous file in the gallery list.
419
+ */
420
+ prevFile()
421
+ {
422
+ let tmpRemote = this.pict.AppData.RetoldRemote;
423
+ let tmpItems = tmpRemote.GalleryItems || [];
424
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
425
+
426
+ // Find the previous file (skip folders)
427
+ for (let i = tmpIndex - 1; i >= 0; i--)
428
+ {
429
+ if (tmpItems[i].Type === 'file')
430
+ {
431
+ tmpRemote.GalleryCursorIndex = i;
432
+ let tmpApp = this.pict.PictApplication;
433
+ if (tmpApp && tmpApp.navigateToFile)
434
+ {
435
+ tmpApp.navigateToFile(tmpItems[i].Path);
436
+ }
437
+ return;
438
+ }
439
+ }
440
+ }
441
+
442
+ _toggleViewMode()
443
+ {
444
+ let tmpRemote = this.pict.AppData.RetoldRemote;
445
+ tmpRemote.ViewMode = (tmpRemote.ViewMode === 'gallery') ? 'list' : 'gallery';
446
+
447
+ let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
448
+ if (tmpGalleryView)
449
+ {
450
+ tmpGalleryView.renderGallery();
451
+ }
452
+
453
+ // Persist the change
454
+ let tmpApp = this.pict.PictApplication;
455
+ if (tmpApp && typeof tmpApp.saveSettings === 'function')
456
+ {
457
+ tmpApp.saveSettings();
458
+ }
459
+ }
460
+
461
+ _focusSearch()
462
+ {
463
+ let tmpSearch = document.getElementById('RetoldRemote-Gallery-Search');
464
+ if (tmpSearch)
465
+ {
466
+ tmpSearch.focus();
467
+ }
468
+ }
469
+
470
+ // ──────────────────────────────────────────────
471
+ // Help panel
472
+ // ──────────────────────────────────────────────
473
+
474
+ /**
475
+ * Toggle the keyboard shortcuts help panel.
476
+ * Works in both gallery and viewer modes, including fullscreen.
477
+ */
478
+ _toggleHelpPanel()
479
+ {
480
+ let tmpExisting = document.getElementById('RetoldRemote-Help-Panel');
481
+ if (tmpExisting)
482
+ {
483
+ tmpExisting.remove();
484
+ this._helpPanelVisible = false;
485
+ return;
486
+ }
487
+
488
+ this._helpPanelVisible = true;
489
+
490
+ let tmpPanel = document.createElement('div');
491
+ tmpPanel.id = 'RetoldRemote-Help-Panel';
492
+ tmpPanel.innerHTML = this._buildHelpPanelHTML();
493
+
494
+ // In fullscreen mode, append to the fullscreen element so it's visible;
495
+ // otherwise append to document.body
496
+ let tmpParent = document.fullscreenElement || document.body;
497
+ tmpParent.appendChild(tmpPanel);
498
+ }
499
+
500
+ /**
501
+ * Build the HTML for the help panel flyout.
502
+ */
503
+ _buildHelpPanelHTML()
504
+ {
505
+ let tmpRemote = this.pict.AppData.RetoldRemote;
506
+ let tmpActiveMode = tmpRemote.ActiveMode || 'gallery';
507
+
508
+ let tmpHTML = '<div class="retold-remote-help-backdrop" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleHelpPanel()">';
509
+ tmpHTML += '<div class="retold-remote-help-flyout" onclick="event.stopPropagation()">';
510
+
511
+ tmpHTML += '<div class="retold-remote-help-header">';
512
+ tmpHTML += '<span class="retold-remote-help-title">Keyboard Shortcuts</span>';
513
+ tmpHTML += '<button class="retold-remote-help-close" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleHelpPanel()">&times;</button>';
514
+ tmpHTML += '</div>';
515
+
516
+ // Gallery shortcuts
517
+ tmpHTML += '<div class="retold-remote-help-section">';
518
+ tmpHTML += '<div class="retold-remote-help-section-title">Gallery / File List</div>';
519
+
520
+ let tmpGalleryShortcuts =
521
+ [
522
+ ['← → ↑ ↓', 'Navigate tiles'],
523
+ ['Enter', 'Open selected item'],
524
+ ['Escape', 'Go up one folder'],
525
+ ['Home / End', 'Jump to first / last'],
526
+ ['g', 'Toggle gallery / list view'],
527
+ ['/', 'Focus search bar'],
528
+ ['f', 'Toggle filter panel'],
529
+ ['s', 'Focus sort dropdown'],
530
+ ['c', 'Settings / config panel'],
531
+ ['d', 'Distraction-free mode']
532
+ ];
533
+ for (let i = 0; i < tmpGalleryShortcuts.length; i++)
534
+ {
535
+ tmpHTML += '<div class="retold-remote-help-row">';
536
+ tmpHTML += '<kbd class="retold-remote-help-key">' + tmpGalleryShortcuts[i][0] + '</kbd>';
537
+ tmpHTML += '<span class="retold-remote-help-desc">' + tmpGalleryShortcuts[i][1] + '</span>';
538
+ tmpHTML += '</div>';
539
+ }
540
+ tmpHTML += '</div>';
541
+
542
+ // Viewer shortcuts
543
+ tmpHTML += '<div class="retold-remote-help-section">';
544
+ tmpHTML += '<div class="retold-remote-help-section-title">Media Viewer</div>';
545
+
546
+ let tmpViewerShortcuts =
547
+ [
548
+ ['← / k', 'Previous file'],
549
+ ['→ / j', 'Next file'],
550
+ ['Escape', 'Back to gallery'],
551
+ ['f', 'Toggle fullscreen'],
552
+ ['i', 'Toggle file info'],
553
+ ['Space', 'Play / pause media'],
554
+ ['z', 'Cycle fit mode'],
555
+ ['+ / -', 'Zoom in / out'],
556
+ ['0', 'Reset zoom'],
557
+ ['d', 'Distraction-free mode']
558
+ ];
559
+ for (let i = 0; i < tmpViewerShortcuts.length; i++)
560
+ {
561
+ tmpHTML += '<div class="retold-remote-help-row">';
562
+ tmpHTML += '<kbd class="retold-remote-help-key">' + tmpViewerShortcuts[i][0] + '</kbd>';
563
+ tmpHTML += '<span class="retold-remote-help-desc">' + tmpViewerShortcuts[i][1] + '</span>';
564
+ tmpHTML += '</div>';
565
+ }
566
+ tmpHTML += '</div>';
567
+
568
+ // Global
569
+ tmpHTML += '<div class="retold-remote-help-section">';
570
+ tmpHTML += '<div class="retold-remote-help-section-title">Global</div>';
571
+
572
+ let tmpGlobalShortcuts =
573
+ [
574
+ ['F1', 'Toggle this help panel'],
575
+ ['Escape', 'Close help panel']
576
+ ];
577
+ for (let i = 0; i < tmpGlobalShortcuts.length; i++)
578
+ {
579
+ tmpHTML += '<div class="retold-remote-help-row">';
580
+ tmpHTML += '<kbd class="retold-remote-help-key">' + tmpGlobalShortcuts[i][0] + '</kbd>';
581
+ tmpHTML += '<span class="retold-remote-help-desc">' + tmpGlobalShortcuts[i][1] + '</span>';
582
+ tmpHTML += '</div>';
583
+ }
584
+ tmpHTML += '</div>';
585
+
586
+ // Active mode indicator
587
+ tmpHTML += '<div class="retold-remote-help-footer">';
588
+ tmpHTML += 'Current mode: <strong>' + (tmpActiveMode === 'viewer' ? 'Media Viewer' : 'Gallery') + '</strong>';
589
+ tmpHTML += '</div>';
590
+
591
+ tmpHTML += '</div>'; // end flyout
592
+ tmpHTML += '</div>'; // end backdrop
593
+
594
+ return tmpHTML;
595
+ }
596
+
597
+ /**
598
+ * F2 — Toggle the settings/configuration panel.
599
+ * Opens the sidebar if collapsed, switches to the Settings tab,
600
+ * or toggles back to the Files tab if Settings is already showing.
601
+ */
602
+ _toggleSettingsPanel()
603
+ {
604
+ let tmpLayoutView = this.pict.views['ContentEditor-Layout'];
605
+ if (!tmpLayoutView)
606
+ {
607
+ return;
608
+ }
609
+
610
+ let tmpWrap = document.querySelector('.content-editor-sidebar-wrap');
611
+ if (!tmpWrap)
612
+ {
613
+ return;
614
+ }
615
+
616
+ let tmpIsCollapsed = tmpWrap.classList.contains('collapsed');
617
+ let tmpSettingsTab = document.querySelector('.content-editor-sidebar-tab[data-tab="settings"]');
618
+ let tmpIsSettingsActive = tmpSettingsTab && tmpSettingsTab.classList.contains('active');
619
+
620
+ if (tmpIsCollapsed)
621
+ {
622
+ // Sidebar is collapsed: open it and switch to settings
623
+ tmpLayoutView.toggleSidebar();
624
+ tmpLayoutView.switchSidebarTab('settings');
625
+ }
626
+ else if (tmpIsSettingsActive)
627
+ {
628
+ // Settings already showing: switch back to files
629
+ tmpLayoutView.switchSidebarTab('files');
630
+ }
631
+ else
632
+ {
633
+ // Sidebar open on files: switch to settings
634
+ tmpLayoutView.switchSidebarTab('settings');
635
+ }
636
+ }
637
+
638
+ /**
639
+ * F3 — Drop down the saved filter presets list.
640
+ * If the filter panel is closed, open it first.
641
+ * Then focus/open the preset select dropdown.
642
+ */
643
+ _toggleFilterPresets()
644
+ {
645
+ let tmpRemote = this.pict.AppData.RetoldRemote;
646
+ let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
647
+
648
+ // Only works in gallery mode
649
+ if (tmpRemote.ActiveMode !== 'gallery')
650
+ {
651
+ return;
652
+ }
653
+
654
+ // Ensure filter panel is open
655
+ if (!tmpRemote.FilterPanelOpen && tmpGalleryView)
656
+ {
657
+ tmpGalleryView.toggleFilterPanel();
658
+ }
659
+
660
+ // Focus the preset select dropdown after a brief render delay
661
+ setTimeout(() =>
662
+ {
663
+ let tmpPresetSelect = document.getElementById('RetoldRemote-Filter-PresetSelect');
664
+ if (tmpPresetSelect)
665
+ {
666
+ tmpPresetSelect.focus();
667
+ // Programmatically open the dropdown
668
+ tmpPresetSelect.click();
669
+ }
670
+ else
671
+ {
672
+ // No presets saved yet — focus the preset name input instead
673
+ let tmpPresetInput = document.getElementById('RetoldRemote-Filter-PresetName');
674
+ if (tmpPresetInput)
675
+ {
676
+ tmpPresetInput.focus();
677
+ }
678
+ }
679
+ }, 50);
680
+ }
681
+
682
+ /**
683
+ * F4 — Toggle distraction-free mode.
684
+ * Hides/shows both the sidebar and the top bar.
685
+ */
686
+ _toggleDistractionFree()
687
+ {
688
+ let tmpRemote = this.pict.AppData.RetoldRemote;
689
+
690
+ let tmpTopBar = document.getElementById('ContentEditor-TopBar-Container');
691
+ let tmpSidebarWrap = document.querySelector('.content-editor-sidebar-wrap');
692
+
693
+ if (!tmpTopBar || !tmpSidebarWrap)
694
+ {
695
+ return;
696
+ }
697
+
698
+ // Check current state — if either is visible, hide both; if both hidden, show both
699
+ let tmpIsDistractionFree = tmpRemote._distractionFreeMode || false;
700
+
701
+ if (tmpIsDistractionFree)
702
+ {
703
+ // Restore
704
+ tmpTopBar.style.display = '';
705
+ tmpSidebarWrap.style.display = '';
706
+ tmpRemote._distractionFreeMode = false;
707
+
708
+ // Restore viewer nav header
709
+ let tmpViewerHeader = document.querySelector('.retold-remote-viewer-header');
710
+ if (tmpViewerHeader)
711
+ {
712
+ tmpViewerHeader.style.display = '';
713
+ }
714
+ }
715
+ else
716
+ {
717
+ // Hide
718
+ tmpTopBar.style.display = 'none';
719
+ tmpSidebarWrap.style.display = 'none';
720
+ tmpRemote._distractionFreeMode = true;
721
+
722
+ // Hide viewer nav header if setting says so
723
+ if (!tmpRemote.DistractionFreeShowNav)
724
+ {
725
+ let tmpViewerHeader = document.querySelector('.retold-remote-viewer-header');
726
+ if (tmpViewerHeader)
727
+ {
728
+ tmpViewerHeader.style.display = 'none';
729
+ }
730
+ }
731
+ }
732
+
733
+ // Recalculate gallery columns after layout change
734
+ setTimeout(() => this.recalculateColumns(), 100);
735
+ }
736
+
737
+ _toggleFullscreen()
738
+ {
739
+ let tmpViewer = document.getElementById('RetoldRemote-Viewer-Container');
740
+ if (!tmpViewer) return;
741
+
742
+ if (document.fullscreenElement)
743
+ {
744
+ document.exitFullscreen();
745
+ }
746
+ else
747
+ {
748
+ tmpViewer.requestFullscreen();
749
+ }
750
+ }
751
+
752
+ _toggleFileInfo()
753
+ {
754
+ let tmpInfoOverlay = document.getElementById('RetoldRemote-FileInfo-Overlay');
755
+ if (tmpInfoOverlay)
756
+ {
757
+ tmpInfoOverlay.style.display = (tmpInfoOverlay.style.display === 'none') ? '' : 'none';
758
+ }
759
+ }
760
+
761
+ _togglePlayPause()
762
+ {
763
+ let tmpVideo = document.querySelector('#RetoldRemote-Viewer-Container video');
764
+ let tmpAudio = document.querySelector('#RetoldRemote-Viewer-Container audio');
765
+ let tmpMedia = tmpVideo || tmpAudio;
766
+
767
+ if (tmpMedia)
768
+ {
769
+ if (tmpMedia.paused)
770
+ {
771
+ tmpMedia.play();
772
+ }
773
+ else
774
+ {
775
+ tmpMedia.pause();
776
+ }
777
+ }
778
+ }
779
+
780
+ _zoomIn()
781
+ {
782
+ let tmpImageViewer = this.pict.views['RetoldRemote-ImageViewer'];
783
+ if (tmpImageViewer && tmpImageViewer.zoomIn)
784
+ {
785
+ tmpImageViewer.zoomIn();
786
+ }
787
+ }
788
+
789
+ _zoomOut()
790
+ {
791
+ let tmpImageViewer = this.pict.views['RetoldRemote-ImageViewer'];
792
+ if (tmpImageViewer && tmpImageViewer.zoomOut)
793
+ {
794
+ tmpImageViewer.zoomOut();
795
+ }
796
+ }
797
+
798
+ _zoomReset()
799
+ {
800
+ let tmpImageViewer = this.pict.views['RetoldRemote-ImageViewer'];
801
+ if (tmpImageViewer && tmpImageViewer.zoomReset)
802
+ {
803
+ tmpImageViewer.zoomReset();
804
+ }
805
+ }
806
+
807
+ _cycleFitMode()
808
+ {
809
+ let tmpImageViewer = this.pict.views['RetoldRemote-ImageViewer'];
810
+ if (tmpImageViewer && tmpImageViewer.cycleFitMode)
811
+ {
812
+ tmpImageViewer.cycleFitMode();
813
+ }
814
+ }
815
+ }
816
+
817
+ GalleryNavigationProvider.default_configuration = _DefaultProviderConfiguration;
818
+
819
+ module.exports = GalleryNavigationProvider;