retold-remote 0.0.22 → 0.0.25

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 (47) hide show
  1. package/css/retold-remote.css +87 -20
  2. package/docs/README.md +59 -11
  3. package/docs/_sidebar.md +1 -0
  4. package/docs/collections.md +30 -0
  5. package/docs/ebook-reader.md +75 -1
  6. package/docs/image-explorer.md +27 -1
  7. package/docs/server-setup.md +28 -18
  8. package/docs/stack-launcher.md +218 -0
  9. package/docs/ultravisor-integration.md +2 -0
  10. package/package.json +10 -7
  11. package/source/Pict-Application-RetoldRemote.js +2 -0
  12. package/source/RetoldRemote-ExtensionMaps.js +1 -1
  13. package/source/cli/RetoldRemote-Server-Setup.js +240 -2
  14. package/source/cli/RetoldRemote-Stack-Launcher.js +387 -0
  15. package/source/cli/RetoldRemote-Stack-Run.js +41 -0
  16. package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
  17. package/source/providers/CollectionManager-AddItems.js +166 -0
  18. package/source/providers/Pict-Provider-GalleryNavigation.js +46 -0
  19. package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +5 -0
  20. package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
  21. package/source/server/RetoldRemote-CollectionExportService.js +696 -0
  22. package/source/server/RetoldRemote-CollectionService.js +5 -0
  23. package/source/server/RetoldRemote-EbookService.js +194 -3
  24. package/source/server/RetoldRemote-SubimageService.js +530 -0
  25. package/source/server/RetoldRemote-ToolDetector.js +50 -0
  26. package/source/server/RetoldRemote-UltravisorOperations.js +6 -6
  27. package/source/views/MediaViewer-EbookViewer.js +419 -1
  28. package/source/views/MediaViewer-PdfViewer.js +963 -0
  29. package/source/views/PictView-Remote-CollectionsPanel.js +166 -0
  30. package/source/views/PictView-Remote-ImageExplorer.js +606 -1
  31. package/source/views/PictView-Remote-ImageViewer.js +2 -2
  32. package/source/views/PictView-Remote-Layout.js +12 -0
  33. package/source/views/PictView-Remote-MediaViewer.js +83 -25
  34. package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
  35. package/web-application/css/retold-remote.css +87 -20
  36. package/web-application/docs/README.md +59 -11
  37. package/web-application/docs/_sidebar.md +1 -0
  38. package/web-application/docs/collections.md +30 -0
  39. package/web-application/docs/ebook-reader.md +75 -1
  40. package/web-application/docs/image-explorer.md +27 -1
  41. package/web-application/docs/server-setup.md +28 -18
  42. package/web-application/docs/stack-launcher.md +218 -0
  43. package/web-application/docs/ultravisor-integration.md +2 -0
  44. package/web-application/retold-remote.js +399 -45
  45. package/web-application/retold-remote.js.map +1 -1
  46. package/web-application/retold-remote.min.js +13 -12
  47. package/web-application/retold-remote.min.js.map +1 -1
@@ -387,8 +387,18 @@ class RetoldRemoteCollectionsPanelView extends libPictView
387
387
  tmpManager.sortActiveCollection(null, tmpNewDir);
388
388
  };
389
389
 
390
+ let tmpExportBtn = document.createElement('button');
391
+ tmpExportBtn.className = 'retold-remote-collections-export-btn';
392
+ tmpExportBtn.textContent = '\u21e9 Export';
393
+ tmpExportBtn.title = 'Export collection to a folder';
394
+ tmpExportBtn.onclick = () =>
395
+ {
396
+ tmpSelf._showExportDialog(tmpCollection.GUID, tmpCollection.Name);
397
+ };
398
+
390
399
  tmpControls.appendChild(tmpSortSelect);
391
400
  tmpControls.appendChild(tmpDirBtn);
401
+ tmpControls.appendChild(tmpExportBtn);
392
402
  pRoot.appendChild(tmpControls);
393
403
  }
394
404
 
@@ -986,6 +996,162 @@ class RetoldRemoteCollectionsPanelView extends libPictView
986
996
  return '\uD83D\uDCC4';
987
997
  }
988
998
  }
999
+
1000
+ // -- Export Dialog -------------------------------------------------------
1001
+
1002
+ /**
1003
+ * Show the export dialog for a collection.
1004
+ *
1005
+ * @param {string} pGUID - Collection GUID
1006
+ * @param {string} pCollectionName - Collection name (for default folder)
1007
+ */
1008
+ _showExportDialog(pGUID, pCollectionName)
1009
+ {
1010
+ let tmpSelf = this;
1011
+
1012
+ // Build a default folder name from the collection name
1013
+ let tmpDefaultFolder = (pCollectionName || 'collection-export')
1014
+ .replace(/[<>:"/\\|?*\x00-\x1F]/g, '_')
1015
+ .replace(/\s+/g, '_')
1016
+ .substring(0, 80);
1017
+
1018
+ // Use the current browsed folder as a prefix if available
1019
+ let tmpRemote = this.pict.AppData.RetoldRemote;
1020
+ let tmpCurrentFolder = tmpRemote.CurrentBrowsedFolder || '';
1021
+ let tmpDefaultPath = tmpCurrentFolder
1022
+ ? tmpCurrentFolder.replace(/\/+$/, '') + '/' + tmpDefaultFolder
1023
+ : tmpDefaultFolder;
1024
+
1025
+ // Create a simple inline prompt in the collections detail area
1026
+ let tmpRoot = document.getElementById('RetoldRemote-Collections-Detail');
1027
+ if (!tmpRoot)
1028
+ {
1029
+ return;
1030
+ }
1031
+
1032
+ // Find or create the export dialog container
1033
+ let tmpExistingDialog = document.getElementById('RetoldRemote-ExportDialog');
1034
+ if (tmpExistingDialog)
1035
+ {
1036
+ tmpExistingDialog.parentElement.removeChild(tmpExistingDialog);
1037
+ }
1038
+
1039
+ let tmpDialog = document.createElement('div');
1040
+ tmpDialog.id = 'RetoldRemote-ExportDialog';
1041
+ tmpDialog.style.cssText = 'padding:10px;border-top:1px solid var(--retold-border);background:var(--retold-bg-secondary,#21252b);';
1042
+
1043
+ tmpDialog.innerHTML =
1044
+ '<div style="font-size:0.78rem;color:var(--retold-text);margin-bottom:6px;">Export to folder (within content root):</div>'
1045
+ + '<input type="text" id="RetoldRemote-ExportPath" value="' + tmpDefaultPath.replace(/"/g, '&quot;') + '" '
1046
+ + 'style="width:100%;box-sizing:border-box;background:var(--retold-bg-input,#1e1e1e);color:var(--retold-text);border:1px solid var(--retold-border);border-radius:4px;padding:4px 8px;font-size:0.78rem;font-family:inherit;margin-bottom:6px;">'
1047
+ + '<div style="display:flex;gap:6px;">'
1048
+ + '<button id="RetoldRemote-ExportConfirmBtn" style="flex:1;padding:4px 8px;font-size:0.75rem;">Export</button>'
1049
+ + '<button id="RetoldRemote-ExportCancelBtn" style="padding:4px 8px;font-size:0.75rem;">Cancel</button>'
1050
+ + '</div>'
1051
+ + '<div id="RetoldRemote-ExportStatus" style="font-size:0.72rem;color:var(--retold-text-dim);margin-top:6px;display:none;"></div>';
1052
+
1053
+ tmpRoot.appendChild(tmpDialog);
1054
+
1055
+ // Focus the path input
1056
+ let tmpPathInput = document.getElementById('RetoldRemote-ExportPath');
1057
+ if (tmpPathInput)
1058
+ {
1059
+ tmpPathInput.focus();
1060
+ tmpPathInput.select();
1061
+ }
1062
+
1063
+ // Cancel handler
1064
+ document.getElementById('RetoldRemote-ExportCancelBtn').onclick = () =>
1065
+ {
1066
+ tmpDialog.parentElement.removeChild(tmpDialog);
1067
+ };
1068
+
1069
+ // Export handler
1070
+ document.getElementById('RetoldRemote-ExportConfirmBtn').onclick = () =>
1071
+ {
1072
+ let tmpDestPath = tmpPathInput ? tmpPathInput.value.trim() : '';
1073
+ if (!tmpDestPath)
1074
+ {
1075
+ return;
1076
+ }
1077
+
1078
+ let tmpBtn = document.getElementById('RetoldRemote-ExportConfirmBtn');
1079
+ let tmpStatus = document.getElementById('RetoldRemote-ExportStatus');
1080
+ if (tmpBtn) tmpBtn.disabled = true;
1081
+ if (tmpBtn) tmpBtn.textContent = 'Exporting\u2026';
1082
+ if (tmpStatus) tmpStatus.style.display = '';
1083
+ if (tmpStatus) tmpStatus.textContent = 'Exporting\u2026';
1084
+
1085
+ fetch('/api/collections/' + encodeURIComponent(pGUID) + '/export',
1086
+ {
1087
+ method: 'POST',
1088
+ headers: { 'Content-Type': 'application/json' },
1089
+ body: JSON.stringify({ DestinationPath: tmpDestPath })
1090
+ })
1091
+ .then((pResponse) => pResponse.json())
1092
+ .then((pResult) =>
1093
+ {
1094
+ if (pResult && pResult.Success)
1095
+ {
1096
+ let tmpMsg = 'Exported ' + pResult.ExportedCount + ' of ' + pResult.TotalItems + ' items';
1097
+ if (pResult.ErrorCount > 0)
1098
+ {
1099
+ tmpMsg += ' (' + pResult.ErrorCount + ' errors)';
1100
+ }
1101
+ tmpMsg += ' to ' + pResult.DestinationPath;
1102
+
1103
+ if (tmpStatus) tmpStatus.textContent = tmpMsg;
1104
+ if (tmpBtn) tmpBtn.textContent = 'Done';
1105
+
1106
+ let tmpToast = tmpSelf.pict.providers['RetoldRemote-ToastNotification'];
1107
+ if (tmpToast)
1108
+ {
1109
+ tmpToast.showToast(tmpMsg);
1110
+ }
1111
+
1112
+ // Auto-dismiss after a moment
1113
+ setTimeout(() =>
1114
+ {
1115
+ if (tmpDialog.parentElement)
1116
+ {
1117
+ tmpDialog.parentElement.removeChild(tmpDialog);
1118
+ }
1119
+ }, 3000);
1120
+ }
1121
+ else
1122
+ {
1123
+ let tmpErrMsg = (pResult && pResult.Error) || 'Export failed';
1124
+ if (tmpStatus) tmpStatus.textContent = tmpErrMsg;
1125
+ if (tmpStatus) tmpStatus.style.color = '#e06c75';
1126
+ if (tmpBtn) tmpBtn.textContent = 'Retry';
1127
+ if (tmpBtn) tmpBtn.disabled = false;
1128
+ }
1129
+ })
1130
+ .catch((pError) =>
1131
+ {
1132
+ if (tmpStatus) tmpStatus.textContent = 'Request failed: ' + pError.message;
1133
+ if (tmpStatus) tmpStatus.style.color = '#e06c75';
1134
+ if (tmpBtn) tmpBtn.textContent = 'Retry';
1135
+ if (tmpBtn) tmpBtn.disabled = false;
1136
+ });
1137
+ };
1138
+
1139
+ // Enter key in input triggers export
1140
+ if (tmpPathInput)
1141
+ {
1142
+ tmpPathInput.onkeydown = (pEvent) =>
1143
+ {
1144
+ if (pEvent.key === 'Enter')
1145
+ {
1146
+ document.getElementById('RetoldRemote-ExportConfirmBtn').click();
1147
+ }
1148
+ if (pEvent.key === 'Escape')
1149
+ {
1150
+ tmpDialog.parentElement.removeChild(tmpDialog);
1151
+ }
1152
+ };
1153
+ }
1154
+ }
989
1155
  }
990
1156
 
991
1157
  RetoldRemoteCollectionsPanelView.default_configuration = _ViewConfiguration;