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
@@ -0,0 +1,1486 @@
1
+ /**
2
+ * Retold Remote -- Collections Panel View
3
+ *
4
+ * Right-side flyout panel for managing user-defined collections.
5
+ * Mirrors the left sidebar architecture: flex child inside
6
+ * .content-editor-body, with collapse/expand, resize handle,
7
+ * and mobile responsive bottom-drawer mode.
8
+ *
9
+ * Three modes:
10
+ * list — browseable list of all collections
11
+ * detail — items inside a selected collection
12
+ * edit — metadata editing for a collection
13
+ *
14
+ * @license MIT
15
+ */
16
+ const libPictView = require('pict-view');
17
+
18
+ const _ViewConfiguration =
19
+ {
20
+ ViewIdentifier: "RetoldRemote-CollectionsPanel",
21
+ DefaultRenderable: "RetoldRemote-CollectionsPanel",
22
+ DefaultDestinationAddress: "#RetoldRemote-Collections-Container",
23
+ AutoRender: false,
24
+
25
+ CSS: /*css*/`
26
+ /* ---- Collections Panel Container ---- */
27
+ .retold-remote-collections-panel
28
+ {
29
+ display: flex;
30
+ flex-direction: column;
31
+ height: 100%;
32
+ overflow: hidden;
33
+ }
34
+ .retold-remote-collections-header
35
+ {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 8px;
39
+ padding: 8px 12px;
40
+ border-bottom: 1px solid var(--retold-border);
41
+ background: var(--retold-bg-secondary);
42
+ flex-shrink: 0;
43
+ }
44
+ .retold-remote-collections-header-title
45
+ {
46
+ flex: 1;
47
+ font-size: 0.82rem;
48
+ font-weight: 600;
49
+ color: var(--retold-text-primary);
50
+ white-space: nowrap;
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ }
54
+ .retold-remote-collections-header-btn
55
+ {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ width: 26px;
60
+ height: 26px;
61
+ padding: 0;
62
+ border: 1px solid var(--retold-border);
63
+ border-radius: 4px;
64
+ background: transparent;
65
+ color: var(--retold-text-muted);
66
+ font-size: 0.82rem;
67
+ cursor: pointer;
68
+ transition: color 0.15s, border-color 0.15s;
69
+ font-family: inherit;
70
+ flex-shrink: 0;
71
+ }
72
+ .retold-remote-collections-header-btn:hover
73
+ {
74
+ color: var(--retold-text-primary);
75
+ border-color: var(--retold-accent);
76
+ }
77
+ /* ---- Search ---- */
78
+ .retold-remote-collections-search
79
+ {
80
+ padding: 6px 12px;
81
+ border-bottom: 1px solid var(--retold-border);
82
+ flex-shrink: 0;
83
+ }
84
+ .retold-remote-collections-search input
85
+ {
86
+ width: 100%;
87
+ padding: 5px 8px;
88
+ border: 1px solid var(--retold-border);
89
+ border-radius: 3px;
90
+ background: var(--retold-bg-tertiary);
91
+ color: var(--retold-text-secondary);
92
+ font-size: 0.78rem;
93
+ font-family: inherit;
94
+ box-sizing: border-box;
95
+ outline: none;
96
+ }
97
+ .retold-remote-collections-search input:focus
98
+ {
99
+ border-color: var(--retold-accent);
100
+ }
101
+ /* ---- Collection List ---- */
102
+ .retold-remote-collections-body
103
+ {
104
+ flex: 1;
105
+ overflow-y: auto;
106
+ overflow-x: hidden;
107
+ }
108
+ .retold-remote-collections-empty
109
+ {
110
+ padding: 24px 16px;
111
+ text-align: center;
112
+ font-size: 0.78rem;
113
+ color: var(--retold-text-dim);
114
+ }
115
+ .retold-remote-collection-card
116
+ {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 10px;
120
+ padding: 10px 12px;
121
+ border-bottom: 1px solid var(--retold-border);
122
+ cursor: pointer;
123
+ transition: background 0.15s;
124
+ }
125
+ .retold-remote-collection-card:hover
126
+ {
127
+ background: rgba(128, 128, 128, 0.08);
128
+ }
129
+ .retold-remote-collection-card-icon
130
+ {
131
+ flex-shrink: 0;
132
+ width: 32px;
133
+ height: 32px;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ border-radius: 4px;
138
+ background: var(--retold-bg-tertiary);
139
+ color: var(--retold-accent);
140
+ font-size: 1rem;
141
+ }
142
+ .retold-remote-collection-card-info
143
+ {
144
+ flex: 1;
145
+ min-width: 0;
146
+ }
147
+ .retold-remote-collection-card-name
148
+ {
149
+ font-size: 0.82rem;
150
+ font-weight: 500;
151
+ color: var(--retold-text-primary);
152
+ white-space: nowrap;
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ }
156
+ .retold-remote-collection-card-meta
157
+ {
158
+ font-size: 0.68rem;
159
+ color: var(--retold-text-dim);
160
+ margin-top: 2px;
161
+ }
162
+ /* ---- Detail mode ---- */
163
+ .retold-remote-collections-detail-controls
164
+ {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 6px;
168
+ padding: 6px 12px;
169
+ border-bottom: 1px solid var(--retold-border);
170
+ flex-shrink: 0;
171
+ }
172
+ .retold-remote-collections-sort-select
173
+ {
174
+ padding: 3px 6px;
175
+ border: 1px solid var(--retold-border);
176
+ border-radius: 3px;
177
+ background: var(--retold-bg-tertiary);
178
+ color: var(--retold-text-secondary);
179
+ font-size: 0.72rem;
180
+ font-family: inherit;
181
+ flex: 1;
182
+ }
183
+ .retold-remote-collections-sort-dir
184
+ {
185
+ padding: 3px 6px;
186
+ border: 1px solid var(--retold-border);
187
+ border-radius: 3px;
188
+ background: transparent;
189
+ color: var(--retold-text-muted);
190
+ font-size: 0.72rem;
191
+ cursor: pointer;
192
+ font-family: inherit;
193
+ }
194
+ .retold-remote-collections-sort-dir:hover
195
+ {
196
+ border-color: var(--retold-accent);
197
+ color: var(--retold-text-primary);
198
+ }
199
+ /* ---- Item rows ---- */
200
+ .retold-remote-collection-item
201
+ {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ padding: 6px 12px;
206
+ border-bottom: 1px solid var(--retold-border);
207
+ cursor: pointer;
208
+ transition: background 0.15s;
209
+ position: relative;
210
+ }
211
+ .retold-remote-collection-item:hover
212
+ {
213
+ background: rgba(128, 128, 128, 0.08);
214
+ }
215
+ .retold-remote-collection-item-drag
216
+ {
217
+ flex-shrink: 0;
218
+ width: 16px;
219
+ cursor: grab;
220
+ color: var(--retold-text-dim);
221
+ font-size: 0.68rem;
222
+ text-align: center;
223
+ user-select: none;
224
+ }
225
+ .retold-remote-collection-item-icon
226
+ {
227
+ flex-shrink: 0;
228
+ width: 24px;
229
+ height: 24px;
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ color: var(--retold-text-muted);
234
+ font-size: 0.82rem;
235
+ }
236
+ .retold-remote-collection-item-icon img
237
+ {
238
+ width: 24px;
239
+ height: 24px;
240
+ object-fit: cover;
241
+ border-radius: 2px;
242
+ }
243
+ .retold-remote-collection-item-name
244
+ {
245
+ flex: 1;
246
+ min-width: 0;
247
+ font-size: 0.78rem;
248
+ color: var(--retold-text-secondary);
249
+ white-space: nowrap;
250
+ overflow: hidden;
251
+ text-overflow: ellipsis;
252
+ }
253
+ .retold-remote-collection-item-type
254
+ {
255
+ flex-shrink: 0;
256
+ font-size: 0.62rem;
257
+ padding: 1px 4px;
258
+ border-radius: 2px;
259
+ background: var(--retold-bg-tertiary);
260
+ color: var(--retold-text-dim);
261
+ text-transform: uppercase;
262
+ }
263
+ .retold-remote-collection-item-remove
264
+ {
265
+ flex-shrink: 0;
266
+ display: none;
267
+ width: 20px;
268
+ height: 20px;
269
+ align-items: center;
270
+ justify-content: center;
271
+ border: none;
272
+ border-radius: 3px;
273
+ background: transparent;
274
+ color: var(--retold-text-dim);
275
+ font-size: 0.82rem;
276
+ cursor: pointer;
277
+ padding: 0;
278
+ }
279
+ .retold-remote-collection-item:hover .retold-remote-collection-item-remove
280
+ {
281
+ display: inline-flex;
282
+ }
283
+ .retold-remote-collection-item-remove:hover
284
+ {
285
+ color: var(--retold-danger-muted, #e55);
286
+ background: rgba(200, 50, 50, 0.1);
287
+ }
288
+ /* ---- Operation Plan mode ---- */
289
+ .retold-remote-collections-op-controls
290
+ {
291
+ flex-direction: column;
292
+ gap: 6px;
293
+ }
294
+ .retold-remote-collections-op-summary
295
+ {
296
+ font-size: 0.75rem;
297
+ color: var(--retold-text-dim);
298
+ padding: 4px 0;
299
+ }
300
+ .retold-remote-collections-op-buttons
301
+ {
302
+ display: flex;
303
+ gap: 6px;
304
+ }
305
+ .retold-remote-collections-op-execute-btn
306
+ {
307
+ flex: 1;
308
+ padding: 6px 12px;
309
+ border: none;
310
+ border-radius: 3px;
311
+ background: var(--retold-accent, #4a90d9);
312
+ color: #fff;
313
+ font-size: 0.78rem;
314
+ font-weight: 600;
315
+ cursor: pointer;
316
+ }
317
+ .retold-remote-collections-op-execute-btn:hover
318
+ {
319
+ opacity: 0.9;
320
+ }
321
+ .retold-remote-collections-op-execute-btn:disabled
322
+ {
323
+ opacity: 0.5;
324
+ cursor: default;
325
+ }
326
+ .retold-remote-collections-op-undo-btn
327
+ {
328
+ padding: 6px 12px;
329
+ border: 1px solid var(--retold-border);
330
+ border-radius: 3px;
331
+ background: transparent;
332
+ color: var(--retold-text-secondary);
333
+ font-size: 0.78rem;
334
+ cursor: pointer;
335
+ }
336
+ .retold-remote-collections-op-undo-btn:hover
337
+ {
338
+ background: var(--retold-bg-tertiary);
339
+ }
340
+ .retold-remote-collection-op-item
341
+ {
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 4px;
345
+ padding: 6px 8px;
346
+ border-bottom: 1px solid var(--retold-border);
347
+ font-size: 0.75rem;
348
+ flex-wrap: wrap;
349
+ }
350
+ .retold-remote-collection-op-item.op-status-completed
351
+ {
352
+ opacity: 0.6;
353
+ }
354
+ .retold-remote-collection-op-item.op-status-skipped
355
+ {
356
+ opacity: 0.4;
357
+ text-decoration: line-through;
358
+ }
359
+ .retold-remote-collection-op-item.op-status-failed
360
+ {
361
+ background: rgba(200, 50, 50, 0.05);
362
+ }
363
+ .retold-remote-collection-op-status
364
+ {
365
+ flex-shrink: 0;
366
+ width: 16px;
367
+ text-align: center;
368
+ font-size: 0.8rem;
369
+ }
370
+ .op-status-completed .retold-remote-collection-op-status
371
+ {
372
+ color: var(--retold-success, #4a4);
373
+ }
374
+ .op-status-failed .retold-remote-collection-op-status
375
+ {
376
+ color: var(--retold-danger-muted, #e55);
377
+ }
378
+ .op-status-pending .retold-remote-collection-op-status
379
+ {
380
+ color: var(--retold-text-dim);
381
+ }
382
+ .retold-remote-collection-op-source
383
+ {
384
+ flex: 1;
385
+ min-width: 0;
386
+ overflow: hidden;
387
+ text-overflow: ellipsis;
388
+ white-space: nowrap;
389
+ color: var(--retold-text-dim);
390
+ }
391
+ .retold-remote-collection-op-arrow
392
+ {
393
+ flex-shrink: 0;
394
+ color: var(--retold-text-dim);
395
+ padding: 0 2px;
396
+ }
397
+ .retold-remote-collection-op-dest
398
+ {
399
+ flex: 2;
400
+ min-width: 0;
401
+ overflow: hidden;
402
+ text-overflow: ellipsis;
403
+ white-space: nowrap;
404
+ color: var(--retold-text-secondary);
405
+ }
406
+ .retold-remote-collection-op-dest-input
407
+ {
408
+ width: 100%;
409
+ padding: 2px 4px;
410
+ border: 1px solid var(--retold-accent);
411
+ border-radius: 2px;
412
+ background: var(--retold-bg-tertiary);
413
+ color: var(--retold-text-secondary);
414
+ font-size: 0.75rem;
415
+ font-family: inherit;
416
+ box-sizing: border-box;
417
+ }
418
+ .retold-remote-collection-op-badge
419
+ {
420
+ flex-shrink: 0;
421
+ font-size: 0.6rem;
422
+ padding: 1px 4px;
423
+ border-radius: 2px;
424
+ background: var(--retold-bg-tertiary);
425
+ color: var(--retold-text-dim);
426
+ text-transform: uppercase;
427
+ font-weight: 600;
428
+ letter-spacing: 0.3px;
429
+ }
430
+ .retold-remote-collection-op-error
431
+ {
432
+ width: 100%;
433
+ font-size: 0.68rem;
434
+ color: var(--retold-danger-muted, #e55);
435
+ padding: 2px 0 0 20px;
436
+ }
437
+ /* ---- Edit mode ---- */
438
+ .retold-remote-collections-edit
439
+ {
440
+ padding: 12px;
441
+ }
442
+ .retold-remote-collections-edit-group
443
+ {
444
+ margin-bottom: 12px;
445
+ }
446
+ .retold-remote-collections-edit-label
447
+ {
448
+ font-size: 0.7rem;
449
+ font-weight: 600;
450
+ text-transform: uppercase;
451
+ letter-spacing: 0.5px;
452
+ color: var(--retold-text-dim);
453
+ margin-bottom: 4px;
454
+ }
455
+ .retold-remote-collections-edit-input
456
+ {
457
+ width: 100%;
458
+ padding: 6px 8px;
459
+ border: 1px solid var(--retold-border);
460
+ border-radius: 3px;
461
+ background: var(--retold-bg-tertiary);
462
+ color: var(--retold-text-secondary);
463
+ font-size: 0.82rem;
464
+ font-family: inherit;
465
+ box-sizing: border-box;
466
+ }
467
+ .retold-remote-collections-edit-input:focus
468
+ {
469
+ border-color: var(--retold-accent);
470
+ outline: none;
471
+ }
472
+ .retold-remote-collections-edit-textarea
473
+ {
474
+ width: 100%;
475
+ min-height: 80px;
476
+ padding: 6px 8px;
477
+ border: 1px solid var(--retold-border);
478
+ border-radius: 3px;
479
+ background: var(--retold-bg-tertiary);
480
+ color: var(--retold-text-secondary);
481
+ font-size: 0.78rem;
482
+ font-family: inherit;
483
+ box-sizing: border-box;
484
+ resize: vertical;
485
+ }
486
+ .retold-remote-collections-edit-textarea:focus
487
+ {
488
+ border-color: var(--retold-accent);
489
+ outline: none;
490
+ }
491
+ .retold-remote-collections-edit-actions
492
+ {
493
+ display: flex;
494
+ gap: 8px;
495
+ margin-top: 16px;
496
+ }
497
+ .retold-remote-collections-edit-btn
498
+ {
499
+ padding: 6px 14px;
500
+ border: 1px solid var(--retold-border);
501
+ border-radius: 4px;
502
+ background: transparent;
503
+ color: var(--retold-text-secondary);
504
+ font-size: 0.78rem;
505
+ cursor: pointer;
506
+ font-family: inherit;
507
+ }
508
+ .retold-remote-collections-edit-btn:hover
509
+ {
510
+ border-color: var(--retold-accent);
511
+ color: var(--retold-text-primary);
512
+ }
513
+ .retold-remote-collections-edit-btn-primary
514
+ {
515
+ background: var(--retold-accent);
516
+ border-color: var(--retold-accent);
517
+ color: #fff;
518
+ }
519
+ .retold-remote-collections-edit-btn-primary:hover
520
+ {
521
+ opacity: 0.9;
522
+ }
523
+ .retold-remote-collections-edit-btn-danger
524
+ {
525
+ border-color: var(--retold-danger-muted, #e55);
526
+ color: var(--retold-danger-muted, #e55);
527
+ margin-top: 16px;
528
+ }
529
+ .retold-remote-collections-edit-btn-danger:hover
530
+ {
531
+ background: rgba(200, 50, 50, 0.1);
532
+ }
533
+ /* ---- Drag-and-drop feedback ---- */
534
+ .retold-remote-collection-item.dragging
535
+ {
536
+ opacity: 0.4;
537
+ }
538
+ .retold-remote-collection-item.drag-over
539
+ {
540
+ border-top: 2px solid var(--retold-accent);
541
+ }
542
+ `,
543
+
544
+ Templates:
545
+ [
546
+ {
547
+ Hash: "RetoldRemote-CollectionsPanel",
548
+ Template: /*html*/`<div class="retold-remote-collections-panel" id="RetoldRemote-CollectionsPanel-Root"></div>`
549
+ }
550
+ ],
551
+
552
+ Renderables:
553
+ [
554
+ {
555
+ RenderableHash: "RetoldRemote-CollectionsPanel",
556
+ TemplateHash: "RetoldRemote-CollectionsPanel",
557
+ DestinationAddress: "#RetoldRemote-Collections-Container"
558
+ }
559
+ ]
560
+ };
561
+
562
+ class RetoldRemoteCollectionsPanelView extends libPictView
563
+ {
564
+ constructor(pFable, pOptions, pServiceHash)
565
+ {
566
+ super(pFable, pOptions, pServiceHash);
567
+
568
+ this._draggedItemId = null;
569
+ }
570
+
571
+ onAfterRender()
572
+ {
573
+ super.onAfterRender();
574
+ this.renderContent();
575
+ }
576
+
577
+ /**
578
+ * Main render dispatch — renders the appropriate mode content
579
+ * into the panel root.
580
+ */
581
+ renderContent()
582
+ {
583
+ let tmpRoot = document.getElementById('RetoldRemote-CollectionsPanel-Root');
584
+ if (!tmpRoot)
585
+ {
586
+ return;
587
+ }
588
+
589
+ let tmpRemote = this.pict.AppData.RetoldRemote;
590
+
591
+ switch (tmpRemote.CollectionsPanelMode)
592
+ {
593
+ case 'detail':
594
+ this._renderDetailMode(tmpRoot);
595
+ break;
596
+ case 'edit':
597
+ this._renderEditMode(tmpRoot);
598
+ break;
599
+ case 'list':
600
+ default:
601
+ this._renderListMode(tmpRoot);
602
+ break;
603
+ }
604
+ }
605
+
606
+ // -- List Mode --------------------------------------------------------
607
+
608
+ _renderListMode(pRoot)
609
+ {
610
+ let tmpSelf = this;
611
+ let tmpRemote = this.pict.AppData.RetoldRemote;
612
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
613
+
614
+ pRoot.innerHTML = '';
615
+
616
+ // Header
617
+ let tmpHeader = document.createElement('div');
618
+ tmpHeader.className = 'retold-remote-collections-header';
619
+
620
+ let tmpTitle = document.createElement('div');
621
+ tmpTitle.className = 'retold-remote-collections-header-title';
622
+ tmpTitle.textContent = 'Collections';
623
+
624
+ let tmpNewBtn = document.createElement('button');
625
+ tmpNewBtn.className = 'retold-remote-collections-header-btn';
626
+ tmpNewBtn.title = 'New Collection';
627
+ tmpNewBtn.textContent = '+';
628
+ tmpNewBtn.onclick = () =>
629
+ {
630
+ let tmpName = prompt('Collection name:');
631
+ if (tmpName && tmpName.trim())
632
+ {
633
+ tmpManager.createCollection(tmpName.trim(), (pError, pCollection) =>
634
+ {
635
+ if (!pError && pCollection)
636
+ {
637
+ // Open the newly created collection
638
+ tmpRemote.CollectionsPanelMode = 'detail';
639
+ tmpManager.fetchCollection(pCollection.GUID);
640
+ }
641
+ });
642
+ }
643
+ };
644
+
645
+ tmpHeader.appendChild(tmpTitle);
646
+ tmpHeader.appendChild(tmpNewBtn);
647
+ pRoot.appendChild(tmpHeader);
648
+
649
+ // Search
650
+ let tmpSearchWrap = document.createElement('div');
651
+ tmpSearchWrap.className = 'retold-remote-collections-search';
652
+
653
+ let tmpSearchInput = document.createElement('input');
654
+ tmpSearchInput.type = 'text';
655
+ tmpSearchInput.placeholder = 'Search collections...';
656
+ tmpSearchInput.value = tmpRemote.CollectionSearchQuery || '';
657
+ tmpSearchInput.oninput = (pEvent) =>
658
+ {
659
+ tmpRemote.CollectionSearchQuery = pEvent.target.value;
660
+ tmpSelf._renderCollectionCards(pRoot.querySelector('.retold-remote-collections-body'));
661
+ };
662
+
663
+ tmpSearchWrap.appendChild(tmpSearchInput);
664
+ pRoot.appendChild(tmpSearchWrap);
665
+
666
+ // Body (scrollable list)
667
+ let tmpBody = document.createElement('div');
668
+ tmpBody.className = 'retold-remote-collections-body';
669
+ pRoot.appendChild(tmpBody);
670
+
671
+ this._renderCollectionCards(tmpBody);
672
+ }
673
+
674
+ _renderCollectionCards(pBody)
675
+ {
676
+ if (!pBody)
677
+ {
678
+ return;
679
+ }
680
+
681
+ let tmpSelf = this;
682
+ let tmpRemote = this.pict.AppData.RetoldRemote;
683
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
684
+ let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
685
+
686
+ pBody.innerHTML = '';
687
+
688
+ let tmpCollections = tmpManager.searchCollections(tmpRemote.CollectionSearchQuery);
689
+
690
+ if (!tmpCollections || tmpCollections.length === 0)
691
+ {
692
+ let tmpEmpty = document.createElement('div');
693
+ tmpEmpty.className = 'retold-remote-collections-empty';
694
+ tmpEmpty.textContent = tmpRemote.CollectionSearchQuery ? 'No collections match your search.' : 'No collections yet. Click + to create one.';
695
+ pBody.appendChild(tmpEmpty);
696
+ return;
697
+ }
698
+
699
+ for (let i = 0; i < tmpCollections.length; i++)
700
+ {
701
+ let tmpCollection = tmpCollections[i];
702
+
703
+ let tmpCard = document.createElement('div');
704
+ tmpCard.className = 'retold-remote-collection-card';
705
+ tmpCard.onclick = () =>
706
+ {
707
+ tmpRemote.CollectionsPanelMode = 'detail';
708
+ tmpManager.fetchCollection(tmpCollection.GUID);
709
+ };
710
+
711
+ // Icon
712
+ let tmpIconDiv = document.createElement('div');
713
+ tmpIconDiv.className = 'retold-remote-collection-card-icon';
714
+ if (tmpIconProvider && typeof tmpIconProvider.getIcon === 'function')
715
+ {
716
+ tmpIconDiv.innerHTML = tmpIconProvider.getIcon('bookmark', 18);
717
+ }
718
+ else
719
+ {
720
+ tmpIconDiv.textContent = '\u2630';
721
+ }
722
+
723
+ // Info
724
+ let tmpInfoDiv = document.createElement('div');
725
+ tmpInfoDiv.className = 'retold-remote-collection-card-info';
726
+
727
+ let tmpNameDiv = document.createElement('div');
728
+ tmpNameDiv.className = 'retold-remote-collection-card-name';
729
+ tmpNameDiv.textContent = tmpCollection.Name || 'Untitled';
730
+
731
+ let tmpMetaDiv = document.createElement('div');
732
+ tmpMetaDiv.className = 'retold-remote-collection-card-meta';
733
+ tmpMetaDiv.textContent = tmpCollection.ItemCount + ' item' + (tmpCollection.ItemCount !== 1 ? 's' : '');
734
+
735
+ tmpInfoDiv.appendChild(tmpNameDiv);
736
+ tmpInfoDiv.appendChild(tmpMetaDiv);
737
+
738
+ tmpCard.appendChild(tmpIconDiv);
739
+ tmpCard.appendChild(tmpInfoDiv);
740
+ pBody.appendChild(tmpCard);
741
+ }
742
+ }
743
+
744
+ // -- Detail Mode ------------------------------------------------------
745
+
746
+ _renderDetailMode(pRoot)
747
+ {
748
+ let tmpSelf = this;
749
+ let tmpRemote = this.pict.AppData.RetoldRemote;
750
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
751
+ let tmpCollection = tmpRemote.ActiveCollection;
752
+
753
+ pRoot.innerHTML = '';
754
+
755
+ if (!tmpCollection)
756
+ {
757
+ pRoot.innerHTML = '<div class="retold-remote-collections-empty">Loading...</div>';
758
+ return;
759
+ }
760
+
761
+ // Header with back button and collection name
762
+ let tmpHeader = document.createElement('div');
763
+ tmpHeader.className = 'retold-remote-collections-header';
764
+
765
+ let tmpBackBtn = document.createElement('button');
766
+ tmpBackBtn.className = 'retold-remote-collections-header-btn';
767
+ tmpBackBtn.title = 'Back to list';
768
+ tmpBackBtn.textContent = '\u2190';
769
+ tmpBackBtn.onclick = () =>
770
+ {
771
+ tmpRemote.CollectionsPanelMode = 'list';
772
+ tmpRemote.ActiveCollectionGUID = null;
773
+ tmpRemote.ActiveCollection = null;
774
+ tmpSelf.renderContent();
775
+ };
776
+
777
+ let tmpTitle = document.createElement('div');
778
+ tmpTitle.className = 'retold-remote-collections-header-title';
779
+ tmpTitle.textContent = tmpCollection.Name || 'Untitled';
780
+
781
+ let tmpEditBtn = document.createElement('button');
782
+ tmpEditBtn.className = 'retold-remote-collections-header-btn';
783
+ tmpEditBtn.title = 'Edit collection';
784
+ tmpEditBtn.textContent = '\u270E';
785
+ tmpEditBtn.onclick = () =>
786
+ {
787
+ tmpRemote.CollectionsPanelMode = 'edit';
788
+ tmpSelf.renderContent();
789
+ };
790
+
791
+ tmpHeader.appendChild(tmpBackBtn);
792
+ tmpHeader.appendChild(tmpTitle);
793
+ tmpHeader.appendChild(tmpEditBtn);
794
+ pRoot.appendChild(tmpHeader);
795
+
796
+ // Check if this is an operation-plan collection
797
+ let tmpIsOperationPlan = (tmpCollection.CollectionType === 'operation-plan');
798
+
799
+ if (tmpIsOperationPlan)
800
+ {
801
+ // Operation plan controls: summary + execute/undo buttons
802
+ let tmpOpControls = document.createElement('div');
803
+ tmpOpControls.className = 'retold-remote-collections-detail-controls retold-remote-collections-op-controls';
804
+
805
+ // Count pending, completed, failed, skipped
806
+ let tmpItems = tmpCollection.Items || [];
807
+ let tmpPending = 0;
808
+ let tmpCompleted = 0;
809
+ let tmpFailed = 0;
810
+ let tmpSkipped = 0;
811
+ for (let i = 0; i < tmpItems.length; i++)
812
+ {
813
+ let tmpStatus = tmpItems[i].OperationStatus;
814
+ if (tmpStatus === 'completed') tmpCompleted++;
815
+ else if (tmpStatus === 'failed') tmpFailed++;
816
+ else if (tmpStatus === 'skipped') tmpSkipped++;
817
+ else if (tmpItems[i].Operation) tmpPending++;
818
+ }
819
+
820
+ let tmpSummary = document.createElement('div');
821
+ tmpSummary.className = 'retold-remote-collections-op-summary';
822
+ let tmpSummaryParts = [];
823
+ if (tmpPending > 0) tmpSummaryParts.push(tmpPending + ' pending');
824
+ if (tmpCompleted > 0) tmpSummaryParts.push(tmpCompleted + ' done');
825
+ if (tmpFailed > 0) tmpSummaryParts.push(tmpFailed + ' failed');
826
+ if (tmpSkipped > 0) tmpSummaryParts.push(tmpSkipped + ' skipped');
827
+ tmpSummary.textContent = tmpSummaryParts.join(' \u00b7 ') || 'No operations';
828
+ tmpOpControls.appendChild(tmpSummary);
829
+
830
+ let tmpBtnRow = document.createElement('div');
831
+ tmpBtnRow.className = 'retold-remote-collections-op-buttons';
832
+
833
+ if (tmpPending > 0)
834
+ {
835
+ let tmpExecBtn = document.createElement('button');
836
+ tmpExecBtn.className = 'retold-remote-collections-op-execute-btn';
837
+ tmpExecBtn.textContent = 'Execute ' + tmpPending + ' Move' + (tmpPending > 1 ? 's' : '');
838
+ tmpExecBtn.onclick = () =>
839
+ {
840
+ tmpExecBtn.disabled = true;
841
+ tmpExecBtn.textContent = 'Moving...';
842
+ tmpManager.executeCollectionOperations(tmpCollection.GUID);
843
+ };
844
+ tmpBtnRow.appendChild(tmpExecBtn);
845
+ }
846
+
847
+ if (tmpCollection.OperationBatchGUID && tmpCompleted > 0)
848
+ {
849
+ let tmpUndoBtn = document.createElement('button');
850
+ tmpUndoBtn.className = 'retold-remote-collections-op-undo-btn';
851
+ tmpUndoBtn.textContent = 'Undo';
852
+ tmpUndoBtn.onclick = () =>
853
+ {
854
+ tmpUndoBtn.disabled = true;
855
+ tmpUndoBtn.textContent = 'Undoing...';
856
+ tmpManager.undoCollectionOperations(tmpCollection.GUID);
857
+ };
858
+ tmpBtnRow.appendChild(tmpUndoBtn);
859
+ }
860
+
861
+ tmpOpControls.appendChild(tmpBtnRow);
862
+ pRoot.appendChild(tmpOpControls);
863
+ }
864
+ else
865
+ {
866
+ // Standard sort controls for bookmark collections
867
+ let tmpControls = document.createElement('div');
868
+ tmpControls.className = 'retold-remote-collections-detail-controls';
869
+
870
+ let tmpSortSelect = document.createElement('select');
871
+ tmpSortSelect.className = 'retold-remote-collections-sort-select';
872
+
873
+ let tmpSortOptions = [
874
+ { value: 'manual', label: 'Manual' },
875
+ { value: 'name', label: 'Name' },
876
+ { value: 'modified', label: 'Date Added' },
877
+ { value: 'type', label: 'Type' }
878
+ ];
879
+
880
+ for (let i = 0; i < tmpSortOptions.length; i++)
881
+ {
882
+ let tmpOpt = document.createElement('option');
883
+ tmpOpt.value = tmpSortOptions[i].value;
884
+ tmpOpt.textContent = tmpSortOptions[i].label;
885
+ if (tmpCollection.SortMode === tmpSortOptions[i].value)
886
+ {
887
+ tmpOpt.selected = true;
888
+ }
889
+ tmpSortSelect.appendChild(tmpOpt);
890
+ }
891
+
892
+ tmpSortSelect.onchange = (pEvent) =>
893
+ {
894
+ tmpManager.sortActiveCollection(pEvent.target.value, null);
895
+ };
896
+
897
+ let tmpDirBtn = document.createElement('button');
898
+ tmpDirBtn.className = 'retold-remote-collections-sort-dir';
899
+ tmpDirBtn.textContent = tmpCollection.SortDirection === 'desc' ? '\u2193' : '\u2191';
900
+ tmpDirBtn.title = tmpCollection.SortDirection === 'desc' ? 'Descending' : 'Ascending';
901
+ tmpDirBtn.onclick = () =>
902
+ {
903
+ let tmpNewDir = (tmpRemote.ActiveCollection.SortDirection === 'desc') ? 'asc' : 'desc';
904
+ tmpManager.sortActiveCollection(null, tmpNewDir);
905
+ };
906
+
907
+ tmpControls.appendChild(tmpSortSelect);
908
+ tmpControls.appendChild(tmpDirBtn);
909
+ pRoot.appendChild(tmpControls);
910
+ }
911
+
912
+ // Item list
913
+ let tmpBody = document.createElement('div');
914
+ tmpBody.className = 'retold-remote-collections-body';
915
+ pRoot.appendChild(tmpBody);
916
+
917
+ if (tmpIsOperationPlan)
918
+ {
919
+ this._renderOperationItemList(tmpBody, tmpCollection);
920
+ }
921
+ else
922
+ {
923
+ this._renderItemList(tmpBody, tmpCollection);
924
+ }
925
+ }
926
+
927
+ _renderItemList(pBody, pCollection)
928
+ {
929
+ let tmpSelf = this;
930
+ let tmpRemote = this.pict.AppData.RetoldRemote;
931
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
932
+ let tmpItems = pCollection.Items || [];
933
+
934
+ pBody.innerHTML = '';
935
+
936
+ if (tmpItems.length === 0)
937
+ {
938
+ let tmpEmpty = document.createElement('div');
939
+ tmpEmpty.className = 'retold-remote-collections-empty';
940
+ tmpEmpty.textContent = 'No items yet. Browse files and add them to this collection.';
941
+ pBody.appendChild(tmpEmpty);
942
+ return;
943
+ }
944
+
945
+ for (let i = 0; i < tmpItems.length; i++)
946
+ {
947
+ let tmpItem = tmpItems[i];
948
+ let tmpRow = document.createElement('div');
949
+ tmpRow.className = 'retold-remote-collection-item';
950
+ tmpRow.setAttribute('data-item-id', tmpItem.ID);
951
+
952
+ // Drag handle (for manual sort)
953
+ if (pCollection.SortMode === 'manual')
954
+ {
955
+ let tmpDrag = document.createElement('div');
956
+ tmpDrag.className = 'retold-remote-collection-item-drag';
957
+ tmpDrag.textContent = '\u2630';
958
+ tmpDrag.draggable = true;
959
+
960
+ tmpDrag.ondragstart = (pEvent) =>
961
+ {
962
+ tmpSelf._draggedItemId = tmpItem.ID;
963
+ tmpRow.classList.add('dragging');
964
+ pEvent.dataTransfer.effectAllowed = 'move';
965
+ };
966
+
967
+ tmpDrag.ondragend = () =>
968
+ {
969
+ tmpRow.classList.remove('dragging');
970
+ tmpSelf._draggedItemId = null;
971
+ // Remove all drag-over classes
972
+ let tmpAllItems = pBody.querySelectorAll('.retold-remote-collection-item');
973
+ for (let j = 0; j < tmpAllItems.length; j++)
974
+ {
975
+ tmpAllItems[j].classList.remove('drag-over');
976
+ }
977
+ };
978
+
979
+ tmpRow.appendChild(tmpDrag);
980
+ }
981
+
982
+ // Drop target events
983
+ tmpRow.ondragover = (pEvent) =>
984
+ {
985
+ pEvent.preventDefault();
986
+ pEvent.dataTransfer.dropEffect = 'move';
987
+ tmpRow.classList.add('drag-over');
988
+ };
989
+ tmpRow.ondragleave = () =>
990
+ {
991
+ tmpRow.classList.remove('drag-over');
992
+ };
993
+ tmpRow.ondrop = (pEvent) =>
994
+ {
995
+ pEvent.preventDefault();
996
+ tmpRow.classList.remove('drag-over');
997
+
998
+ if (tmpSelf._draggedItemId && tmpSelf._draggedItemId !== tmpItem.ID)
999
+ {
1000
+ // Build new order: move dragged item before this item
1001
+ let tmpCurrentItems = pCollection.Items || [];
1002
+ let tmpNewOrder = [];
1003
+ for (let j = 0; j < tmpCurrentItems.length; j++)
1004
+ {
1005
+ if (tmpCurrentItems[j].ID === tmpSelf._draggedItemId)
1006
+ {
1007
+ continue;
1008
+ }
1009
+ if (tmpCurrentItems[j].ID === tmpItem.ID)
1010
+ {
1011
+ tmpNewOrder.push(tmpSelf._draggedItemId);
1012
+ }
1013
+ tmpNewOrder.push(tmpCurrentItems[j].ID);
1014
+ }
1015
+ tmpManager.reorderItems(pCollection.GUID, tmpNewOrder);
1016
+ }
1017
+ };
1018
+
1019
+ // Item icon/thumbnail
1020
+ let tmpIconDiv = document.createElement('div');
1021
+ tmpIconDiv.className = 'retold-remote-collection-item-icon';
1022
+
1023
+ let tmpPath = tmpItem.Path || '';
1024
+ let tmpExt = tmpPath.replace(/^.*\./, '').toLowerCase();
1025
+ let tmpMediaType = this.pict.PictApplication._getMediaType(tmpExt);
1026
+
1027
+ if (tmpMediaType === 'image' && tmpItem.Type === 'file')
1028
+ {
1029
+ let tmpImg = document.createElement('img');
1030
+ let tmpMediaProvider = this.pict.providers['RetoldRemote-Provider'];
1031
+ if (tmpMediaProvider)
1032
+ {
1033
+ tmpImg.src = tmpMediaProvider.getThumbnailURL(tmpPath, 48, 48);
1034
+ }
1035
+ tmpImg.alt = '';
1036
+ tmpImg.loading = 'lazy';
1037
+ tmpIconDiv.appendChild(tmpImg);
1038
+ }
1039
+ else
1040
+ {
1041
+ tmpIconDiv.textContent = this._getTypeIcon(tmpItem.Type, tmpMediaType);
1042
+ }
1043
+
1044
+ // Item name
1045
+ let tmpNameDiv = document.createElement('div');
1046
+ tmpNameDiv.className = 'retold-remote-collection-item-name';
1047
+ tmpNameDiv.textContent = tmpItem.Label || tmpPath.split('/').pop() || tmpPath;
1048
+ tmpNameDiv.title = tmpPath;
1049
+
1050
+ // Type badge
1051
+ let tmpTypeDiv = document.createElement('div');
1052
+ tmpTypeDiv.className = 'retold-remote-collection-item-type';
1053
+ tmpTypeDiv.textContent = tmpItem.Type || 'file';
1054
+
1055
+ // Remove button
1056
+ let tmpRemoveBtn = document.createElement('button');
1057
+ tmpRemoveBtn.className = 'retold-remote-collection-item-remove';
1058
+ tmpRemoveBtn.title = 'Remove from collection';
1059
+ tmpRemoveBtn.textContent = '\u00d7';
1060
+ tmpRemoveBtn.onclick = (pEvent) =>
1061
+ {
1062
+ pEvent.stopPropagation();
1063
+ tmpManager.removeItemFromCollection(pCollection.GUID, tmpItem.ID);
1064
+ };
1065
+
1066
+ // Click to navigate — set up collection browsing context
1067
+ let tmpItemIndex = i;
1068
+ tmpRow.onclick = () =>
1069
+ {
1070
+ if (tmpItem.Type === 'file' || tmpItem.Type === 'subfile' || tmpItem.Type === 'image-crop' ||
1071
+ tmpItem.Type === 'video-clip' || tmpItem.Type === 'video-frame')
1072
+ {
1073
+ // Enter collection browsing mode so next/prev navigate through collection items
1074
+ tmpRemote.BrowsingCollection = true;
1075
+ tmpRemote.BrowsingCollectionIndex = tmpItemIndex;
1076
+ tmpSelf.pict.PictApplication.navigateToFile(tmpItem.Path);
1077
+ }
1078
+ else if (tmpItem.Type === 'folder' || tmpItem.Type === 'folder-contents')
1079
+ {
1080
+ tmpSelf.pict.PictApplication.loadFileList(tmpItem.Path);
1081
+ }
1082
+ };
1083
+
1084
+ tmpRow.appendChild(tmpIconDiv);
1085
+ tmpRow.appendChild(tmpNameDiv);
1086
+ tmpRow.appendChild(tmpTypeDiv);
1087
+ tmpRow.appendChild(tmpRemoveBtn);
1088
+ pBody.appendChild(tmpRow);
1089
+ }
1090
+ }
1091
+
1092
+ // -- Operation Plan Item Rendering ------------------------------------
1093
+
1094
+ _renderOperationItemList(pBody, pCollection)
1095
+ {
1096
+ let tmpSelf = this;
1097
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
1098
+ let tmpItems = pCollection.Items || [];
1099
+
1100
+ pBody.innerHTML = '';
1101
+
1102
+ if (tmpItems.length === 0)
1103
+ {
1104
+ let tmpEmpty = document.createElement('div');
1105
+ tmpEmpty.className = 'retold-remote-collections-empty';
1106
+ tmpEmpty.textContent = 'No items in this sort plan.';
1107
+ pBody.appendChild(tmpEmpty);
1108
+ return;
1109
+ }
1110
+
1111
+ for (let i = 0; i < tmpItems.length; i++)
1112
+ {
1113
+ let tmpItem = tmpItems[i];
1114
+ let tmpRow = document.createElement('div');
1115
+ tmpRow.className = 'retold-remote-collection-op-item';
1116
+
1117
+ // Status color
1118
+ let tmpStatus = tmpItem.OperationStatus || 'pending';
1119
+ tmpRow.classList.add('op-status-' + tmpStatus);
1120
+
1121
+ // Status indicator
1122
+ let tmpStatusDiv = document.createElement('div');
1123
+ tmpStatusDiv.className = 'retold-remote-collection-op-status';
1124
+ if (tmpStatus === 'completed')
1125
+ {
1126
+ tmpStatusDiv.textContent = '\u2713';
1127
+ tmpStatusDiv.title = 'Completed';
1128
+ }
1129
+ else if (tmpStatus === 'failed')
1130
+ {
1131
+ tmpStatusDiv.textContent = '\u2717';
1132
+ tmpStatusDiv.title = tmpItem.OperationError || 'Failed';
1133
+ }
1134
+ else if (tmpStatus === 'skipped')
1135
+ {
1136
+ tmpStatusDiv.textContent = '\u2014';
1137
+ tmpStatusDiv.title = 'Skipped';
1138
+ }
1139
+ else
1140
+ {
1141
+ tmpStatusDiv.textContent = '\u25CB';
1142
+ tmpStatusDiv.title = 'Pending';
1143
+ }
1144
+
1145
+ // Source path (filename only, full path in tooltip)
1146
+ let tmpSourceDiv = document.createElement('div');
1147
+ tmpSourceDiv.className = 'retold-remote-collection-op-source';
1148
+ let tmpSourcePath = tmpItem.Path || '';
1149
+ tmpSourceDiv.textContent = tmpSourcePath.split('/').pop() || tmpSourcePath;
1150
+ tmpSourceDiv.title = tmpSourcePath;
1151
+
1152
+ // Arrow
1153
+ let tmpArrow = document.createElement('div');
1154
+ tmpArrow.className = 'retold-remote-collection-op-arrow';
1155
+ tmpArrow.textContent = '\u2192';
1156
+
1157
+ // Destination path (editable)
1158
+ let tmpDestDiv = document.createElement('div');
1159
+ tmpDestDiv.className = 'retold-remote-collection-op-dest';
1160
+ let tmpDestPath = tmpItem.DestinationPath || '';
1161
+ tmpDestDiv.textContent = tmpDestPath || '(no destination)';
1162
+ tmpDestDiv.title = tmpDestPath;
1163
+
1164
+ // Make destination editable on click (only for pending items)
1165
+ if (tmpStatus === 'pending')
1166
+ {
1167
+ tmpDestDiv.style.cursor = 'pointer';
1168
+ tmpDestDiv.onclick = (pEvent) =>
1169
+ {
1170
+ pEvent.stopPropagation();
1171
+ tmpSelf._startEditDestination(tmpDestDiv, tmpItem, pCollection);
1172
+ };
1173
+ }
1174
+
1175
+ // Operation badge
1176
+ let tmpOpBadge = document.createElement('div');
1177
+ tmpOpBadge.className = 'retold-remote-collection-op-badge';
1178
+ tmpOpBadge.textContent = (tmpItem.Operation || 'move').toUpperCase();
1179
+
1180
+ // Skip/remove button (only for pending items)
1181
+ let tmpSkipBtn = document.createElement('button');
1182
+ tmpSkipBtn.className = 'retold-remote-collection-item-remove';
1183
+ tmpSkipBtn.title = 'Skip this operation';
1184
+ tmpSkipBtn.textContent = '\u00d7';
1185
+
1186
+ if (tmpStatus === 'pending')
1187
+ {
1188
+ tmpSkipBtn.onclick = (pEvent) =>
1189
+ {
1190
+ pEvent.stopPropagation();
1191
+ tmpManager.skipItemOperation(tmpItem.ID);
1192
+ };
1193
+ }
1194
+ else
1195
+ {
1196
+ tmpSkipBtn.style.visibility = 'hidden';
1197
+ }
1198
+
1199
+ // Error message (if failed)
1200
+ if (tmpStatus === 'failed' && tmpItem.OperationError)
1201
+ {
1202
+ let tmpErrorDiv = document.createElement('div');
1203
+ tmpErrorDiv.className = 'retold-remote-collection-op-error';
1204
+ tmpErrorDiv.textContent = tmpItem.OperationError;
1205
+ tmpRow.appendChild(tmpErrorDiv);
1206
+ }
1207
+
1208
+ tmpRow.appendChild(tmpStatusDiv);
1209
+ tmpRow.appendChild(tmpSourceDiv);
1210
+ tmpRow.appendChild(tmpArrow);
1211
+ tmpRow.appendChild(tmpDestDiv);
1212
+ tmpRow.appendChild(tmpOpBadge);
1213
+ tmpRow.appendChild(tmpSkipBtn);
1214
+ pBody.appendChild(tmpRow);
1215
+ }
1216
+ }
1217
+
1218
+ /**
1219
+ * Start inline editing of an operation item's destination path.
1220
+ */
1221
+ _startEditDestination(pDestDiv, pItem, pCollection)
1222
+ {
1223
+ let tmpSelf = this;
1224
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
1225
+
1226
+ let tmpInput = document.createElement('input');
1227
+ tmpInput.type = 'text';
1228
+ tmpInput.className = 'retold-remote-collection-op-dest-input';
1229
+ tmpInput.value = pItem.DestinationPath || '';
1230
+
1231
+ let tmpFinishEdit = () =>
1232
+ {
1233
+ let tmpNewDest = tmpInput.value.trim();
1234
+ if (tmpNewDest && tmpNewDest !== pItem.DestinationPath)
1235
+ {
1236
+ tmpManager.setItemDestination(pItem.ID, tmpNewDest);
1237
+ }
1238
+ pDestDiv.textContent = tmpNewDest || pItem.DestinationPath || '(no destination)';
1239
+ pDestDiv.title = tmpNewDest || pItem.DestinationPath || '';
1240
+ };
1241
+
1242
+ tmpInput.onblur = tmpFinishEdit;
1243
+ tmpInput.onkeydown = (pEvent) =>
1244
+ {
1245
+ if (pEvent.key === 'Enter')
1246
+ {
1247
+ pEvent.preventDefault();
1248
+ tmpInput.blur();
1249
+ }
1250
+ else if (pEvent.key === 'Escape')
1251
+ {
1252
+ pEvent.preventDefault();
1253
+ pDestDiv.textContent = pItem.DestinationPath || '(no destination)';
1254
+ pDestDiv.title = pItem.DestinationPath || '';
1255
+ }
1256
+ };
1257
+
1258
+ pDestDiv.textContent = '';
1259
+ pDestDiv.appendChild(tmpInput);
1260
+ tmpInput.focus();
1261
+ tmpInput.select();
1262
+ }
1263
+
1264
+ // -- Edit Mode --------------------------------------------------------
1265
+
1266
+ _renderEditMode(pRoot)
1267
+ {
1268
+ let tmpSelf = this;
1269
+ let tmpRemote = this.pict.AppData.RetoldRemote;
1270
+ let tmpManager = this.pict.providers['RetoldRemote-CollectionManager'];
1271
+ let tmpCollection = tmpRemote.ActiveCollection;
1272
+
1273
+ pRoot.innerHTML = '';
1274
+
1275
+ if (!tmpCollection)
1276
+ {
1277
+ pRoot.innerHTML = '<div class="retold-remote-collections-empty">No collection selected.</div>';
1278
+ return;
1279
+ }
1280
+
1281
+ // Header
1282
+ let tmpHeader = document.createElement('div');
1283
+ tmpHeader.className = 'retold-remote-collections-header';
1284
+
1285
+ let tmpBackBtn = document.createElement('button');
1286
+ tmpBackBtn.className = 'retold-remote-collections-header-btn';
1287
+ tmpBackBtn.title = 'Back to detail';
1288
+ tmpBackBtn.textContent = '\u2190';
1289
+ tmpBackBtn.onclick = () =>
1290
+ {
1291
+ tmpRemote.CollectionsPanelMode = 'detail';
1292
+ tmpSelf.renderContent();
1293
+ };
1294
+
1295
+ let tmpTitle = document.createElement('div');
1296
+ tmpTitle.className = 'retold-remote-collections-header-title';
1297
+ tmpTitle.textContent = 'Edit Collection';
1298
+
1299
+ tmpHeader.appendChild(tmpBackBtn);
1300
+ tmpHeader.appendChild(tmpTitle);
1301
+ pRoot.appendChild(tmpHeader);
1302
+
1303
+ // Edit form
1304
+ let tmpBody = document.createElement('div');
1305
+ tmpBody.className = 'retold-remote-collections-body';
1306
+
1307
+ let tmpForm = document.createElement('div');
1308
+ tmpForm.className = 'retold-remote-collections-edit';
1309
+
1310
+ // Name
1311
+ tmpForm.appendChild(this._createEditGroup('Name', 'input', tmpCollection.Name || '', 'edit-name'));
1312
+
1313
+ // Description
1314
+ tmpForm.appendChild(this._createEditGroup('Description (Markdown)', 'textarea', tmpCollection.Description || '', 'edit-description'));
1315
+
1316
+ // Cover Image
1317
+ let tmpCoverGroup = this._createEditGroup('Cover Image Path', 'input', tmpCollection.CoverImage || '', 'edit-cover');
1318
+
1319
+ // "Use current file" button
1320
+ let tmpCurrentFile = this.pict.AppData.ContentEditor.CurrentFile;
1321
+ if (tmpCurrentFile)
1322
+ {
1323
+ let tmpUseCurrent = document.createElement('button');
1324
+ tmpUseCurrent.className = 'retold-remote-collections-edit-btn';
1325
+ tmpUseCurrent.textContent = 'Use current file';
1326
+ tmpUseCurrent.style.marginTop = '4px';
1327
+ tmpUseCurrent.style.fontSize = '0.72rem';
1328
+ tmpUseCurrent.onclick = () =>
1329
+ {
1330
+ let tmpInput = document.getElementById('retold-remote-edit-cover');
1331
+ if (tmpInput)
1332
+ {
1333
+ tmpInput.value = tmpCurrentFile;
1334
+ }
1335
+ };
1336
+ tmpCoverGroup.appendChild(tmpUseCurrent);
1337
+ }
1338
+ tmpForm.appendChild(tmpCoverGroup);
1339
+
1340
+ // Tags
1341
+ tmpForm.appendChild(this._createEditGroup('Tags (comma-separated)', 'input', (tmpCollection.Tags || []).join(', '), 'edit-tags'));
1342
+
1343
+ // Action buttons
1344
+ let tmpActions = document.createElement('div');
1345
+ tmpActions.className = 'retold-remote-collections-edit-actions';
1346
+
1347
+ let tmpSaveBtn = document.createElement('button');
1348
+ tmpSaveBtn.className = 'retold-remote-collections-edit-btn retold-remote-collections-edit-btn-primary';
1349
+ tmpSaveBtn.textContent = 'Save';
1350
+ tmpSaveBtn.onclick = () =>
1351
+ {
1352
+ let tmpNameInput = document.getElementById('retold-remote-edit-name');
1353
+ let tmpDescInput = document.getElementById('retold-remote-edit-description');
1354
+ let tmpCoverInput = document.getElementById('retold-remote-edit-cover');
1355
+ let tmpTagsInput = document.getElementById('retold-remote-edit-tags');
1356
+
1357
+ let tmpUpdated =
1358
+ {
1359
+ GUID: tmpCollection.GUID,
1360
+ Name: tmpNameInput ? tmpNameInput.value : tmpCollection.Name,
1361
+ Description: tmpDescInput ? tmpDescInput.value : tmpCollection.Description,
1362
+ CoverImage: tmpCoverInput ? tmpCoverInput.value : tmpCollection.CoverImage,
1363
+ Tags: tmpTagsInput ? tmpTagsInput.value.split(',').map((t) => t.trim()).filter((t) => t) : tmpCollection.Tags
1364
+ };
1365
+
1366
+ tmpManager.updateCollection(tmpUpdated, (pError) =>
1367
+ {
1368
+ if (!pError)
1369
+ {
1370
+ tmpRemote.CollectionsPanelMode = 'detail';
1371
+ tmpManager.fetchCollection(tmpCollection.GUID);
1372
+
1373
+ let tmpToast = tmpManager._getToast();
1374
+ if (tmpToast)
1375
+ {
1376
+ tmpToast.show('Collection saved');
1377
+ }
1378
+ }
1379
+ });
1380
+ };
1381
+
1382
+ let tmpCancelBtn = document.createElement('button');
1383
+ tmpCancelBtn.className = 'retold-remote-collections-edit-btn';
1384
+ tmpCancelBtn.textContent = 'Cancel';
1385
+ tmpCancelBtn.onclick = () =>
1386
+ {
1387
+ tmpRemote.CollectionsPanelMode = 'detail';
1388
+ tmpSelf.renderContent();
1389
+ };
1390
+
1391
+ tmpActions.appendChild(tmpSaveBtn);
1392
+ tmpActions.appendChild(tmpCancelBtn);
1393
+ tmpForm.appendChild(tmpActions);
1394
+
1395
+ // Delete button
1396
+ let tmpDeleteBtn = document.createElement('button');
1397
+ tmpDeleteBtn.className = 'retold-remote-collections-edit-btn retold-remote-collections-edit-btn-danger';
1398
+ tmpDeleteBtn.textContent = 'Delete Collection';
1399
+ tmpDeleteBtn.onclick = () =>
1400
+ {
1401
+ if (confirm('Delete this collection? This cannot be undone.'))
1402
+ {
1403
+ tmpManager.deleteCollection(tmpCollection.GUID);
1404
+ }
1405
+ };
1406
+ tmpForm.appendChild(tmpDeleteBtn);
1407
+
1408
+ tmpBody.appendChild(tmpForm);
1409
+ pRoot.appendChild(tmpBody);
1410
+ }
1411
+
1412
+ /**
1413
+ * Create a labelled form group (label + input/textarea).
1414
+ */
1415
+ _createEditGroup(pLabel, pType, pValue, pId)
1416
+ {
1417
+ let tmpGroup = document.createElement('div');
1418
+ tmpGroup.className = 'retold-remote-collections-edit-group';
1419
+
1420
+ let tmpLabel = document.createElement('div');
1421
+ tmpLabel.className = 'retold-remote-collections-edit-label';
1422
+ tmpLabel.textContent = pLabel;
1423
+
1424
+ let tmpInput;
1425
+ if (pType === 'textarea')
1426
+ {
1427
+ tmpInput = document.createElement('textarea');
1428
+ tmpInput.className = 'retold-remote-collections-edit-textarea';
1429
+ }
1430
+ else
1431
+ {
1432
+ tmpInput = document.createElement('input');
1433
+ tmpInput.className = 'retold-remote-collections-edit-input';
1434
+ tmpInput.type = 'text';
1435
+ }
1436
+
1437
+ tmpInput.value = pValue;
1438
+ tmpInput.id = 'retold-remote-' + pId;
1439
+
1440
+ tmpGroup.appendChild(tmpLabel);
1441
+ tmpGroup.appendChild(tmpInput);
1442
+ return tmpGroup;
1443
+ }
1444
+
1445
+ // -- Helpers ----------------------------------------------------------
1446
+
1447
+ /**
1448
+ * Get a text icon character for an item type.
1449
+ */
1450
+ _getTypeIcon(pType, pMediaType)
1451
+ {
1452
+ switch (pType)
1453
+ {
1454
+ case 'folder':
1455
+ case 'folder-contents':
1456
+ return '\uD83D\uDCC1';
1457
+ case 'subfile':
1458
+ return '\uD83D\uDDC4';
1459
+ case 'image-crop':
1460
+ return '\u2702';
1461
+ case 'video-clip':
1462
+ case 'video-frame':
1463
+ return '\uD83C\uDFAC';
1464
+ default:
1465
+ break;
1466
+ }
1467
+
1468
+ switch (pMediaType)
1469
+ {
1470
+ case 'image':
1471
+ return '\uD83D\uDDBC';
1472
+ case 'video':
1473
+ return '\uD83C\uDFAC';
1474
+ case 'audio':
1475
+ return '\uD83C\uDFB5';
1476
+ case 'document':
1477
+ return '\uD83D\uDCC4';
1478
+ default:
1479
+ return '\uD83D\uDCC4';
1480
+ }
1481
+ }
1482
+ }
1483
+
1484
+ RetoldRemoteCollectionsPanelView.default_configuration = _ViewConfiguration;
1485
+
1486
+ module.exports = RetoldRemoteCollectionsPanelView;