retold-remote 0.0.7 → 0.0.10

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.
@@ -21,8 +21,6 @@ html, body
21
21
  line-height: 1.5;
22
22
  -webkit-font-smoothing: antialiased;
23
23
  -moz-osx-font-smoothing: grayscale;
24
- /* Safe area insets for notched devices */
25
- padding-bottom: env(safe-area-inset-bottom);
26
24
  }
27
25
 
28
26
  /* Scrollbar styling */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-remote",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "Retold Remote - NAS media browser with gallery views and keyboard navigation",
5
5
  "main": "source/Pict-RetoldRemote-Bundle.js",
6
6
  "bin": {
@@ -13,6 +13,8 @@ const libProviderToastNotification = require('./providers/Pict-Provider-ToastNot
13
13
  const libProviderCollectionManager = require('./providers/Pict-Provider-CollectionManager.js');
14
14
  const libProviderAISortManager = require('./providers/Pict-Provider-AISortManager.js');
15
15
 
16
+ const libExtensionMaps = require('./RetoldRemote-ExtensionMaps.js');
17
+
16
18
  // Views (replace parent views)
17
19
  const libViewLayout = require('./views/PictView-Remote-Layout.js');
18
20
  const libViewTopBar = require('./views/PictView-Remote-TopBar.js');
@@ -149,6 +151,11 @@ class RetoldRemoteApplication extends libContentEditorApplication
149
151
  BrowsingCollection: false, // true when viewer is navigating collection items
150
152
  BrowsingCollectionIndex: -1, // index into ActiveCollection.Items
151
153
 
154
+ // Favorites
155
+ FavoritesGUID: null,
156
+ FavoritesCollection: null,
157
+ FavoritesPathSet: {}, // path → itemID for O(1) favorited-state checks
158
+
152
159
  // AI Sort settings
153
160
  AISortSettings:
154
161
  {
@@ -206,11 +213,14 @@ class RetoldRemoteApplication extends libContentEditorApplication
206
213
  // Render the collections panel (starts collapsed)
207
214
  this.pict.views['RetoldRemote-CollectionsPanel'].render();
208
215
 
209
- // Fetch collections list
216
+ // Fetch collections list and ensure favorites collection exists
210
217
  let tmpCollManager = this.pict.providers['RetoldRemote-CollectionManager'];
211
218
  if (tmpCollManager)
212
219
  {
213
- tmpCollManager.fetchCollections();
220
+ tmpCollManager.fetchCollections(() =>
221
+ {
222
+ tmpCollManager.ensureFavoritesCollection();
223
+ });
214
224
  }
215
225
 
216
226
  // Update the collections button icon
@@ -306,19 +316,17 @@ class RetoldRemoteApplication extends libContentEditorApplication
306
316
  }
307
317
 
308
318
  /**
309
- * Override _getMediaType to add document category.
319
+ * Override _getMediaType to use comprehensive extension maps.
320
+ *
321
+ * The parent class has a hardcoded subset of video/audio/image extensions.
322
+ * We use RetoldRemote-ExtensionMaps which covers many more formats
323
+ * (mpg, mpeg, ts, mts, 3gp, heic, etc.).
310
324
  *
311
325
  * @param {string} pExtension - Lowercase extension without dot
312
- * @returns {string} 'image', 'video', 'audio', 'document', or 'other'
326
+ * @returns {string} 'image', 'video', 'audio', 'document', 'text', or 'other'
313
327
  */
314
328
  _getMediaType(pExtension)
315
329
  {
316
- let tmpDocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true };
317
- if (tmpDocumentExtensions[pExtension])
318
- {
319
- return 'document';
320
- }
321
-
322
330
  let tmpTextExtensions =
323
331
  {
324
332
  'js': true, 'mjs': true, 'cjs': true, 'ts': true, 'tsx': true, 'jsx': true,
@@ -338,7 +346,8 @@ class RetoldRemoteApplication extends libContentEditorApplication
338
346
  return 'text';
339
347
  }
340
348
 
341
- return super._getMediaType(pExtension);
349
+ // Use the comprehensive extension maps (covers mpg, mpeg, ts, heic, etc.)
350
+ return libExtensionMaps.getCategory(pExtension);
342
351
  }
343
352
 
344
353
  /**
@@ -409,6 +418,10 @@ class RetoldRemoteApplication extends libContentEditorApplication
409
418
  if (tmpTopBar)
410
419
  {
411
420
  tmpTopBar.updateInfo();
421
+ if (typeof tmpTopBar.updateFavoritesIcon === 'function')
422
+ {
423
+ tmpTopBar.updateFavoritesIcon();
424
+ }
412
425
  }
413
426
  }
414
427
 
@@ -447,6 +460,10 @@ class RetoldRemoteApplication extends libContentEditorApplication
447
460
  if (tmpTopBar)
448
461
  {
449
462
  tmpTopBar.updateInfo();
463
+ if (typeof tmpTopBar.updateFavoritesIcon === 'function')
464
+ {
465
+ tmpTopBar.updateFavoritesIcon();
466
+ }
450
467
  }
451
468
  }
452
469
 
@@ -758,6 +775,7 @@ class RetoldRemoteApplication extends libContentEditorApplication
758
775
  CollectionsPanelOpen: tmpRemote.CollectionsPanelOpen,
759
776
  CollectionsPanelWidth: tmpRemote.CollectionsPanelWidth,
760
777
  LastUsedCollectionGUID: tmpRemote.LastUsedCollectionGUID,
778
+ FavoritesGUID: tmpRemote.FavoritesGUID,
761
779
  AISortSettings: tmpRemote.AISortSettings
762
780
  };
763
781
  localStorage.setItem('retold-remote-settings', JSON.stringify(tmpSettings));
@@ -806,6 +824,7 @@ class RetoldRemoteApplication extends libContentEditorApplication
806
824
  if (typeof (tmpSettings.CollectionsPanelOpen) === 'boolean') tmpRemote.CollectionsPanelOpen = tmpSettings.CollectionsPanelOpen;
807
825
  if (tmpSettings.CollectionsPanelWidth) tmpRemote.CollectionsPanelWidth = tmpSettings.CollectionsPanelWidth;
808
826
  if (tmpSettings.LastUsedCollectionGUID) tmpRemote.LastUsedCollectionGUID = tmpSettings.LastUsedCollectionGUID;
827
+ if (tmpSettings.FavoritesGUID) tmpRemote.FavoritesGUID = tmpSettings.FavoritesGUID;
809
828
  if (tmpSettings.AISortSettings && typeof tmpSettings.AISortSettings === 'object')
810
829
  {
811
830
  if (tmpSettings.AISortSettings.AIEndpoint) tmpRemote.AISortSettings.AIEndpoint = tmpSettings.AISortSettings.AIEndpoint;
@@ -158,7 +158,7 @@ class CollectionManagerProvider extends libPictProvider
158
158
  let tmpToast = tmpSelf._getToast();
159
159
  if (tmpToast)
160
160
  {
161
- tmpToast.show('Collection created: ' + (pData.Name || pName));
161
+ tmpToast.showToast('Collection created: ' + (pData.Name || pName));
162
162
  }
163
163
 
164
164
  return tmpCallback(null, pData);
@@ -255,7 +255,7 @@ class CollectionManagerProvider extends libPictProvider
255
255
  let tmpToast = tmpSelf._getToast();
256
256
  if (tmpToast)
257
257
  {
258
- tmpToast.show('Collection deleted');
258
+ tmpToast.showToast('Collection deleted');
259
259
  }
260
260
 
261
261
  return tmpCallback(null);
@@ -310,7 +310,7 @@ class CollectionManagerProvider extends libPictProvider
310
310
  if (tmpToast)
311
311
  {
312
312
  let tmpCollectionName = pData.Name || 'collection';
313
- tmpToast.show('Added ' + pItems.length + ' item' + (pItems.length > 1 ? 's' : '') + ' to ' + tmpCollectionName);
313
+ tmpToast.showToast('Added ' + pItems.length + ' item' + (pItems.length > 1 ? 's' : '') + ' to ' + tmpCollectionName);
314
314
  }
315
315
 
316
316
  return tmpCallback(null, pData);
@@ -443,7 +443,7 @@ class CollectionManagerProvider extends libPictProvider
443
443
  let tmpToast = tmpSelf._getToast();
444
444
  if (tmpToast)
445
445
  {
446
- tmpToast.show('Copied ' + pItemIDs.length + ' item' + (pItemIDs.length > 1 ? 's' : ''));
446
+ tmpToast.showToast('Copied ' + pItemIDs.length + ' item' + (pItemIDs.length > 1 ? 's' : ''));
447
447
  }
448
448
 
449
449
  return tmpCallback(null, pData);
@@ -463,6 +463,39 @@ class CollectionManagerProvider extends libPictProvider
463
463
  togglePanel()
464
464
  {
465
465
  let tmpRemote = this._getRemote();
466
+
467
+ // On mobile, delegate to the sidebar collections tab instead of the right-side panel
468
+ let tmpLayoutView = this.pict.views['ContentEditor-Layout'];
469
+ if (tmpLayoutView && tmpLayoutView.isMobileDrawer())
470
+ {
471
+ // Check if collections tab is already active
472
+ let tmpActiveTab = document.querySelector('.content-editor-sidebar-tab.active');
473
+ let tmpIsCollectionsActive = tmpActiveTab && tmpActiveTab.getAttribute('data-tab') === 'collections';
474
+
475
+ if (tmpIsCollectionsActive)
476
+ {
477
+ // Switch back to files tab
478
+ tmpLayoutView.switchSidebarTab('files');
479
+ }
480
+ else
481
+ {
482
+ // Open sidebar if collapsed, then switch to collections tab
483
+ if (tmpRemote.SidebarCollapsed)
484
+ {
485
+ tmpLayoutView.toggleSidebar();
486
+ }
487
+ tmpLayoutView.switchSidebarTab('collections');
488
+ }
489
+
490
+ // Update topbar button state
491
+ let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
492
+ if (tmpTopBar && typeof tmpTopBar.updateCollectionsIcon === 'function')
493
+ {
494
+ tmpTopBar.updateCollectionsIcon();
495
+ }
496
+ return;
497
+ }
498
+
466
499
  tmpRemote.CollectionsPanelOpen = !tmpRemote.CollectionsPanelOpen;
467
500
 
468
501
  let tmpWrap = document.getElementById('RetoldRemote-Collections-Wrap');
@@ -531,6 +564,264 @@ class CollectionManagerProvider extends libPictProvider
531
564
  }
532
565
  }
533
566
 
567
+ // -- Favorites Methods ------------------------------------------------
568
+
569
+ /**
570
+ * Ensure the favorites collection exists.
571
+ * If FavoritesGUID is set, loads it. Otherwise searches existing
572
+ * collections for CollectionType === 'favorites'. If not found,
573
+ * creates one with a well-known GUID.
574
+ *
575
+ * @param {Function} [fCallback] - Optional callback(pError)
576
+ */
577
+ ensureFavoritesCollection(fCallback)
578
+ {
579
+ let tmpSelf = this;
580
+ let tmpCallback = (typeof fCallback === 'function') ? fCallback : () => {};
581
+ let tmpRemote = this._getRemote();
582
+
583
+ // If we already have a GUID, just load it
584
+ if (tmpRemote.FavoritesGUID)
585
+ {
586
+ return this._loadFavoritesCollection(tmpRemote.FavoritesGUID, tmpCallback);
587
+ }
588
+
589
+ // Search existing collections for a favorites-type collection
590
+ let tmpCollections = tmpRemote.Collections || [];
591
+ for (let i = 0; i < tmpCollections.length; i++)
592
+ {
593
+ if (tmpCollections[i].CollectionType === 'favorites')
594
+ {
595
+ tmpRemote.FavoritesGUID = tmpCollections[i].GUID;
596
+ this.pict.PictApplication.saveSettings();
597
+ return this._loadFavoritesCollection(tmpCollections[i].GUID, tmpCallback);
598
+ }
599
+ }
600
+
601
+ // Not found — create one
602
+ let tmpGUID = 'favorites-default';
603
+
604
+ fetch('/api/collections/' + encodeURIComponent(tmpGUID),
605
+ {
606
+ method: 'PUT',
607
+ headers: { 'Content-Type': 'application/json' },
608
+ body: JSON.stringify({ Name: 'Favorites', CollectionType: 'favorites', Icon: 'heart' })
609
+ })
610
+ .then((pResponse) => pResponse.json())
611
+ .then((pData) =>
612
+ {
613
+ tmpRemote.FavoritesGUID = tmpGUID;
614
+ tmpSelf.pict.PictApplication.saveSettings();
615
+ tmpSelf._loadFavoritesCollection(tmpGUID, tmpCallback);
616
+ })
617
+ .catch((pError) =>
618
+ {
619
+ tmpSelf.log.error('Failed to create favorites collection: ' + pError.message);
620
+ return tmpCallback(pError);
621
+ });
622
+ }
623
+
624
+ /**
625
+ * Load the favorites collection and rebuild the path set.
626
+ *
627
+ * @param {string} pGUID - Collection GUID
628
+ * @param {Function} [fCallback] - Optional callback(pError)
629
+ */
630
+ _loadFavoritesCollection(pGUID, fCallback)
631
+ {
632
+ let tmpSelf = this;
633
+ let tmpCallback = (typeof fCallback === 'function') ? fCallback : () => {};
634
+
635
+ fetch('/api/collections/' + encodeURIComponent(pGUID))
636
+ .then((pResponse) =>
637
+ {
638
+ if (!pResponse.ok)
639
+ {
640
+ throw new Error('Favorites collection not found');
641
+ }
642
+ return pResponse.json();
643
+ })
644
+ .then((pData) =>
645
+ {
646
+ let tmpRemote = tmpSelf._getRemote();
647
+ tmpRemote.FavoritesCollection = pData;
648
+ tmpSelf._rebuildFavoritesPathSet(pData);
649
+
650
+ // Update heart icon in topbar
651
+ let tmpTopBar = tmpSelf.pict.views['ContentEditor-TopBar'];
652
+ if (tmpTopBar && typeof tmpTopBar.updateFavoritesIcon === 'function')
653
+ {
654
+ tmpTopBar.updateFavoritesIcon();
655
+ }
656
+
657
+ // Update favorites pane if visible
658
+ tmpSelf._renderFavoritesPane();
659
+
660
+ return tmpCallback(null);
661
+ })
662
+ .catch((pError) =>
663
+ {
664
+ tmpSelf.log.error('Failed to load favorites collection: ' + pError.message);
665
+ return tmpCallback(pError);
666
+ });
667
+ }
668
+
669
+ /**
670
+ * Rebuild the FavoritesPathSet from a collection's Items array.
671
+ *
672
+ * @param {Object} pCollection - Collection object with Items array
673
+ */
674
+ _rebuildFavoritesPathSet(pCollection)
675
+ {
676
+ let tmpRemote = this._getRemote();
677
+ tmpRemote.FavoritesPathSet = {};
678
+
679
+ if (!pCollection || !Array.isArray(pCollection.Items))
680
+ {
681
+ return;
682
+ }
683
+
684
+ for (let i = 0; i < pCollection.Items.length; i++)
685
+ {
686
+ let tmpItem = pCollection.Items[i];
687
+ if (tmpItem.Path)
688
+ {
689
+ tmpRemote.FavoritesPathSet[tmpItem.Path] = tmpItem.ID;
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Check if a file path is in favorites.
696
+ *
697
+ * @param {string} [pPath] - File path (defaults to currently viewed file)
698
+ * @returns {boolean} True if favorited
699
+ */
700
+ isFavorited(pPath)
701
+ {
702
+ let tmpRemote = this._getRemote();
703
+ let tmpFilePath = pPath || this.pict.AppData.ContentEditor.CurrentFile;
704
+ return !!(tmpRemote.FavoritesPathSet[tmpFilePath]);
705
+ }
706
+
707
+ /**
708
+ * Toggle a file in/out of favorites.
709
+ *
710
+ * @param {string} [pPath] - File path (defaults to currently viewed file)
711
+ */
712
+ toggleFavorite(pPath)
713
+ {
714
+ let tmpSelf = this;
715
+ let tmpRemote = this._getRemote();
716
+ let tmpFilePath = pPath || this.pict.AppData.ContentEditor.CurrentFile;
717
+
718
+ if (!tmpFilePath)
719
+ {
720
+ return;
721
+ }
722
+
723
+ if (!tmpRemote.FavoritesGUID)
724
+ {
725
+ // Favorites collection not ready yet
726
+ return;
727
+ }
728
+
729
+ if (this.isFavorited(tmpFilePath))
730
+ {
731
+ // Remove from favorites
732
+ let tmpItemID = tmpRemote.FavoritesPathSet[tmpFilePath];
733
+ this.removeItemFromCollection(tmpRemote.FavoritesGUID, tmpItemID, (pError, pData) =>
734
+ {
735
+ if (!pError && pData)
736
+ {
737
+ tmpRemote.FavoritesCollection = pData;
738
+ tmpSelf._rebuildFavoritesPathSet(pData);
739
+ }
740
+
741
+ // Update heart icon
742
+ let tmpTopBar = tmpSelf.pict.views['ContentEditor-TopBar'];
743
+ if (tmpTopBar && typeof tmpTopBar.updateFavoritesIcon === 'function')
744
+ {
745
+ tmpTopBar.updateFavoritesIcon();
746
+ }
747
+
748
+ tmpSelf._renderFavoritesPane();
749
+
750
+ let tmpToast = tmpSelf._getToast();
751
+ if (tmpToast)
752
+ {
753
+ tmpToast.showToast('Removed from favorites');
754
+ }
755
+ });
756
+ }
757
+ else
758
+ {
759
+ // Add to favorites — build item using same logic as addCurrentFileToCollection
760
+ let tmpItem =
761
+ {
762
+ Type: 'file',
763
+ Path: tmpFilePath,
764
+ Label: '',
765
+ Note: ''
766
+ };
767
+
768
+ // Detect archive subfile
769
+ let tmpArchiveMatch = tmpFilePath.match(/^(.*?\.(zip|7z|rar|tar|tgz|cbz|cbr|tar\.gz|tar\.bz2|tar\.xz))\/(.*)/i);
770
+ if (tmpArchiveMatch)
771
+ {
772
+ tmpItem.Type = 'subfile';
773
+ tmpItem.ArchivePath = tmpArchiveMatch[1];
774
+ }
775
+
776
+ // Include hash if available
777
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
778
+ if (tmpProvider)
779
+ {
780
+ let tmpHash = tmpProvider.getHashForPath(tmpFilePath);
781
+ if (tmpHash)
782
+ {
783
+ tmpItem.Hash = tmpHash;
784
+ }
785
+ }
786
+
787
+ this.addItemsToCollection(tmpRemote.FavoritesGUID, [tmpItem], (pError, pData) =>
788
+ {
789
+ if (!pError && pData)
790
+ {
791
+ tmpRemote.FavoritesCollection = pData;
792
+ tmpSelf._rebuildFavoritesPathSet(pData);
793
+ }
794
+
795
+ // Update heart icon
796
+ let tmpTopBar = tmpSelf.pict.views['ContentEditor-TopBar'];
797
+ if (tmpTopBar && typeof tmpTopBar.updateFavoritesIcon === 'function')
798
+ {
799
+ tmpTopBar.updateFavoritesIcon();
800
+ }
801
+
802
+ tmpSelf._renderFavoritesPane();
803
+
804
+ let tmpToast = tmpSelf._getToast();
805
+ if (tmpToast)
806
+ {
807
+ tmpToast.showToast('Added to favorites');
808
+ }
809
+ });
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Render the favorites pane if the favorites tab is active.
815
+ */
816
+ _renderFavoritesPane()
817
+ {
818
+ let tmpLayoutView = this.pict.views['ContentEditor-Layout'];
819
+ if (tmpLayoutView && typeof tmpLayoutView.renderFavoritesList === 'function')
820
+ {
821
+ tmpLayoutView.renderFavoritesList();
822
+ }
823
+ }
824
+
534
825
  // -- Convenience Methods ----------------------------------------------
535
826
 
536
827
  /**
@@ -985,7 +1276,7 @@ class CollectionManagerProvider extends libPictProvider
985
1276
  let tmpToast = tmpSelf._getToast();
986
1277
  if (tmpToast)
987
1278
  {
988
- tmpToast.show('Sort plan created: ' + (pData.Name || pName));
1279
+ tmpToast.showToast('Sort plan created: ' + (pData.Name || pName));
989
1280
  }
990
1281
 
991
1282
  return tmpCallback(null, pData);
@@ -1035,11 +1326,11 @@ class CollectionManagerProvider extends libPictProvider
1035
1326
  {
1036
1327
  if (pData.TotalFailed > 0)
1037
1328
  {
1038
- tmpToast.show('Moved ' + pData.TotalMoved + ' files (' + pData.TotalFailed + ' failed)');
1329
+ tmpToast.showToast('Moved ' + pData.TotalMoved + ' files (' + pData.TotalFailed + ' failed)');
1039
1330
  }
1040
1331
  else
1041
1332
  {
1042
- tmpToast.show('Successfully moved ' + pData.TotalMoved + ' files');
1333
+ tmpToast.showToast('Successfully moved ' + pData.TotalMoved + ' files');
1043
1334
  }
1044
1335
  }
1045
1336
 
@@ -1051,7 +1342,7 @@ class CollectionManagerProvider extends libPictProvider
1051
1342
  let tmpToast = tmpSelf._getToast();
1052
1343
  if (tmpToast)
1053
1344
  {
1054
- tmpToast.show('Failed to execute operations: ' + pError.message);
1345
+ tmpToast.showToast('Failed to execute operations: ' + pError.message);
1055
1346
  }
1056
1347
  return tmpCallback(pError);
1057
1348
  });
@@ -1075,7 +1366,7 @@ class CollectionManagerProvider extends libPictProvider
1075
1366
  let tmpToast = tmpSelf._getToast();
1076
1367
  if (tmpToast)
1077
1368
  {
1078
- tmpToast.show('No batch to undo');
1369
+ tmpToast.showToast('No batch to undo');
1079
1370
  }
1080
1371
  return tmpCallback(new Error('No batch to undo'));
1081
1372
  }
@@ -1114,7 +1405,7 @@ class CollectionManagerProvider extends libPictProvider
1114
1405
  let tmpToast = tmpSelf._getToast();
1115
1406
  if (tmpToast)
1116
1407
  {
1117
- tmpToast.show('Undo complete: ' + pData.TotalReversed + ' files restored');
1408
+ tmpToast.showToast('Undo complete: ' + pData.TotalReversed + ' files restored');
1118
1409
  }
1119
1410
 
1120
1411
  return tmpCallback(null, pData);
@@ -1125,7 +1416,7 @@ class CollectionManagerProvider extends libPictProvider
1125
1416
  let tmpToast = tmpSelf._getToast();
1126
1417
  if (tmpToast)
1127
1418
  {
1128
- tmpToast.show('Failed to undo: ' + pError.message);
1419
+ tmpToast.showToast('Failed to undo: ' + pError.message);
1129
1420
  }
1130
1421
  return tmpCallback(pError);
1131
1422
  });
@@ -1277,10 +1277,11 @@ class GalleryNavigationProvider extends libPictProvider
1277
1277
  let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
1278
1278
  let tmpContentPath = tmpProvider ? tmpProvider.getContentURL(tmpFilePath) : ('/content/' + encodeURIComponent(tmpFilePath));
1279
1279
  let tmpStreamURL = window.location.origin + tmpContentPath;
1280
- // On Windows, VLC's native handler expects the raw URL.
1280
+ // On Windows and mobile (iOS/Android), VLC handles the raw URL natively.
1281
1281
  // On macOS/Linux our custom handlers URL-decode, so we encode.
1282
1282
  let tmpIsWindows = /Windows/.test(navigator.userAgent);
1283
- let tmpVLCURL = tmpIsWindows
1283
+ let tmpIsMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
1284
+ let tmpVLCURL = (tmpIsWindows || tmpIsMobile)
1284
1285
  ? ('vlc://' + tmpStreamURL)
1285
1286
  : ('vlc://' + encodeURIComponent(tmpStreamURL));
1286
1287
 
@@ -470,6 +470,22 @@ class RetoldRemoteIconProvider extends libPictProvider
470
470
  + '</svg>';
471
471
  },
472
472
 
473
+ 'heart': (pSize) =>
474
+ {
475
+ let c = tmpSelf._colors;
476
+ return '<svg width="' + pSize + '" height="' + pSize + '" viewBox="0 0 24 24" fill="none">'
477
+ + '<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="' + c.Primary + '" stroke-width="1.8" fill="none" />'
478
+ + '</svg>';
479
+ },
480
+
481
+ 'heart-filled': (pSize) =>
482
+ {
483
+ let c = tmpSelf._colors;
484
+ return '<svg width="' + pSize + '" height="' + pSize + '" viewBox="0 0 24 24" fill="none">'
485
+ + '<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="#e74c3c" stroke-width="1.8" fill="#e74c3c" />'
486
+ + '</svg>';
487
+ },
488
+
473
489
  'drag-handle': (pSize) =>
474
490
  {
475
491
  let c = tmpSelf._colors;
@@ -759,6 +759,58 @@ class RetoldRemoteThemeProvider extends libPictProvider
759
759
  PdfText: '#CC4422'
760
760
  }
761
761
  });
762
+
763
+ // ===================================================================
764
+ // DEBUG THEME (unique color per container for layout debugging)
765
+ // ===================================================================
766
+
767
+ tmpSelf._addTheme('mobile-debug',
768
+ {
769
+ Name: 'Mobile Container Debug',
770
+ Category: 'Debug',
771
+ Description: 'Unique color per container for layout debugging',
772
+ Variables:
773
+ {
774
+ '--retold-bg-primary': '#FF0000',
775
+ '--retold-bg-secondary': '#00CCCC',
776
+ '--retold-bg-tertiary': '#00AA00',
777
+ '--retold-bg-panel': '#FFAA00',
778
+ '--retold-bg-viewer': '#333333',
779
+ '--retold-bg-hover': 'rgba(255, 255, 255, 0.2)',
780
+ '--retold-bg-selected': 'rgba(255, 255, 255, 0.3)',
781
+ '--retold-bg-thumb': '#AA00AA',
782
+ '--retold-text-primary': '#FFFFFF',
783
+ '--retold-text-secondary': '#EEEEEE',
784
+ '--retold-text-muted': '#CCCCCC',
785
+ '--retold-text-dim': '#AAAAAA',
786
+ '--retold-text-placeholder': '#888888',
787
+ '--retold-accent': '#FFFF00',
788
+ '--retold-accent-hover': '#FFFF88',
789
+ '--retold-border': '#FFFFFF',
790
+ '--retold-border-light': '#CCCCCC',
791
+ '--retold-danger': '#FF0000',
792
+ '--retold-danger-muted': '#CC4444',
793
+ '--retold-scrollbar': '#888888',
794
+ '--retold-scrollbar-hover': '#AAAAAA',
795
+ '--retold-selection-bg': 'rgba(255, 255, 0, 0.3)',
796
+ '--retold-focus-outline': '#FFFF00',
797
+ '--retold-font-family': "system-ui, -apple-system, sans-serif",
798
+ '--retold-font-mono': "'SF Mono', 'Consolas', monospace"
799
+ },
800
+ IconColors:
801
+ {
802
+ Primary: '#FFFFFF',
803
+ Accent: '#FFFF00',
804
+ Muted: '#CCCCCC',
805
+ Light: '#333333',
806
+ WarmBeige: '#FFAA00',
807
+ TealTint: '#00CCCC',
808
+ Lavender: '#AA00AA',
809
+ AmberTint: '#FFAA00',
810
+ PdfFill: '#FF4444',
811
+ PdfText: '#FFFFFF'
812
+ }
813
+ });
762
814
  }
763
815
 
764
816
  /**
@@ -82,6 +82,17 @@ function handleAudioExplorerKey(pGalleryNav, pEvent)
82
82
  }
83
83
  }
84
84
  break;
85
+
86
+ case 'h':
87
+ pEvent.preventDefault();
88
+ {
89
+ let tmpFavCollManager = pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];
90
+ if (tmpFavCollManager)
91
+ {
92
+ tmpFavCollManager.toggleFavorite();
93
+ }
94
+ }
95
+ break;
85
96
  }
86
97
  }
87
98
 
@@ -184,6 +184,17 @@ function handleGalleryKey(pGalleryNav, pEvent)
184
184
  }
185
185
  break;
186
186
 
187
+ case 'h':
188
+ pEvent.preventDefault();
189
+ {
190
+ let tmpFavCollManager = pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];
191
+ if (tmpFavCollManager)
192
+ {
193
+ tmpFavCollManager.toggleFavorite();
194
+ }
195
+ }
196
+ break;
197
+
187
198
  }
188
199
  }
189
200
 
@@ -51,6 +51,17 @@ function handleVideoExplorerKey(pGalleryNav, pEvent)
51
51
  }
52
52
  }
53
53
  break;
54
+
55
+ case 'h':
56
+ pEvent.preventDefault();
57
+ {
58
+ let tmpFavCollManager = pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];
59
+ if (tmpFavCollManager)
60
+ {
61
+ tmpFavCollManager.toggleFavorite();
62
+ }
63
+ }
64
+ break;
54
65
  }
55
66
  }
56
67
 
@@ -191,6 +191,17 @@ function handleViewerKey(pGalleryNav, pEvent)
191
191
  pEvent.preventDefault();
192
192
  pGalleryNav.switchViewerType('text');
193
193
  break;
194
+
195
+ case 'h':
196
+ pEvent.preventDefault();
197
+ {
198
+ let tmpFavCollManager = pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];
199
+ if (tmpFavCollManager)
200
+ {
201
+ tmpFavCollManager.toggleFavorite();
202
+ }
203
+ }
204
+ break;
194
205
  }
195
206
  }
196
207