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.
Files changed (63) hide show
  1. package/docs/README.md +181 -0
  2. package/docs/_cover.md +14 -0
  3. package/docs/_sidebar.md +10 -0
  4. package/docs/_topbar.md +3 -0
  5. package/docs/audio-viewer.md +133 -0
  6. package/docs/ebook-reader.md +90 -0
  7. package/docs/image-viewer.md +90 -0
  8. package/docs/server-setup.md +262 -0
  9. package/docs/video-viewer.md +134 -0
  10. package/html/docs.html +59 -0
  11. package/package.json +21 -7
  12. package/source/Pict-Application-RetoldRemote.js +143 -2
  13. package/source/RetoldRemote-ExtensionMaps.js +33 -0
  14. package/source/cli/RetoldRemote-Server-Setup.js +82 -67
  15. package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
  16. package/source/providers/Pict-Provider-CollectionManager.js +934 -0
  17. package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
  18. package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
  19. package/source/providers/Pict-Provider-GalleryNavigation.js +270 -353
  20. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
  21. package/source/providers/Pict-Provider-ToastNotification.js +96 -0
  22. package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
  23. package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
  24. package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
  25. package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
  26. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
  27. package/source/server/RetoldRemote-ArchiveService.js +2 -12
  28. package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
  29. package/source/server/RetoldRemote-CollectionService.js +684 -0
  30. package/source/server/RetoldRemote-EbookService.js +7 -16
  31. package/source/server/RetoldRemote-MediaService.js +3 -14
  32. package/source/server/RetoldRemote-ParimeCache.js +349 -0
  33. package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
  34. package/source/server/RetoldRemote-VideoFrameService.js +7 -15
  35. package/source/views/PictView-Remote-AudioExplorer.js +10 -43
  36. package/source/views/PictView-Remote-CollectionsPanel.js +1087 -0
  37. package/source/views/PictView-Remote-Gallery.js +237 -44
  38. package/source/views/PictView-Remote-ImageViewer.js +1 -34
  39. package/source/views/PictView-Remote-Layout.js +410 -20
  40. package/source/views/PictView-Remote-MediaViewer.js +338 -51
  41. package/source/views/PictView-Remote-SettingsPanel.js +155 -138
  42. package/source/views/PictView-Remote-TopBar.js +615 -14
  43. package/source/views/PictView-Remote-VLCSetup.js +766 -0
  44. package/source/views/PictView-Remote-VideoExplorer.js +20 -54
  45. package/web-application/css/docuserve.css +73 -0
  46. package/web-application/docs/README.md +181 -0
  47. package/web-application/docs/_cover.md +14 -0
  48. package/web-application/docs/_sidebar.md +10 -0
  49. package/web-application/docs/_topbar.md +3 -0
  50. package/web-application/docs/audio-viewer.md +133 -0
  51. package/web-application/docs/ebook-reader.md +90 -0
  52. package/web-application/docs/image-viewer.md +90 -0
  53. package/web-application/docs/server-setup.md +262 -0
  54. package/web-application/docs/video-viewer.md +134 -0
  55. package/web-application/docs.html +59 -0
  56. package/web-application/js/pict-docuserve.min.js +58 -0
  57. package/web-application/js/pict.min.js +2 -2
  58. package/web-application/js/pict.min.js.map +1 -1
  59. package/web-application/retold-remote.js +2558 -439
  60. package/web-application/retold-remote.js.map +1 -1
  61. package/web-application/retold-remote.min.js +41 -11
  62. package/web-application/retold-remote.min.js.map +1 -1
  63. package/server.js +0 -43
@@ -20,22 +20,76 @@ const _ViewConfiguration =
20
20
  border-bottom: 1px solid var(--retold-border);
21
21
  gap: 16px;
22
22
  }
23
- .retold-remote-topbar-brand
23
+ .retold-remote-topbar-sidebar-toggle
24
+ {
25
+ flex-shrink: 0;
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: 32px;
30
+ height: 32px;
31
+ padding: 0;
32
+ margin: 0;
33
+ border: 1px solid var(--retold-border);
34
+ border-radius: 4px;
35
+ background: transparent;
36
+ color: var(--retold-text-muted);
37
+ font-size: 1rem;
38
+ line-height: 1;
39
+ cursor: pointer;
40
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
41
+ font-family: inherit;
42
+ -webkit-tap-highlight-color: transparent;
43
+ }
44
+ .retold-remote-topbar-sidebar-toggle:hover,
45
+ .retold-remote-topbar-sidebar-toggle:active,
46
+ .retold-remote-topbar-df-toggle:hover,
47
+ .retold-remote-topbar-df-toggle:active
48
+ {
49
+ color: var(--retold-text-primary);
50
+ border-color: var(--retold-accent);
51
+ background: rgba(128, 128, 128, 0.1);
52
+ }
53
+ .retold-remote-topbar-df-toggle
24
54
  {
25
- font-size: 0.85rem;
26
- font-weight: 700;
27
- color: var(--retold-accent);
28
55
  flex-shrink: 0;
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ width: 32px;
60
+ height: 32px;
61
+ padding: 0;
62
+ margin: 0;
63
+ border: 1px solid var(--retold-border);
64
+ border-radius: 4px;
65
+ background: transparent;
66
+ color: var(--retold-text-muted);
67
+ font-size: 1rem;
68
+ line-height: 1;
69
+ cursor: pointer;
70
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
71
+ font-family: inherit;
72
+ -webkit-tap-highlight-color: transparent;
29
73
  }
30
74
  .retold-remote-topbar-location
31
75
  {
76
+ position: relative;
32
77
  flex: 1;
33
78
  font-size: 0.82rem;
34
79
  color: var(--retold-text-muted);
80
+ white-space: nowrap;
81
+ text-align: center;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ overflow: visible;
86
+ min-width: 0;
87
+ }
88
+ .retold-remote-topbar-location-inner
89
+ {
35
90
  overflow: hidden;
36
91
  text-overflow: ellipsis;
37
92
  white-space: nowrap;
38
- text-align: center;
39
93
  }
40
94
  .retold-remote-topbar-location-crumb
41
95
  {
@@ -47,11 +101,120 @@ const _ViewConfiguration =
47
101
  {
48
102
  text-decoration: underline;
49
103
  }
104
+ .retold-remote-topbar-home-crumb
105
+ {
106
+ cursor: pointer;
107
+ display: inline-flex;
108
+ align-items: center;
109
+ vertical-align: middle;
110
+ opacity: 0.8;
111
+ }
112
+ .retold-remote-topbar-home-crumb:hover
113
+ {
114
+ opacity: 1;
115
+ }
50
116
  .retold-remote-topbar-sep
51
117
  {
52
118
  color: var(--retold-text-placeholder);
53
119
  margin: 0 3px;
54
120
  }
121
+ /* Breadcrumb overflow hamburger */
122
+ .retold-remote-topbar-breadcrumb-overflow
123
+ {
124
+ position: relative;
125
+ display: inline-flex;
126
+ align-items: center;
127
+ vertical-align: middle;
128
+ }
129
+ .retold-remote-topbar-overflow-btn
130
+ {
131
+ display: inline-flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ width: 28px;
135
+ height: 28px;
136
+ padding: 0;
137
+ margin: 0;
138
+ border: 1px solid var(--retold-border);
139
+ border-radius: 4px;
140
+ background: transparent;
141
+ color: var(--retold-text-muted);
142
+ font-size: 0.9rem;
143
+ line-height: 1;
144
+ cursor: pointer;
145
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
146
+ font-family: inherit;
147
+ -webkit-tap-highlight-color: transparent;
148
+ }
149
+ .retold-remote-topbar-overflow-btn:hover,
150
+ .retold-remote-topbar-overflow-btn:active
151
+ {
152
+ color: var(--retold-text-primary);
153
+ border-color: var(--retold-accent);
154
+ background: rgba(128, 128, 128, 0.1);
155
+ }
156
+ .retold-remote-topbar-overflow-dropdown
157
+ {
158
+ display: none;
159
+ position: absolute;
160
+ top: 100%;
161
+ left: 0;
162
+ margin-top: 4px;
163
+ min-width: 200px;
164
+ max-width: 300px;
165
+ background: var(--retold-bg-secondary);
166
+ border: 1px solid var(--retold-border);
167
+ border-radius: 6px;
168
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
169
+ z-index: 1000;
170
+ overflow: hidden;
171
+ }
172
+ .retold-remote-topbar-overflow-dropdown.open
173
+ {
174
+ display: block;
175
+ }
176
+ .retold-remote-topbar-overflow-item
177
+ {
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 8px;
181
+ width: 100%;
182
+ padding: 12px 16px;
183
+ border: none;
184
+ border-bottom: 1px solid var(--retold-border);
185
+ background: transparent;
186
+ color: var(--retold-text-primary);
187
+ font-size: 0.9rem;
188
+ text-align: left;
189
+ cursor: pointer;
190
+ font-family: inherit;
191
+ -webkit-tap-highlight-color: transparent;
192
+ min-height: 44px;
193
+ box-sizing: border-box;
194
+ }
195
+ .retold-remote-topbar-overflow-item:last-child
196
+ {
197
+ border-bottom: none;
198
+ }
199
+ .retold-remote-topbar-overflow-item:hover,
200
+ .retold-remote-topbar-overflow-item:active
201
+ {
202
+ background: rgba(128, 128, 128, 0.12);
203
+ color: var(--retold-accent);
204
+ }
205
+ .retold-remote-topbar-overflow-item-icon
206
+ {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ flex-shrink: 0;
210
+ opacity: 0.7;
211
+ }
212
+ .retold-remote-topbar-overflow-item-label
213
+ {
214
+ overflow: hidden;
215
+ text-overflow: ellipsis;
216
+ white-space: nowrap;
217
+ }
55
218
  .retold-remote-topbar-info
56
219
  {
57
220
  flex-shrink: 0;
@@ -81,6 +244,64 @@ const _ViewConfiguration =
81
244
  color: var(--retold-text-primary);
82
245
  border-color: var(--retold-accent);
83
246
  }
247
+ .retold-remote-topbar-addcoll-btn
248
+ {
249
+ font-size: 0.72rem;
250
+ }
251
+ .retold-remote-topbar-collections-btn.panel-open
252
+ {
253
+ color: var(--retold-accent);
254
+ border-color: var(--retold-accent);
255
+ background: rgba(128, 128, 128, 0.1);
256
+ }
257
+ /* Add-to-collection dropdown */
258
+ .retold-remote-addcoll-dropdown
259
+ {
260
+ position: absolute;
261
+ top: 100%;
262
+ right: 0;
263
+ margin-top: 4px;
264
+ min-width: 220px;
265
+ max-width: 320px;
266
+ max-height: 300px;
267
+ overflow-y: auto;
268
+ background: var(--retold-bg-secondary);
269
+ border: 1px solid var(--retold-border);
270
+ border-radius: 6px;
271
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
272
+ z-index: 1000;
273
+ }
274
+ .retold-remote-addcoll-dropdown-item
275
+ {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 8px;
279
+ width: 100%;
280
+ padding: 10px 14px;
281
+ border: none;
282
+ border-bottom: 1px solid var(--retold-border);
283
+ background: transparent;
284
+ color: var(--retold-text-primary);
285
+ font-size: 0.82rem;
286
+ text-align: left;
287
+ cursor: pointer;
288
+ font-family: inherit;
289
+ box-sizing: border-box;
290
+ }
291
+ .retold-remote-addcoll-dropdown-item:last-child
292
+ {
293
+ border-bottom: none;
294
+ }
295
+ .retold-remote-addcoll-dropdown-item:hover
296
+ {
297
+ background: rgba(128, 128, 128, 0.12);
298
+ color: var(--retold-accent);
299
+ }
300
+ .retold-remote-addcoll-dropdown-new
301
+ {
302
+ color: var(--retold-accent);
303
+ font-weight: 500;
304
+ }
84
305
  .retold-remote-topbar-filter-btn
85
306
  {
86
307
  position: relative;
@@ -136,12 +357,14 @@ const _ViewConfiguration =
136
357
  Hash: "RetoldRemote-TopBar",
137
358
  Template: /*html*/`
138
359
  <div class="retold-remote-topbar">
139
- <div class="retold-remote-topbar-brand">Retold Remote</div>
360
+ <button class="retold-remote-topbar-sidebar-toggle" id="RetoldRemote-TopBar-SidebarToggle" onclick="pict.views['ContentEditor-Layout'].toggleSidebar()" title="Toggle Sidebar"></button>
361
+ <button class="retold-remote-topbar-df-toggle" id="RetoldRemote-TopBar-DFToggle" onclick="pict.views['ContentEditor-TopBar'].toggleDistractionFree()" title="Distraction-free mode (d)"></button>
140
362
  <div class="retold-remote-topbar-location" id="RetoldRemote-TopBar-Location"></div>
141
363
  <div class="retold-remote-topbar-info" id="RetoldRemote-TopBar-Info"></div>
142
364
  <div class="retold-remote-topbar-actions">
365
+ <button class="retold-remote-topbar-btn retold-remote-topbar-addcoll-btn" id="RetoldRemote-TopBar-AddToCollectionBtn" onclick="pict.views['ContentEditor-TopBar'].addToCollection(event)" title="Add to collection">+&#9733;</button>
366
+ <button class="retold-remote-topbar-sidebar-toggle retold-remote-topbar-collections-btn" id="RetoldRemote-TopBar-CollectionsBtn" onclick="pict.views['ContentEditor-TopBar'].toggleCollections()" title="Toggle Collections panel (b)">&#9733;</button>
143
367
  <button class="retold-remote-topbar-filter-btn" id="RetoldRemote-TopBar-FilterBtn" onclick="pict.views['ContentEditor-TopBar'].toggleFilterBar()" title="Toggle filter bar (/)">&#9698;</button>
144
- <button class="retold-remote-topbar-btn" onclick="pict.views['ContentEditor-Layout'].toggleSidebar()" title="Toggle Sidebar">&#9776;</button>
145
368
  </div>
146
369
  </div>
147
370
  `
@@ -168,12 +391,73 @@ class RetoldRemoteTopBarView extends libPictView
168
391
  onAfterRender()
169
392
  {
170
393
  super.onAfterRender();
394
+ this.updateSidebarToggleIcon();
395
+ this.updateDFToggleIcon();
171
396
  this.updateLocation();
172
397
  this.updateInfo();
173
398
  }
174
399
 
400
+ /**
401
+ * Inject the SVG sidebar icon into the sidebar toggle button.
402
+ */
403
+ updateSidebarToggleIcon()
404
+ {
405
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-SidebarToggle');
406
+ if (!tmpBtn)
407
+ {
408
+ return;
409
+ }
410
+
411
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
412
+ if (tmpIconProvider)
413
+ {
414
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('sidebar', 18);
415
+ }
416
+ else
417
+ {
418
+ tmpBtn.innerHTML = '&#9776;';
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Inject the SVG expand icon into the distraction-free toggle button.
424
+ */
425
+ updateDFToggleIcon()
426
+ {
427
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-DFToggle');
428
+ if (!tmpBtn)
429
+ {
430
+ return;
431
+ }
432
+
433
+ // Four-corner expand icon
434
+ tmpBtn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
435
+ + '<polyline points="15 3 21 3 21 9" />'
436
+ + '<polyline points="9 21 3 21 3 15" />'
437
+ + '<polyline points="21 15 21 21 15 21" />'
438
+ + '<polyline points="3 9 3 3 9 3" />'
439
+ + '</svg>';
440
+ }
441
+
442
+ /**
443
+ * Toggle distraction-free mode via the GalleryNavigation provider.
444
+ */
445
+ toggleDistractionFree()
446
+ {
447
+ let tmpNav = this.pict.providers['RetoldRemote-GalleryNavigation'];
448
+ if (tmpNav && tmpNav._toggleDistractionFree)
449
+ {
450
+ tmpNav._toggleDistractionFree();
451
+ }
452
+ }
453
+
175
454
  /**
176
455
  * Update the breadcrumb location display.
456
+ *
457
+ * When more than one folder deep, shows a hamburger button to the
458
+ * left of the home icon with a dropdown listing the intermediate
459
+ * path segments. The breadcrumb itself shows only
460
+ * [home] / [current folder].
177
461
  */
178
462
  updateLocation()
179
463
  {
@@ -186,25 +470,149 @@ class RetoldRemoteTopBarView extends libPictView
186
470
  let tmpRemote = this.pict.AppData.RetoldRemote;
187
471
  let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
188
472
 
473
+ // Build the home icon for the root crumb
474
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
475
+ let tmpHomeIcon = tmpIconProvider ? tmpIconProvider.getIcon('home', 16) : '/';
476
+ let tmpHomeCrumb = '<span class="retold-remote-topbar-home-crumb" onclick="pict.PictApplication.loadFileList(\'\')" title="Home">' + tmpHomeIcon + '</span>';
477
+
189
478
  if (!tmpCurrentLocation)
190
479
  {
191
- tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'\')">/</span>';
480
+ tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-inner">' + tmpHomeCrumb + '</span>';
192
481
  return;
193
482
  }
194
483
 
195
484
  let tmpParts = tmpCurrentLocation.split('/').filter((p) => p);
196
- let tmpHTML = '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'\')">/</span>';
197
485
 
198
- for (let i = 0; i < tmpParts.length; i++)
486
+ // Shallow path (1 level): home / folder no hamburger needed
487
+ if (tmpParts.length <= 1)
488
+ {
489
+ let tmpInner = tmpHomeCrumb;
490
+ for (let i = 0; i < tmpParts.length; i++)
491
+ {
492
+ let tmpPath = tmpParts.slice(0, i + 1).join('/');
493
+ tmpInner += '<span class="retold-remote-topbar-sep">/</span>';
494
+ tmpInner += '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'' + tmpPath + '\')">' + tmpParts[i] + '</span>';
495
+ }
496
+ tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-inner">' + tmpInner + '</span>';
497
+ return;
498
+ }
499
+
500
+ // Deep path (2+ levels): show hamburger with intermediate folders
501
+ let tmpFolderIcon = tmpIconProvider ? tmpIconProvider.getIcon('folder', 16) : '';
502
+ let tmpHomeIconSmall = tmpIconProvider ? tmpIconProvider.getIcon('home', 16) : '/';
503
+
504
+ // Build dropdown items: home first, then each intermediate folder
505
+ let tmpDropdownHTML = '';
506
+
507
+ // Home item
508
+ tmpDropdownHTML += '<button class="retold-remote-topbar-overflow-item" onclick="pict.PictApplication.loadFileList(\'\'); pict.views[\'ContentEditor-TopBar\'].closeBreadcrumbDropdown();">';
509
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-icon">' + tmpHomeIconSmall + '</span>';
510
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-label">Home</span>';
511
+ tmpDropdownHTML += '</button>';
512
+
513
+ // Intermediate folders (all except the last segment, which is shown in the breadcrumb)
514
+ for (let i = 0; i < tmpParts.length - 1; i++)
199
515
  {
200
516
  let tmpPath = tmpParts.slice(0, i + 1).join('/');
201
- tmpHTML += '<span class="retold-remote-topbar-sep">/</span>';
202
- tmpHTML += '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'' + tmpPath + '\')">' + tmpParts[i] + '</span>';
517
+ tmpDropdownHTML += '<button class="retold-remote-topbar-overflow-item" onclick="pict.PictApplication.loadFileList(\'' + tmpPath + '\'); pict.views[\'ContentEditor-TopBar\'].closeBreadcrumbDropdown();">';
518
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-icon">' + tmpFolderIcon + '</span>';
519
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-label">' + tmpParts[i] + '</span>';
520
+ tmpDropdownHTML += '</button>';
203
521
  }
204
522
 
523
+ // Assemble: [hamburger + dropdown] [inner: home / current folder]
524
+ let tmpLastPart = tmpParts[tmpParts.length - 1];
525
+ let tmpLastPath = tmpParts.join('/');
526
+
527
+ let tmpHTML = '';
528
+ // The overflow wrapper sits outside the truncation inner
529
+ tmpHTML += '<span class="retold-remote-topbar-breadcrumb-overflow">';
530
+ tmpHTML += '<button class="retold-remote-topbar-overflow-btn" onclick="pict.views[\'ContentEditor-TopBar\'].toggleBreadcrumbDropdown()" title="Navigate to parent folders">&#9776;</button>';
531
+ tmpHTML += '<div class="retold-remote-topbar-overflow-dropdown" id="RetoldRemote-BreadcrumbDropdown">';
532
+ tmpHTML += tmpDropdownHTML;
533
+ tmpHTML += '</div>';
534
+ tmpHTML += '</span>';
535
+ // The visible crumbs: home / current-folder
536
+ tmpHTML += '<span class="retold-remote-topbar-location-inner">';
537
+ tmpHTML += tmpHomeCrumb;
538
+ tmpHTML += '<span class="retold-remote-topbar-sep">/</span>';
539
+ tmpHTML += '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'' + tmpLastPath + '\')">' + tmpLastPart + '</span>';
540
+ tmpHTML += '</span>';
541
+
205
542
  tmpLocationEl.innerHTML = tmpHTML;
206
543
  }
207
544
 
545
+ /**
546
+ * Toggle the breadcrumb overflow dropdown.
547
+ */
548
+ toggleBreadcrumbDropdown()
549
+ {
550
+ let tmpDropdown = document.getElementById('RetoldRemote-BreadcrumbDropdown');
551
+ if (!tmpDropdown)
552
+ {
553
+ return;
554
+ }
555
+
556
+ let tmpIsOpen = tmpDropdown.classList.contains('open');
557
+
558
+ if (tmpIsOpen)
559
+ {
560
+ this.closeBreadcrumbDropdown();
561
+ }
562
+ else
563
+ {
564
+ tmpDropdown.classList.add('open');
565
+
566
+ // Close on outside click/tap
567
+ let tmpSelf = this;
568
+ let tmpCloseHandler = function(pEvent)
569
+ {
570
+ // Ignore clicks inside the dropdown or on the toggle button
571
+ if (tmpDropdown.contains(pEvent.target))
572
+ {
573
+ return;
574
+ }
575
+ let tmpBtn = tmpDropdown.parentElement && tmpDropdown.parentElement.querySelector('.retold-remote-topbar-overflow-btn');
576
+ if (tmpBtn && tmpBtn.contains(pEvent.target))
577
+ {
578
+ return;
579
+ }
580
+ tmpSelf.closeBreadcrumbDropdown();
581
+ document.removeEventListener('click', tmpCloseHandler, true);
582
+ document.removeEventListener('touchstart', tmpCloseHandler, true);
583
+ };
584
+
585
+ // Defer attaching so the current click doesn't immediately close it
586
+ setTimeout(function()
587
+ {
588
+ document.addEventListener('click', tmpCloseHandler, true);
589
+ document.addEventListener('touchstart', tmpCloseHandler, true);
590
+ }, 0);
591
+
592
+ // Store the handler so closeBreadcrumbDropdown can clean it up
593
+ this._breadcrumbCloseHandler = tmpCloseHandler;
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Close the breadcrumb overflow dropdown.
599
+ */
600
+ closeBreadcrumbDropdown()
601
+ {
602
+ let tmpDropdown = document.getElementById('RetoldRemote-BreadcrumbDropdown');
603
+ if (tmpDropdown)
604
+ {
605
+ tmpDropdown.classList.remove('open');
606
+ }
607
+
608
+ if (this._breadcrumbCloseHandler)
609
+ {
610
+ document.removeEventListener('click', this._breadcrumbCloseHandler, true);
611
+ document.removeEventListener('touchstart', this._breadcrumbCloseHandler, true);
612
+ this._breadcrumbCloseHandler = null;
613
+ }
614
+ }
615
+
208
616
  /**
209
617
  * Toggle the filter bar visibility.
210
618
  * If hidden, show it and focus the search box.
@@ -215,6 +623,12 @@ class RetoldRemoteTopBarView extends libPictView
215
623
  let tmpRemote = this.pict.AppData.RetoldRemote;
216
624
  tmpRemote.FilterBarVisible = !tmpRemote.FilterBarVisible;
217
625
 
626
+ // When hiding the filter bar, also close the advanced filter panel
627
+ if (!tmpRemote.FilterBarVisible)
628
+ {
629
+ tmpRemote.FilterPanelOpen = false;
630
+ }
631
+
218
632
  let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
219
633
  if (tmpGalleryView)
220
634
  {
@@ -299,18 +713,29 @@ class RetoldRemoteTopBarView extends libPictView
299
713
  let tmpItem = tmpItems[tmpIndex];
300
714
  if (tmpItem)
301
715
  {
302
- tmpInfoEl.textContent = tmpItem.Name;
716
+ let tmpPos = (tmpIndex + 1) + '/' + tmpItems.length;
717
+ tmpInfoEl.textContent = tmpPos + ' \u00b7 ' + tmpItem.Name;
303
718
  }
304
719
  return;
305
720
  }
306
721
 
722
+ let tmpItems = tmpRemote.GalleryItems || [];
723
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
724
+ let tmpCursorText = '';
725
+
726
+ if (tmpItems.length > 0)
727
+ {
728
+ tmpCursorText = (tmpIndex + 1) + '/' + tmpItems.length;
729
+ }
730
+
307
731
  if (!tmpSummary)
308
732
  {
309
- tmpInfoEl.textContent = '';
733
+ tmpInfoEl.textContent = tmpCursorText;
310
734
  return;
311
735
  }
312
736
 
313
737
  let tmpParts = [];
738
+ if (tmpCursorText) tmpParts.push(tmpCursorText);
314
739
  if (tmpSummary.Folders > 0) tmpParts.push(tmpSummary.Folders + ' folders');
315
740
  if (tmpSummary.Images > 0) tmpParts.push(tmpSummary.Images + ' images');
316
741
  if (tmpSummary.Videos > 0) tmpParts.push(tmpSummary.Videos + ' videos');
@@ -320,6 +745,182 @@ class RetoldRemoteTopBarView extends libPictView
320
745
 
321
746
  tmpInfoEl.textContent = tmpParts.join(' \u00b7 ');
322
747
  }
748
+
749
+ // -- Collections Panel ------------------------------------------------
750
+
751
+ /**
752
+ * Toggle the collections panel.
753
+ */
754
+ toggleCollections()
755
+ {
756
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
757
+ if (tmpManager)
758
+ {
759
+ tmpManager.togglePanel();
760
+ }
761
+ }
762
+
763
+ /**
764
+ * Update the collections toggle button icon/state.
765
+ */
766
+ updateCollectionsIcon()
767
+ {
768
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-CollectionsBtn');
769
+ if (!tmpBtn)
770
+ {
771
+ return;
772
+ }
773
+
774
+ let tmpRemote = this.pict.AppData.RetoldRemote;
775
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
776
+
777
+ if (tmpRemote.CollectionsPanelOpen)
778
+ {
779
+ tmpBtn.classList.add('panel-open');
780
+ if (tmpIconProvider && typeof tmpIconProvider.getIcon === 'function')
781
+ {
782
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('bookmark-filled', 16);
783
+ }
784
+ }
785
+ else
786
+ {
787
+ tmpBtn.classList.remove('panel-open');
788
+ if (tmpIconProvider && typeof tmpIconProvider.getIcon === 'function')
789
+ {
790
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('bookmark', 16);
791
+ }
792
+ }
793
+ }
794
+
795
+ /**
796
+ * Add current file/folder to a collection.
797
+ * Quick-add: single click adds to last-used collection.
798
+ * If no last-used collection, opens the picker dropdown.
799
+ *
800
+ * @param {Event} pEvent - Click event
801
+ */
802
+ addToCollection(pEvent)
803
+ {
804
+ let tmpRemote = this.pict.AppData.RetoldRemote;
805
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
806
+ if (!tmpManager)
807
+ {
808
+ return;
809
+ }
810
+
811
+ // Quick-add: if we have a last-used collection, add directly
812
+ if (tmpRemote.LastUsedCollectionGUID)
813
+ {
814
+ let tmpAdded = tmpManager.addCurrentFileToCollection(tmpRemote.LastUsedCollectionGUID);
815
+ if (tmpAdded)
816
+ {
817
+ return;
818
+ }
819
+ }
820
+
821
+ // Fall through to picker dropdown
822
+ this.showAddToCollectionDropdown(pEvent);
823
+ }
824
+
825
+ /**
826
+ * Show the add-to-collection picker dropdown.
827
+ *
828
+ * @param {Event} [pEvent] - Optional click event for positioning
829
+ */
830
+ showAddToCollectionDropdown(pEvent)
831
+ {
832
+ let tmpSelf = this;
833
+ let tmpRemote = this.pict.AppData.RetoldRemote;
834
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
835
+
836
+ // Remove any existing dropdown
837
+ this._closeAddToCollectionDropdown();
838
+
839
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-AddToCollectionBtn');
840
+ if (!tmpBtn)
841
+ {
842
+ return;
843
+ }
844
+
845
+ // Ensure we have the latest collections
846
+ tmpManager.fetchCollections(() =>
847
+ {
848
+ let tmpCollections = tmpRemote.Collections || [];
849
+
850
+ let tmpDropdown = document.createElement('div');
851
+ tmpDropdown.className = 'retold-remote-addcoll-dropdown';
852
+ tmpDropdown.id = 'RetoldRemote-AddToCollection-Dropdown';
853
+
854
+ // "New Collection" option
855
+ let tmpNewItem = document.createElement('button');
856
+ tmpNewItem.className = 'retold-remote-addcoll-dropdown-item retold-remote-addcoll-dropdown-new';
857
+ tmpNewItem.textContent = '+ New Collection...';
858
+ tmpNewItem.onclick = () =>
859
+ {
860
+ tmpSelf._closeAddToCollectionDropdown();
861
+ let tmpName = prompt('Collection name:');
862
+ if (tmpName && tmpName.trim())
863
+ {
864
+ tmpManager.createCollection(tmpName.trim(), (pError, pCollection) =>
865
+ {
866
+ if (!pError && pCollection)
867
+ {
868
+ tmpManager.addCurrentFileToCollection(pCollection.GUID);
869
+ }
870
+ });
871
+ }
872
+ };
873
+ tmpDropdown.appendChild(tmpNewItem);
874
+
875
+ // Existing collections
876
+ for (let i = 0; i < tmpCollections.length; i++)
877
+ {
878
+ let tmpCollection = tmpCollections[i];
879
+ let tmpItem = document.createElement('button');
880
+ tmpItem.className = 'retold-remote-addcoll-dropdown-item';
881
+ tmpItem.textContent = tmpCollection.Name || 'Untitled';
882
+ tmpItem.onclick = () =>
883
+ {
884
+ tmpSelf._closeAddToCollectionDropdown();
885
+ tmpManager.addCurrentFileToCollection(tmpCollection.GUID);
886
+ };
887
+ tmpDropdown.appendChild(tmpItem);
888
+ }
889
+
890
+ // Position relative to the button
891
+ tmpBtn.style.position = 'relative';
892
+ tmpBtn.appendChild(tmpDropdown);
893
+
894
+ // Close on outside click
895
+ setTimeout(() =>
896
+ {
897
+ document.addEventListener('click', tmpSelf._boundCloseDropdown = (pClickEvent) =>
898
+ {
899
+ if (!tmpDropdown.contains(pClickEvent.target) && pClickEvent.target !== tmpBtn)
900
+ {
901
+ tmpSelf._closeAddToCollectionDropdown();
902
+ }
903
+ });
904
+ }, 10);
905
+ });
906
+ }
907
+
908
+ /**
909
+ * Close the add-to-collection dropdown.
910
+ */
911
+ _closeAddToCollectionDropdown()
912
+ {
913
+ let tmpDropdown = document.getElementById('RetoldRemote-AddToCollection-Dropdown');
914
+ if (tmpDropdown)
915
+ {
916
+ tmpDropdown.remove();
917
+ }
918
+ if (this._boundCloseDropdown)
919
+ {
920
+ document.removeEventListener('click', this._boundCloseDropdown);
921
+ this._boundCloseDropdown = null;
922
+ }
923
+ }
323
924
  }
324
925
 
325
926
  RetoldRemoteTopBarView.default_configuration = _ViewConfiguration;