@parallelworks/ui 0.19.0 → 0.20.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 (233) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  3. package/dist/ai/adapter/mock/index.js +2 -80
  4. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  5. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  6. package/dist/ai/adapter/openai/errors.js +37 -0
  7. package/dist/ai/adapter/openai/index.js +5 -333
  8. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  9. package/dist/ai/adapter/openai/responses.js +108 -0
  10. package/dist/ai/adapter/openai/sse.js +112 -0
  11. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  12. package/dist/ai/agent/classifier.js +12 -0
  13. package/dist/ai/agent/diff.js +223 -0
  14. package/dist/ai/agent/diffHighlight.js +101 -0
  15. package/dist/ai/agent/permissions.js +29 -0
  16. package/dist/ai/agent/toolfmt.js +383 -0
  17. package/dist/ai/agent/transcript.js +127 -0
  18. package/dist/ai/components/AllocationSelector.js +56 -0
  19. package/dist/ai/components/AttachmentManager.js +147 -0
  20. package/dist/ai/components/AttachmentUpload.js +179 -0
  21. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  22. package/dist/ai/components/BranchNavigator.js +57 -0
  23. package/dist/ai/components/ChatEmptyState.js +171 -0
  24. package/dist/ai/components/ChatInput.js +265 -0
  25. package/dist/ai/components/ChatLayout.js +15 -0
  26. package/dist/ai/components/ChatMessage.js +341 -0
  27. package/dist/ai/components/ChatMessageList.js +301 -0
  28. package/dist/ai/components/ChatSidebar.js +345 -0
  29. package/dist/ai/components/ChatThread.js +198 -0
  30. package/dist/ai/components/ComposerChrome.js +50 -0
  31. package/dist/ai/components/ComposerFrame.js +33 -0
  32. package/dist/ai/components/DragOverlay.js +20 -0
  33. package/dist/ai/components/ModelSelector.js +343 -0
  34. package/dist/ai/components/ShareDialog.js +260 -0
  35. package/dist/ai/components/SlashMenu.js +91 -0
  36. package/dist/ai/components/agent/ActivityLine.js +151 -0
  37. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  38. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  39. package/dist/ai/components/attachmentKeys.js +10 -0
  40. package/dist/ai/core/ChatProvider.js +848 -0
  41. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  42. package/dist/ai/core/chatReducer.js +223 -0
  43. package/dist/ai/core/config.js +46 -0
  44. package/dist/ai/core/events.js +4 -0
  45. package/dist/ai/core/exportConversation.js +42 -0
  46. package/dist/ai/core/greeting.js +38 -0
  47. package/dist/ai/core/parts.js +84 -0
  48. package/dist/ai/core/useDragDrop.js +36 -0
  49. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  50. package/dist/ai/core/useSSEStream.js +98 -0
  51. package/dist/ai/index.js +43 -6644
  52. package/dist/ai/strings.js +175 -0
  53. package/dist/ai/ui/Avatar.js +35 -0
  54. package/dist/ai/ui/Dropdown.js +110 -0
  55. package/dist/ai/ui/Markdown.js +79 -0
  56. package/dist/ai/ui/Notice.js +77 -0
  57. package/dist/ai/ui/codeBreaks.js +36 -0
  58. package/dist/ai/ui/prose.js +13 -0
  59. package/dist/ai/ui/sidebar.js +148 -0
  60. package/dist/ai/utils.js +23 -0
  61. package/dist/assets/editor.worker-DfmPPyOL.js +60 -0
  62. package/dist/assets/json.worker-Dqgjpq9F.js +92 -0
  63. package/dist/assets/yaml.worker-DLx0whrM.js +1441 -0
  64. package/dist/components/AddMenu.js +46 -0
  65. package/dist/components/Avatar.js +46 -0
  66. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  67. package/dist/components/Breadcrumbs.js +83 -0
  68. package/dist/components/Button.js +83 -0
  69. package/dist/components/Callout.js +23 -0
  70. package/dist/components/Card.js +31 -0
  71. package/dist/components/CollapsiblePanel.js +77 -0
  72. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  73. package/dist/components/CopyToClipboard.js +81 -0
  74. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  75. package/dist/components/DescriptionList.js +42 -0
  76. package/dist/components/Dropdown.js +349 -0
  77. package/dist/components/EmptyState.js +27 -0
  78. package/dist/components/Filter.js +32 -0
  79. package/dist/components/FilterPill.js +13 -0
  80. package/dist/components/IconButton.js +34 -0
  81. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  82. package/dist/components/Input.js +88 -0
  83. package/dist/components/LetterBadge.js +13 -0
  84. package/dist/components/Listbox.js +123 -0
  85. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  86. package/dist/components/Markdown.js +15 -0
  87. package/dist/components/MultiSelect.js +171 -0
  88. package/dist/components/Overview.js +39 -0
  89. package/dist/components/PageHeader.js +194 -0
  90. package/dist/components/Pagination.js +35 -0
  91. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +13 -5
  92. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  93. package/dist/components/SectionHeader.js +11 -0
  94. package/dist/components/SettingModeOption.js +30 -0
  95. package/dist/components/SettingRow.js +35 -0
  96. package/dist/components/SettingsCard.js +31 -0
  97. package/dist/components/SettingsGroup.js +17 -0
  98. package/dist/components/Sort.js +42 -0
  99. package/dist/components/StatusBadge.js +63 -0
  100. package/dist/components/SwitchToggle.js +59 -0
  101. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  102. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  103. package/dist/components/Toggle.js +20 -0
  104. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  105. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  106. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  107. package/dist/components/ghostButton.js +7 -0
  108. package/dist/components/inputClasses.js +4 -0
  109. package/dist/components/keys.js +27 -0
  110. package/dist/components/relativeTime.js +32 -0
  111. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  112. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  113. package/dist/editor/Editor.js +13 -0
  114. package/dist/editor/EditorField.js +53 -0
  115. package/dist/editor/FormCodePanel.js +173 -0
  116. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +5 -26
  117. package/dist/editor/index.js +6 -1
  118. package/dist/editor/themes.js +91 -0
  119. package/dist/editor/workers.d.ts +5 -0
  120. package/dist/editor/workers.js +35 -0
  121. package/dist/editor/yaml.d.ts +9 -1
  122. package/dist/editor/yaml.js +15 -0
  123. package/dist/editor/yaml.worker.d.ts +1 -0
  124. package/dist/editor/yamlLanguage.d.ts +6 -0
  125. package/dist/editor/yamlLanguage.js +312 -0
  126. package/dist/file-explorer/FileExplorer.js +1405 -0
  127. package/dist/file-explorer/FileExplorerContext.js +370 -0
  128. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  129. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  130. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  131. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  132. package/dist/file-explorer/FilePreview/index.js +266 -0
  133. package/dist/file-explorer/FilePreview/parse.js +63 -0
  134. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  135. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  136. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  137. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  138. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  139. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  140. package/dist/file-explorer/FileRow.js +165 -0
  141. package/dist/file-explorer/FileRowsWindow.js +92 -0
  142. package/dist/file-explorer/FileTable.js +172 -0
  143. package/dist/file-explorer/FileTableHeaders.js +49 -0
  144. package/dist/file-explorer/LoadMoreRow.js +46 -0
  145. package/dist/file-explorer/TreeView.js +376 -0
  146. package/dist/file-explorer/helpers.js +537 -0
  147. package/dist/file-explorer/index.js +7 -5671
  148. package/dist/file-explorer/lib/batch.js +39 -0
  149. package/dist/file-explorer/lib/errors.js +9 -0
  150. package/dist/file-explorer/lib/paging.js +124 -0
  151. package/dist/file-explorer/lib/utils.js +322 -0
  152. package/dist/file-explorer/toasts.js +400 -0
  153. package/dist/file-explorer/treeRows.js +73 -0
  154. package/dist/file-explorer/useNearEnd.js +27 -0
  155. package/dist/form/FieldWrapper.js +54 -0
  156. package/dist/form/Fields/BooleanField.js +51 -0
  157. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  158. package/dist/form/Fields/ColorField.js +33 -0
  159. package/dist/form/Fields/DropdownField.js +49 -0
  160. package/dist/form/Fields/DurationField.js +130 -0
  161. package/dist/form/Fields/LabelField.js +19 -0
  162. package/dist/form/Fields/MultiDropdownField.js +40 -0
  163. package/dist/form/Fields/NumberField.js +51 -0
  164. package/dist/form/Fields/ObjectField.js +54 -0
  165. package/dist/form/Fields/RadioField.js +49 -0
  166. package/dist/form/Fields/RangeField.js +58 -0
  167. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  168. package/dist/form/Fields/StepField.js +6 -0
  169. package/dist/form/Fields/StringField.js +37 -0
  170. package/dist/form/Fields/TextAreaField.js +31 -0
  171. package/dist/form/Fields/durationValidity.js +11 -0
  172. package/dist/form/Form.js +643 -0
  173. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  174. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  175. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  176. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  177. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  178. package/dist/form/Wizard/WizardContainer.js +138 -0
  179. package/dist/form/Wizard/WizardNavigation.js +92 -0
  180. package/dist/form/Wizard/WizardStepContent.js +35 -0
  181. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  182. package/dist/form/Wizard/useWizardState.js +118 -0
  183. package/dist/form/Wizard/utils.js +34 -0
  184. package/dist/form/fieldContext.js +26 -0
  185. package/dist/form/fieldRegistry.js +50 -0
  186. package/dist/form/index.js +17 -2321
  187. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  188. package/dist/form/useParsedOpts.js +62 -0
  189. package/dist/form/usePrevious.js +11 -0
  190. package/dist/form/utils/checkShowIfObject.js +14 -0
  191. package/dist/form/utils/duration.js +69 -0
  192. package/dist/form/utils/getParentValue.js +23 -0
  193. package/dist/form/utils/getPlaceholder.js +9 -0
  194. package/dist/form/utils/getValueUsingPath.js +23 -0
  195. package/dist/form/utils/secondaryField.js +22 -0
  196. package/dist/graph/AnnotationBanner.js +141 -0
  197. package/dist/graph/Collapse.js +41 -0
  198. package/dist/graph/DependencyGraph.js +1398 -0
  199. package/dist/graph/JobSummary.js +104 -0
  200. package/dist/graph/MatrixGroup.js +107 -0
  201. package/dist/graph/Reveal.js +57 -0
  202. package/dist/graph/TreeView.js +711 -0
  203. package/dist/graph/index.js +1 -2519
  204. package/dist/graph/types.js +6 -0
  205. package/dist/icons.d.ts +4 -4
  206. package/dist/icons.js +5 -5
  207. package/dist/index.js +46 -599
  208. package/dist/list/ActionToolbar.js +184 -0
  209. package/dist/list/HoverCard.js +73 -0
  210. package/dist/list/ListPage.js +145 -0
  211. package/dist/list/ListPager.js +59 -0
  212. package/dist/list/ListTable.js +163 -0
  213. package/dist/list/ListViewControls.js +372 -0
  214. package/dist/list/RowContextMenu.js +351 -0
  215. package/dist/list/UserHoverCard.js +117 -0
  216. package/dist/list/index.js +11 -2
  217. package/dist/list/listSearch.js +137 -0
  218. package/dist/list/listView.js +210 -0
  219. package/dist/list/textEntry.js +10 -0
  220. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  221. package/dist/logviewer/index.js +2 -1
  222. package/dist/logviewer/segments.js +80 -0
  223. package/dist/safeUrl.d.ts +6 -0
  224. package/dist/safeUrl.js +22 -0
  225. package/package.json +7 -9
  226. package/dist/assets/editor.worker-BmHUIVG4.js +0 -14807
  227. package/dist/assets/json.worker-D8bJ92nw.js +0 -22207
  228. package/dist/assets/yaml.worker-C4gyGAko.js +0 -50201
  229. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  230. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  231. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  232. package/dist/chunks/list-CJm875m7.js +0 -1418
  233. /package/dist/{chunks/MarkdownImpl-CyeTVvSt.js → components/MarkdownImpl.js} +0 -0
@@ -0,0 +1,1405 @@
1
+ import { safeUrl } from "../safeUrl.js";
2
+ import { useNotify, useSlots, useStrings } from "../components/Provider.js";
3
+ import { AccessIcon, AlertIcon, AngleDownIcon, ChevronRightIcon, ClipboardIcon, CollapseIcon, DownloadFileIcon, EyeIcon, FileIcon, FileTextIcon, FolderIcon, LinkIcon, RefreshIcon, ShareIcon, TrashIcon, UploadIcon } from "../icons.js";
4
+ import { keyedByContent } from "../components/keys.js";
5
+ import { TOOLTIP_ID, TooltipInfo } from "../components/Tooltip.js";
6
+ import { ConfirmModal } from "../components/ConfirmModal.js";
7
+ import CopyToClipboard, { CopyCodeBlock } from "../components/CopyToClipboard.js";
8
+ import { CreateModal } from "../components/CreateModal.js";
9
+ import Loader from "../components/Loader.js";
10
+ import { assembleRowMenu, useCopySubmenu, useRowMenu } from "../list/RowContextMenu.js";
11
+ import { StatusBadge } from "../components/StatusBadge.js";
12
+ import { Table } from "../components/Table.js";
13
+ import { ListActionsHeader, ListSkeleton, listTableFixedProps, listTableProps } from "../list/ListTable.js";
14
+ import { calculateUploadNodesTotalSize, createTreeBuilder, formatFileSize, getDirectoryNameError, getDirectoryObjectPath, getNodeChildren, getObjectKeyFromNode, getParentPath, getUploadNodesFromDataTransferItems, getUriScheme, pruneNodesInsideSelectedDirectories, removeLeadingAndTrailingSlashes } from "./lib/utils.js";
15
+ import { useFileExplorer } from "./FileExplorerContext.js";
16
+ import { PREVIEW_URL_EXPIRES_IN, canPreview } from "./FilePreview/previewType.js";
17
+ import { CorsError } from "./lib/errors.js";
18
+ import { clearPreviewCache } from "./FilePreview/previewCache.js";
19
+ import { DragDropZone, NoDataAvailablePreview, NoFileSelectedPreview, NotFoundPreview, SIDEBAR_WIDTH_STORAGE_KEY, ShareFileButton, getNodeIcon, renderUploadNodes } from "./helpers.js";
20
+ import { FilePreview } from "./FilePreview/index.js";
21
+ import { FilePreviewInline } from "./FilePreview/Inline.js";
22
+ import { UserRow } from "./FileRow.js";
23
+ import { FileTableHeaders } from "./FileTableHeaders.js";
24
+ import { FileTable } from "./FileTable.js";
25
+ import { advanceListing, beginLoadingMore, canAutoLoadMore, clearListingError, clearListingsUnder, hasMore, incompletePaths, isListed, isLoadingMore, loadMoreError, nextHydrationStep, recordListingError, replaceListing } from "./lib/paging.js";
26
+ import { TreeView } from "./TreeView.js";
27
+ import { Menu, MenuButton, MenuItem, MenuItems, MenuSeparator } from "@headlessui/react";
28
+ import cx from "classnames";
29
+ import { useCallback, useEffect, useEffectEvent, useLayoutEffect, useMemo, useRef, useState } from "react";
30
+ import { useLocalStorage } from "usehooks-ts";
31
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
32
+ import { DateTime } from "luxon";
33
+ import { Resizable } from "re-resizable";
34
+ //#region src/file-explorer/FileExplorer.tsx
35
+ function UriCard({ value }) {
36
+ const t = useStrings().fileExplorer;
37
+ const scheme = getUriScheme(value);
38
+ return /* @__PURE__ */ jsxs(CopyToClipboard, {
39
+ text: value,
40
+ showIcon: false,
41
+ className: "group flex items-stretch rounded-lg border theme-border bg-(--theme-input-bg) overflow-hidden cursor-copy hover:border-(--theme-link)/50 transition-colors duration-150",
42
+ children: [
43
+ scheme && /* @__PURE__ */ jsx("span", {
44
+ className: "shrink-0 inline-flex items-center px-3 bg-(--theme-muted-panel-bg) border-r theme-border font-mono text-[11px] font-semibold uppercase tracking-widest theme-muted-text",
45
+ children: scheme
46
+ }),
47
+ /* @__PURE__ */ jsx("span", {
48
+ className: "flex-1 min-w-0 inline-flex items-center px-3 py-2 font-mono text-[12.5px] leading-snug break-all text-(--theme-input)",
49
+ children: value
50
+ }),
51
+ /* @__PURE__ */ jsx("span", {
52
+ className: "shrink-0 inline-flex items-center justify-center px-3 border-l theme-border theme-muted-text group-hover:text-(--theme-link) group-hover:bg-(--theme-link)/5 transition-colors duration-150",
53
+ title: t.chrome.copyToClipboard,
54
+ children: /* @__PURE__ */ jsx(ClipboardIcon, { className: "h-3.5 w-3.5" })
55
+ })
56
+ ]
57
+ });
58
+ }
59
+ function isStorageDirectory(node) {
60
+ return node?.type === "directory" && !!node.storageId;
61
+ }
62
+ function getErrorMessage(error) {
63
+ return error instanceof Error ? error.message : String(error);
64
+ }
65
+ /** Directory paths from the storage root down to `path`. */
66
+ function getAncestorDirPaths(path) {
67
+ const isDir = path.endsWith("/");
68
+ const segments = (isDir ? path.slice(0, -1) : path).split("/").filter(Boolean);
69
+ const dirSegmentCount = isDir ? segments.length : segments.length - 1;
70
+ const ancestors = [];
71
+ for (let i = 1; i <= dirSegmentCount; i++) ancestors.push(segments.slice(0, i).join("/") + "/");
72
+ return ancestors;
73
+ }
74
+ /** The tree's width is the host's when the tree is embedded on its own, and this
75
+ * component's when it is showing the details pane beside it. */
76
+ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
77
+ if (treeOnly) return /* @__PURE__ */ jsx("div", {
78
+ className: "h-full w-full min-w-0",
79
+ children
80
+ });
81
+ return /* @__PURE__ */ jsx(Resizable, {
82
+ size: {
83
+ width: sidebarWidth,
84
+ height: "100%"
85
+ },
86
+ minWidth: 200,
87
+ maxWidth: 800,
88
+ enable: {
89
+ top: false,
90
+ right: true,
91
+ bottom: false,
92
+ left: false,
93
+ topRight: false,
94
+ bottomRight: false,
95
+ bottomLeft: false,
96
+ topLeft: false
97
+ },
98
+ onResizeStop: (_e, _direction, _ref, d) => {
99
+ onWidthChange((prev) => prev + d.width);
100
+ },
101
+ children
102
+ });
103
+ }
104
+ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand }) {
105
+ const copySubmenu = useCopySubmenu();
106
+ const t = useStrings().fileExplorer;
107
+ const notify = useNotify();
108
+ const slots = useSlots();
109
+ const fileExplorerContext = useFileExplorer();
110
+ const [listings, setListings] = useState(() => /* @__PURE__ */ new Map());
111
+ const fetchesInFlightRef = useRef(/* @__PURE__ */ new Map());
112
+ const incompleteDirsRef = useRef(/* @__PURE__ */ new Set());
113
+ const incompleteDirs = useMemo(() => {
114
+ const next = incompletePaths(listings);
115
+ const previous = incompleteDirsRef.current;
116
+ if (previous.size === next.size && [...next].every((p) => previous.has(p))) return previous;
117
+ incompleteDirsRef.current = next;
118
+ return next;
119
+ }, [listings]);
120
+ const [buildTree] = useState(createTreeBuilder);
121
+ const { treeMap, parentToChildrenMap, rootNodes } = useMemo(() => buildTree(storages, {
122
+ listings,
123
+ forceUserHierarchy: showUserHierarchy,
124
+ unsortedParents: incompleteDirs
125
+ }), [
126
+ buildTree,
127
+ storages,
128
+ listings,
129
+ showUserHierarchy,
130
+ incompleteDirs
131
+ ]);
132
+ const [loadingPaths, setLoadingPaths] = useState(/* @__PURE__ */ new Set());
133
+ const normalizedSelectedPath = useMemo(() => {
134
+ if (!selectedPath) return "";
135
+ if (selectedPath.endsWith("/")) return selectedPath;
136
+ if (treeMap[selectedPath]) return selectedPath;
137
+ return `${selectedPath}/`;
138
+ }, [selectedPath, treeMap]);
139
+ const setSelectedPath = onPathChange;
140
+ const [expandedPaths, setExpandedPaths] = useState(() => /* @__PURE__ */ new Set(["", ...initialExpandedPaths ?? []]));
141
+ const onExpandedPathsChangeRef = useRef(onExpandedPathsChange);
142
+ onExpandedPathsChangeRef.current = onExpandedPathsChange;
143
+ useEffect(() => {
144
+ onExpandedPathsChangeRef.current?.([...expandedPaths]);
145
+ }, [expandedPaths]);
146
+ const selectedNode = useMemo(() => normalizedSelectedPath ? treeMap[normalizedSelectedPath] ?? null : null, [normalizedSelectedPath, treeMap]);
147
+ const seenRefreshKey = useRef(refreshKey);
148
+ useEffect(() => {
149
+ if (refreshKey === seenRefreshKey.current) return;
150
+ seenRefreshKey.current = refreshKey;
151
+ if (selectedNode?.type === "directory" && selectedNode.storageId) fetchNodeChildren(selectedNode, { replace: true });
152
+ }, [refreshKey, selectedNode]);
153
+ const [checkedItems, setCheckedItems] = useState(/* @__PURE__ */ new Set());
154
+ const [checkedItemsPath, setCheckedItemsPath] = useState(selectedPath);
155
+ if (checkedItemsPath !== selectedPath) {
156
+ setCheckedItemsPath(selectedPath);
157
+ setCheckedItems(/* @__PURE__ */ new Set());
158
+ }
159
+ 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);
163
+ const selectedNodeCanWrite = selectedStorage?.canWrite === true;
164
+ const selectedNodeCanUpload = selectedStorage?.canUpload !== false;
165
+ const selectedNodeCanShare = selectedStorage?.canShare !== false;
166
+ const selectedNodeCanManageAccess = selectedStorage?.canManageAccess !== false;
167
+ const [accessDrawerOpen, setAccessDrawerOpen] = useState(false);
168
+ const [connectionIssuesMessage, updateConnectionIssuesMessage] = useState(/* @__PURE__ */ new Map());
169
+ const [hasCorsIssue, setHasCorsIssue] = useState(/* @__PURE__ */ new Map());
170
+ const [uploadModalOpen, setUploadModalOpen] = useState(false);
171
+ const [createFolderTarget, setCreateFolderTarget] = useState(null);
172
+ const [pendingUpload, setPendingUpload] = useState(null);
173
+ const [waitForUpload, setWaitForUpload] = useState(false);
174
+ const fileInputRef = useRef(null);
175
+ const folderInputRef = useRef(null);
176
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
177
+ const [pendingDelete, setPendingDelete] = useState(null);
178
+ const [previewNode, setPreviewNode] = useState(null);
179
+ const [previewOpen, setPreviewOpen] = useState(false);
180
+ const [detailTab, setDetailTab] = useState("details");
181
+ useEffect(() => clearPreviewCache, []);
182
+ const { openMenu, contextMenu } = useRowMenu();
183
+ 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
+ const handleToggleExpand = useCallback((path) => {
189
+ setExpandedPaths((prev) => {
190
+ const next = new Set(prev);
191
+ if (!next.delete(path)) next.add(path);
192
+ return next;
193
+ });
194
+ }, []);
195
+ const collapseAll = useCallback(() => {
196
+ setExpandedPaths(/* @__PURE__ */ new Set([""]));
197
+ }, []);
198
+ const getStorageProviderAndClient = async (storageId) => {
199
+ const storage = storages.find((s) => s.id === storageId);
200
+ if (!storage) {
201
+ notify.error(t.messages.storageUnavailable);
202
+ return;
203
+ }
204
+ const { provider, client } = await getProviderAndClient(storage).catch(() => ({
205
+ provider: null,
206
+ client: null
207
+ }));
208
+ if (!provider || !client) {
209
+ const message = t.messages.connectFailed;
210
+ updateConnectionIssuesMessage((prev) => new Map(prev).set(storage.id, message));
211
+ return null;
212
+ }
213
+ updateConnectionIssuesMessage((prev) => {
214
+ const next = new Map(prev);
215
+ next.delete(storage.id);
216
+ return next;
217
+ });
218
+ return {
219
+ storage,
220
+ provider,
221
+ client
222
+ };
223
+ };
224
+ const fetchNodeChildren = useEffectEvent(async (node, options) => {
225
+ const more = options?.more ?? false;
226
+ const silent = !more && (options?.silent ?? false);
227
+ const nodeStorageId = node.storageId ?? "";
228
+ if (!options?.replace && fetchesInFlightRef.current.has(node.path)) return { corsError: false };
229
+ const request = silent ? void 0 : {};
230
+ if (request) fetchesInFlightRef.current.set(node.path, request);
231
+ const owns = () => request !== void 0 && fetchesInFlightRef.current.get(node.path) === request;
232
+ if (more) setListings((prev) => beginLoadingMore(prev, node.path));
233
+ else if (request) setLoadingPaths((prev) => new Set(prev).add(node.path));
234
+ const cursor = more ? listings.get(node.path)?.cursor : void 0;
235
+ try {
236
+ const factory = await getStorageProviderAndClient(node.storageId);
237
+ if (!factory) throw new Error("Failed to connect to storage provider.");
238
+ const { storage, provider, client } = factory;
239
+ if (provider?.listDirectoryInput && provider?.convertDataToStorageObjects) {
240
+ const dir_path = node.root ? "" : node.relativePath ?? "";
241
+ const input = provider.listDirectoryInput(storage, dir_path, cursor ? { cursor } : {});
242
+ const objects = await client.listDirectory(input);
243
+ const entries = provider.convertDataToStorageObjects(objects);
244
+ const nextCursor = provider.nextPageCursor?.(objects);
245
+ if (owns()) {
246
+ const page = {
247
+ entries,
248
+ cursor: nextCursor
249
+ };
250
+ setListings((prev) => options?.replace ? replaceListing(prev, node.path, page) : advanceListing(prev, node.path, page));
251
+ }
252
+ }
253
+ if (owns()) setHasCorsIssue((prev) => {
254
+ if (!prev.has(nodeStorageId)) return prev;
255
+ const next = new Map(prev);
256
+ next.delete(nodeStorageId);
257
+ return next;
258
+ });
259
+ return { corsError: false };
260
+ } catch (error) {
261
+ const isCorsError = error instanceof CorsError;
262
+ const errorMessage = isCorsError ? error.message || "CORS error" : error instanceof Error ? error.message : "Unknown error";
263
+ if (owns()) {
264
+ 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
+ if (isCorsError) setHasCorsIssue((prev) => new Map(prev).set(nodeStorageId, true));
267
+ }
268
+ return { corsError: isCorsError };
269
+ } finally {
270
+ if (owns()) {
271
+ fetchesInFlightRef.current.delete(node.path);
272
+ if (!more) setLoadingPaths((prev) => {
273
+ const next = new Set(prev);
274
+ next.delete(node.path);
275
+ return next;
276
+ });
277
+ }
278
+ }
279
+ });
280
+ const handleSelectTreeItem = async (path, node) => {
281
+ if (normalizedSelectedPath !== path) {
282
+ setCheckedItems(/* @__PURE__ */ new Set());
283
+ setDetailTab("details");
284
+ }
285
+ setSelectedPath(path);
286
+ if (node.type === "directory" && node?.storageId && !isListed(listings, path)) await fetchNodeChildren(node);
287
+ };
288
+ useEffect(() => {
289
+ if (!normalizedSelectedPath) return;
290
+ const ancestors = getAncestorDirPaths(normalizedSelectedPath);
291
+ if (ancestors.length === 0) return;
292
+ setExpandedPaths((prev) => {
293
+ if (ancestors.every((p) => prev.has(p))) return prev;
294
+ const next = new Set(prev);
295
+ for (const p of ancestors) next.add(p);
296
+ return next;
297
+ });
298
+ }, [normalizedSelectedPath]);
299
+ const [stuck, setStuck] = useState(null);
300
+ const hydrationStuck = stuck?.target === normalizedSelectedPath;
301
+ const hydrationPagesRef = useRef({
302
+ path: "",
303
+ pages: 0
304
+ });
305
+ useLayoutEffect(() => {
306
+ if (!normalizedSelectedPath) return;
307
+ if (hydrationPagesRef.current.path !== normalizedSelectedPath) hydrationPagesRef.current = {
308
+ path: normalizedSelectedPath,
309
+ pages: 0
310
+ };
311
+ const step = nextHydrationStep({
312
+ targetResolved: !!treeMap[normalizedSelectedPath],
313
+ ancestors: getAncestorDirPaths(normalizedSelectedPath),
314
+ isListableDir: (path) => {
315
+ const node = treeMap[path];
316
+ return !!node && node.type === "directory" && !!node.storageId;
317
+ },
318
+ listings,
319
+ inFlight: fetchesInFlightRef.current,
320
+ pagesPulled: hydrationPagesRef.current.pages
321
+ });
322
+ if (step.kind === "stuck") {
323
+ setStuck({
324
+ target: normalizedSelectedPath,
325
+ deepest: step.path
326
+ });
327
+ return;
328
+ }
329
+ if (step.kind === "done") return;
330
+ const node = treeMap[step.path];
331
+ if (!node) return;
332
+ if (step.kind === "fetch") {
333
+ fetchNodeChildren(node);
334
+ return;
335
+ }
336
+ hydrationPagesRef.current.pages += 1;
337
+ fetchNodeChildren(node, { more: true });
338
+ }, [
339
+ normalizedSelectedPath,
340
+ treeMap,
341
+ listings
342
+ ]);
343
+ useEffect(() => {
344
+ for (const path of expandedPaths) {
345
+ const node = treeMap[path];
346
+ if (node?.type === "directory" && node.storageId && !listings.has(path) && !fetchesInFlightRef.current.has(path)) {
347
+ fetchNodeChildren(node);
348
+ return;
349
+ }
350
+ }
351
+ }, [
352
+ expandedPaths,
353
+ treeMap,
354
+ listings
355
+ ]);
356
+ const handleBatchCheckbox = () => {
357
+ if (selectedNode?.type !== "directory") return;
358
+ const selectAll = selectedInFolderCount < selectedNodeChildren.length;
359
+ const newCheckedItems = new Set(checkedItems);
360
+ for (const item of selectedNodeChildren) if (selectAll) newCheckedItems.add(item.path);
361
+ else newCheckedItems.delete(item.path);
362
+ setCheckedItems(newCheckedItems);
363
+ };
364
+ const handleItemCheckbox = (node, isChecked) => {
365
+ setCheckedItems((prev) => {
366
+ const newCheckedItems = new Set(prev);
367
+ if (isChecked) newCheckedItems.add(node.path);
368
+ else newCheckedItems.delete(node.path);
369
+ return newCheckedItems;
370
+ });
371
+ };
372
+ const selectedInFolderCount = useMemo(() => selectedNodeChildren.reduce((count, item) => checkedItems.has(item.path) ? count + 1 : count, 0), [selectedNodeChildren, checkedItems]);
373
+ const folderHasMore = selectedNode ? hasMore(listings, selectedNode.path) : false;
374
+ const allItemsSelected = selectedNodeChildren.length > 0 && selectedInFolderCount === selectedNodeChildren.length && !folderHasMore;
375
+ const loadMoreProps = selectedNode && folderHasMore ? {
376
+ loading: isLoadingMore(listings, selectedNode.path),
377
+ error: loadMoreError(listings, selectedNode.path),
378
+ auto: canAutoLoadMore(listings, selectedNode.path),
379
+ onLoadMore: () => {
380
+ fetchNodeChildren(selectedNode, { more: true });
381
+ }
382
+ } : void 0;
383
+ const treePaging = {
384
+ incompletePaths: incompleteDirs,
385
+ isLoading: (path) => isLoadingMore(listings, path),
386
+ hasFailed: (path) => !!loadMoreError(listings, path),
387
+ onLoadMore: (node) => {
388
+ fetchNodeChildren(node, { more: true });
389
+ }
390
+ };
391
+ const onDropFiles = async (targetPath, files) => {
392
+ const targetStorageId = treeMap[targetPath]?.storageId;
393
+ if (!storageCanWrite(targetStorageId)) {
394
+ notify.error(t.messages.noWriteAccess);
395
+ return;
396
+ }
397
+ if (!storageCanUpload(targetStorageId)) {
398
+ notify.error(t.messages.uploadsUnsupported);
399
+ return;
400
+ }
401
+ let uploadNodes = [];
402
+ let totalSize = 0;
403
+ if (waitForUpload) {
404
+ notify.warning(t.messages.waitForCurrentUpload);
405
+ return;
406
+ }
407
+ setWaitForUpload(true);
408
+ if (files instanceof DataTransferItemList && files.length && files[0] && typeof files[0].webkitGetAsEntry === "function") {
409
+ const prepareData = await getUploadNodesFromDataTransferItems(files);
410
+ uploadNodes = prepareData.uploadNodes;
411
+ totalSize = prepareData.totalSize;
412
+ } else {
413
+ uploadNodes = Array.from(files).map((file) => ({
414
+ name: file.name,
415
+ file,
416
+ relativePath: file.name
417
+ }));
418
+ totalSize = calculateUploadNodesTotalSize(uploadNodes);
419
+ }
420
+ setPendingUpload({
421
+ targetPath,
422
+ uploadNodes,
423
+ totalSize
424
+ });
425
+ setUploadModalOpen(true);
426
+ setWaitForUpload(false);
427
+ };
428
+ const hasFolderSelected = () => {
429
+ if (checkedItems.size > 0) return Array.from(checkedItems).map((path) => treeMap[path]).some((node) => node && node.type === "directory");
430
+ if (selectedNode && selectedNode.type === "directory") return true;
431
+ return false;
432
+ };
433
+ 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) {
438
+ notify.error(t.messages.noFilesForDownload);
439
+ return;
440
+ }
441
+ const factory = await getStorageProviderAndClient(filesToDownload[0].storageId);
442
+ if (!factory) return;
443
+ const { provider, client } = factory;
444
+ if (!provider?.getFileInput) {
445
+ notify.error(t.messages.downloadUnsupported);
446
+ return;
447
+ }
448
+ for (const [index, node] of filesToDownload.entries()) {
449
+ const input = provider.getFileInput(node.storageName ?? "", node, void 0, node.name);
450
+ const [presignedUrl, error] = await client.getFile(input);
451
+ if (error || !presignedUrl) {
452
+ notify.error(error?.message ?? t.messages.downloadLinkFailed);
453
+ continue;
454
+ }
455
+ triggerBrowserDownload(presignedUrl, node.name);
456
+ if (index < filesToDownload.length - 1) await new Promise((resolve) => setTimeout(resolve, 150));
457
+ }
458
+ };
459
+ const onShareFileButtonClick = async (node) => {
460
+ const factory = await getStorageProviderAndClient(node.storageId);
461
+ if (!factory) return;
462
+ const { provider, client } = factory;
463
+ if (!provider?.getFileInput) {
464
+ notify.error(t.messages.sharingUnsupported);
465
+ return;
466
+ }
467
+ const input = provider.getFileInput(node.storageName ?? "", node);
468
+ const [presignedUrl, error] = await client.getFile(input);
469
+ if (error || !presignedUrl) {
470
+ notify.error(error?.message ?? t.messages.fileLinkFailed);
471
+ return;
472
+ }
473
+ await navigator.clipboard.writeText(presignedUrl);
474
+ notify.info(t.messages.fileLinkCopied);
475
+ };
476
+ const triggerBrowserDownload = (url, filename) => {
477
+ const href = safeUrl(url);
478
+ if (!href) return;
479
+ const a = document.createElement("a");
480
+ a.href = href;
481
+ a.download = filename;
482
+ document.body.appendChild(a);
483
+ a.click();
484
+ setTimeout(() => {
485
+ document.body.removeChild(a);
486
+ }, 300);
487
+ };
488
+ const getPresignedUrl = async (node, downloadFilename) => {
489
+ const factory = await getStorageProviderAndClient(node.storageId);
490
+ if (!factory) return [null, /* @__PURE__ */ new Error("Failed to connect to storage provider.")];
491
+ const { provider, client } = factory;
492
+ if (!provider?.getFileInput) return [null, /* @__PURE__ */ new Error("Preview is not supported for this storage.")];
493
+ const input = provider.getFileInput(node.storageName ?? "", node, PREVIEW_URL_EXPIRES_IN, downloadFilename);
494
+ return client.getFile(input);
495
+ };
496
+ const previewSiblings = useMemo(() => previewNode ? getNodeChildren(parentToChildrenMap, getParentPath(previewNode.path)).filter((n) => n.type === "file") : [], [previewNode, parentToChildrenMap]);
497
+ const openPreview = (node) => {
498
+ if (node.type !== "file") return;
499
+ if (onOpenFile?.(node, storages.find((s) => s.id === node.storageId))) return;
500
+ setPreviewNode(node);
501
+ setPreviewOpen(true);
502
+ };
503
+ const closePreview = () => {
504
+ setPreviewOpen(false);
505
+ };
506
+ const openUserNode = (node) => {
507
+ handleSelectTreeItem(node.path, node);
508
+ if (!expandedPaths.has(node.path)) handleToggleExpand(node.path);
509
+ };
510
+ const getNodeUris = (node) => {
511
+ const storage = storages.find((s) => s.id === node.storageId);
512
+ if (!storage || !objectUris) return [];
513
+ return objectUris(storage, getObjectKeyFromNode(node));
514
+ };
515
+ const copyNodeUri = async (node) => {
516
+ const uri = getNodeUris(node)[0];
517
+ if (!uri) {
518
+ notify.error(t.messages.noUri);
519
+ return;
520
+ }
521
+ try {
522
+ await navigator.clipboard.writeText(uri);
523
+ notify.info(t.messages.uriCopied);
524
+ } catch {
525
+ notify.error(t.messages.uriCopyFailed);
526
+ }
527
+ };
528
+ const downloadNode = async (node) => {
529
+ const [presignedUrl, error] = await getPresignedUrl(node, node.name);
530
+ if (error || !presignedUrl) {
531
+ notify.error(error?.message ?? t.messages.downloadLinkFailed);
532
+ return;
533
+ }
534
+ triggerBrowserDownload(presignedUrl, node.name);
535
+ };
536
+ const deleteNode = (node) => {
537
+ if (storages.find((s) => s.id === node.storageId)?.canWrite !== true) {
538
+ notify.error(t.messages.noWriteAccess);
539
+ return;
540
+ }
541
+ setPendingDelete([node]);
542
+ setDeleteModalOpen(true);
543
+ };
544
+ const openCreateFolder = async (node) => {
545
+ if (!isStorageDirectory(node)) return;
546
+ if (!isListed(listings, node.path)) await fetchNodeChildren(node);
547
+ setCreateFolderTarget(node);
548
+ };
549
+ const getCreateFolderActions = (node, storage) => {
550
+ if (node.type !== "directory" || storage?.canWrite !== true || storage.canUpload === false) return [];
551
+ return [{
552
+ key: "create-folder",
553
+ label: t.createFolder.action,
554
+ icon: /* @__PURE__ */ jsx(FolderIcon, {}),
555
+ onSelect: () => openCreateFolder(node)
556
+ }];
557
+ };
558
+ const buildRowMenu = (node) => {
559
+ const storage = storages.find((s) => s.id === node.storageId);
560
+ const nodeCanWrite = storage?.canWrite === true;
561
+ const nodeCanShare = storage?.canShare !== false;
562
+ const isFile = node.type === "file";
563
+ const uris = getNodeUris(node);
564
+ const actions = [];
565
+ if (isFile && canPreview(node)) actions.push({
566
+ key: "preview",
567
+ label: t.preview.preview,
568
+ icon: /* @__PURE__ */ jsx(EyeIcon, {}),
569
+ onSelect: () => openPreview(node)
570
+ });
571
+ if (isFile) {
572
+ actions.push({
573
+ key: "download",
574
+ label: t.preview.download,
575
+ icon: /* @__PURE__ */ jsx(DownloadFileIcon, {}),
576
+ onSelect: () => downloadNode(node)
577
+ });
578
+ if (nodeCanShare) actions.push({
579
+ key: "share",
580
+ label: t.preview.shareFile,
581
+ icon: /* @__PURE__ */ jsx(ShareIcon, {}),
582
+ onSelect: () => onShareFileButtonClick(node)
583
+ });
584
+ }
585
+ actions.push(...getCreateFolderActions(node, storage));
586
+ if (nodeCanWrite && !node.root) actions.push({
587
+ key: "delete",
588
+ label: t.preview.delete,
589
+ icon: /* @__PURE__ */ jsx(TrashIcon, {}),
590
+ destructive: true,
591
+ onSelect: () => deleteNode(node)
592
+ });
593
+ const lead = [{
594
+ kind: "action",
595
+ label: isFile ? t.preview.viewDetails : t.preview.openFolder,
596
+ icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}),
597
+ onSelect: () => handleSelectTreeItem(node.path, node)
598
+ }];
599
+ const uriExtras = uris.map((value) => ({
600
+ label: `${getUriScheme(value).toUpperCase()} URI`,
601
+ value,
602
+ icon: /* @__PURE__ */ jsx(LinkIcon, {})
603
+ }));
604
+ return assembleRowMenu({
605
+ lead,
606
+ actions,
607
+ copy: uriExtras.length > 0 ? copySubmenu({
608
+ name: isFile ? node.name : void 0,
609
+ extra: uriExtras
610
+ }) : void 0
611
+ });
612
+ };
613
+ const openNodeContextMenu = (e, node) => {
614
+ const items = buildRowMenu(node);
615
+ if (items.length === 0) return;
616
+ e.preventDefault();
617
+ e.stopPropagation();
618
+ openMenu(e.clientX, e.clientY, items);
619
+ };
620
+ const renderBreadcrumb = () => {
621
+ const chain = [];
622
+ let cur = selectedNode ?? void 0;
623
+ const seen = /* @__PURE__ */ new Set();
624
+ while (cur && !seen.has(cur.path)) {
625
+ seen.add(cur.path);
626
+ chain.unshift(cur);
627
+ if (cur.root) break;
628
+ cur = treeMap[getParentPath(cur.path)];
629
+ }
630
+ if (chain.length === 0) return null;
631
+ return /* @__PURE__ */ jsx("nav", {
632
+ className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden text-[13px]",
633
+ children: chain.map((node, i) => {
634
+ const isLast = i === chain.length - 1;
635
+ const label = node.displayName || node.name;
636
+ return /* @__PURE__ */ jsxs("span", {
637
+ className: "flex min-w-0 items-center gap-1",
638
+ children: [i > 0 && /* @__PURE__ */ jsx(ChevronRightIcon, { className: "h-3 w-3 shrink-0 text-(--theme-muted-text-color)" }), isLast ? /* @__PURE__ */ jsx("span", {
639
+ className: "truncate font-semibold text-(--theme-app)",
640
+ children: label
641
+ }) : /* @__PURE__ */ jsx("button", {
642
+ type: "button",
643
+ onClick: () => handleSelectTreeItem(node.path, node),
644
+ className: "max-w-[16rem] shrink-0 cursor-pointer truncate text-(--theme-muted-text-color) transition-colors hover:text-(--theme-app)",
645
+ children: label
646
+ })]
647
+ }, node.path);
648
+ })
649
+ });
650
+ };
651
+ 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 "";
657
+ const objects = filesToDownload.flatMap((node) => {
658
+ const storage = storages.find((s) => s.id === node.storageId);
659
+ return storage ? [{
660
+ storage,
661
+ key: getObjectKeyFromNode(node),
662
+ name: node.name
663
+ }] : [];
664
+ });
665
+ return downloadCommand.build(objects);
666
+ };
667
+ const onDeleteClick = () => {
668
+ if (!selectedNodeCanWrite) {
669
+ notify.error(t.messages.noWriteAccess);
670
+ return;
671
+ }
672
+ let nodesToDelete = [];
673
+ if (checkedItems.size > 0) nodesToDelete = Array.from(checkedItems).map((path) => treeMap[path]).filter((node) => Boolean(node));
674
+ else if (selectedNode) nodesToDelete = [selectedNode];
675
+ setPendingDelete(pruneNodesInsideSelectedDirectories(nodesToDelete));
676
+ setDeleteModalOpen(true);
677
+ };
678
+ const handlePostDelete = (selectedNode) => {
679
+ if (!selectedNode) return;
680
+ if (selectedNode.type === "file") {
681
+ let parentPath = getParentPath(selectedNode.path);
682
+ let parentNode = treeMap[parentPath];
683
+ while (parentNode) {
684
+ if (getNodeChildren(parentToChildrenMap, parentPath).length > 0) break;
685
+ if (parentNode.storageId && parentNode.root) break;
686
+ parentPath = getParentPath(parentPath);
687
+ parentNode = treeMap[parentPath];
688
+ }
689
+ if (parentNode) handleSelectTreeItem(parentPath, parentNode);
690
+ } else if (selectedNode.type === "directory") {
691
+ const currentFolderChildren = getNodeChildren(parentToChildrenMap, selectedNode.path);
692
+ if (currentFolderChildren.length > 0 && currentFolderChildren.every((child) => checkedItems.has(child.path))) {
693
+ const parentPath = getParentPath(selectedNode.path);
694
+ const parentNode = treeMap[parentPath];
695
+ if (parentNode) handleSelectTreeItem(parentPath, parentNode);
696
+ }
697
+ }
698
+ };
699
+ const handleDelete = async () => {
700
+ if (!pendingDelete || pendingDelete.length === 0) {
701
+ notify.error(t.messages.noItemsForDeletion);
702
+ return;
703
+ }
704
+ const parentPath = getParentPath(pendingDelete[0].path);
705
+ const parentNode = treeMap[parentPath];
706
+ const factory = await getStorageProviderAndClient(pendingDelete[0].storageId);
707
+ if (!factory) return;
708
+ const { storage, provider, client } = factory;
709
+ if (!provider?.deleteFilesInput) {
710
+ notify.error(t.messages.deleteUnsupported);
711
+ return;
712
+ }
713
+ try {
714
+ notify.loading(t.messages.deleting);
715
+ const input = provider.deleteFilesInput(storage.bucketName || storage.name, pendingDelete);
716
+ const response = await client.deleteFiles(input);
717
+ notify.dismiss();
718
+ if (parentNode) await fetchNodeChildren(parentNode, { replace: true });
719
+ setPendingDelete(null);
720
+ setCheckedItems(/* @__PURE__ */ new Set());
721
+ setDeleteModalOpen(false);
722
+ if (response?.Errors?.length) {
723
+ notify.error(t.messages.deleteSomeFailed);
724
+ return;
725
+ }
726
+ const deletedPaths = new Set(pendingDelete.map((n) => n.path));
727
+ const deletedDirPrefixes = pendingDelete.filter((n) => n.type === "directory").map((n) => n.path.endsWith("/") ? n.path : `${n.path}/`);
728
+ const selectedRemoved = !!selectedNode && (deletedPaths.has(selectedNode.path) || deletedDirPrefixes.some((p) => selectedNode.path.startsWith(p)));
729
+ if (selectedNode && (selectedNode.type === "directory" || selectedRemoved)) handlePostDelete(selectedNode);
730
+ notify.success(t.messages.deleteSuccess);
731
+ } catch (err) {
732
+ notify.dismiss();
733
+ notify.error(t.messages.deleteFailed(err instanceof Error ? err.message : String(err)));
734
+ }
735
+ };
736
+ const handleUploadInputChange = (files, selectedNode) => {
737
+ if (!files || !selectedNode || selectedNode.type === "file") return;
738
+ if (!storageCanWrite(selectedNode.storageId)) {
739
+ notify.error(t.messages.noWriteAccess);
740
+ return;
741
+ }
742
+ if (!storageCanUpload(selectedNode.storageId)) {
743
+ notify.error(t.messages.uploadsUnsupported);
744
+ return;
745
+ }
746
+ if (waitForUpload) {
747
+ notify.warning(t.messages.waitForCurrentUpload);
748
+ return;
749
+ }
750
+ setWaitForUpload(true);
751
+ const uploadNodes = Array.from(files).map((file) => ({
752
+ name: file.name,
753
+ file,
754
+ relativePath: file.webkitRelativePath || file.name
755
+ }));
756
+ const totalSize = calculateUploadNodesTotalSize(uploadNodes);
757
+ setPendingUpload({
758
+ targetPath: selectedNode.path,
759
+ uploadNodes,
760
+ totalSize
761
+ });
762
+ setUploadModalOpen(true);
763
+ setWaitForUpload(false);
764
+ if (fileInputRef.current) fileInputRef.current.value = "";
765
+ if (folderInputRef.current) folderInputRef.current.value = "";
766
+ };
767
+ const handleUpload = async () => {
768
+ if (!pendingUpload) {
769
+ notify.error(t.messages.noFilesToUpload);
770
+ return;
771
+ }
772
+ if (waitForUpload) {
773
+ notify.info(t.messages.waitForUpload);
774
+ return;
775
+ }
776
+ const targetNode = treeMap[pendingUpload.targetPath];
777
+ if (!targetNode || !targetNode.storageId) {
778
+ notify.error(t.messages.invalidUploadTarget);
779
+ return;
780
+ }
781
+ const factory = await getStorageProviderAndClient(targetNode.storageId);
782
+ if (!factory) {
783
+ notify.error(t.messages.connectFailed);
784
+ return;
785
+ }
786
+ const { storage, provider, client } = factory;
787
+ const storageName = storage.bucketName || storage.name;
788
+ const cleanTargetPath = targetNode.root ? "" : removeLeadingAndTrailingSlashes(targetNode.relativePath || "");
789
+ if (!provider?.uploadFileInput) {
790
+ notify.error(t.messages.uploadUnsupported);
791
+ return;
792
+ }
793
+ const sessionId = fileExplorerContext.addUploadSession(storageName, cleanTargetPath, pendingUpload.uploadNodes, client, provider, targetNode, (node) => fetchNodeChildren(node, { replace: true }));
794
+ const sessionQueueIndex = fileExplorerContext.getUploadQueueIndex(sessionId);
795
+ if (sessionQueueIndex === -1) {
796
+ notify.update(sessionId, {
797
+ render: t.messages.uploadInitFailed,
798
+ type: "error",
799
+ icon: null,
800
+ isLoading: false,
801
+ closeButton: null
802
+ });
803
+ return;
804
+ } else if (sessionQueueIndex > 0 && fileExplorerContext.uploadQueue[sessionQueueIndex - 1] !== sessionId) {
805
+ notify.update(sessionId, {
806
+ render: t.messages.uploadQueuedInfo,
807
+ type: "info",
808
+ icon: null,
809
+ isLoading: false,
810
+ autoClose: false
811
+ });
812
+ return;
813
+ }
814
+ setUploadModalOpen(false);
815
+ setPendingUpload(null);
816
+ setWaitForUpload(false);
817
+ };
818
+ const validateFolderName = (name) => {
819
+ if (createFolderTarget?.type !== "directory") return null;
820
+ const error = getDirectoryNameError(name, getNodeChildren(parentToChildrenMap, createFolderTarget.path));
821
+ return error ? t.createFolder.validation[error] : null;
822
+ };
823
+ const handleCreateFolder = async (name) => {
824
+ if (!isStorageDirectory(createFolderTarget)) {
825
+ notify.error(t.createFolder.invalidTarget);
826
+ return false;
827
+ }
828
+ if (!storageCanWrite(createFolderTarget.storageId)) {
829
+ notify.error(t.createFolder.noWriteAccess);
830
+ return false;
831
+ }
832
+ if (!storageCanUpload(createFolderTarget.storageId)) {
833
+ notify.error(t.createFolder.unsupported);
834
+ return false;
835
+ }
836
+ try {
837
+ const factory = await getStorageProviderAndClient(createFolderTarget.storageId);
838
+ if (!factory) return false;
839
+ const { storage, provider, client } = factory;
840
+ if (!provider.uploadFileInput) {
841
+ notify.error(t.createFolder.unsupported);
842
+ return false;
843
+ }
844
+ const input = provider.uploadFileInput(storage.bucketName || storage.name, getDirectoryObjectPath(createFolderTarget, name), new Blob());
845
+ await client.uploadFile(input);
846
+ await fetchNodeChildren(createFolderTarget, { replace: true });
847
+ notify.success(t.createFolder.success(name));
848
+ return true;
849
+ } catch (error) {
850
+ notify.error(t.createFolder.error(getErrorMessage(error)));
851
+ return false;
852
+ }
853
+ };
854
+ const handleRefresh = async (opts) => {
855
+ const dirs = [...expandedPaths].map((path) => treeMap[path]).filter((node) => !!node && node.type === "directory" && !!node.storageId);
856
+ if (selectedNode?.type === "directory" && selectedNode.storageId && !dirs.some((dir) => dir.path === selectedNode.path)) dirs.push(selectedNode);
857
+ if (dirs.length > 0) return { corsError: (await Promise.all(dirs.map((dir) => fetchNodeChildren(dir, {
858
+ replace: true,
859
+ silent: opts?.silent
860
+ })))).some((result) => result.corsError) };
861
+ if (onRefresh) onRefresh();
862
+ return { corsError: false };
863
+ };
864
+ return /* @__PURE__ */ jsxs("div", {
865
+ className: "w-full h-full flex relative",
866
+ children: [
867
+ /* @__PURE__ */ jsxs("div", {
868
+ className: "w-full h-full flex",
869
+ children: [/* @__PURE__ */ jsx(TreeFrame, {
870
+ treeOnly,
871
+ sidebarWidth,
872
+ onWidthChange: setSidebarWidth,
873
+ children: /* @__PURE__ */ jsxs("div", {
874
+ className: "h-full flex flex-col border-r theme-border bg-(--theme-panel-bg)/60",
875
+ children: [/* @__PURE__ */ jsx("div", {
876
+ className: "flex items-center justify-end h-10 px-3 shrink-0 border-b theme-border",
877
+ children: /* @__PURE__ */ jsxs("div", {
878
+ className: "flex gap-x-0.5",
879
+ children: [/* @__PURE__ */ jsx("button", {
880
+ type: "button",
881
+ "aria-label": t.chrome.refresh,
882
+ "data-tooltip-id": TOOLTIP_ID,
883
+ "data-tooltip-content": "Refresh",
884
+ "data-tooltip-class-name": "z-50",
885
+ className: "inline-flex items-center justify-center h-7 w-7 rounded-md cursor-pointer theme-muted-text hover:theme-text hover:theme-hover transition-colors duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-(--theme-link)",
886
+ onClick: () => handleRefresh(),
887
+ children: /* @__PURE__ */ jsx(RefreshIcon, { className: "h-3.5 w-3.5" })
888
+ }), /* @__PURE__ */ jsx("button", {
889
+ type: "button",
890
+ "aria-label": t.chrome.collapse,
891
+ "data-tooltip-id": TOOLTIP_ID,
892
+ "data-tooltip-content": "Collapse All",
893
+ "data-tooltip-class-name": "z-50",
894
+ className: "inline-flex items-center justify-center h-7 w-7 rounded-md cursor-pointer theme-muted-text hover:theme-text hover:theme-hover transition-colors duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-(--theme-link)",
895
+ onClick: collapseAll,
896
+ children: /* @__PURE__ */ jsx(CollapseIcon, { className: "h-3.5 w-3.5" })
897
+ })]
898
+ })
899
+ }), /* @__PURE__ */ jsx(TreeView, {
900
+ rootNodes,
901
+ parentToChildrenMap,
902
+ expandedPaths,
903
+ selectedPath: normalizedSelectedPath,
904
+ loadingPaths,
905
+ onToggleExpand: handleToggleExpand,
906
+ onSelect: (path, node) => {
907
+ handleSelectTreeItem(path, node);
908
+ if (treeOnly && node.type === "file") openPreview(node);
909
+ },
910
+ onDropToFolder: onDropFiles,
911
+ onContextMenu: openNodeContextMenu,
912
+ onPreviewFile: openPreview,
913
+ paging: treePaging
914
+ })]
915
+ })
916
+ }), !treeOnly && /* @__PURE__ */ jsx("div", {
917
+ className: "h-full flex flex-col flex-1 min-w-0",
918
+ children: !selectedNode && normalizedSelectedPath ? hydrationStuck ? /* @__PURE__ */ jsx(NotFoundPreview, {
919
+ path: normalizedSelectedPath,
920
+ onRetry: () => {
921
+ const deepest = stuck?.deepest;
922
+ hydrationPagesRef.current = {
923
+ path: "",
924
+ pages: 0
925
+ };
926
+ setStuck(null);
927
+ if (deepest) setListings((prev) => hasMore(prev, deepest) ? clearListingError(prev, deepest) : clearListingsUnder(prev, deepest));
928
+ }
929
+ }) : /* @__PURE__ */ jsx(ListSkeleton, {}) : !selectedNode && showUserHierarchy ? /* @__PURE__ */ jsxs("div", {
930
+ className: "overflow-auto h-full",
931
+ children: [/* @__PURE__ */ jsxs(Table, {
932
+ ...listTableProps,
933
+ children: [
934
+ /* @__PURE__ */ jsx(Table.Header, {
935
+ caps: false,
936
+ className: "py-2",
937
+ style: { minWidth: 200 },
938
+ children: "Name"
939
+ }),
940
+ /* @__PURE__ */ jsx(Table.Header, {
941
+ caps: false,
942
+ className: "w-[22%] py-2",
943
+ children: "Type"
944
+ }),
945
+ /* @__PURE__ */ jsx(Table.Header, {
946
+ caps: false,
947
+ className: "w-[22%] py-2",
948
+ children: "Storages"
949
+ }),
950
+ /* @__PURE__ */ jsx(ListActionsHeader, { actionCount: 1 }),
951
+ rootNodes.map((rootNode) => /* @__PURE__ */ jsx(UserRow, {
952
+ node: rootNode,
953
+ icon: getNodeIcon(rootNode, slots.storageIconUrl, t.chrome.storageIconAlt),
954
+ storageCount: getNodeChildren(parentToChildrenMap, rootNode.path).length,
955
+ getItems: () => [{
956
+ kind: "action",
957
+ label: t.preview.open,
958
+ icon: /* @__PURE__ */ jsx(FolderIcon, {}),
959
+ onSelect: () => openUserNode(rootNode)
960
+ }],
961
+ openMenu,
962
+ onOpen: openUserNode
963
+ }, rootNode.path))
964
+ ]
965
+ }), rootNodes.length === 0 && /* @__PURE__ */ jsx(NoDataAvailablePreview, {
966
+ selectedStorage: null,
967
+ onRefresh: handleRefresh
968
+ })]
969
+ }) : !selectedNode ? /* @__PURE__ */ jsx(NoFileSelectedPreview, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
970
+ /* @__PURE__ */ jsxs("div", {
971
+ className: "flex items-center justify-between gap-3 px-4 h-12 shrink-0 border-b theme-border",
972
+ children: [renderBreadcrumb(), /* @__PURE__ */ jsxs("div", {
973
+ className: "flex shrink-0 items-center gap-2",
974
+ children: [
975
+ folderHasMore && /* @__PURE__ */ jsx(StatusBadge, {
976
+ variant: "muted",
977
+ className: "whitespace-nowrap tabular-nums",
978
+ children: t.partialCount(selectedNodeChildren.length)
979
+ }),
980
+ selectedInFolderCount > 0 && /* @__PURE__ */ jsx(StatusBadge, {
981
+ variant: "muted",
982
+ className: "whitespace-nowrap tabular-nums",
983
+ children: t.selectedCount(selectedInFolderCount)
984
+ }),
985
+ !selectedNodeCanWrite && /* @__PURE__ */ jsx(StatusBadge, {
986
+ variant: "muted",
987
+ className: "uppercase tracking-wider",
988
+ children: t.readOnly
989
+ }),
990
+ /* @__PURE__ */ jsx("input", {
991
+ type: "file",
992
+ multiple: true,
993
+ ref: fileInputRef,
994
+ className: "hidden",
995
+ onChange: (e) => {
996
+ handleUploadInputChange(e.target.files, selectedNode);
997
+ }
998
+ }),
999
+ /* @__PURE__ */ jsx("input", {
1000
+ type: "file",
1001
+ multiple: true,
1002
+ webkitdirectory: "true",
1003
+ ref: folderInputRef,
1004
+ className: "hidden",
1005
+ onChange: (e) => {
1006
+ handleUploadInputChange(e.target.files, selectedNode);
1007
+ }
1008
+ }),
1009
+ selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsxs("button", {
1010
+ type: "button",
1011
+ "aria-label": t.createFolder.action,
1012
+ className: "btn btn-info cursor-pointer flex items-center justify-center",
1013
+ disabled: selectedNode.type === "file" || !selectedNode.storageId,
1014
+ onClick: () => setCreateFolderTarget(selectedNode),
1015
+ children: [/* @__PURE__ */ jsx(FolderIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1016
+ className: "hidden md:inline",
1017
+ children: t.createFolder.action
1018
+ })]
1019
+ }),
1020
+ selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsxs(MenuButton, {
1021
+ "aria-label": t.chrome.upload,
1022
+ className: "btn btn-info cursor-pointer flex items-center justify-between pr-0.5!",
1023
+ disabled: selectedNode.type === "file" || !selectedNode.storageId,
1024
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(UploadIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1025
+ className: "hidden md:inline",
1026
+ children: t.chrome.upload
1027
+ })] }), /* @__PURE__ */ jsx(AngleDownIcon, { className: "h-4 w-4 ml-2" })]
1028
+ }), /* @__PURE__ */ jsxs(MenuItems, {
1029
+ transition: true,
1030
+ anchor: "bottom end",
1031
+ className: "theme-panel w-36 rounded-md border p-1 mt-1 shadow-lg z-50 origin-top-right transition duration-100\n ease-out data-closed:scale-95 data-closed:opacity-0 focus:outline-none",
1032
+ children: [/* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsx("button", {
1033
+ type: "button",
1034
+ className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
1035
+ onClick: () => fileInputRef.current?.click(),
1036
+ children: "Select Files"
1037
+ }) }), /* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsx("button", {
1038
+ type: "button",
1039
+ className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
1040
+ onClick: () => folderInputRef.current?.click(),
1041
+ children: "Select Folder"
1042
+ }) })]
1043
+ })] }),
1044
+ /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsxs(MenuButton, {
1045
+ "aria-label": t.chrome.download,
1046
+ className: "btn btn-info cursor-pointer flex items-center justify-between pr-0.5!",
1047
+ disabled: checkedItems.size === 0 && selectedNode.type !== "file" || hasFolderSelected() || !selectedNode.storageId,
1048
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DownloadFileIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1049
+ className: "hidden md:inline",
1050
+ children: t.chrome.download
1051
+ })] }), /* @__PURE__ */ jsx(AngleDownIcon, { className: "h-4 w-4 ml-2" })]
1052
+ }), /* @__PURE__ */ jsxs(MenuItems, {
1053
+ transition: true,
1054
+ anchor: "bottom end",
1055
+ className: "theme-panel w-72 rounded-md border p-1 mt-1 shadow-lg z-50 origin-top-right transition duration-100 \n ease-out data-closed:scale-95 data-closed:opacity-0 focus:outline-none",
1056
+ children: [/* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsx("button", {
1057
+ type: "button",
1058
+ className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
1059
+ onClick: handleDownload,
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", {
1062
+ className: "text-sm w-full text-center pointer-events-none px-2 py-1.5",
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
+ })()] }) })] })]
1077
+ })] }),
1078
+ /* @__PURE__ */ jsxs("button", {
1079
+ type: "button",
1080
+ "aria-label": t.chrome.access,
1081
+ className: "btn btn-info cursor-pointer flex items-center justify-center",
1082
+ disabled: !selectedNode.storageId,
1083
+ onClick: () => setAccessDrawerOpen(true),
1084
+ hidden: !groups || groups.length === 0 || !selectedNodeCanManageAccess,
1085
+ children: [/* @__PURE__ */ jsx(AccessIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1086
+ className: "hidden md:inline",
1087
+ children: t.chrome.manageAccess
1088
+ })]
1089
+ }),
1090
+ selectedNodeCanWrite && /* @__PURE__ */ jsxs("button", {
1091
+ type: "button",
1092
+ "aria-label": t.chrome.delete,
1093
+ className: "btn btn-destructive cursor-pointer flex items-center justify-center",
1094
+ disabled: checkedItems.size === 0 && selectedNode.type !== "file" || !selectedNode.storageId,
1095
+ onClick: onDeleteClick,
1096
+ children: [/* @__PURE__ */ jsx(TrashIcon, { className: "h-4 w-4 mr-2" }), /* @__PURE__ */ jsx("span", {
1097
+ className: "hidden md:inline",
1098
+ children: t.chrome.delete
1099
+ })]
1100
+ })
1101
+ ]
1102
+ })]
1103
+ }),
1104
+ selectedNode.type === "directory" && /* @__PURE__ */ jsxs(DragDropZone, {
1105
+ className: "min-h-0 flex-1 overflow-hidden",
1106
+ onDropFiles: selectedNodeCanWrite ? (files) => {
1107
+ onDropFiles(selectedNode.path, files);
1108
+ } : void 0,
1109
+ children: [loadingPaths.has(selectedNode.path) && /* @__PURE__ */ jsx(ListSkeleton, {}), !loadingPaths.has(selectedNode.path) && (selectedNodeChildren.length > 0 || folderHasMore ? /* @__PURE__ */ jsx(FileTable, {
1110
+ nodes: selectedNodeChildren,
1111
+ checkedItems,
1112
+ allSelected: allItemsSelected,
1113
+ someSelected: selectedInFolderCount > 0,
1114
+ onSelectAll: handleBatchCheckbox,
1115
+ getItems: buildRowMenu,
1116
+ openMenu,
1117
+ onOpen: (n) => handleSelectTreeItem(n.path, n),
1118
+ onToggleCheck: handleItemCheckbox,
1119
+ onPreview: openPreview,
1120
+ loadMore: loadMoreProps
1121
+ }, selectedNode.path) : /* @__PURE__ */ jsxs("div", {
1122
+ className: "h-full flex flex-col",
1123
+ children: [/* @__PURE__ */ jsx(Table, {
1124
+ ...listTableFixedProps,
1125
+ wrapperClassName: "overflow-x-auto",
1126
+ children: /* @__PURE__ */ jsx(FileTableHeaders, {
1127
+ allSelected: allItemsSelected,
1128
+ someSelected: selectedInFolderCount > 0,
1129
+ onSelectAll: handleBatchCheckbox
1130
+ })
1131
+ }), /* @__PURE__ */ jsx(NoDataAvailablePreview, {
1132
+ selectedStorage: storages.find((s) => s.id === selectedNode.storageId) || null,
1133
+ canWrite: selectedNodeCanWrite,
1134
+ onRefresh: handleRefresh,
1135
+ error: hasCorsIssue.get(selectedNode.storageId ?? "") ? new CorsError(connectionIssuesMessage.get(selectedNode.storageId ?? "") || "") : null,
1136
+ connectionIssueMessage: selectedNode.storageId ? connectionIssuesMessage.get(selectedNode.storageId) : null
1137
+ })]
1138
+ }))]
1139
+ }),
1140
+ selectedNode.type === "file" && (() => {
1141
+ const uris = getNodeUris(selectedNode);
1142
+ const fileIcon = getNodeIcon(selectedNode, slots.storageIconUrl, t.chrome.storageIconAlt) ?? /* @__PURE__ */ jsx(FileIcon, { className: "h-5 w-5 theme-muted-text" });
1143
+ const modifiedDt = selectedNode.modified ? DateTime.fromISO(selectedNode.modified) : null;
1144
+ const createdDt = selectedNode.created ? DateTime.fromISO(selectedNode.created) : null;
1145
+ const previewable = canPreview(selectedNode);
1146
+ const effectiveTab = previewable ? detailTab : "details";
1147
+ const tabClass = (active) => cx("mx-2.5 border-b-2 px-1 py-2.5 text-[13px] font-medium cursor-pointer transition-colors", active ? "theme-text border-(--theme-element)" : "theme-muted-text border-transparent hover:theme-text");
1148
+ return /* @__PURE__ */ jsxs("div", {
1149
+ className: "flex h-full min-h-0 flex-col",
1150
+ children: [/* @__PURE__ */ jsxs("div", {
1151
+ className: "flex shrink-0 items-center border-b theme-border px-3",
1152
+ children: [/* @__PURE__ */ jsx("button", {
1153
+ type: "button",
1154
+ className: tabClass(effectiveTab === "details"),
1155
+ onClick: () => setDetailTab("details"),
1156
+ children: t.preview.detailsTab
1157
+ }), previewable && /* @__PURE__ */ jsx("button", {
1158
+ type: "button",
1159
+ className: tabClass(effectiveTab === "preview"),
1160
+ onClick: () => setDetailTab("preview"),
1161
+ children: t.preview.previewTab
1162
+ })]
1163
+ }), effectiveTab === "preview" && previewable ? /* @__PURE__ */ jsx(FilePreviewInline, {
1164
+ node: selectedNode,
1165
+ storage: storages.find((s) => s.id === selectedNode.storageId) ?? null,
1166
+ getPresignedUrl,
1167
+ onOpenFull: openPreview,
1168
+ onDownload: downloadNode,
1169
+ active: !previewOpen
1170
+ }) : /* @__PURE__ */ jsxs("div", {
1171
+ className: "min-h-0 flex-1 overflow-auto",
1172
+ children: [
1173
+ /* @__PURE__ */ jsxs("div", {
1174
+ className: "flex items-start gap-4 px-6 pt-6 pb-5 border-b theme-border",
1175
+ children: [/* @__PURE__ */ jsx("div", {
1176
+ className: "shrink-0 inline-flex items-center justify-center w-12 h-12 rounded-lg theme-muted-panel",
1177
+ children: /* @__PURE__ */ jsx("div", {
1178
+ className: "*:h-5 *:w-5 *:mr-0",
1179
+ children: fileIcon
1180
+ })
1181
+ }), /* @__PURE__ */ jsxs("div", {
1182
+ className: "flex-1 min-w-0",
1183
+ children: [/* @__PURE__ */ jsx("h2", {
1184
+ className: "text-lg font-semibold leading-tight break-all",
1185
+ children: selectedNode.name
1186
+ }), /* @__PURE__ */ jsxs("div", {
1187
+ className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-1 text-xs theme-muted-text",
1188
+ children: [
1189
+ selectedNode.contentType && /* @__PURE__ */ jsx("span", {
1190
+ className: "inline-flex items-center rounded-full border theme-border px-2 py-0.5 font-mono text-[10.5px] uppercase tracking-wider",
1191
+ children: selectedNode.contentType
1192
+ }),
1193
+ typeof selectedNode.size === "number" && /* @__PURE__ */ jsx("span", {
1194
+ className: "tabular-nums",
1195
+ children: formatFileSize(selectedNode.size)
1196
+ }),
1197
+ modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1198
+ "aria-hidden": true,
1199
+ children: "·"
1200
+ }), /* @__PURE__ */ jsxs("span", { children: ["Modified ", modifiedDt.toRelative()] })] })
1201
+ ]
1202
+ })]
1203
+ })]
1204
+ }),
1205
+ uris.length > 0 && /* @__PURE__ */ jsx("div", {
1206
+ className: "px-6 pt-5 space-y-2",
1207
+ children: uris.map((uri) => /* @__PURE__ */ jsx(UriCard, { value: uri }, uri))
1208
+ }),
1209
+ selectedNodeCanShare && /* @__PURE__ */ jsxs("div", {
1210
+ className: "flex items-center gap-2 px-6 pt-4",
1211
+ children: [/* @__PURE__ */ jsx(ShareFileButton, {
1212
+ node: selectedNode,
1213
+ onShare: onShareFileButtonClick
1214
+ }), /* @__PURE__ */ jsx(TooltipInfo, {
1215
+ text: "This will generate a pre-signed URL for this file. Anyone with this link can temporarily access and download the file, even if they don't have an account. The link will automatically expire after 12 hours.",
1216
+ place: "right"
1217
+ })]
1218
+ }),
1219
+ /* @__PURE__ */ jsxs("div", {
1220
+ className: "px-6 py-5 mt-5 border-t theme-border",
1221
+ children: [/* @__PURE__ */ jsx("h3", {
1222
+ className: "text-[11px] font-semibold uppercase tracking-widest theme-muted-text mb-3",
1223
+ children: "Details"
1224
+ }), /* @__PURE__ */ jsxs("dl", {
1225
+ className: "grid grid-cols-[minmax(0,140px)_1fr] gap-x-6 gap-y-2 text-sm",
1226
+ children: [
1227
+ typeof selectedNode.size === "number" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1228
+ className: "theme-muted-text",
1229
+ children: t.chrome.size
1230
+ }), /* @__PURE__ */ jsx("dd", {
1231
+ className: "tabular-nums",
1232
+ children: formatFileSize(selectedNode.size)
1233
+ })] }),
1234
+ selectedNode.contentType && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1235
+ className: "theme-muted-text",
1236
+ children: "Content Type"
1237
+ }), /* @__PURE__ */ jsx("dd", {
1238
+ className: "font-mono text-xs",
1239
+ children: selectedNode.contentType
1240
+ })] }),
1241
+ createdDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1242
+ className: "theme-muted-text",
1243
+ children: t.chrome.created
1244
+ }), /* @__PURE__ */ jsxs("dd", { children: [
1245
+ createdDt.toLocaleString(DateTime.DATETIME_MED),
1246
+ " ",
1247
+ /* @__PURE__ */ jsxs("span", {
1248
+ className: "theme-muted-text",
1249
+ children: ["· ", createdDt.toRelative()]
1250
+ })
1251
+ ] })] }),
1252
+ modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1253
+ className: "theme-muted-text",
1254
+ children: "Last Modified"
1255
+ }), /* @__PURE__ */ jsxs("dd", { children: [
1256
+ modifiedDt.toLocaleString(DateTime.DATETIME_MED),
1257
+ " ",
1258
+ /* @__PURE__ */ jsxs("span", {
1259
+ className: "theme-muted-text",
1260
+ children: ["· ", modifiedDt.toRelative()]
1261
+ })
1262
+ ] })] }),
1263
+ selectedNode.hash && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1264
+ className: "theme-muted-text",
1265
+ children: t.chrome.hash
1266
+ }), /* @__PURE__ */ jsx("dd", {
1267
+ className: "font-mono text-xs break-all",
1268
+ children: selectedNode.hash.replace(/^"|"$/g, "")
1269
+ })] }),
1270
+ selectedNode.storageClass && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
1271
+ className: "theme-muted-text",
1272
+ children: "Storage Class"
1273
+ }), /* @__PURE__ */ jsx("dd", {
1274
+ className: "font-mono text-xs uppercase tracking-wider",
1275
+ children: selectedNode.storageClass
1276
+ })] })
1277
+ ]
1278
+ })]
1279
+ })
1280
+ ]
1281
+ })]
1282
+ });
1283
+ })()
1284
+ ] })
1285
+ })]
1286
+ }),
1287
+ contextMenu,
1288
+ /* @__PURE__ */ jsx(FilePreview, {
1289
+ open: previewOpen,
1290
+ node: previewNode,
1291
+ siblings: previewSiblings,
1292
+ storage: storages.find((s) => s.id === previewNode?.storageId) ?? null,
1293
+ getPresignedUrl,
1294
+ onNavigate: setPreviewNode,
1295
+ onClose: closePreview,
1296
+ onDownload: downloadNode,
1297
+ onCopyUri: copyNodeUri
1298
+ }),
1299
+ /* @__PURE__ */ jsx(CreateModal, {
1300
+ open: !!createFolderTarget,
1301
+ onClose: () => setCreateFolderTarget(null),
1302
+ typeIcon: /* @__PURE__ */ jsx(FolderIcon, { className: "w-3 h-3" }),
1303
+ typeLabel: t.createFolder.folder,
1304
+ namePlaceholder: t.createFolder.namePlaceholder,
1305
+ submitLabel: t.createFolder.submit,
1306
+ submittingLabel: t.createFolder.creating,
1307
+ validateName: validateFolderName,
1308
+ onSubmit: handleCreateFolder
1309
+ }),
1310
+ waitForUpload && /* @__PURE__ */ jsx("div", {
1311
+ className: "absolute inset-0 bg-(--theme-app-bg)/75 backdrop-blur-[2px] transition-opacity flex items-center justify-center z-50",
1312
+ children: /* @__PURE__ */ jsx(Loader, { full: true })
1313
+ }),
1314
+ /* @__PURE__ */ jsx(ConfirmModal, {
1315
+ open: uploadModalOpen && !!pendingUpload,
1316
+ onClose: () => {
1317
+ setUploadModalOpen(false);
1318
+ setPendingUpload(null);
1319
+ },
1320
+ align: "center",
1321
+ title: t.confirmUpload.title,
1322
+ description: t.confirmUpload.text(pendingUpload?.targetPath || t.confirmUpload.defaultPath),
1323
+ confirmLabel: t.confirmUpload.confirm,
1324
+ onConfirm: handleUpload,
1325
+ confirmDisabled: pendingUpload ? isUploadSizeExceeded(pendingUpload.totalSize) : false,
1326
+ children: /* @__PURE__ */ jsxs("div", {
1327
+ className: "text-sm flex flex-col gap-2 mt-2 text-left",
1328
+ children: [
1329
+ /* @__PURE__ */ jsx("ul", {
1330
+ className: "list-disc pl-5 max-h-40 overflow-auto theme-muted-panel py-1",
1331
+ children: pendingUpload?.uploadNodes && renderUploadNodes(pendingUpload.uploadNodes)
1332
+ }),
1333
+ /* @__PURE__ */ jsxs("div", { children: [
1334
+ /* @__PURE__ */ jsx("span", {
1335
+ className: "font-semibold",
1336
+ children: t.confirmUpload.target
1337
+ }),
1338
+ " ",
1339
+ pendingUpload?.targetPath || t.confirmUpload.noPath
1340
+ ] }),
1341
+ /* @__PURE__ */ jsxs("div", {
1342
+ className: "text-xs mt-1",
1343
+ children: [
1344
+ /* @__PURE__ */ jsx("span", {
1345
+ className: "font-semibold",
1346
+ children: t.confirmUpload.totalSize
1347
+ }),
1348
+ " ",
1349
+ /* @__PURE__ */ jsx("span", {
1350
+ className: cx(isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && "text-amber-800 font-semibold"),
1351
+ children: formatFileSize(pendingUpload?.totalSize ?? 0)
1352
+ })
1353
+ ]
1354
+ }),
1355
+ isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && /* @__PURE__ */ jsx("div", {
1356
+ className: "mt-3 p-3 bg-amber-50 border border-amber-200 rounded-lg",
1357
+ children: /* @__PURE__ */ jsxs("div", {
1358
+ className: "flex items-start gap-2",
1359
+ children: [/* @__PURE__ */ jsx(AlertIcon, { className: "w-4 h-4 text-amber-600 shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
1360
+ className: "text-amber-800",
1361
+ children: [/* @__PURE__ */ jsx("div", {
1362
+ className: "font-semibold mb-1",
1363
+ children: t.confirmUpload.sizeLimitTitle
1364
+ }), /* @__PURE__ */ jsxs("div", {
1365
+ className: "text-sm",
1366
+ children: [/* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitBody(formatFileSize(pendingUpload?.totalSize ?? 0), formatFileSize(MAX_UPLOAD_SIZE)) }), /* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitHint })]
1367
+ })]
1368
+ })]
1369
+ })
1370
+ })
1371
+ ]
1372
+ })
1373
+ }),
1374
+ /* @__PURE__ */ jsx(ConfirmModal, {
1375
+ open: deleteModalOpen && !!pendingDelete,
1376
+ onClose: () => {
1377
+ setDeleteModalOpen(false);
1378
+ setPendingDelete(null);
1379
+ setCheckedItems(/* @__PURE__ */ new Set());
1380
+ },
1381
+ align: "center",
1382
+ destructive: true,
1383
+ title: t.confirmDelete.title,
1384
+ description: t.confirmDelete.text,
1385
+ confirmLabel: t.confirmDelete.action,
1386
+ onConfirm: handleDelete,
1387
+ children: /* @__PURE__ */ jsx("div", {
1388
+ className: "text-sm flex flex-col gap-2 mt-2 text-left",
1389
+ children: /* @__PURE__ */ jsx("ul", {
1390
+ className: "list-disc pl-5 max-h-40 overflow-auto theme-muted-panel py-1",
1391
+ children: pendingDelete?.map((node) => /* @__PURE__ */ jsx("li", { children: node.path }, node.path))
1392
+ })
1393
+ })
1394
+ }),
1395
+ selectedNode?.storageId && !groupsLoading && slots.accessDrawer?.({
1396
+ open: accessDrawerOpen,
1397
+ setOpen: setAccessDrawerOpen,
1398
+ groups,
1399
+ storage: storages.find((s) => s.id === selectedNode.storageId)
1400
+ })
1401
+ ]
1402
+ });
1403
+ }
1404
+ //#endregion
1405
+ export { FileExplorer as default };