retold-remote 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/README.md +181 -0
- package/docs/_cover.md +14 -0
- package/docs/_sidebar.md +10 -0
- package/docs/_topbar.md +3 -0
- package/docs/audio-viewer.md +133 -0
- package/docs/ebook-reader.md +90 -0
- package/docs/image-viewer.md +90 -0
- package/docs/server-setup.md +262 -0
- package/docs/video-viewer.md +134 -0
- package/html/docs.html +59 -0
- package/package.json +21 -7
- package/source/Pict-Application-RetoldRemote.js +143 -2
- package/source/RetoldRemote-ExtensionMaps.js +33 -0
- package/source/cli/RetoldRemote-Server-Setup.js +82 -67
- package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
- package/source/providers/Pict-Provider-CollectionManager.js +934 -0
- package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
- package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
- package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
- package/source/providers/Pict-Provider-ToastNotification.js +96 -0
- package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
- package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
- package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
- package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
- package/source/server/RetoldRemote-ArchiveService.js +2 -12
- package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
- package/source/server/RetoldRemote-CollectionService.js +684 -0
- package/source/server/RetoldRemote-EbookService.js +7 -16
- package/source/server/RetoldRemote-MediaService.js +3 -14
- package/source/server/RetoldRemote-ParimeCache.js +349 -0
- package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
- package/source/server/RetoldRemote-VideoFrameService.js +7 -15
- package/source/views/PictView-Remote-AudioExplorer.js +10 -43
- package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
- package/source/views/PictView-Remote-Gallery.js +237 -44
- package/source/views/PictView-Remote-ImageViewer.js +1 -34
- package/source/views/PictView-Remote-Layout.js +410 -20
- package/source/views/PictView-Remote-MediaViewer.js +338 -51
- package/source/views/PictView-Remote-SettingsPanel.js +155 -138
- package/source/views/PictView-Remote-TopBar.js +615 -14
- package/source/views/PictView-Remote-VLCSetup.js +766 -0
- package/source/views/PictView-Remote-VideoExplorer.js +20 -54
- package/web-application/css/docuserve.css +73 -0
- package/web-application/docs/README.md +181 -0
- package/web-application/docs/_cover.md +14 -0
- package/web-application/docs/_sidebar.md +10 -0
- package/web-application/docs/_topbar.md +3 -0
- package/web-application/docs/audio-viewer.md +133 -0
- package/web-application/docs/ebook-reader.md +90 -0
- package/web-application/docs/image-viewer.md +90 -0
- package/web-application/docs/server-setup.md +262 -0
- package/web-application/docs/video-viewer.md +134 -0
- package/web-application/docs.html +59 -0
- package/web-application/js/pict-docuserve.min.js +58 -0
- package/web-application/js/pict.min.js +2 -2
- package/web-application/js/pict.min.js.map +1 -1
- package/web-application/retold-remote.js +2558 -439
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +41 -11
- package/web-application/retold-remote.min.js.map +1 -1
- package/server.js +0 -43
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
const libPictProvider = require('pict-provider');
|
|
2
2
|
|
|
3
|
+
const libHandleGalleryKey = require('./keyboard-handlers/KeyHandler-Gallery.js');
|
|
4
|
+
const libHandleViewerKey = require('./keyboard-handlers/KeyHandler-Viewer.js');
|
|
5
|
+
const libHandleSidebarKey = require('./keyboard-handlers/KeyHandler-Sidebar.js');
|
|
6
|
+
const libHandleVideoExplorerKey = require('./keyboard-handlers/KeyHandler-VideoExplorer.js');
|
|
7
|
+
const libHandleAudioExplorerKey = require('./keyboard-handlers/KeyHandler-AudioExplorer.js');
|
|
8
|
+
|
|
3
9
|
const _DefaultProviderConfiguration =
|
|
4
10
|
{
|
|
5
11
|
ProviderIdentifier: 'RetoldRemote-GalleryNavigation',
|
|
@@ -171,109 +177,93 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
171
177
|
|
|
172
178
|
document.addEventListener('keydown', this._keydownHandler);
|
|
173
179
|
this._keydownBound = true;
|
|
180
|
+
|
|
181
|
+
// Set up edge-swipe gestures for distraction-free mode on touch devices
|
|
182
|
+
this._setupDFSwipeGestures();
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
/**
|
|
177
|
-
*
|
|
186
|
+
* Set up touch swipe gestures near the top edge for toggling
|
|
187
|
+
* distraction-free mode.
|
|
188
|
+
*
|
|
189
|
+
* - Swipe UP starting within the top 60px of the viewport → enter DF
|
|
190
|
+
* - Swipe DOWN starting within the top 40px of the viewport → exit DF
|
|
191
|
+
*
|
|
192
|
+
* Only single-finger vertical swipes that exceed the threshold are
|
|
193
|
+
* recognised. Horizontal movement greater than vertical is ignored.
|
|
178
194
|
*/
|
|
179
|
-
|
|
195
|
+
_setupDFSwipeGestures()
|
|
180
196
|
{
|
|
181
|
-
|
|
182
|
-
let tmpItems = tmpRemote.GalleryItems || [];
|
|
183
|
-
let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
|
|
184
|
-
|
|
185
|
-
switch (pEvent.key)
|
|
197
|
+
if (this._dfSwipeBound)
|
|
186
198
|
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.moveCursor(Math.min(tmpIndex + 1, tmpItems.length - 1));
|
|
190
|
-
break;
|
|
191
|
-
|
|
192
|
-
case 'ArrowLeft':
|
|
193
|
-
pEvent.preventDefault();
|
|
194
|
-
this.moveCursor(Math.max(tmpIndex - 1, 0));
|
|
195
|
-
break;
|
|
196
|
-
|
|
197
|
-
case 'ArrowDown':
|
|
198
|
-
pEvent.preventDefault();
|
|
199
|
-
this.moveCursor(Math.min(tmpIndex + this._columnsPerRow, tmpItems.length - 1));
|
|
200
|
-
break;
|
|
201
|
-
|
|
202
|
-
case 'ArrowUp':
|
|
203
|
-
pEvent.preventDefault();
|
|
204
|
-
this.moveCursor(Math.max(tmpIndex - this._columnsPerRow, 0));
|
|
205
|
-
break;
|
|
206
|
-
|
|
207
|
-
case 'Enter':
|
|
208
|
-
pEvent.preventDefault();
|
|
209
|
-
this.openCurrent();
|
|
210
|
-
break;
|
|
211
|
-
|
|
212
|
-
case 'Escape':
|
|
213
|
-
pEvent.preventDefault();
|
|
214
|
-
this.navigateUp();
|
|
215
|
-
break;
|
|
216
|
-
|
|
217
|
-
case 'g':
|
|
218
|
-
pEvent.preventDefault();
|
|
219
|
-
this._toggleViewMode();
|
|
220
|
-
break;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
221
201
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
202
|
+
let tmpSelf = this;
|
|
203
|
+
let tmpSwipeThreshold = 60;
|
|
204
|
+
let tmpEdgeEnter = 60;
|
|
205
|
+
let tmpEdgeExit = 40;
|
|
206
|
+
let tmpStartY = 0;
|
|
207
|
+
let tmpStartX = 0;
|
|
208
|
+
let tmpStartClientY = 0;
|
|
209
|
+
let tmpTouchCount = 0;
|
|
210
|
+
|
|
211
|
+
this._dfSwipeTouchStart = function (pEvent)
|
|
212
|
+
{
|
|
213
|
+
tmpTouchCount = pEvent.touches.length;
|
|
214
|
+
if (tmpTouchCount !== 1)
|
|
215
|
+
{
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
tmpStartX = pEvent.touches[0].clientX;
|
|
219
|
+
tmpStartY = pEvent.touches[0].clientY;
|
|
220
|
+
tmpStartClientY = tmpStartY;
|
|
221
|
+
};
|
|
226
222
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
this._dfSwipeTouchEnd = function (pEvent)
|
|
224
|
+
{
|
|
225
|
+
if (tmpTouchCount !== 1)
|
|
226
|
+
{
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
let tmpEndX = pEvent.changedTouches[0].clientX;
|
|
231
|
+
let tmpEndY = pEvent.changedTouches[0].clientY;
|
|
232
|
+
let tmpDeltaX = tmpEndX - tmpStartX;
|
|
233
|
+
let tmpDeltaY = tmpEndY - tmpStartY;
|
|
236
234
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
243
|
-
if (tmpGalleryView)
|
|
244
|
-
{
|
|
245
|
-
tmpGalleryView.toggleFilterPanel();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
235
|
+
// Must be primarily vertical
|
|
236
|
+
if (Math.abs(tmpDeltaY) < tmpSwipeThreshold || Math.abs(tmpDeltaX) > Math.abs(tmpDeltaY))
|
|
237
|
+
{
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
249
240
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
// Ensure the filter bar is visible first
|
|
254
|
-
this._showFilterBar();
|
|
255
|
-
setTimeout(() =>
|
|
256
|
-
{
|
|
257
|
-
let tmpSortSelect = document.getElementById('RetoldRemote-Gallery-Sort');
|
|
258
|
-
if (tmpSortSelect)
|
|
259
|
-
{
|
|
260
|
-
tmpSortSelect.focus();
|
|
261
|
-
}
|
|
262
|
-
}, 50);
|
|
263
|
-
}
|
|
264
|
-
break;
|
|
241
|
+
let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
|
|
242
|
+
let tmpIsDF = tmpRemote._distractionFreeMode || false;
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
244
|
+
if (!tmpIsDF && tmpDeltaY < 0 && tmpStartClientY <= tmpEdgeEnter)
|
|
245
|
+
{
|
|
246
|
+
// Swipe up from top edge → enter DF
|
|
247
|
+
tmpSelf._toggleDistractionFree();
|
|
248
|
+
}
|
|
249
|
+
else if (tmpIsDF && tmpDeltaY > 0 && tmpStartClientY <= tmpEdgeExit)
|
|
250
|
+
{
|
|
251
|
+
// Swipe down from top edge → exit DF
|
|
252
|
+
tmpSelf._toggleDistractionFree();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
270
255
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
256
|
+
document.addEventListener('touchstart', this._dfSwipeTouchStart, { passive: true });
|
|
257
|
+
document.addEventListener('touchend', this._dfSwipeTouchEnd, { passive: true });
|
|
258
|
+
this._dfSwipeBound = true;
|
|
259
|
+
}
|
|
275
260
|
|
|
276
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Handle keyboard events in gallery mode.
|
|
263
|
+
*/
|
|
264
|
+
_handleGalleryKey(pEvent)
|
|
265
|
+
{
|
|
266
|
+
libHandleGalleryKey(this, pEvent);
|
|
277
267
|
}
|
|
278
268
|
|
|
279
269
|
/**
|
|
@@ -281,60 +271,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
281
271
|
*/
|
|
282
272
|
_handleSidebarKey(pEvent)
|
|
283
273
|
{
|
|
284
|
-
|
|
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
|
-
}
|
|
274
|
+
libHandleSidebarKey(this, pEvent);
|
|
338
275
|
}
|
|
339
276
|
|
|
340
277
|
/**
|
|
@@ -415,131 +352,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
415
352
|
*/
|
|
416
353
|
_handleViewerKey(pEvent)
|
|
417
354
|
{
|
|
418
|
-
|
|
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
|
-
|
|
478
|
-
switch (pEvent.key)
|
|
479
|
-
{
|
|
480
|
-
case 'Escape':
|
|
481
|
-
pEvent.preventDefault();
|
|
482
|
-
this.closeViewer();
|
|
483
|
-
break;
|
|
484
|
-
|
|
485
|
-
case 'ArrowRight':
|
|
486
|
-
case 'j':
|
|
487
|
-
pEvent.preventDefault();
|
|
488
|
-
this.nextFile();
|
|
489
|
-
break;
|
|
490
|
-
|
|
491
|
-
case 'ArrowLeft':
|
|
492
|
-
case 'k':
|
|
493
|
-
pEvent.preventDefault();
|
|
494
|
-
this.prevFile();
|
|
495
|
-
break;
|
|
496
|
-
|
|
497
|
-
case 'f':
|
|
498
|
-
pEvent.preventDefault();
|
|
499
|
-
this._toggleFullscreen();
|
|
500
|
-
break;
|
|
501
|
-
|
|
502
|
-
case 'i':
|
|
503
|
-
pEvent.preventDefault();
|
|
504
|
-
this._toggleFileInfo();
|
|
505
|
-
break;
|
|
506
|
-
|
|
507
|
-
case ' ':
|
|
508
|
-
pEvent.preventDefault();
|
|
509
|
-
this._togglePlayPause();
|
|
510
|
-
break;
|
|
511
|
-
|
|
512
|
-
case '+':
|
|
513
|
-
case '=':
|
|
514
|
-
pEvent.preventDefault();
|
|
515
|
-
this._zoomIn();
|
|
516
|
-
break;
|
|
517
|
-
|
|
518
|
-
case '-':
|
|
519
|
-
pEvent.preventDefault();
|
|
520
|
-
this._zoomOut();
|
|
521
|
-
break;
|
|
522
|
-
|
|
523
|
-
case '0':
|
|
524
|
-
pEvent.preventDefault();
|
|
525
|
-
this._zoomReset();
|
|
526
|
-
break;
|
|
527
|
-
|
|
528
|
-
case 'z':
|
|
529
|
-
pEvent.preventDefault();
|
|
530
|
-
this._cycleFitMode();
|
|
531
|
-
break;
|
|
532
|
-
|
|
533
|
-
case 'Enter':
|
|
534
|
-
pEvent.preventDefault();
|
|
535
|
-
this._openWithVLC();
|
|
536
|
-
break;
|
|
537
|
-
|
|
538
|
-
case 'd':
|
|
539
|
-
pEvent.preventDefault();
|
|
540
|
-
this._toggleDistractionFree();
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
355
|
+
libHandleViewerKey(this, pEvent);
|
|
543
356
|
}
|
|
544
357
|
|
|
545
358
|
/**
|
|
@@ -547,17 +360,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
547
360
|
*/
|
|
548
361
|
_handleVideoExplorerKey(pEvent)
|
|
549
362
|
{
|
|
550
|
-
|
|
551
|
-
{
|
|
552
|
-
case 'Escape':
|
|
553
|
-
pEvent.preventDefault();
|
|
554
|
-
let tmpVEX = this.pict.views['RetoldRemote-VideoExplorer'];
|
|
555
|
-
if (tmpVEX)
|
|
556
|
-
{
|
|
557
|
-
tmpVEX.goBack();
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
363
|
+
libHandleVideoExplorerKey(this, pEvent);
|
|
561
364
|
}
|
|
562
365
|
|
|
563
366
|
/**
|
|
@@ -565,49 +368,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
565
368
|
*/
|
|
566
369
|
_handleAudioExplorerKey(pEvent)
|
|
567
370
|
{
|
|
568
|
-
|
|
569
|
-
if (!tmpAEX)
|
|
570
|
-
{
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
switch (pEvent.key)
|
|
575
|
-
{
|
|
576
|
-
case 'Escape':
|
|
577
|
-
pEvent.preventDefault();
|
|
578
|
-
if (tmpAEX._selectionStart >= 0)
|
|
579
|
-
{
|
|
580
|
-
tmpAEX.clearSelection();
|
|
581
|
-
}
|
|
582
|
-
else
|
|
583
|
-
{
|
|
584
|
-
tmpAEX.goBack();
|
|
585
|
-
}
|
|
586
|
-
break;
|
|
587
|
-
case '+':
|
|
588
|
-
case '=':
|
|
589
|
-
pEvent.preventDefault();
|
|
590
|
-
tmpAEX.zoomIn();
|
|
591
|
-
break;
|
|
592
|
-
case '-':
|
|
593
|
-
case '_':
|
|
594
|
-
pEvent.preventDefault();
|
|
595
|
-
tmpAEX.zoomOut();
|
|
596
|
-
break;
|
|
597
|
-
case '0':
|
|
598
|
-
pEvent.preventDefault();
|
|
599
|
-
tmpAEX.zoomToFit();
|
|
600
|
-
break;
|
|
601
|
-
case 'z':
|
|
602
|
-
case 'Z':
|
|
603
|
-
pEvent.preventDefault();
|
|
604
|
-
tmpAEX.zoomToSelection();
|
|
605
|
-
break;
|
|
606
|
-
case ' ':
|
|
607
|
-
pEvent.preventDefault();
|
|
608
|
-
tmpAEX.playSelection();
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
371
|
+
libHandleAudioExplorerKey(this, pEvent);
|
|
611
372
|
}
|
|
612
373
|
|
|
613
374
|
/**
|
|
@@ -641,6 +402,13 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
641
402
|
// Scroll the tile into view if needed
|
|
642
403
|
tmpNewTile.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
643
404
|
}
|
|
405
|
+
|
|
406
|
+
// Update the top bar info to reflect the new cursor position
|
|
407
|
+
let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
|
|
408
|
+
if (tmpTopBar)
|
|
409
|
+
{
|
|
410
|
+
tmpTopBar.updateInfo();
|
|
411
|
+
}
|
|
644
412
|
}
|
|
645
413
|
|
|
646
414
|
/**
|
|
@@ -683,6 +451,59 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
683
451
|
}
|
|
684
452
|
}
|
|
685
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Open the currently selected gallery item, forcing a specific media type
|
|
456
|
+
* regardless of its file extension.
|
|
457
|
+
*
|
|
458
|
+
* @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
|
|
459
|
+
*/
|
|
460
|
+
openCurrentAs(pMediaType)
|
|
461
|
+
{
|
|
462
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
463
|
+
let tmpItems = tmpRemote.GalleryItems || [];
|
|
464
|
+
let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
|
|
465
|
+
|
|
466
|
+
if (tmpIndex >= tmpItems.length)
|
|
467
|
+
{
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let tmpItem = tmpItems[tmpIndex];
|
|
472
|
+
|
|
473
|
+
if (tmpItem.Type === 'folder' || tmpItem.Type === 'archive')
|
|
474
|
+
{
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let tmpApp = this.pict.PictApplication;
|
|
479
|
+
if (tmpApp && tmpApp.navigateToFileAs)
|
|
480
|
+
{
|
|
481
|
+
tmpApp.navigateToFileAs(tmpItem.Path, pMediaType);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Re-open the currently viewed file with a different media type.
|
|
487
|
+
*
|
|
488
|
+
* @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
|
|
489
|
+
*/
|
|
490
|
+
switchViewerType(pMediaType)
|
|
491
|
+
{
|
|
492
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
493
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
494
|
+
|
|
495
|
+
if (!tmpFilePath)
|
|
496
|
+
{
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
501
|
+
if (tmpViewer)
|
|
502
|
+
{
|
|
503
|
+
tmpViewer.showMedia(tmpFilePath, pMediaType);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
686
507
|
/**
|
|
687
508
|
* Navigate up one directory level.
|
|
688
509
|
*/
|
|
@@ -717,12 +538,30 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
717
538
|
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
718
539
|
tmpRemote.ActiveMode = 'gallery';
|
|
719
540
|
|
|
541
|
+
// Exit collection browsing mode
|
|
542
|
+
tmpRemote.BrowsingCollection = false;
|
|
543
|
+
tmpRemote.BrowsingCollectionIndex = -1;
|
|
544
|
+
|
|
720
545
|
let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
|
|
721
546
|
let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
|
|
722
547
|
|
|
723
548
|
if (tmpGalleryContainer) tmpGalleryContainer.style.display = '';
|
|
724
549
|
if (tmpViewerContainer) tmpViewerContainer.style.display = 'none';
|
|
725
550
|
|
|
551
|
+
// Clean up swipe and DF exit listeners
|
|
552
|
+
let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
553
|
+
if (tmpMediaViewer)
|
|
554
|
+
{
|
|
555
|
+
if (tmpMediaViewer._cleanupSwipe)
|
|
556
|
+
{
|
|
557
|
+
tmpMediaViewer._cleanupSwipe();
|
|
558
|
+
}
|
|
559
|
+
if (tmpMediaViewer._cleanupDFExitHotspot)
|
|
560
|
+
{
|
|
561
|
+
tmpMediaViewer._cleanupDFExitHotspot();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
726
565
|
// Restore the hash to the browse route (use hashed identifier when available)
|
|
727
566
|
let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
|
|
728
567
|
let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
@@ -739,10 +578,37 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
739
578
|
|
|
740
579
|
/**
|
|
741
580
|
* Navigate to the next file in the gallery list.
|
|
581
|
+
* When browsing a collection, navigates through collection items instead.
|
|
742
582
|
*/
|
|
743
583
|
nextFile()
|
|
744
584
|
{
|
|
745
585
|
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
586
|
+
|
|
587
|
+
// Collection browsing mode — navigate through ActiveCollection.Items
|
|
588
|
+
if (tmpRemote.BrowsingCollection && tmpRemote.ActiveCollection)
|
|
589
|
+
{
|
|
590
|
+
let tmpCollItems = tmpRemote.ActiveCollection.Items || [];
|
|
591
|
+
let tmpCollIndex = tmpRemote.BrowsingCollectionIndex;
|
|
592
|
+
|
|
593
|
+
for (let i = tmpCollIndex + 1; i < tmpCollItems.length; i++)
|
|
594
|
+
{
|
|
595
|
+
let tmpItem = tmpCollItems[i];
|
|
596
|
+
if (tmpItem.Type === 'file' || tmpItem.Type === 'subfile' ||
|
|
597
|
+
tmpItem.Type === 'image-crop' || tmpItem.Type === 'video-clip' ||
|
|
598
|
+
tmpItem.Type === 'video-frame')
|
|
599
|
+
{
|
|
600
|
+
tmpRemote.BrowsingCollectionIndex = i;
|
|
601
|
+
let tmpApp = this.pict.PictApplication;
|
|
602
|
+
if (tmpApp && tmpApp.navigateToFile)
|
|
603
|
+
{
|
|
604
|
+
tmpApp.navigateToFile(tmpItem.Path);
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
746
612
|
let tmpItems = tmpRemote.GalleryItems || [];
|
|
747
613
|
let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
|
|
748
614
|
|
|
@@ -764,10 +630,37 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
764
630
|
|
|
765
631
|
/**
|
|
766
632
|
* Navigate to the previous file in the gallery list.
|
|
633
|
+
* When browsing a collection, navigates through collection items instead.
|
|
767
634
|
*/
|
|
768
635
|
prevFile()
|
|
769
636
|
{
|
|
770
637
|
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
638
|
+
|
|
639
|
+
// Collection browsing mode — navigate through ActiveCollection.Items
|
|
640
|
+
if (tmpRemote.BrowsingCollection && tmpRemote.ActiveCollection)
|
|
641
|
+
{
|
|
642
|
+
let tmpCollItems = tmpRemote.ActiveCollection.Items || [];
|
|
643
|
+
let tmpCollIndex = tmpRemote.BrowsingCollectionIndex;
|
|
644
|
+
|
|
645
|
+
for (let i = tmpCollIndex - 1; i >= 0; i--)
|
|
646
|
+
{
|
|
647
|
+
let tmpItem = tmpCollItems[i];
|
|
648
|
+
if (tmpItem.Type === 'file' || tmpItem.Type === 'subfile' ||
|
|
649
|
+
tmpItem.Type === 'image-crop' || tmpItem.Type === 'video-clip' ||
|
|
650
|
+
tmpItem.Type === 'video-frame')
|
|
651
|
+
{
|
|
652
|
+
tmpRemote.BrowsingCollectionIndex = i;
|
|
653
|
+
let tmpApp = this.pict.PictApplication;
|
|
654
|
+
if (tmpApp && tmpApp.navigateToFile)
|
|
655
|
+
{
|
|
656
|
+
tmpApp.navigateToFile(tmpItem.Path);
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
771
664
|
let tmpItems = tmpRemote.GalleryItems || [];
|
|
772
665
|
let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
|
|
773
666
|
|
|
@@ -882,6 +775,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
882
775
|
{
|
|
883
776
|
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
884
777
|
tmpRemote.FilterBarVisible = false;
|
|
778
|
+
tmpRemote.FilterPanelOpen = false;
|
|
885
779
|
|
|
886
780
|
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
887
781
|
if (tmpGalleryView)
|
|
@@ -901,7 +795,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
901
795
|
tmpGalleryView.clearAllFilters();
|
|
902
796
|
}
|
|
903
797
|
|
|
904
|
-
this.
|
|
798
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Filters cleared');
|
|
905
799
|
}
|
|
906
800
|
|
|
907
801
|
// ──────────────────────────────────────────────
|
|
@@ -967,7 +861,8 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
967
861
|
['s', 'Focus sort dropdown'],
|
|
968
862
|
['x', 'Clear all filters'],
|
|
969
863
|
['c', 'Settings / config panel'],
|
|
970
|
-
['d', 'Distraction-free mode']
|
|
864
|
+
['d', 'Distraction-free mode'],
|
|
865
|
+
['e', 'Video explorer (on video files)']
|
|
971
866
|
];
|
|
972
867
|
for (let i = 0; i < tmpGalleryShortcuts.length; i++)
|
|
973
868
|
{
|
|
@@ -1044,6 +939,14 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
1044
939
|
}
|
|
1045
940
|
tmpHTML += '</div>';
|
|
1046
941
|
|
|
942
|
+
// Documentation link
|
|
943
|
+
tmpHTML += '<div class="retold-remote-help-section">';
|
|
944
|
+
tmpHTML += '<div class="retold-remote-help-row" style="justify-content: center; padding: 8px 0;">';
|
|
945
|
+
tmpHTML += '<a href="docs.html" target="_blank" style="color: var(--retold-accent, #569cd6); text-decoration: none; font-size: 0.9rem; cursor: pointer;">';
|
|
946
|
+
tmpHTML += 'View Documentation ↗</a>';
|
|
947
|
+
tmpHTML += '</div>';
|
|
948
|
+
tmpHTML += '</div>';
|
|
949
|
+
|
|
1047
950
|
// Active mode indicator
|
|
1048
951
|
tmpHTML += '<div class="retold-remote-help-footer">';
|
|
1049
952
|
let tmpModeLabel = 'Gallery';
|
|
@@ -1195,6 +1098,13 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
1195
1098
|
}
|
|
1196
1099
|
}
|
|
1197
1100
|
|
|
1101
|
+
// Sync DF toggle/hotspot in the media viewer
|
|
1102
|
+
let tmpMediaViewer = this.pict.views['RetoldRemote-MediaViewer'];
|
|
1103
|
+
if (tmpMediaViewer && tmpMediaViewer._updateDFControls)
|
|
1104
|
+
{
|
|
1105
|
+
tmpMediaViewer._updateDFControls();
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1198
1108
|
// Recalculate gallery columns after layout change
|
|
1199
1109
|
setTimeout(() => this.recalculateColumns(), 100);
|
|
1200
1110
|
}
|
|
@@ -1232,7 +1142,8 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
1232
1142
|
let tmpInfoOverlay = document.getElementById('RetoldRemote-FileInfo-Overlay');
|
|
1233
1143
|
if (tmpInfoOverlay)
|
|
1234
1144
|
{
|
|
1235
|
-
|
|
1145
|
+
let tmpIsHidden = window.getComputedStyle(tmpInfoOverlay).display === 'none';
|
|
1146
|
+
tmpInfoOverlay.style.display = tmpIsHidden ? 'block' : 'none';
|
|
1236
1147
|
}
|
|
1237
1148
|
}
|
|
1238
1149
|
|
|
@@ -1318,7 +1229,7 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
1318
1229
|
}
|
|
1319
1230
|
|
|
1320
1231
|
// Show a brief toast
|
|
1321
|
-
this.
|
|
1232
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Opening in VLC...');
|
|
1322
1233
|
|
|
1323
1234
|
// POST to the server to open the file
|
|
1324
1235
|
fetch('/api/media/open',
|
|
@@ -1335,50 +1246,56 @@ class GalleryNavigationProvider extends libPictProvider
|
|
|
1335
1246
|
{
|
|
1336
1247
|
if (!pData.Success)
|
|
1337
1248
|
{
|
|
1338
|
-
this.
|
|
1249
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Failed to open: ' + (pData.Error || 'Unknown error'));
|
|
1339
1250
|
}
|
|
1340
1251
|
})
|
|
1341
1252
|
.catch((pError) =>
|
|
1342
1253
|
{
|
|
1343
|
-
this.
|
|
1254
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Failed to open: ' + pError.message);
|
|
1344
1255
|
});
|
|
1345
1256
|
}
|
|
1346
1257
|
|
|
1347
1258
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
* @param {string} pMessage - Text to display
|
|
1259
|
+
* Stream the current media file to VLC on the client device via vlc:// protocol link.
|
|
1351
1260
|
*/
|
|
1352
|
-
|
|
1261
|
+
_streamWithVLC()
|
|
1353
1262
|
{
|
|
1354
|
-
let
|
|
1355
|
-
|
|
1356
|
-
{
|
|
1357
|
-
tmpIndicator = document.createElement('div');
|
|
1358
|
-
tmpIndicator.id = 'RetoldRemote-FitIndicator';
|
|
1359
|
-
tmpIndicator.className = 'retold-remote-fit-indicator';
|
|
1263
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1264
|
+
let tmpMediaType = tmpRemote.CurrentViewerMediaType;
|
|
1360
1265
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
tmpContainer.appendChild(tmpIndicator);
|
|
1365
|
-
}
|
|
1266
|
+
if (tmpMediaType !== 'video' && tmpMediaType !== 'audio')
|
|
1267
|
+
{
|
|
1268
|
+
return;
|
|
1366
1269
|
}
|
|
1367
1270
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
if (this._toastTimeout)
|
|
1271
|
+
let tmpFilePath = tmpRemote.CurrentViewerFile;
|
|
1272
|
+
if (!tmpFilePath)
|
|
1372
1273
|
{
|
|
1373
|
-
|
|
1274
|
+
return;
|
|
1374
1275
|
}
|
|
1375
1276
|
|
|
1376
|
-
let
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1277
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
1278
|
+
let tmpContentPath = tmpProvider ? tmpProvider.getContentURL(tmpFilePath) : ('/content/' + encodeURIComponent(tmpFilePath));
|
|
1279
|
+
let tmpStreamURL = window.location.origin + tmpContentPath;
|
|
1280
|
+
// On Windows, VLC's native handler expects the raw URL.
|
|
1281
|
+
// On macOS/Linux our custom handlers URL-decode, so we encode.
|
|
1282
|
+
let tmpIsWindows = /Windows/.test(navigator.userAgent);
|
|
1283
|
+
let tmpVLCURL = tmpIsWindows
|
|
1284
|
+
? ('vlc://' + tmpStreamURL)
|
|
1285
|
+
: ('vlc://' + encodeURIComponent(tmpStreamURL));
|
|
1286
|
+
|
|
1287
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Opening VLC...');
|
|
1288
|
+
|
|
1289
|
+
// Use a temporary anchor element to trigger the protocol handler
|
|
1290
|
+
// without navigating the current page away
|
|
1291
|
+
let tmpLink = document.createElement('a');
|
|
1292
|
+
tmpLink.href = tmpVLCURL;
|
|
1293
|
+
tmpLink.style.display = 'none';
|
|
1294
|
+
document.body.appendChild(tmpLink);
|
|
1295
|
+
tmpLink.click();
|
|
1296
|
+
document.body.removeChild(tmpLink);
|
|
1381
1297
|
}
|
|
1298
|
+
|
|
1382
1299
|
}
|
|
1383
1300
|
|
|
1384
1301
|
GalleryNavigationProvider.default_configuration = _DefaultProviderConfiguration;
|