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.
- package/LICENSE +21 -0
- package/css/retold-remote.css +83 -0
- package/html/codejar.js +511 -0
- package/html/index.html +23 -0
- package/package.json +68 -0
- package/server.js +43 -0
- package/source/Pict-Application-RetoldRemote-Configuration.json +7 -0
- package/source/Pict-Application-RetoldRemote.js +622 -0
- package/source/Pict-RetoldRemote-Bundle.js +14 -0
- package/source/cli/RetoldRemote-CLI-Program.js +15 -0
- package/source/cli/RetoldRemote-CLI-Run.js +3 -0
- package/source/cli/RetoldRemote-Server-Setup.js +257 -0
- package/source/cli/commands/RetoldRemote-Command-Serve.js +87 -0
- package/source/providers/Pict-Provider-GalleryFilterSort.js +597 -0
- package/source/providers/Pict-Provider-GalleryNavigation.js +819 -0
- package/source/providers/Pict-Provider-RetoldRemote.js +273 -0
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +640 -0
- package/source/providers/Pict-Provider-RetoldRemoteTheme.js +879 -0
- package/source/server/RetoldRemote-MediaService.js +536 -0
- package/source/server/RetoldRemote-PathRegistry.js +121 -0
- package/source/server/RetoldRemote-ThumbnailCache.js +89 -0
- package/source/server/RetoldRemote-ToolDetector.js +78 -0
- package/source/views/PictView-Remote-Gallery.js +1437 -0
- package/source/views/PictView-Remote-ImageViewer.js +363 -0
- package/source/views/PictView-Remote-Layout.js +420 -0
- package/source/views/PictView-Remote-MediaViewer.js +530 -0
- package/source/views/PictView-Remote-SettingsPanel.js +318 -0
- package/source/views/PictView-Remote-TopBar.js +206 -0
- package/web-application/codejar.js +511 -0
- package/web-application/css/retold-remote.css +83 -0
- package/web-application/index.html +23 -0
- package/web-application/js/pict.min.js +12 -0
- package/web-application/js/pict.min.js.map +1 -0
- package/web-application/retold-remote.compatible.js +5764 -0
- package/web-application/retold-remote.compatible.js.map +1 -0
- package/web-application/retold-remote.compatible.min.js +120 -0
- package/web-application/retold-remote.compatible.min.js.map +1 -0
- package/web-application/retold-remote.js +5763 -0
- package/web-application/retold-remote.js.map +1 -0
- package/web-application/retold-remote.min.js +120 -0
- 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()">×</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;
|