retold-remote 0.0.5 → 0.0.7

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 (71) hide show
  1. package/css/retold-remote.css +3 -0
  2. package/docs/README.md +181 -0
  3. package/docs/_cover.md +14 -0
  4. package/docs/_sidebar.md +10 -0
  5. package/docs/_topbar.md +3 -0
  6. package/docs/audio-viewer.md +133 -0
  7. package/docs/ebook-reader.md +90 -0
  8. package/docs/image-viewer.md +90 -0
  9. package/docs/server-setup.md +262 -0
  10. package/docs/video-viewer.md +134 -0
  11. package/html/docs.html +59 -0
  12. package/html/index.html +1 -1
  13. package/package.json +20 -6
  14. package/source/Pict-Application-RetoldRemote.js +122 -2
  15. package/source/RetoldRemote-ExtensionMaps.js +33 -0
  16. package/source/cli/RetoldRemote-Server-Setup.js +211 -67
  17. package/source/cli/commands/RetoldRemote-Command-Serve.js +5 -26
  18. package/source/providers/Pict-Provider-AISortManager.js +456 -0
  19. package/source/providers/Pict-Provider-CollectionManager.js +1200 -0
  20. package/source/providers/Pict-Provider-FormattingUtilities.js +109 -0
  21. package/source/providers/Pict-Provider-GalleryFilterSort.js +2 -11
  22. package/source/providers/Pict-Provider-GalleryNavigation.js +187 -409
  23. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +52 -0
  24. package/source/providers/Pict-Provider-ToastNotification.js +96 -0
  25. package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +88 -0
  26. package/source/providers/keyboard-handlers/KeyHandler-Gallery.js +190 -0
  27. package/source/providers/keyboard-handlers/KeyHandler-Sidebar.js +65 -0
  28. package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +57 -0
  29. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +197 -0
  30. package/source/server/RetoldRemote-AISortService.js +879 -0
  31. package/source/server/RetoldRemote-ArchiveService.js +2 -12
  32. package/source/server/RetoldRemote-AudioWaveformService.js +7 -16
  33. package/source/server/RetoldRemote-CollectionService.js +843 -0
  34. package/source/server/RetoldRemote-EbookService.js +7 -16
  35. package/source/server/RetoldRemote-FileOperationService.js +560 -0
  36. package/source/server/RetoldRemote-MediaService.js +15 -14
  37. package/source/server/RetoldRemote-MetadataCache.js +411 -0
  38. package/source/server/RetoldRemote-ParimeCache.js +349 -0
  39. package/source/server/RetoldRemote-ThumbnailCache.js +52 -20
  40. package/source/server/RetoldRemote-VideoFrameService.js +7 -15
  41. package/source/views/PictView-Remote-AudioExplorer.js +10 -43
  42. package/source/views/PictView-Remote-CollectionsPanel.js +1486 -0
  43. package/source/views/PictView-Remote-Gallery.js +237 -44
  44. package/source/views/PictView-Remote-ImageViewer.js +1 -34
  45. package/source/views/PictView-Remote-Layout.js +411 -20
  46. package/source/views/PictView-Remote-MediaViewer.js +311 -28
  47. package/source/views/PictView-Remote-SettingsPanel.js +275 -133
  48. package/source/views/PictView-Remote-TopBar.js +701 -14
  49. package/source/views/PictView-Remote-VLCSetup.js +8 -39
  50. package/source/views/PictView-Remote-VideoExplorer.js +20 -54
  51. package/web-application/css/docuserve.css +73 -0
  52. package/web-application/css/retold-remote.css +3 -0
  53. package/web-application/docs/README.md +181 -0
  54. package/web-application/docs/_cover.md +14 -0
  55. package/web-application/docs/_sidebar.md +10 -0
  56. package/web-application/docs/_topbar.md +3 -0
  57. package/web-application/docs/audio-viewer.md +133 -0
  58. package/web-application/docs/ebook-reader.md +90 -0
  59. package/web-application/docs/image-viewer.md +90 -0
  60. package/web-application/docs/server-setup.md +262 -0
  61. package/web-application/docs/video-viewer.md +134 -0
  62. package/web-application/docs.html +59 -0
  63. package/web-application/index.html +1 -1
  64. package/web-application/js/pict-docuserve.min.js +58 -0
  65. package/web-application/js/pict.min.js +2 -2
  66. package/web-application/js/pict.min.js.map +1 -1
  67. package/web-application/retold-remote.js +2652 -970
  68. package/web-application/retold-remote.js.map +1 -1
  69. package/web-application/retold-remote.min.js +60 -13
  70. package/web-application/retold-remote.min.js.map +1 -1
  71. 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
24
  {
25
- font-size: 0.85rem;
26
- font-weight: 700;
27
- color: var(--retold-accent);
28
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
54
+ {
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;
@@ -112,6 +333,31 @@ const _ViewConfiguration =
112
333
  border-color: var(--retold-text-muted);
113
334
  background: rgba(128, 128, 128, 0.06);
114
335
  }
336
+ .retold-remote-topbar-aisort-btn
337
+ {
338
+ padding: 4px 8px;
339
+ border: 1px solid var(--retold-border);
340
+ border-radius: 3px;
341
+ background: transparent;
342
+ color: var(--retold-text-muted);
343
+ font-size: 0.72rem;
344
+ cursor: pointer;
345
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
346
+ font-family: inherit;
347
+ white-space: nowrap;
348
+ }
349
+ .retold-remote-topbar-aisort-btn:hover
350
+ {
351
+ color: var(--retold-text-primary);
352
+ border-color: var(--retold-accent);
353
+ background: rgba(128, 128, 128, 0.1);
354
+ }
355
+ .retold-remote-topbar-aisort-btn.generating
356
+ {
357
+ color: var(--retold-accent);
358
+ border-color: var(--retold-accent);
359
+ cursor: wait;
360
+ }
115
361
  .retold-remote-topbar-filter-badge
116
362
  {
117
363
  position: absolute;
@@ -136,12 +382,15 @@ const _ViewConfiguration =
136
382
  Hash: "RetoldRemote-TopBar",
137
383
  Template: /*html*/`
138
384
  <div class="retold-remote-topbar">
139
- <div class="retold-remote-topbar-brand">Retold Remote</div>
385
+ <button class="retold-remote-topbar-sidebar-toggle" id="RetoldRemote-TopBar-SidebarToggle" onclick="pict.views['ContentEditor-Layout'].toggleSidebar()" title="Toggle Sidebar"></button>
386
+ <button class="retold-remote-topbar-df-toggle" id="RetoldRemote-TopBar-DFToggle" onclick="pict.views['ContentEditor-TopBar'].toggleDistractionFree()" title="Distraction-free mode (d)"></button>
140
387
  <div class="retold-remote-topbar-location" id="RetoldRemote-TopBar-Location"></div>
141
388
  <div class="retold-remote-topbar-info" id="RetoldRemote-TopBar-Info"></div>
142
389
  <div class="retold-remote-topbar-actions">
390
+ <button class="retold-remote-topbar-aisort-btn" id="RetoldRemote-TopBar-AISortBtn" onclick="pict.views['ContentEditor-TopBar'].triggerAISort()" title="AI Sort (generate sort plan for current folder)" style="display:none;">AI Sort</button>
391
+ <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>
392
+ <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
393
  <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
394
  </div>
146
395
  </div>
147
396
  `
@@ -168,12 +417,73 @@ class RetoldRemoteTopBarView extends libPictView
168
417
  onAfterRender()
169
418
  {
170
419
  super.onAfterRender();
420
+ this.updateSidebarToggleIcon();
421
+ this.updateDFToggleIcon();
171
422
  this.updateLocation();
172
423
  this.updateInfo();
173
424
  }
174
425
 
426
+ /**
427
+ * Inject the SVG sidebar icon into the sidebar toggle button.
428
+ */
429
+ updateSidebarToggleIcon()
430
+ {
431
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-SidebarToggle');
432
+ if (!tmpBtn)
433
+ {
434
+ return;
435
+ }
436
+
437
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
438
+ if (tmpIconProvider)
439
+ {
440
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('sidebar', 18);
441
+ }
442
+ else
443
+ {
444
+ tmpBtn.innerHTML = '&#9776;';
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Inject the SVG expand icon into the distraction-free toggle button.
450
+ */
451
+ updateDFToggleIcon()
452
+ {
453
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-DFToggle');
454
+ if (!tmpBtn)
455
+ {
456
+ return;
457
+ }
458
+
459
+ // Four-corner expand icon
460
+ 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">'
461
+ + '<polyline points="15 3 21 3 21 9" />'
462
+ + '<polyline points="9 21 3 21 3 15" />'
463
+ + '<polyline points="21 15 21 21 15 21" />'
464
+ + '<polyline points="3 9 3 3 9 3" />'
465
+ + '</svg>';
466
+ }
467
+
468
+ /**
469
+ * Toggle distraction-free mode via the GalleryNavigation provider.
470
+ */
471
+ toggleDistractionFree()
472
+ {
473
+ let tmpNav = this.pict.providers['RetoldRemote-GalleryNavigation'];
474
+ if (tmpNav && tmpNav._toggleDistractionFree)
475
+ {
476
+ tmpNav._toggleDistractionFree();
477
+ }
478
+ }
479
+
175
480
  /**
176
481
  * Update the breadcrumb location display.
482
+ *
483
+ * When more than one folder deep, shows a hamburger button to the
484
+ * left of the home icon with a dropdown listing the intermediate
485
+ * path segments. The breadcrumb itself shows only
486
+ * [home] / [current folder].
177
487
  */
178
488
  updateLocation()
179
489
  {
@@ -186,25 +496,149 @@ class RetoldRemoteTopBarView extends libPictView
186
496
  let tmpRemote = this.pict.AppData.RetoldRemote;
187
497
  let tmpCurrentLocation = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
188
498
 
499
+ // Build the home icon for the root crumb
500
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
501
+ let tmpHomeIcon = tmpIconProvider ? tmpIconProvider.getIcon('home', 16) : '/';
502
+ let tmpHomeCrumb = '<span class="retold-remote-topbar-home-crumb" onclick="pict.PictApplication.loadFileList(\'\')" title="Home">' + tmpHomeIcon + '</span>';
503
+
189
504
  if (!tmpCurrentLocation)
190
505
  {
191
- tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'\')">/</span>';
506
+ tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-inner">' + tmpHomeCrumb + '</span>';
192
507
  return;
193
508
  }
194
509
 
195
510
  let tmpParts = tmpCurrentLocation.split('/').filter((p) => p);
196
- let tmpHTML = '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'\')">/</span>';
197
511
 
198
- for (let i = 0; i < tmpParts.length; i++)
512
+ // Shallow path (1 level): home / folder no hamburger needed
513
+ if (tmpParts.length <= 1)
514
+ {
515
+ let tmpInner = tmpHomeCrumb;
516
+ for (let i = 0; i < tmpParts.length; i++)
517
+ {
518
+ let tmpPath = tmpParts.slice(0, i + 1).join('/');
519
+ tmpInner += '<span class="retold-remote-topbar-sep">/</span>';
520
+ tmpInner += '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'' + tmpPath + '\')">' + tmpParts[i] + '</span>';
521
+ }
522
+ tmpLocationEl.innerHTML = '<span class="retold-remote-topbar-location-inner">' + tmpInner + '</span>';
523
+ return;
524
+ }
525
+
526
+ // Deep path (2+ levels): show hamburger with intermediate folders
527
+ let tmpFolderIcon = tmpIconProvider ? tmpIconProvider.getIcon('folder', 16) : '';
528
+ let tmpHomeIconSmall = tmpIconProvider ? tmpIconProvider.getIcon('home', 16) : '/';
529
+
530
+ // Build dropdown items: home first, then each intermediate folder
531
+ let tmpDropdownHTML = '';
532
+
533
+ // Home item
534
+ tmpDropdownHTML += '<button class="retold-remote-topbar-overflow-item" onclick="pict.PictApplication.loadFileList(\'\'); pict.views[\'ContentEditor-TopBar\'].closeBreadcrumbDropdown();">';
535
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-icon">' + tmpHomeIconSmall + '</span>';
536
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-label">Home</span>';
537
+ tmpDropdownHTML += '</button>';
538
+
539
+ // Intermediate folders (all except the last segment, which is shown in the breadcrumb)
540
+ for (let i = 0; i < tmpParts.length - 1; i++)
199
541
  {
200
542
  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>';
543
+ tmpDropdownHTML += '<button class="retold-remote-topbar-overflow-item" onclick="pict.PictApplication.loadFileList(\'' + tmpPath + '\'); pict.views[\'ContentEditor-TopBar\'].closeBreadcrumbDropdown();">';
544
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-icon">' + tmpFolderIcon + '</span>';
545
+ tmpDropdownHTML += '<span class="retold-remote-topbar-overflow-item-label">' + tmpParts[i] + '</span>';
546
+ tmpDropdownHTML += '</button>';
203
547
  }
204
548
 
549
+ // Assemble: [hamburger + dropdown] [inner: home / current folder]
550
+ let tmpLastPart = tmpParts[tmpParts.length - 1];
551
+ let tmpLastPath = tmpParts.join('/');
552
+
553
+ let tmpHTML = '';
554
+ // The overflow wrapper sits outside the truncation inner
555
+ tmpHTML += '<span class="retold-remote-topbar-breadcrumb-overflow">';
556
+ tmpHTML += '<button class="retold-remote-topbar-overflow-btn" onclick="pict.views[\'ContentEditor-TopBar\'].toggleBreadcrumbDropdown()" title="Navigate to parent folders">&#9776;</button>';
557
+ tmpHTML += '<div class="retold-remote-topbar-overflow-dropdown" id="RetoldRemote-BreadcrumbDropdown">';
558
+ tmpHTML += tmpDropdownHTML;
559
+ tmpHTML += '</div>';
560
+ tmpHTML += '</span>';
561
+ // The visible crumbs: home / current-folder
562
+ tmpHTML += '<span class="retold-remote-topbar-location-inner">';
563
+ tmpHTML += tmpHomeCrumb;
564
+ tmpHTML += '<span class="retold-remote-topbar-sep">/</span>';
565
+ tmpHTML += '<span class="retold-remote-topbar-location-crumb" onclick="pict.PictApplication.loadFileList(\'' + tmpLastPath + '\')">' + tmpLastPart + '</span>';
566
+ tmpHTML += '</span>';
567
+
205
568
  tmpLocationEl.innerHTML = tmpHTML;
206
569
  }
207
570
 
571
+ /**
572
+ * Toggle the breadcrumb overflow dropdown.
573
+ */
574
+ toggleBreadcrumbDropdown()
575
+ {
576
+ let tmpDropdown = document.getElementById('RetoldRemote-BreadcrumbDropdown');
577
+ if (!tmpDropdown)
578
+ {
579
+ return;
580
+ }
581
+
582
+ let tmpIsOpen = tmpDropdown.classList.contains('open');
583
+
584
+ if (tmpIsOpen)
585
+ {
586
+ this.closeBreadcrumbDropdown();
587
+ }
588
+ else
589
+ {
590
+ tmpDropdown.classList.add('open');
591
+
592
+ // Close on outside click/tap
593
+ let tmpSelf = this;
594
+ let tmpCloseHandler = function(pEvent)
595
+ {
596
+ // Ignore clicks inside the dropdown or on the toggle button
597
+ if (tmpDropdown.contains(pEvent.target))
598
+ {
599
+ return;
600
+ }
601
+ let tmpBtn = tmpDropdown.parentElement && tmpDropdown.parentElement.querySelector('.retold-remote-topbar-overflow-btn');
602
+ if (tmpBtn && tmpBtn.contains(pEvent.target))
603
+ {
604
+ return;
605
+ }
606
+ tmpSelf.closeBreadcrumbDropdown();
607
+ document.removeEventListener('click', tmpCloseHandler, true);
608
+ document.removeEventListener('touchstart', tmpCloseHandler, true);
609
+ };
610
+
611
+ // Defer attaching so the current click doesn't immediately close it
612
+ setTimeout(function()
613
+ {
614
+ document.addEventListener('click', tmpCloseHandler, true);
615
+ document.addEventListener('touchstart', tmpCloseHandler, true);
616
+ }, 0);
617
+
618
+ // Store the handler so closeBreadcrumbDropdown can clean it up
619
+ this._breadcrumbCloseHandler = tmpCloseHandler;
620
+ }
621
+ }
622
+
623
+ /**
624
+ * Close the breadcrumb overflow dropdown.
625
+ */
626
+ closeBreadcrumbDropdown()
627
+ {
628
+ let tmpDropdown = document.getElementById('RetoldRemote-BreadcrumbDropdown');
629
+ if (tmpDropdown)
630
+ {
631
+ tmpDropdown.classList.remove('open');
632
+ }
633
+
634
+ if (this._breadcrumbCloseHandler)
635
+ {
636
+ document.removeEventListener('click', this._breadcrumbCloseHandler, true);
637
+ document.removeEventListener('touchstart', this._breadcrumbCloseHandler, true);
638
+ this._breadcrumbCloseHandler = null;
639
+ }
640
+ }
641
+
208
642
  /**
209
643
  * Toggle the filter bar visibility.
210
644
  * If hidden, show it and focus the search box.
@@ -215,6 +649,12 @@ class RetoldRemoteTopBarView extends libPictView
215
649
  let tmpRemote = this.pict.AppData.RetoldRemote;
216
650
  tmpRemote.FilterBarVisible = !tmpRemote.FilterBarVisible;
217
651
 
652
+ // When hiding the filter bar, also close the advanced filter panel
653
+ if (!tmpRemote.FilterBarVisible)
654
+ {
655
+ tmpRemote.FilterPanelOpen = false;
656
+ }
657
+
218
658
  let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
219
659
  if (tmpGalleryView)
220
660
  {
@@ -280,9 +720,11 @@ class RetoldRemoteTopBarView extends libPictView
280
720
 
281
721
  /**
282
722
  * Update the info display with folder summary.
723
+ * Also updates the AI Sort button visibility.
283
724
  */
284
725
  updateInfo()
285
726
  {
727
+ this.updateAISortButton();
286
728
  let tmpInfoEl = document.getElementById('RetoldRemote-TopBar-Info');
287
729
  if (!tmpInfoEl)
288
730
  {
@@ -299,18 +741,29 @@ class RetoldRemoteTopBarView extends libPictView
299
741
  let tmpItem = tmpItems[tmpIndex];
300
742
  if (tmpItem)
301
743
  {
302
- tmpInfoEl.textContent = tmpItem.Name;
744
+ let tmpPos = (tmpIndex + 1) + '/' + tmpItems.length;
745
+ tmpInfoEl.textContent = tmpPos + ' \u00b7 ' + tmpItem.Name;
303
746
  }
304
747
  return;
305
748
  }
306
749
 
750
+ let tmpItems = tmpRemote.GalleryItems || [];
751
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
752
+ let tmpCursorText = '';
753
+
754
+ if (tmpItems.length > 0)
755
+ {
756
+ tmpCursorText = (tmpIndex + 1) + '/' + tmpItems.length;
757
+ }
758
+
307
759
  if (!tmpSummary)
308
760
  {
309
- tmpInfoEl.textContent = '';
761
+ tmpInfoEl.textContent = tmpCursorText;
310
762
  return;
311
763
  }
312
764
 
313
765
  let tmpParts = [];
766
+ if (tmpCursorText) tmpParts.push(tmpCursorText);
314
767
  if (tmpSummary.Folders > 0) tmpParts.push(tmpSummary.Folders + ' folders');
315
768
  if (tmpSummary.Images > 0) tmpParts.push(tmpSummary.Images + ' images');
316
769
  if (tmpSummary.Videos > 0) tmpParts.push(tmpSummary.Videos + ' videos');
@@ -320,6 +773,240 @@ class RetoldRemoteTopBarView extends libPictView
320
773
 
321
774
  tmpInfoEl.textContent = tmpParts.join(' \u00b7 ');
322
775
  }
776
+
777
+ // -- AI Sort ----------------------------------------------------------
778
+
779
+ /**
780
+ * Trigger AI sort for the current folder.
781
+ */
782
+ triggerAISort()
783
+ {
784
+ let tmpAISortManager = this.pict.providers['RetoldRemote-AISortManager'];
785
+ if (!tmpAISortManager)
786
+ {
787
+ return;
788
+ }
789
+
790
+ let tmpCurrentPath = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
791
+
792
+ // Show generating state
793
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-AISortBtn');
794
+ if (tmpBtn)
795
+ {
796
+ tmpBtn.classList.add('generating');
797
+ tmpBtn.textContent = 'Sorting...';
798
+ }
799
+
800
+ tmpAISortManager.generateSortPlan(tmpCurrentPath,
801
+ (pError, pResult) =>
802
+ {
803
+ // Reset button state
804
+ if (tmpBtn)
805
+ {
806
+ tmpBtn.classList.remove('generating');
807
+ tmpBtn.textContent = 'AI Sort';
808
+ }
809
+ });
810
+ }
811
+
812
+ /**
813
+ * Update the AI Sort button visibility.
814
+ * Shows only when browsing a folder (gallery mode).
815
+ */
816
+ updateAISortButton()
817
+ {
818
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-AISortBtn');
819
+ if (!tmpBtn)
820
+ {
821
+ return;
822
+ }
823
+
824
+ let tmpAISortManager = this.pict.providers['RetoldRemote-AISortManager'];
825
+ if (tmpAISortManager && tmpAISortManager.isAvailable())
826
+ {
827
+ tmpBtn.style.display = '';
828
+ }
829
+ else
830
+ {
831
+ tmpBtn.style.display = 'none';
832
+ }
833
+ }
834
+
835
+ // -- Collections Panel ------------------------------------------------
836
+
837
+ /**
838
+ * Toggle the collections panel.
839
+ */
840
+ toggleCollections()
841
+ {
842
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
843
+ if (tmpManager)
844
+ {
845
+ tmpManager.togglePanel();
846
+ }
847
+ }
848
+
849
+ /**
850
+ * Update the collections toggle button icon/state.
851
+ */
852
+ updateCollectionsIcon()
853
+ {
854
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-CollectionsBtn');
855
+ if (!tmpBtn)
856
+ {
857
+ return;
858
+ }
859
+
860
+ let tmpRemote = this.pict.AppData.RetoldRemote;
861
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
862
+
863
+ if (tmpRemote.CollectionsPanelOpen)
864
+ {
865
+ tmpBtn.classList.add('panel-open');
866
+ if (tmpIconProvider && typeof tmpIconProvider.getIcon === 'function')
867
+ {
868
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('bookmark-filled', 16);
869
+ }
870
+ }
871
+ else
872
+ {
873
+ tmpBtn.classList.remove('panel-open');
874
+ if (tmpIconProvider && typeof tmpIconProvider.getIcon === 'function')
875
+ {
876
+ tmpBtn.innerHTML = tmpIconProvider.getIcon('bookmark', 16);
877
+ }
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Add current file/folder to a collection.
883
+ * Quick-add: single click adds to last-used collection.
884
+ * If no last-used collection, opens the picker dropdown.
885
+ *
886
+ * @param {Event} pEvent - Click event
887
+ */
888
+ addToCollection(pEvent)
889
+ {
890
+ let tmpRemote = this.pict.AppData.RetoldRemote;
891
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
892
+ if (!tmpManager)
893
+ {
894
+ return;
895
+ }
896
+
897
+ // Quick-add: if we have a last-used collection, add directly
898
+ if (tmpRemote.LastUsedCollectionGUID)
899
+ {
900
+ let tmpAdded = tmpManager.addCurrentFileToCollection(tmpRemote.LastUsedCollectionGUID);
901
+ if (tmpAdded)
902
+ {
903
+ return;
904
+ }
905
+ }
906
+
907
+ // Fall through to picker dropdown
908
+ this.showAddToCollectionDropdown(pEvent);
909
+ }
910
+
911
+ /**
912
+ * Show the add-to-collection picker dropdown.
913
+ *
914
+ * @param {Event} [pEvent] - Optional click event for positioning
915
+ */
916
+ showAddToCollectionDropdown(pEvent)
917
+ {
918
+ let tmpSelf = this;
919
+ let tmpRemote = this.pict.AppData.RetoldRemote;
920
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
921
+
922
+ // Remove any existing dropdown
923
+ this._closeAddToCollectionDropdown();
924
+
925
+ let tmpBtn = document.getElementById('RetoldRemote-TopBar-AddToCollectionBtn');
926
+ if (!tmpBtn)
927
+ {
928
+ return;
929
+ }
930
+
931
+ // Ensure we have the latest collections
932
+ tmpManager.fetchCollections(() =>
933
+ {
934
+ let tmpCollections = tmpRemote.Collections || [];
935
+
936
+ let tmpDropdown = document.createElement('div');
937
+ tmpDropdown.className = 'retold-remote-addcoll-dropdown';
938
+ tmpDropdown.id = 'RetoldRemote-AddToCollection-Dropdown';
939
+
940
+ // "New Collection" option
941
+ let tmpNewItem = document.createElement('button');
942
+ tmpNewItem.className = 'retold-remote-addcoll-dropdown-item retold-remote-addcoll-dropdown-new';
943
+ tmpNewItem.textContent = '+ New Collection...';
944
+ tmpNewItem.onclick = () =>
945
+ {
946
+ tmpSelf._closeAddToCollectionDropdown();
947
+ let tmpName = prompt('Collection name:');
948
+ if (tmpName && tmpName.trim())
949
+ {
950
+ tmpManager.createCollection(tmpName.trim(), (pError, pCollection) =>
951
+ {
952
+ if (!pError && pCollection)
953
+ {
954
+ tmpManager.addCurrentFileToCollection(pCollection.GUID);
955
+ }
956
+ });
957
+ }
958
+ };
959
+ tmpDropdown.appendChild(tmpNewItem);
960
+
961
+ // Existing collections
962
+ for (let i = 0; i < tmpCollections.length; i++)
963
+ {
964
+ let tmpCollection = tmpCollections[i];
965
+ let tmpItem = document.createElement('button');
966
+ tmpItem.className = 'retold-remote-addcoll-dropdown-item';
967
+ tmpItem.textContent = tmpCollection.Name || 'Untitled';
968
+ tmpItem.onclick = () =>
969
+ {
970
+ tmpSelf._closeAddToCollectionDropdown();
971
+ tmpManager.addCurrentFileToCollection(tmpCollection.GUID);
972
+ };
973
+ tmpDropdown.appendChild(tmpItem);
974
+ }
975
+
976
+ // Position relative to the button
977
+ tmpBtn.style.position = 'relative';
978
+ tmpBtn.appendChild(tmpDropdown);
979
+
980
+ // Close on outside click
981
+ setTimeout(() =>
982
+ {
983
+ document.addEventListener('click', tmpSelf._boundCloseDropdown = (pClickEvent) =>
984
+ {
985
+ if (!tmpDropdown.contains(pClickEvent.target) && pClickEvent.target !== tmpBtn)
986
+ {
987
+ tmpSelf._closeAddToCollectionDropdown();
988
+ }
989
+ });
990
+ }, 10);
991
+ });
992
+ }
993
+
994
+ /**
995
+ * Close the add-to-collection dropdown.
996
+ */
997
+ _closeAddToCollectionDropdown()
998
+ {
999
+ let tmpDropdown = document.getElementById('RetoldRemote-AddToCollection-Dropdown');
1000
+ if (tmpDropdown)
1001
+ {
1002
+ tmpDropdown.remove();
1003
+ }
1004
+ if (this._boundCloseDropdown)
1005
+ {
1006
+ document.removeEventListener('click', this._boundCloseDropdown);
1007
+ this._boundCloseDropdown = null;
1008
+ }
1009
+ }
323
1010
  }
324
1011
 
325
1012
  RetoldRemoteTopBarView.default_configuration = _ViewConfiguration;