@parallelworks/ui 0.20.0 → 0.21.0

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 (154) hide show
  1. package/dist/ai/adapter/openai/createOpenAIStream.js +3 -2
  2. package/dist/ai/agent/diff.d.ts +0 -1
  3. package/dist/ai/agent/diff.js +3 -2
  4. package/dist/ai/agent/toolfmt.js +2 -3
  5. package/dist/ai/ai.css +5 -0
  6. package/dist/ai/components/AllocationSelector.js +3 -2
  7. package/dist/ai/components/AttachmentManager.js +1 -1
  8. package/dist/ai/components/BranchNavigator.js +4 -2
  9. package/dist/ai/components/ChatInput.d.ts +2 -5
  10. package/dist/ai/components/ChatInput.js +10 -7
  11. package/dist/ai/components/ChatMessage.js +7 -7
  12. package/dist/ai/components/ChatSidebar.d.ts +0 -1
  13. package/dist/ai/components/ChatSidebar.js +4 -6
  14. package/dist/ai/components/ChatThread.js +5 -8
  15. package/dist/ai/components/ModelSelector.js +3 -2
  16. package/dist/ai/components/ShareDialog.js +6 -3
  17. package/dist/ai/components/SlashMenu.d.ts +0 -1
  18. package/dist/ai/components/agent/ActivityLine.d.ts +0 -1
  19. package/dist/ai/components/agent/ActivityLine.js +2 -2
  20. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -26
  21. package/dist/ai/components/agent/AgentMessageParts.js +4 -5
  22. package/dist/ai/core/ChatProvider.d.ts +2 -6
  23. package/dist/ai/core/ChatProvider.js +42 -67
  24. package/dist/ai/core/KeyboardShortcutsProvider.js +1 -1
  25. package/dist/ai/core/chatReducer.d.ts +0 -6
  26. package/dist/ai/core/chatReducer.js +1 -15
  27. package/dist/ai/core/useKeyboardShortcuts.d.ts +3 -1
  28. package/dist/ai/core/useKeyboardShortcuts.js +9 -11
  29. package/dist/ai/core/useSSEStream.d.ts +0 -1
  30. package/dist/ai/core/useSSEStream.js +5 -17
  31. package/dist/ai/strings.d.ts +0 -1
  32. package/dist/ai/strings.js +0 -1
  33. package/dist/ai/types.d.ts +1 -1
  34. package/dist/ai/ui/Avatar.d.ts +1 -2
  35. package/dist/ai/ui/Avatar.js +7 -18
  36. package/dist/ai/ui/Markdown.d.ts +1 -3
  37. package/dist/ai/ui/Markdown.js +1 -2
  38. package/dist/ai/ui/sidebar.d.ts +0 -1
  39. package/dist/ai/ui/sidebar.js +3 -1
  40. package/dist/ai/utils.js +1 -1
  41. package/dist/components/Avatar.js +4 -2
  42. package/dist/components/BareModal.js +1 -5
  43. package/dist/components/Breadcrumbs.js +21 -14
  44. package/dist/components/CollapsiblePanel.d.ts +1 -3
  45. package/dist/components/CollapsiblePanel.js +5 -5
  46. package/dist/components/Dropdown.d.ts +0 -4
  47. package/dist/components/Dropdown.js +5 -6
  48. package/dist/components/Listbox.js +14 -18
  49. package/dist/components/MultiSelect.js +5 -8
  50. package/dist/components/Pagination.js +1 -1
  51. package/dist/components/Provider.d.ts +0 -8
  52. package/dist/components/SwitchToggle.js +3 -5
  53. package/dist/components/Table.d.ts +1 -3
  54. package/dist/components/dropdownUtils.js +5 -6
  55. package/dist/components/relativeTime.d.ts +0 -7
  56. package/dist/components/relativeTime.js +1 -1
  57. package/dist/duration.js +1 -1
  58. package/dist/editor/Editor.d.ts +1 -1
  59. package/dist/editor/Editor.js +1 -1
  60. package/dist/editor/EditorField.d.ts +0 -1
  61. package/dist/editor/EditorField.js +8 -8
  62. package/dist/editor/FormCodePanel.js +14 -20
  63. package/dist/editor/yamlLanguage.js +2 -2
  64. package/dist/file-explorer/FileExplorer.js +56 -59
  65. package/dist/file-explorer/FileExplorerContext.d.ts +1 -2
  66. package/dist/file-explorer/FileExplorerContext.js +92 -125
  67. package/dist/file-explorer/FilePreview/Inline.d.ts +1 -2
  68. package/dist/file-explorer/FilePreview/Inline.js +2 -2
  69. package/dist/file-explorer/FilePreview/Viewer.js +0 -4
  70. package/dist/file-explorer/FilePreview/index.d.ts +1 -2
  71. package/dist/file-explorer/FilePreview/index.js +3 -3
  72. package/dist/file-explorer/FilePreview/parse.d.ts +2 -1
  73. package/dist/file-explorer/FilePreview/useObjectBlobUrl.d.ts +5 -4
  74. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +23 -36
  75. package/dist/file-explorer/FilePreview/useObjectText.d.ts +1 -2
  76. package/dist/file-explorer/FilePreview/useObjectText.js +5 -7
  77. package/dist/file-explorer/FileRow.js +1 -1
  78. package/dist/file-explorer/FileRowsWindow.d.ts +5 -12
  79. package/dist/file-explorer/FileTable.d.ts +2 -6
  80. package/dist/file-explorer/LoadMoreRow.d.ts +8 -0
  81. package/dist/file-explorer/TreeView.d.ts +0 -1
  82. package/dist/file-explorer/TreeView.js +6 -6
  83. package/dist/file-explorer/helpers.js +2 -4
  84. package/dist/file-explorer/lib/fileExplorer.d.ts +0 -2
  85. package/dist/file-explorer/lib/types.d.ts +0 -1
  86. package/dist/file-explorer/lib/utils.js +15 -20
  87. package/dist/file-explorer/toasts.js +36 -34
  88. package/dist/form/Fields/BooleanField.js +5 -4
  89. package/dist/form/Fields/CheckboxGroupField.js +5 -4
  90. package/dist/form/Fields/ColorField.js +5 -4
  91. package/dist/form/Fields/DropdownField.d.ts +0 -1
  92. package/dist/form/Fields/DropdownField.js +10 -13
  93. package/dist/form/Fields/DurationField.js +2 -2
  94. package/dist/form/Fields/LabelField.js +5 -5
  95. package/dist/form/Fields/MultiDropdownField.d.ts +1 -1
  96. package/dist/form/Fields/MultiDropdownField.js +8 -11
  97. package/dist/form/Fields/NumberField.js +6 -5
  98. package/dist/form/Fields/ObjectField.d.ts +1 -2
  99. package/dist/form/Fields/ObjectField.js +22 -21
  100. package/dist/form/Fields/RadioField.js +4 -3
  101. package/dist/form/Fields/RangeField.js +7 -6
  102. package/dist/form/Fields/StringField.js +2 -1
  103. package/dist/form/Fields/TextAreaField.js +2 -1
  104. package/dist/form/Form.d.ts +7 -8
  105. package/dist/form/Form.js +52 -78
  106. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -2
  107. package/dist/form/SpecialTypes/DropdownWrapper.js +1 -3
  108. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +1 -3
  109. package/dist/form/SpecialTypes/FormikCustomDropdown.js +4 -7
  110. package/dist/form/SpecialTypes/FormikCustomInput.js +13 -11
  111. package/dist/form/Wizard/WizardContainer.js +8 -25
  112. package/dist/form/Wizard/WizardNavigation.d.ts +1 -1
  113. package/dist/form/Wizard/WizardNavigation.js +5 -6
  114. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  115. package/dist/form/Wizard/WizardStepContent.js +2 -2
  116. package/dist/form/Wizard/WizardStepIndicator.js +7 -17
  117. package/dist/form/Wizard/types.d.ts +4 -46
  118. package/dist/form/Wizard/useWizardState.d.ts +2 -10
  119. package/dist/form/Wizard/useWizardState.js +42 -92
  120. package/dist/form/Wizard/utils.js +6 -11
  121. package/dist/form/lib.js +28 -54
  122. package/dist/form/types/fieldTypes.d.ts +4 -5
  123. package/dist/form/utils/dependentOptions.d.ts +6 -0
  124. package/dist/form/utils/dependentOptions.js +21 -0
  125. package/dist/form/utils/getParentValue.d.ts +2 -1
  126. package/dist/form/utils/getParentValue.js +10 -15
  127. package/dist/form/utils/getValueUsingPath.js +1 -1
  128. package/dist/graph/AnnotationBanner.d.ts +1 -1
  129. package/dist/graph/AnnotationBanner.js +1 -1
  130. package/dist/graph/Collapse.d.ts +3 -3
  131. package/dist/graph/Collapse.js +3 -8
  132. package/dist/graph/DependencyGraph.d.ts +5 -4
  133. package/dist/graph/DependencyGraph.js +172 -222
  134. package/dist/graph/JobSummary.d.ts +10 -8
  135. package/dist/graph/JobSummary.js +13 -13
  136. package/dist/graph/MatrixGroup.d.ts +5 -17
  137. package/dist/graph/MatrixGroup.js +11 -22
  138. package/dist/graph/TreeView.d.ts +1 -3
  139. package/dist/graph/TreeView.js +104 -491
  140. package/dist/graph/types.d.ts +3 -2
  141. package/dist/graph/util.d.ts +5 -0
  142. package/dist/graph/util.js +18 -0
  143. package/dist/icons.d.ts +2 -2
  144. package/dist/icons.js +2 -2
  145. package/dist/index.d.ts +1 -1
  146. package/dist/list/ListViewControls.js +17 -35
  147. package/dist/list/RowContextMenu.js +7 -7
  148. package/dist/logviewer/LogViewer.js +7 -9
  149. package/dist/logviewer/segments.js +2 -2
  150. package/dist/styles/theme.css +1 -0
  151. package/dist/styles.css +1 -1
  152. package/dist/theme/index.js +5 -5
  153. package/package.json +28 -30
  154. package/dist/ai/ui/buttons.d.ts +0 -1
@@ -56,6 +56,10 @@ function UriCard({ value }) {
56
56
  ]
57
57
  });
58
58
  }
59
+ var MAX_UPLOAD_SIZE = 85899345920;
60
+ function isUploadSizeExceeded(size) {
61
+ return size > MAX_UPLOAD_SIZE;
62
+ }
59
63
  function isStorageDirectory(node) {
60
64
  return node?.type === "directory" && !!node.storageId;
61
65
  }
@@ -68,7 +72,7 @@ function getAncestorDirPaths(path) {
68
72
  const segments = (isDir ? path.slice(0, -1) : path).split("/").filter(Boolean);
69
73
  const dirSegmentCount = isDir ? segments.length : segments.length - 1;
70
74
  const ancestors = [];
71
- for (let i = 1; i <= dirSegmentCount; i++) ancestors.push(segments.slice(0, i).join("/") + "/");
75
+ for (let i = 1; i <= dirSegmentCount; i++) ancestors.push(`${segments.slice(0, i).join("/")}/`);
72
76
  return ancestors;
73
77
  }
74
78
  /** The tree's width is the host's when the tree is embedded on its own, and this
@@ -157,9 +161,10 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
157
161
  setCheckedItems(/* @__PURE__ */ new Set());
158
162
  }
159
163
  const selectedNodeChildren = useMemo(() => selectedNode ? getNodeChildren(parentToChildrenMap, selectedNode.path) : [], [parentToChildrenMap, selectedNode]);
160
- const storageCanWrite = (storageId) => storages.find((s) => s.id === storageId)?.canWrite === true;
161
- const storageCanUpload = (storageId) => storages.find((s) => s.id === storageId)?.canUpload !== false;
162
- const selectedStorage = storages.find((s) => s.id === selectedNode?.storageId);
164
+ const findStorage = (storageId) => storages.find((s) => s.id === storageId);
165
+ const storageCanWrite = (storageId) => findStorage(storageId)?.canWrite === true;
166
+ const storageCanUpload = (storageId) => findStorage(storageId)?.canUpload !== false;
167
+ const selectedStorage = findStorage(selectedNode?.storageId);
163
168
  const selectedNodeCanWrite = selectedStorage?.canWrite === true;
164
169
  const selectedNodeCanUpload = selectedStorage?.canUpload !== false;
165
170
  const selectedNodeCanShare = selectedStorage?.canShare !== false;
@@ -181,10 +186,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
181
186
  useEffect(() => clearPreviewCache, []);
182
187
  const { openMenu, contextMenu } = useRowMenu();
183
188
  const [sidebarWidth, setSidebarWidth] = useLocalStorage(SIDEBAR_WIDTH_STORAGE_KEY, 300);
184
- const MAX_UPLOAD_SIZE = 85899345920;
185
- const isUploadSizeExceeded = (size) => {
186
- return size > MAX_UPLOAD_SIZE;
187
- };
188
189
  const handleToggleExpand = useCallback((path) => {
189
190
  setExpandedPaths((prev) => {
190
191
  const next = new Set(prev);
@@ -196,10 +197,10 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
196
197
  setExpandedPaths(/* @__PURE__ */ new Set([""]));
197
198
  }, []);
198
199
  const getStorageProviderAndClient = async (storageId) => {
199
- const storage = storages.find((s) => s.id === storageId);
200
+ const storage = findStorage(storageId);
200
201
  if (!storage) {
201
202
  notify.error(t.messages.storageUnavailable);
202
- return;
203
+ return null;
203
204
  }
204
205
  const { provider, client } = await getProviderAndClient(storage).catch(() => ({
205
206
  provider: null,
@@ -236,7 +237,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
236
237
  const factory = await getStorageProviderAndClient(node.storageId);
237
238
  if (!factory) throw new Error("Failed to connect to storage provider.");
238
239
  const { storage, provider, client } = factory;
239
- if (provider?.listDirectoryInput && provider?.convertDataToStorageObjects) {
240
+ if (provider.listDirectoryInput && provider.convertDataToStorageObjects) {
240
241
  const dir_path = node.root ? "" : node.relativePath ?? "";
241
242
  const input = provider.listDirectoryInput(storage, dir_path, cursor ? { cursor } : {});
242
243
  const objects = await client.listDirectory(input);
@@ -262,7 +263,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
262
263
  const errorMessage = isCorsError ? error.message || "CORS error" : error instanceof Error ? error.message : "Unknown error";
263
264
  if (owns()) {
264
265
  setListings((prev) => recordListingError(options?.replace ? clearListingsUnder(prev, node.path) : prev, node.path, errorMessage));
265
- updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, `Failed to load contents for ${node?.displayName || node?.name}: ${errorMessage}`));
266
+ updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, `Failed to load contents for ${node.displayName || node.name}: ${errorMessage}`));
266
267
  if (isCorsError) setHasCorsIssue((prev) => new Map(prev).set(nodeStorageId, true));
267
268
  }
268
269
  return { corsError: isCorsError };
@@ -283,7 +284,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
283
284
  setDetailTab("details");
284
285
  }
285
286
  setSelectedPath(path);
286
- if (node.type === "directory" && node?.storageId && !isListed(listings, path)) await fetchNodeChildren(node);
287
+ if (node.type === "directory" && node.storageId && !isListed(listings, path)) await fetchNodeChildren(node);
287
288
  };
288
289
  useEffect(() => {
289
290
  if (!normalizedSelectedPath) return;
@@ -430,18 +431,21 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
430
431
  if (selectedNode && selectedNode.type === "directory") return true;
431
432
  return false;
432
433
  };
434
+ const getFilesToDownload = () => {
435
+ if (checkedItems.size > 0) return Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => node?.type === "file");
436
+ return selectedNode?.type === "file" ? [selectedNode] : [];
437
+ };
433
438
  const handleDownload = async () => {
434
- let filesToDownload = [];
435
- if (checkedItems.size > 0) filesToDownload = Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => !!node && node.type === "file");
436
- else if (selectedNode && selectedNode.type === "file") filesToDownload = [selectedNode];
437
- if (filesToDownload.length === 0) {
439
+ const filesToDownload = getFilesToDownload();
440
+ const firstFile = filesToDownload[0];
441
+ if (!firstFile) {
438
442
  notify.error(t.messages.noFilesForDownload);
439
443
  return;
440
444
  }
441
- const factory = await getStorageProviderAndClient(filesToDownload[0].storageId);
445
+ const factory = await getStorageProviderAndClient(firstFile.storageId);
442
446
  if (!factory) return;
443
447
  const { provider, client } = factory;
444
- if (!provider?.getFileInput) {
448
+ if (!provider.getFileInput) {
445
449
  notify.error(t.messages.downloadUnsupported);
446
450
  return;
447
451
  }
@@ -460,7 +464,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
460
464
  const factory = await getStorageProviderAndClient(node.storageId);
461
465
  if (!factory) return;
462
466
  const { provider, client } = factory;
463
- if (!provider?.getFileInput) {
467
+ if (!provider.getFileInput) {
464
468
  notify.error(t.messages.sharingUnsupported);
465
469
  return;
466
470
  }
@@ -489,14 +493,14 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
489
493
  const factory = await getStorageProviderAndClient(node.storageId);
490
494
  if (!factory) return [null, /* @__PURE__ */ new Error("Failed to connect to storage provider.")];
491
495
  const { provider, client } = factory;
492
- if (!provider?.getFileInput) return [null, /* @__PURE__ */ new Error("Preview is not supported for this storage.")];
496
+ if (!provider.getFileInput) return [null, /* @__PURE__ */ new Error("Preview is not supported for this storage.")];
493
497
  const input = provider.getFileInput(node.storageName ?? "", node, PREVIEW_URL_EXPIRES_IN, downloadFilename);
494
498
  return client.getFile(input);
495
499
  };
496
500
  const previewSiblings = useMemo(() => previewNode ? getNodeChildren(parentToChildrenMap, getParentPath(previewNode.path)).filter((n) => n.type === "file") : [], [previewNode, parentToChildrenMap]);
497
501
  const openPreview = (node) => {
498
502
  if (node.type !== "file") return;
499
- if (onOpenFile?.(node, storages.find((s) => s.id === node.storageId))) return;
503
+ if (onOpenFile?.(node, findStorage(node.storageId))) return;
500
504
  setPreviewNode(node);
501
505
  setPreviewOpen(true);
502
506
  };
@@ -508,7 +512,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
508
512
  if (!expandedPaths.has(node.path)) handleToggleExpand(node.path);
509
513
  };
510
514
  const getNodeUris = (node) => {
511
- const storage = storages.find((s) => s.id === node.storageId);
515
+ const storage = findStorage(node.storageId);
512
516
  if (!storage || !objectUris) return [];
513
517
  return objectUris(storage, getObjectKeyFromNode(node));
514
518
  };
@@ -534,7 +538,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
534
538
  triggerBrowserDownload(presignedUrl, node.name);
535
539
  };
536
540
  const deleteNode = (node) => {
537
- if (storages.find((s) => s.id === node.storageId)?.canWrite !== true) {
541
+ if (findStorage(node.storageId)?.canWrite !== true) {
538
542
  notify.error(t.messages.noWriteAccess);
539
543
  return;
540
544
  }
@@ -556,7 +560,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
556
560
  }];
557
561
  };
558
562
  const buildRowMenu = (node) => {
559
- const storage = storages.find((s) => s.id === node.storageId);
563
+ const storage = findStorage(node.storageId);
560
564
  const nodeCanWrite = storage?.canWrite === true;
561
565
  const nodeCanShare = storage?.canShare !== false;
562
566
  const isFile = node.type === "file";
@@ -649,13 +653,10 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
649
653
  });
650
654
  };
651
655
  const generateDownloadCommand = () => {
652
- let filesToDownload = [];
653
- if (checkedItems.size > 0) filesToDownload = Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => !!node && node.type === "file");
654
- else if (selectedNode && selectedNode.type === "file") filesToDownload = [selectedNode];
655
- if (filesToDownload.length === 0) return "";
656
- if (!downloadCommand) return "";
656
+ const filesToDownload = getFilesToDownload();
657
+ if (!downloadCommand || filesToDownload.length === 0) return "";
657
658
  const objects = filesToDownload.flatMap((node) => {
658
- const storage = storages.find((s) => s.id === node.storageId);
659
+ const storage = findStorage(node.storageId);
659
660
  return storage ? [{
660
661
  storage,
661
662
  key: getObjectKeyFromNode(node),
@@ -697,16 +698,17 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
697
698
  }
698
699
  };
699
700
  const handleDelete = async () => {
700
- if (!pendingDelete || pendingDelete.length === 0) {
701
+ const firstPending = pendingDelete?.[0];
702
+ if (!pendingDelete || !firstPending) {
701
703
  notify.error(t.messages.noItemsForDeletion);
702
704
  return;
703
705
  }
704
- const parentPath = getParentPath(pendingDelete[0].path);
706
+ const parentPath = getParentPath(firstPending.path);
705
707
  const parentNode = treeMap[parentPath];
706
- const factory = await getStorageProviderAndClient(pendingDelete[0].storageId);
708
+ const factory = await getStorageProviderAndClient(firstPending.storageId);
707
709
  if (!factory) return;
708
710
  const { storage, provider, client } = factory;
709
- if (!provider?.deleteFilesInput) {
711
+ if (!provider.deleteFilesInput) {
710
712
  notify.error(t.messages.deleteUnsupported);
711
713
  return;
712
714
  }
@@ -747,7 +749,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
747
749
  notify.warning(t.messages.waitForCurrentUpload);
748
750
  return;
749
751
  }
750
- setWaitForUpload(true);
751
752
  const uploadNodes = Array.from(files).map((file) => ({
752
753
  name: file.name,
753
754
  file,
@@ -760,7 +761,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
760
761
  totalSize
761
762
  });
762
763
  setUploadModalOpen(true);
763
- setWaitForUpload(false);
764
764
  if (fileInputRef.current) fileInputRef.current.value = "";
765
765
  if (folderInputRef.current) folderInputRef.current.value = "";
766
766
  };
@@ -774,7 +774,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
774
774
  return;
775
775
  }
776
776
  const targetNode = treeMap[pendingUpload.targetPath];
777
- if (!targetNode || !targetNode.storageId) {
777
+ if (!targetNode?.storageId) {
778
778
  notify.error(t.messages.invalidUploadTarget);
779
779
  return;
780
780
  }
@@ -786,7 +786,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
786
786
  const { storage, provider, client } = factory;
787
787
  const storageName = storage.bucketName || storage.name;
788
788
  const cleanTargetPath = targetNode.root ? "" : removeLeadingAndTrailingSlashes(targetNode.relativePath || "");
789
- if (!provider?.uploadFileInput) {
789
+ if (!provider.uploadFileInput) {
790
790
  notify.error(t.messages.uploadUnsupported);
791
791
  return;
792
792
  }
@@ -813,7 +813,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
813
813
  }
814
814
  setUploadModalOpen(false);
815
815
  setPendingUpload(null);
816
- setWaitForUpload(false);
817
816
  };
818
817
  const validateFolderName = (name) => {
819
818
  if (createFolderTarget?.type !== "directory") return null;
@@ -861,6 +860,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
861
860
  if (onRefresh) onRefresh();
862
861
  return { corsError: false };
863
862
  };
863
+ const downloadCommandText = generateDownloadCommand();
864
864
  return /* @__PURE__ */ jsxs("div", {
865
865
  className: "w-full h-full flex relative",
866
866
  children: [
@@ -1058,22 +1058,19 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1058
1058
  className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
1059
1059
  onClick: handleDownload,
1060
1060
  children: t.chrome.downloadFromBrowser
1061
- }) }), downloadCommand && generateDownloadCommand() && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(MenuSeparator, { className: "my-0.5 h-px theme-border" }), /* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
1061
+ }) }), downloadCommand && downloadCommandText && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(MenuSeparator, { className: "my-0.5 h-px theme-border" }), /* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
1062
1062
  className: "text-sm w-full text-center pointer-events-none px-2 py-1.5",
1063
1063
  children: downloadCommand.label
1064
- }), (() => {
1065
- const downloadCommand = generateDownloadCommand();
1066
- return /* @__PURE__ */ jsx(CopyCodeBlock, {
1067
- textToCopy: downloadCommand,
1068
- children: /* @__PURE__ */ jsx("div", {
1069
- className: "overflow-x-auto text-xs",
1070
- children: keyedByContent(downloadCommand.split("\n"), (l) => l).map(({ key, item: line }) => /* @__PURE__ */ jsx("div", {
1071
- className: "whitespace-nowrap",
1072
- children: line
1073
- }, key))
1074
- })
1075
- });
1076
- })()] }) })] })]
1064
+ }), /* @__PURE__ */ jsx(CopyCodeBlock, {
1065
+ textToCopy: downloadCommandText,
1066
+ children: /* @__PURE__ */ jsx("div", {
1067
+ className: "overflow-x-auto text-xs",
1068
+ children: keyedByContent(downloadCommandText.split("\n"), (l) => l).map(({ key, item: line }) => /* @__PURE__ */ jsx("div", {
1069
+ className: "whitespace-nowrap",
1070
+ children: line
1071
+ }, key))
1072
+ })
1073
+ })] }) })] })]
1077
1074
  })] }),
1078
1075
  /* @__PURE__ */ jsxs("button", {
1079
1076
  type: "button",
@@ -1081,7 +1078,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1081
1078
  className: "btn btn-info cursor-pointer flex items-center justify-center",
1082
1079
  disabled: !selectedNode.storageId,
1083
1080
  onClick: () => setAccessDrawerOpen(true),
1084
- hidden: !groups || groups.length === 0 || !selectedNodeCanManageAccess,
1081
+ hidden: groups.length === 0 || !selectedNodeCanManageAccess,
1085
1082
  children: [/* @__PURE__ */ jsx(AccessIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1086
1083
  className: "hidden md:inline",
1087
1084
  children: t.chrome.manageAccess
@@ -1129,7 +1126,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1129
1126
  onSelectAll: handleBatchCheckbox
1130
1127
  })
1131
1128
  }), /* @__PURE__ */ jsx(NoDataAvailablePreview, {
1132
- selectedStorage: storages.find((s) => s.id === selectedNode.storageId) || null,
1129
+ selectedStorage: selectedStorage ?? null,
1133
1130
  canWrite: selectedNodeCanWrite,
1134
1131
  onRefresh: handleRefresh,
1135
1132
  error: hasCorsIssue.get(selectedNode.storageId ?? "") ? new CorsError(connectionIssuesMessage.get(selectedNode.storageId ?? "") || "") : null,
@@ -1162,7 +1159,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1162
1159
  })]
1163
1160
  }), effectiveTab === "preview" && previewable ? /* @__PURE__ */ jsx(FilePreviewInline, {
1164
1161
  node: selectedNode,
1165
- storage: storages.find((s) => s.id === selectedNode.storageId) ?? null,
1162
+ storage: selectedStorage ?? null,
1166
1163
  getPresignedUrl,
1167
1164
  onOpenFull: openPreview,
1168
1165
  onDownload: downloadNode,
@@ -1289,7 +1286,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1289
1286
  open: previewOpen,
1290
1287
  node: previewNode,
1291
1288
  siblings: previewSiblings,
1292
- storage: storages.find((s) => s.id === previewNode?.storageId) ?? null,
1289
+ storage: findStorage(previewNode?.storageId) ?? null,
1293
1290
  getPresignedUrl,
1294
1291
  onNavigate: setPreviewNode,
1295
1292
  onClose: closePreview,
@@ -1396,7 +1393,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1396
1393
  open: accessDrawerOpen,
1397
1394
  setOpen: setAccessDrawerOpen,
1398
1395
  groups,
1399
- storage: storages.find((s) => s.id === selectedNode.storageId)
1396
+ storage: selectedStorage
1400
1397
  })
1401
1398
  ]
1402
1399
  });
@@ -1,5 +1,5 @@
1
1
  import type { IFileExplorerClient, IFileExplorerProvider } from './lib/fileExplorer.js';
2
- import type { TreeNode, UploadFileProgress, UploadNode, UploadSession } from './lib/types.js';
2
+ import type { TreeNode, UploadNode, UploadSession } from './lib/types.js';
3
3
  interface IFileExplorerContextValue {
4
4
  uploadSessionMap: Map<string, UploadSession>;
5
5
  uploadQueue: string[];
@@ -7,7 +7,6 @@ interface IFileExplorerContextValue {
7
7
  getUploadSessionById: (sessionId: string) => UploadSession | undefined;
8
8
  getUploadQueueIndex: (sessionId: string) => number;
9
9
  startUploadSession: (sessionId: string) => Promise<void>;
10
- updateFileUploadProgress?: (sessionId: string, fileKey: string, progress: UploadFileProgress) => void;
11
10
  }
12
11
  interface IFileExplorerProviderProps {
13
12
  children: React.ReactNode;
@@ -1,6 +1,6 @@
1
1
  import { useNotify, useStrings } from "../components/Provider.js";
2
2
  import { keyedByContent } from "../components/keys.js";
3
- import { getUploadSessionETA, removeLeadingSlash } from "./lib/utils.js";
3
+ import { calculateUploadNodesTotalSize, getUploadSessionETA, removeLeadingSlash } from "./lib/utils.js";
4
4
  import { UploadSessionToast } from "./toasts.js";
5
5
  import { createContext, use, useRef } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -21,37 +21,26 @@ function FileExplorerProvider({ children }) {
21
21
  const uploadQueueRef = useRef([]);
22
22
  const isProcessingRef = useRef(false);
23
23
  const uploadSessionIdCounter = useRef(0);
24
- const uploadSessionIdPrefix = "upload-session";
25
24
  const generateUploadSessionId = () => {
26
- return `${uploadSessionIdPrefix}-${Date.now()}-${uploadSessionIdCounter.current++}`;
25
+ return `upload-session-${Date.now()}-${uploadSessionIdCounter.current++}`;
27
26
  };
28
27
  const calculateWeightedProgress = (session) => {
29
28
  if (!session.progress?.allFiles || session.progress.allFiles.size === 0) return 0;
30
29
  let totalWeightedProgress = 0;
31
30
  let totalWeight = 0;
32
31
  session.progress.allFiles.forEach((fileProgress) => {
33
- const fileSize = fileProgress.fileSizeBytes || 0;
34
- const filePercentage = fileProgress?.percentage || 0;
35
- totalWeightedProgress += filePercentage / 100 * fileSize;
32
+ const fileSize = fileProgress.fileSizeBytes;
33
+ totalWeightedProgress += (fileProgress.percentage ?? 0) / 100 * fileSize;
36
34
  totalWeight += fileSize;
37
35
  });
38
36
  return totalWeight > 0 ? totalWeightedProgress / totalWeight * 100 : 0;
39
37
  };
40
- const calculateOverallLoadedBytes = (session) => {
41
- if (!session.progress?.allFiles) return {
42
- loaded: 0,
43
- total: session.progress?.totalSizeBytes || 0
44
- };
38
+ const sumLoadedBytes = (session) => {
45
39
  let totalLoaded = 0;
46
- let totalSize = 0;
47
- session.progress.allFiles.forEach((fileProgress) => {
48
- totalLoaded += fileProgress.loadedBytes || 0;
49
- totalSize += fileProgress.fileSizeBytes || 0;
40
+ session.progress?.allFiles?.forEach((fileProgress) => {
41
+ totalLoaded += fileProgress.loadedBytes ?? 0;
50
42
  });
51
- return {
52
- loaded: totalLoaded,
53
- total: totalSize
54
- };
43
+ return totalLoaded;
55
44
  };
56
45
  const updateFileProgressInUploadSession = (session, relativePath, updates) => {
57
46
  if (!session.progress?.allFiles) return;
@@ -77,10 +66,10 @@ function FileExplorerProvider({ children }) {
77
66
  progress: {
78
67
  completedFiles: 0,
79
68
  totalFiles: uploadNodes.length,
80
- totalSizeBytes: uploadNodes.reduce((sum, node) => sum + (node.file?.size || 0), 0),
69
+ totalSizeBytes: calculateUploadNodesTotalSize(uploadNodes),
81
70
  allFiles: new Map(uploadNodes.map((file) => [file.relativePath, {
82
71
  fileKey: file.relativePath,
83
- fileSizeBytes: file.file?.size || 0,
72
+ fileSizeBytes: file.file.size,
84
73
  abortController: new AbortController(),
85
74
  status: "pending",
86
75
  percentage: 0
@@ -154,109 +143,90 @@ function FileExplorerProvider({ children }) {
154
143
  }
155
144
  session.status = "uploading";
156
145
  isProcessingRef.current = true;
146
+ const progressToast = (shown = session) => /* @__PURE__ */ jsx(UploadSessionToast, {
147
+ session: shown,
148
+ queueLength: uploadQueueRef.current.length,
149
+ onCancel: () => handleSessionCancel(session),
150
+ onCancelFile: (path) => cancelFileInSession(sessionId, path)
151
+ });
157
152
  try {
158
153
  notify.update(sessionId, {
159
- render: /* @__PURE__ */ jsx(UploadSessionToast, {
160
- session,
161
- queueLength: uploadQueueRef.current.length,
162
- onCancel: () => handleSessionCancel(session),
163
- onCancelFile: (path) => cancelFileInSession(sessionId, path)
164
- }),
154
+ render: progressToast(),
165
155
  icon: false,
166
156
  isLoading: false,
167
157
  closeButton: false,
168
158
  autoClose: false
169
159
  });
170
160
  const results = await Promise.allSettled(session.uploadNodes.map(async (node) => {
171
- if (node && node.file) {
172
- const fileProgress = session.progress?.allFiles?.get(node.relativePath);
173
- if (!fileProgress) return;
174
- if (fileProgress.status === "cancelled") return {
175
- reason: "Upload cancelled by user",
176
- status: "rejected"
177
- };
161
+ const fileProgress = session.progress?.allFiles?.get(node.relativePath);
162
+ if (!fileProgress) return;
163
+ if (fileProgress.status === "cancelled") throw new Error("Upload cancelled by user");
164
+ updateFileProgressInUploadSession(session, node.relativePath, {
165
+ status: "uploading",
166
+ loadedBytes: 0,
167
+ totalBytes: node.file.size,
168
+ percentage: 0
169
+ });
170
+ const objectPath = session.targetPath ? `${session.targetPath}/${removeLeadingSlash(node.relativePath)}` : removeLeadingSlash(node.relativePath);
171
+ const uploadFileInput = session.provider.uploadFileInput;
172
+ if (!uploadFileInput) {
173
+ updateFileProgressInUploadSession(session, node.relativePath, { status: "failed" });
174
+ throw new Error("Upload is not supported for this storage provider");
175
+ }
176
+ const input = uploadFileInput(session.storageName, objectPath, node.file);
177
+ const onProgress = (progress) => {
178
+ if (session.status === "cancelled" || !session.progress?.allFiles) return;
179
+ const currentFileProgress = session.progress.allFiles.get(node.relativePath);
180
+ if (!currentFileProgress || [
181
+ "failed",
182
+ "cancelled",
183
+ "completed"
184
+ ].includes(currentFileProgress.status ?? "")) return;
178
185
  updateFileProgressInUploadSession(session, node.relativePath, {
179
- status: "uploading",
180
- loadedBytes: 0,
181
- totalBytes: node.file.size,
182
- percentage: 0
186
+ loadedBytes: progress.loadedBytes,
187
+ totalBytes: progress.totalBytes,
188
+ percentage: progress.percentage,
189
+ status: progress.percentage === 100 ? "completed" : "uploading"
190
+ });
191
+ if (progress.percentage === 100) session.progress.completedFiles += 1;
192
+ const totalLoadedBytes = sumLoadedBytes(session);
193
+ session.progress.overallProgress = calculateWeightedProgress(session);
194
+ session.progress.totalLoadedBytes = totalLoadedBytes;
195
+ const { estimatedSecondsLeft, lastProgressTime, lastProgressBytes, shouldUpdateETA } = getUploadSessionETA(totalLoadedBytes, session.progress.totalSizeBytes, session.progress.lastProgressTime, session.progress.lastProgressBytes);
196
+ session.progress.lastProgressTime = lastProgressTime;
197
+ session.progress.lastProgressBytes = lastProgressBytes;
198
+ if (shouldUpdateETA) session.progress.estimatedSecondsLeft = estimatedSecondsLeft;
199
+ notify.update(sessionId, {
200
+ render: progressToast(),
201
+ icon: false,
202
+ isLoading: false,
203
+ closeButton: false,
204
+ autoClose: false
205
+ });
206
+ };
207
+ try {
208
+ await session.client.uploadFile(input, onProgress, fileProgress.abortController);
209
+ updateFileProgressInUploadSession(session, node.relativePath, { status: "completed" });
210
+ } catch (error) {
211
+ const errorMessage = error instanceof Error ? error.message : String(error ?? "");
212
+ const isCancelled = errorMessage.toLowerCase().includes("abort") || errorMessage.toLowerCase().includes("cancel");
213
+ updateFileProgressInUploadSession(session, node.relativePath, { status: isCancelled ? "cancelled" : "failed" });
214
+ notify.update(sessionId, {
215
+ render: progressToast({
216
+ ...session,
217
+ progress: {
218
+ completedFiles: 0,
219
+ totalFiles: 0,
220
+ totalSizeBytes: 0,
221
+ ...session.progress
222
+ }
223
+ }),
224
+ icon: false,
225
+ isLoading: false,
226
+ closeButton: session.status === "failed" || session.status === "cancelled",
227
+ autoClose: false
183
228
  });
184
- const objectPath = session.targetPath ? `${session.targetPath}/${removeLeadingSlash(node.relativePath)}` : removeLeadingSlash(node.relativePath);
185
- const uploadFileInput = session.provider.uploadFileInput;
186
- if (!uploadFileInput) return {
187
- reason: "Upload is not supported for this storage provider",
188
- status: "rejected"
189
- };
190
- const input = uploadFileInput(session.storageName, objectPath, node.file);
191
- const onProgress = (progress) => {
192
- if (session.status === "cancelled") return;
193
- if (session.progress?.allFiles) {
194
- const currentFileProgress = session.progress.allFiles.get(node.relativePath);
195
- if (!currentFileProgress) return;
196
- if ([
197
- "failed",
198
- "cancelled",
199
- "complete"
200
- ].includes(currentFileProgress.status ?? "")) return;
201
- const wasCompleted = currentFileProgress.percentage === 100;
202
- updateFileProgressInUploadSession(session, node.relativePath, {
203
- loadedBytes: progress.loadedBytes,
204
- totalBytes: progress.totalBytes,
205
- percentage: progress.percentage,
206
- status: progress.percentage === 100 ? "completed" : "uploading"
207
- });
208
- if (progress.percentage === 100 && !wasCompleted) session.progress.completedFiles += 1;
209
- const overallProgress = calculateWeightedProgress(session);
210
- const { loaded: totalLoadedBytes } = calculateOverallLoadedBytes(session);
211
- session.progress.overallProgress = overallProgress;
212
- session.progress.totalLoadedBytes = totalLoadedBytes;
213
- const { estimatedSecondsLeft, lastProgressTime, lastProgressBytes, shouldUpdateETA } = getUploadSessionETA(totalLoadedBytes, session.progress.totalSizeBytes, session.progress.lastProgressTime, session.progress.lastProgressBytes);
214
- session.progress.lastProgressTime = lastProgressTime;
215
- session.progress.lastProgressBytes = lastProgressBytes;
216
- if (shouldUpdateETA) session.progress.estimatedSecondsLeft = estimatedSecondsLeft;
217
- notify.update(sessionId, {
218
- render: /* @__PURE__ */ jsx(UploadSessionToast, {
219
- session,
220
- queueLength: uploadQueueRef.current.length,
221
- onCancel: () => handleSessionCancel(session),
222
- onCancelFile: (path) => cancelFileInSession(sessionId, path)
223
- }),
224
- icon: false,
225
- isLoading: false,
226
- closeButton: false,
227
- autoClose: false
228
- });
229
- }
230
- };
231
- try {
232
- await session.client.uploadFile(input, onProgress, fileProgress.abortController);
233
- updateFileProgressInUploadSession(session, node.relativePath, { status: "completed" });
234
- } catch (error) {
235
- const errorMessage = error instanceof Error ? error.message : String(error ?? "");
236
- const isCancelled = errorMessage.toLowerCase().includes("abort") || errorMessage.toLowerCase().includes("cancel");
237
- updateFileProgressInUploadSession(session, node.relativePath, { status: isCancelled ? "cancelled" : "failed" });
238
- notify.update(sessionId, {
239
- render: /* @__PURE__ */ jsx(UploadSessionToast, {
240
- session: {
241
- ...session,
242
- progress: {
243
- completedFiles: 0,
244
- totalFiles: 0,
245
- totalSizeBytes: 0,
246
- ...session.progress
247
- }
248
- },
249
- queueLength: uploadQueueRef.current.length,
250
- onCancel: () => handleSessionCancel(session),
251
- onCancelFile: (path) => cancelFileInSession(sessionId, path)
252
- }),
253
- icon: false,
254
- isLoading: false,
255
- closeButton: session.status === "failed" || session.status === "cancelled",
256
- autoClose: false
257
- });
258
- throw error;
259
- }
229
+ throw error;
260
230
  }
261
231
  }));
262
232
  try {
@@ -266,19 +236,16 @@ function FileExplorerProvider({ children }) {
266
236
  }
267
237
  const failedResults = results.filter((r) => r.status === "rejected");
268
238
  const actualFailedResults = failedResults.filter((result) => {
269
- if (result.status === "rejected") {
270
- const reason = result.reason?.message || result.reason?.toString() || "";
271
- return !reason.toLowerCase().includes("abort") && !reason.toLowerCase().includes("cancel");
272
- }
273
- return false;
239
+ const reason = result.reason?.message || result.reason?.toString() || "";
240
+ return !reason.toLowerCase().includes("abort") && !reason.toLowerCase().includes("cancel");
274
241
  });
275
- const abortedCount = failedResults?.length - actualFailedResults?.length;
242
+ const abortedCount = failedResults.length - actualFailedResults.length;
276
243
  const failedFileNames = actualFailedResults.map((result) => {
277
- const originalIndex = results.findIndex((r) => r === result);
244
+ const originalIndex = results.indexOf(result);
278
245
  return session.uploadNodes[originalIndex]?.name || `File #${originalIndex + 1}`;
279
- }).filter(Boolean);
280
- const successfulCount = results?.length - failedResults?.length;
281
- if (actualFailedResults?.length > 0) notify.update(sessionId, {
246
+ });
247
+ const successfulCount = results.length - failedResults.length;
248
+ if (actualFailedResults.length > 0) notify.update(sessionId, {
282
249
  render: /* @__PURE__ */ jsxs("div", {
283
250
  className: "text-md",
284
251
  children: [
@@ -344,8 +311,8 @@ function FileExplorerProvider({ children }) {
344
311
  }
345
312
  };
346
313
  const processNextUploadSessionInQueue = async () => {
347
- if (isProcessingRef.current || uploadQueueRef.current.length === 0) return;
348
314
  const nextSessionId = uploadQueueRef.current[0];
315
+ if (isProcessingRef.current || nextSessionId === void 0) return;
349
316
  await startUploadSession(nextSessionId);
350
317
  };
351
318
  const contextValue = {
@@ -7,7 +7,6 @@ interface FilePreviewInlineProps {
7
7
  onDownload: (node: TreeNode) => void;
8
8
  /** When false, skip fetching and release any held preview blob. */
9
9
  active?: boolean;
10
- showSecurityIndicator?: boolean;
11
10
  }
12
- export declare function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownload, active, showSecurityIndicator, }: FilePreviewInlineProps): import("react").JSX.Element;
11
+ export declare function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownload, active, }: FilePreviewInlineProps): import("react").JSX.Element;
13
12
  export {};
@@ -7,7 +7,7 @@ import { usePresignedUrl } from "./usePresignedUrl.js";
7
7
  import { FilePreviewViewer } from "./Viewer.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/file-explorer/FilePreview/Inline.tsx
10
- function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownload, active = true, showSecurityIndicator = true }) {
10
+ function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownload, active = true }) {
11
11
  const t = useStrings().fileExplorer;
12
12
  const { url, loading, error } = usePresignedUrl(node, getPresignedUrl, {
13
13
  enabled: active,
@@ -31,7 +31,7 @@ function FilePreviewInline({ node, storage, getPresignedUrl, onOpenFull, onDownl
31
31
  className: "flex shrink-0 items-center justify-between gap-3 border-t theme-border px-4 py-2.5",
32
32
  children: [/* @__PURE__ */ jsxs("div", {
33
33
  className: "flex items-center gap-2 text-xs theme-muted-text",
34
- children: [/* @__PURE__ */ jsxs("span", { children: [typeof node.size === "number" && formatFileSize(node.size), node.contentType && ` · ${node.contentType}`] }), showSecurityIndicator && url && /* @__PURE__ */ jsx(StreamedBadge, {})]
34
+ children: [/* @__PURE__ */ jsxs("span", { children: [typeof node.size === "number" && formatFileSize(node.size), node.contentType && ` · ${node.contentType}`] }), url && /* @__PURE__ */ jsx(StreamedBadge, {})]
35
35
  }), /* @__PURE__ */ jsxs("button", {
36
36
  type: "button",
37
37
  className: "btn btn-neutral cursor-pointer",