@parallelworks/ui 0.16.0 → 0.18.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.
- package/LICENSE +202 -21
- package/dist/chunks/{CodePreviewEditor-DuMZMA_k.js → CodePreviewEditor-4lx3kE-w.js} +2 -2
- package/dist/chunks/{CreateModal-B7smkHkv.js → CreateModal-BY6DLmur.js} +11 -11
- package/dist/chunks/{lib-CUTRDpa6.js → FieldWrapper-Bef01EWO.js} +49 -48
- package/dist/chunks/{Monaco-BXDH2jVj.js → Monaco-C1POOdVv.js} +485 -172
- package/dist/chunks/{PageHeader-CoZWF0pJ.js → PageHeader-l49p1nM9.js} +145 -82
- package/dist/chunks/{Provider-CbFVFB7O.js → Provider-Ghua9PkN.js} +34 -6
- package/dist/chunks/{StatusBadge-zvEtK16a.js → StatusBadge-C1Blx_cW.js} +23 -6
- package/dist/chunks/{SwitchToggle-3V836Cn7.js → SwitchToggle-CHPP8uZQ.js} +17 -14
- package/dist/chunks/{Table-CutP1bRt.js → Table-4OHjdKdO.js} +2 -2
- package/dist/chunks/{Tooltip-n9w7Tkt1.js → Tooltip-Crxo_eU_.js} +3 -3
- package/dist/chunks/{editor-Ko3zX22Y.js → editor-9_uVtUyy.js} +4 -4
- package/dist/chunks/{list-CduoUsMW.js → list-qrojZZNv.js} +146 -33
- package/dist/components/AddMenu.d.ts +1 -0
- package/dist/components/Avatar.d.ts +13 -0
- package/dist/components/Button.d.ts +2 -1
- package/dist/components/Dropdown.d.ts +2 -0
- package/dist/components/Provider.d.ts +59 -2
- package/dist/components/SectionHeader.d.ts +3 -1
- package/dist/components/StatusBadge.d.ts +7 -2
- package/dist/components/relativeTime.d.ts +15 -0
- package/dist/editor/index.js +1 -1
- package/dist/editor/usesGitlab.d.ts +97 -0
- package/dist/file-explorer/index.js +275 -274
- package/dist/file-explorer/lib/paging.d.ts +27 -28
- package/dist/file-explorer/lib/types.d.ts +0 -1
- package/dist/file-explorer/lib/utils.d.ts +16 -5
- package/dist/form/FieldWrapper.d.ts +2 -1
- package/dist/form/Form.d.ts +3 -1
- package/dist/form/index.js +23 -21
- package/dist/form/types/fieldComponentTypes.d.ts +1 -0
- package/dist/form/types/fieldTypes.d.ts +4 -4
- package/dist/form/types/workflow-schema.d.ts +38 -0
- package/dist/form/useParsedOpts.d.ts +1 -1
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +3 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +49 -18
- package/dist/list/ListPage.d.ts +3 -1
- package/dist/list/ListPager.d.ts +3 -1
- package/dist/list/ListTable.d.ts +2 -1
- package/dist/list/RowContextMenu.d.ts +9 -9
- package/dist/list/UserHoverCard.d.ts +45 -0
- package/dist/list/index.d.ts +2 -1
- package/dist/list/index.js +3 -3
- package/dist/list/listView.d.ts +1 -1
- package/dist/logviewer/index.js +7 -7
- package/dist/styles/Geist-Variable.woff2 +0 -0
- package/dist/styles/GeistMono-Variable.woff2 +0 -0
- package/dist/styles/base.css +57 -0
- package/dist/styles/fonts.css +15 -0
- package/dist/styles/theme.css +6 -40
- package/dist/styles.css +1 -1
- package/dist/workflow/DependencyGraph.d.ts +2 -0
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +10 -9
- package/dist/workflow/types.d.ts +5 -0
- package/package.json +12 -11
- package/dist/chunks/{BareModal-BqroLbZ5.js → BareModal-BtRiQezg.js} +1 -1
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import { i as useNotify, l as useSlots, o as useProvisionCorsRules, u as useStrings } from "../chunks/Provider-Ghua9PkN.js";
|
|
2
|
+
import { _ as useRowMenuActive, d as assembleRowMenu, g as useRowMenu, h as useCopySubmenu, u as MoreButton } from "../chunks/PageHeader-l49p1nM9.js";
|
|
1
3
|
import { AccessIcon, AlertIcon, AngleDownIcon, AngleRightIcon, AngleUpIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardIcon, CloseIcon, CloudUploadIcon, CollapseIcon, CustomizeIcon, DiskIcon, DocumentIcon, DownloadFileIcon, ExpandIcon, ExpandVerticalIcon, EyeIcon, FileIcon, FileTextIcon, FolderIcon, FolderOpenIcon, ImageIcon, LinkIcon, LockIcon, MinimizeIcon, MusicIcon, PdfIcon, RefreshIcon, ShareIcon, SpreadsheetIcon, SuccessIcon, TrashIcon, UploadIcon, UserIcon, VideoIcon, ZipIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
|
|
2
|
-
import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as StatusBadge } from "../chunks/StatusBadge-zvEtK16a.js";
|
|
4
|
+
import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-BY6DLmur.js";
|
|
5
|
+
import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-Crxo_eU_.js";
|
|
6
|
+
import { t as StatusBadge } from "../chunks/StatusBadge-C1Blx_cW.js";
|
|
6
7
|
import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { C as ListSkeleton, E as RowSelectCheckbox, O as listTableFixedProps, k as listTableProps, x as ListActionsHeader } from "../chunks/list-CduoUsMW.js";
|
|
8
|
+
import { n as Table } from "../chunks/Table-4OHjdKdO.js";
|
|
9
|
+
import { E as ListSkeleton, M as listTableProps, j as listTableFixedProps, k as RowSelectCheckbox, w as ListActionsHeader } from "../chunks/list-qrojZZNv.js";
|
|
10
10
|
import cx from "classnames";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
11
|
import { Suspense, createContext, lazy, memo, use, useCallback, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
|
|
12
|
+
import { useLocalStorage } from "usehooks-ts";
|
|
13
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
14
|
import { createPortal } from "react-dom";
|
|
14
15
|
import { Menu, MenuButton, MenuItem, MenuItems, MenuSeparator } from "@headlessui/react";
|
|
15
16
|
import Skeleton from "react-loading-skeleton";
|
|
16
17
|
import { DateTime } from "luxon";
|
|
17
18
|
import { Resizable } from "re-resizable";
|
|
18
|
-
import { useLocalStorage } from "usehooks-ts";
|
|
19
19
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
20
20
|
//#region src/file-explorer/lib/utils.ts
|
|
21
21
|
function formatFileSize(bytes) {
|
|
@@ -38,143 +38,143 @@ function getNodeName(node) {
|
|
|
38
38
|
const parts = removeTrailingSlash(node.path || "").split("/");
|
|
39
39
|
return parts[parts.length - 1] ?? "";
|
|
40
40
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
parentToChildrenMap: {}
|
|
45
|
-
};
|
|
46
|
-
const objects = storage.objects ?? [];
|
|
47
|
-
const cspBucketName = storage.bucketName || storage.name;
|
|
48
|
-
const storagePath = storage.user ? ensureTrailingSlash(`${storage.user}/${storage.name}`) : ensureTrailingSlash(storage.name);
|
|
49
|
-
const treeMap = { [storagePath]: {
|
|
50
|
-
name: storage.name,
|
|
51
|
-
displayName: storage?.displayName,
|
|
52
|
-
path: storagePath,
|
|
53
|
-
relativePath: "",
|
|
54
|
-
type: "directory",
|
|
55
|
-
created: storage.created,
|
|
56
|
-
modified: storage.modified,
|
|
57
|
-
storageId: storage.id,
|
|
58
|
-
storageName: cspBucketName,
|
|
59
|
-
storageNodeIcon: storage.type,
|
|
60
|
-
storageImageUrl: storage.imageUrl,
|
|
61
|
-
storageType: storage.type,
|
|
62
|
-
root: true
|
|
63
|
-
} };
|
|
64
|
-
const directories = objects.filter((obj) => obj.type === "directory");
|
|
65
|
-
const files = objects.filter((obj) => obj.type === "file");
|
|
66
|
-
directories.forEach((dir) => {
|
|
67
|
-
const fullPath = ensureTrailingSlash(`${storagePath}${removeLeadingSlash(removeTrailingSlash(dir.path))}`);
|
|
68
|
-
treeMap[fullPath] = {
|
|
69
|
-
name: getNodeName(dir),
|
|
70
|
-
path: fullPath,
|
|
71
|
-
relativePath: dir?.key || dir.path,
|
|
72
|
-
type: dir.type,
|
|
73
|
-
created: dir?.created,
|
|
74
|
-
modified: dir?.modified,
|
|
75
|
-
storageId: storage.id,
|
|
76
|
-
storageName: cspBucketName
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
files.forEach((file) => {
|
|
80
|
-
const relativeFilePath = removeLeadingSlash(file.path);
|
|
81
|
-
const fullPath = `${storagePath}${relativeFilePath}`;
|
|
82
|
-
treeMap[fullPath] = {
|
|
83
|
-
name: getNodeName(file),
|
|
84
|
-
path: fullPath,
|
|
85
|
-
relativePath: file.key || relativeFilePath,
|
|
86
|
-
type: file.type,
|
|
87
|
-
size: file.size,
|
|
88
|
-
created: file?.created,
|
|
89
|
-
modified: file?.modified,
|
|
90
|
-
contentType: file?.contentType,
|
|
91
|
-
hash: file?.hash,
|
|
92
|
-
storageClass: file?.storageClass,
|
|
93
|
-
storageId: storage.id,
|
|
94
|
-
storageName: cspBucketName
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
const parentToChildrenMap = { [storagePath]: [] };
|
|
98
|
-
Object.values(treeMap).forEach((node) => {
|
|
99
|
-
if (node.path === storagePath) return;
|
|
100
|
-
const cleanPath = node.path.endsWith("/") && node.path.length > 1 ? node.path.slice(0, -1) : node.path;
|
|
101
|
-
const lastSlash = cleanPath.lastIndexOf("/");
|
|
102
|
-
const parentPath = lastSlash <= 0 ? storagePath : ensureTrailingSlash(cleanPath.substring(0, lastSlash + 1));
|
|
103
|
-
if (!parentToChildrenMap[parentPath]) parentToChildrenMap[parentPath] = [];
|
|
104
|
-
parentToChildrenMap[parentPath].push(node);
|
|
105
|
-
});
|
|
106
|
-
Object.entries(parentToChildrenMap).forEach(([parentPath, childrenArr]) => {
|
|
107
|
-
if (unsortedParents?.has(parentPath)) return;
|
|
108
|
-
childrenArr.sort((a, b) => {
|
|
109
|
-
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
110
|
-
return a.name.localeCompare(b.name);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
41
|
+
function toNode(entry, storage, storagePath, storageName) {
|
|
42
|
+
const relativePath = removeLeadingSlash(entry.path);
|
|
43
|
+
const isDirectory = entry.type === "directory";
|
|
113
44
|
return {
|
|
114
|
-
|
|
115
|
-
|
|
45
|
+
name: getNodeName(entry),
|
|
46
|
+
path: isDirectory ? ensureTrailingSlash(`${storagePath}${removeTrailingSlash(relativePath)}`) : `${storagePath}${relativePath}`,
|
|
47
|
+
relativePath: entry.key || (isDirectory ? entry.path : relativePath),
|
|
48
|
+
type: entry.type,
|
|
49
|
+
size: entry.size,
|
|
50
|
+
created: entry.created,
|
|
51
|
+
modified: entry.modified,
|
|
52
|
+
contentType: entry.contentType,
|
|
53
|
+
hash: entry.hash,
|
|
54
|
+
storageClass: entry.storageClass,
|
|
55
|
+
storageId: storage.id,
|
|
56
|
+
storageName
|
|
116
57
|
};
|
|
117
58
|
}
|
|
118
|
-
function
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
59
|
+
function compareNodes(a, b) {
|
|
60
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
61
|
+
return (a.displayName || a.name).localeCompare(b.displayName || b.name);
|
|
62
|
+
}
|
|
63
|
+
function extendsEntries(next, previous) {
|
|
64
|
+
return next.length >= previous.length && previous.every((entry, i) => next[i] === entry);
|
|
65
|
+
}
|
|
66
|
+
function childrenOf(dir, sorted) {
|
|
67
|
+
if (sorted) {
|
|
68
|
+
dir.sorted ??= [...dir.directories, ...dir.files].sort(compareNodes);
|
|
69
|
+
return dir.sorted;
|
|
127
70
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
71
|
+
dir.unsorted ??= [...dir.directories, ...dir.files];
|
|
72
|
+
return dir.unsorted;
|
|
73
|
+
}
|
|
74
|
+
/** One accumulator per listed directory, so a landed page costs the page and not
|
|
75
|
+
* the tree. One builder per explorer instance. */
|
|
76
|
+
function createTreeBuilder() {
|
|
77
|
+
const directories = /* @__PURE__ */ new Map();
|
|
78
|
+
let last = null;
|
|
79
|
+
function unchanged(storages, options) {
|
|
80
|
+
if (!last || last.storages !== storages || last.options.forceUserHierarchy !== options.forceUserHierarchy || last.options.unsortedParents !== options.unsortedParents) return false;
|
|
81
|
+
const before = last.options.listings;
|
|
82
|
+
if (options.listings.size !== before.size) return false;
|
|
83
|
+
for (const [dirPath, listing] of options.listings) if (before.get(dirPath)?.entries !== listing.entries) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
function nodesFor(dirPath, storage, storagePath, storageName, entries) {
|
|
87
|
+
const cached = directories.get(dirPath);
|
|
88
|
+
const reusable = cached?.storageName === storageName ? cached : void 0;
|
|
89
|
+
if (reusable && reusable.entries === entries) return reusable;
|
|
90
|
+
let dir;
|
|
91
|
+
if (reusable && extendsEntries(entries, reusable.entries)) dir = reusable;
|
|
92
|
+
else {
|
|
93
|
+
dir = {
|
|
94
|
+
entries: [],
|
|
95
|
+
storageName,
|
|
96
|
+
directories: [],
|
|
97
|
+
files: []
|
|
98
|
+
};
|
|
99
|
+
directories.set(dirPath, dir);
|
|
100
|
+
}
|
|
101
|
+
for (let i = dir.entries.length; i < entries.length; i++) {
|
|
102
|
+
const node = toNode(entries[i], storage, storagePath, storageName);
|
|
103
|
+
if (node.path === dirPath) continue;
|
|
104
|
+
if (node.type === "directory") dir.directories.push(node);
|
|
105
|
+
else dir.files.push(node);
|
|
106
|
+
}
|
|
107
|
+
dir.entries = entries;
|
|
108
|
+
dir.unsorted = void 0;
|
|
109
|
+
dir.sorted = void 0;
|
|
110
|
+
return dir;
|
|
111
|
+
}
|
|
112
|
+
function addStorage(storage, storagePath, options, tree) {
|
|
113
|
+
const storageName = storage.bucketName || storage.name;
|
|
114
|
+
const root = {
|
|
115
|
+
name: storage.name,
|
|
116
|
+
displayName: storage.displayName,
|
|
117
|
+
path: storagePath,
|
|
118
|
+
relativePath: "",
|
|
133
119
|
type: "directory",
|
|
134
|
-
|
|
120
|
+
created: storage.created,
|
|
121
|
+
modified: storage.modified,
|
|
122
|
+
storageId: storage.id,
|
|
123
|
+
storageName,
|
|
124
|
+
storageNodeIcon: storage.type,
|
|
125
|
+
storageImageUrl: storage.imageUrl,
|
|
126
|
+
storageType: storage.type,
|
|
127
|
+
root: true
|
|
135
128
|
};
|
|
136
|
-
|
|
137
|
-
for (const
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
path: storageFullPath,
|
|
144
|
-
relativePath: "",
|
|
145
|
-
type: "directory",
|
|
146
|
-
created: storage?.created,
|
|
147
|
-
modified: storage?.modified,
|
|
148
|
-
storageId: storage.id,
|
|
149
|
-
storageName: cspBucketName,
|
|
150
|
-
storageNodeIcon: storage.type,
|
|
151
|
-
storageImageUrl: storage.imageUrl,
|
|
152
|
-
storageType: storage.type,
|
|
153
|
-
root: true
|
|
154
|
-
};
|
|
155
|
-
parentToChildrenMap[userPath].push(treeMap[storageFullPath]);
|
|
156
|
-
if (storage.objects) {
|
|
157
|
-
const { treeMap: storageTree, parentToChildrenMap: storageChildren } = buildTreeFromStorage(storage, options?.unsortedParents);
|
|
158
|
-
Object.assign(treeMap, storageTree);
|
|
159
|
-
for (const [parent, children] of Object.entries(storageChildren)) {
|
|
160
|
-
if (!parentToChildrenMap[parent]) parentToChildrenMap[parent] = [];
|
|
161
|
-
parentToChildrenMap[parent].push(...children);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
129
|
+
tree.treeMap[storagePath] = root;
|
|
130
|
+
for (const [dirPath, listing] of options.listings) {
|
|
131
|
+
if (!dirPath.startsWith(storagePath)) continue;
|
|
132
|
+
const children = childrenOf(nodesFor(dirPath, storage, storagePath, storageName, listing.entries), !options.unsortedParents?.has(dirPath));
|
|
133
|
+
if (children.length === 0) continue;
|
|
134
|
+
for (const node of children) tree.treeMap[node.path] = node;
|
|
135
|
+
tree.parentToChildrenMap[dirPath] = children;
|
|
164
136
|
}
|
|
137
|
+
return root;
|
|
165
138
|
}
|
|
166
|
-
|
|
167
|
-
if (options
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
139
|
+
return (storages, options) => {
|
|
140
|
+
if (last && unchanged(storages, options)) return last.tree;
|
|
141
|
+
const tree = {
|
|
142
|
+
treeMap: {},
|
|
143
|
+
parentToChildrenMap: {},
|
|
144
|
+
rootNodes: []
|
|
145
|
+
};
|
|
146
|
+
if (storages.length === 1 && !options.forceUserHierarchy) {
|
|
147
|
+
const storage = storages[0];
|
|
148
|
+
const storagePath = ensureTrailingSlash(storage.user ? `${storage.user}/${storage.name}` : storage.name);
|
|
149
|
+
tree.rootNodes.push(addStorage(storage, storagePath, options, tree));
|
|
150
|
+
} else {
|
|
151
|
+
const userGroups = {};
|
|
152
|
+
for (const storage of storages) {
|
|
153
|
+
const username = storage.user ?? "";
|
|
154
|
+
if (!userGroups[username]) userGroups[username] = [];
|
|
155
|
+
userGroups[username].push(storage);
|
|
156
|
+
}
|
|
157
|
+
for (const username of Object.keys(userGroups)) {
|
|
158
|
+
const userPath = ensureTrailingSlash(username);
|
|
159
|
+
const userNode = {
|
|
160
|
+
name: username,
|
|
161
|
+
path: userPath,
|
|
162
|
+
type: "directory",
|
|
163
|
+
displayName: username
|
|
164
|
+
};
|
|
165
|
+
tree.treeMap[userPath] = userNode;
|
|
166
|
+
tree.rootNodes.push(userNode);
|
|
167
|
+
const storageRoots = (userGroups[username] ?? []).map((storage) => addStorage(storage, ensureTrailingSlash(`${userPath}${removeLeadingAndTrailingSlashes(storage.name)}`), options, tree));
|
|
168
|
+
tree.parentToChildrenMap[userPath] = storageRoots.sort(compareNodes);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const dirPath of directories.keys()) if (!options.listings.has(dirPath)) directories.delete(dirPath);
|
|
172
|
+
last = {
|
|
173
|
+
storages,
|
|
174
|
+
options,
|
|
175
|
+
tree
|
|
176
|
+
};
|
|
177
|
+
return tree;
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
function getNodeChildren(parentToChildrenMap, parentPath) {
|
|
@@ -277,6 +277,9 @@ function getCspUri(csp, cspBucketName, key) {
|
|
|
277
277
|
if (!scheme) return null;
|
|
278
278
|
return `${scheme}://${cspBucketName}/${key}`;
|
|
279
279
|
}
|
|
280
|
+
function getUriScheme(uri) {
|
|
281
|
+
return uri.match(/^([a-z0-9+.-]+):\/\//i)?.[1] ?? "";
|
|
282
|
+
}
|
|
280
283
|
function removeLeadingSlash(path) {
|
|
281
284
|
return path.replace(/^\/+/, "");
|
|
282
285
|
}
|
|
@@ -448,7 +451,7 @@ function FileProgressBar({ fileProgress, fileName, progressBarColorScheme, onCan
|
|
|
448
451
|
children: [/* @__PURE__ */ jsx("div", {
|
|
449
452
|
className: cx("h-full transition-all duration-300 relative flex items-center justify-center", barColor, isHovered && "opacity-80"),
|
|
450
453
|
style: { width: `${percentage}%` }
|
|
451
|
-
}), isHovered && !isCompleted && totalBytes > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
454
|
+
}), isHovered && !isCompleted && totalBytes > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
452
455
|
className: "absolute inset-0 flex items-center justify-center text-xs whitespace-nowrap p-0.5 z-10",
|
|
453
456
|
style: {
|
|
454
457
|
WebkitClipPath: `inset(0 ${100 - percentage}% 0 0)`,
|
|
@@ -622,7 +625,7 @@ function UploadSessionToast({ session, queueLength, onCancel, onCancelFile }) {
|
|
|
622
625
|
})
|
|
623
626
|
]
|
|
624
627
|
})]
|
|
625
|
-
}), isToastExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
628
|
+
}), isToastExpanded && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
626
629
|
/* @__PURE__ */ jsxs("div", {
|
|
627
630
|
className: "flex items-center justify-between",
|
|
628
631
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1490,7 +1493,7 @@ function PropagationStage({ status, label }) {
|
|
|
1490
1493
|
"aria-hidden": "true",
|
|
1491
1494
|
children: /* @__PURE__ */ jsx("polyline", { points: "3.5 8.5 6.5 11.5 12.5 5" })
|
|
1492
1495
|
}),
|
|
1493
|
-
status === "active" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400/60" }), /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-blue-500" })] }),
|
|
1496
|
+
status === "active" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400/60" }), /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-blue-500" })] }),
|
|
1494
1497
|
status === "pending" && /* @__PURE__ */ jsx("span", { className: "inline-flex h-1.5 w-1.5 rounded-full theme-muted-text opacity-60" })
|
|
1495
1498
|
]
|
|
1496
1499
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -1686,46 +1689,60 @@ function beginLoadingMore(listings, path) {
|
|
|
1686
1689
|
const listing = listings.get(path);
|
|
1687
1690
|
if (!listing) return listings;
|
|
1688
1691
|
return new Map(listings).set(path, {
|
|
1689
|
-
...listing
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
+
...listing,
|
|
1693
|
+
loadingMore: true,
|
|
1694
|
+
error: void 0
|
|
1692
1695
|
});
|
|
1693
1696
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
function advanceListing(listings, path, page, expectedPagesLoaded) {
|
|
1697
|
-
if ((listings.get(path)?.pagesLoaded ?? 0) !== expectedPagesLoaded) return listings;
|
|
1697
|
+
function advanceListing(listings, path, page) {
|
|
1698
|
+
const listing = listings.get(path);
|
|
1698
1699
|
return new Map(listings).set(path, {
|
|
1699
|
-
|
|
1700
|
-
pagesLoaded:
|
|
1701
|
-
loadingMore: false
|
|
1700
|
+
cursor: page.cursor,
|
|
1701
|
+
pagesLoaded: (listing?.pagesLoaded ?? 0) + 1,
|
|
1702
|
+
loadingMore: false,
|
|
1703
|
+
entries: [...listing?.entries ?? [], ...page.entries]
|
|
1702
1704
|
});
|
|
1703
1705
|
}
|
|
1704
|
-
/**
|
|
1705
|
-
*
|
|
1706
|
+
/** Subfolders the page no longer names lose their records, or a re-created folder
|
|
1707
|
+
* would show old contents; the rest keep theirs, since a refresh lists them
|
|
1708
|
+
* alongside this one and a parent landing later must not undo a child's page. */
|
|
1709
|
+
function replaceListing(listings, path, page) {
|
|
1710
|
+
const prefix = ensureTrailingSlash(path);
|
|
1711
|
+
const subfolders = new Set(page.entries.filter((entry) => entry.type === "directory").map(getNodeName));
|
|
1712
|
+
const next = new Map(listings);
|
|
1713
|
+
next.delete(path);
|
|
1714
|
+
for (const key of listings.keys()) {
|
|
1715
|
+
if (!key.startsWith(prefix)) continue;
|
|
1716
|
+
if (!subfolders.has(key.slice(prefix.length).split("/")[0] ?? "")) next.delete(key);
|
|
1717
|
+
}
|
|
1718
|
+
return advanceListing(next, path, page);
|
|
1719
|
+
}
|
|
1720
|
+
/** `pagesLoaded` stays put, so a failed first page still reads as unlisted. */
|
|
1706
1721
|
function recordListingError(listings, path, error) {
|
|
1707
1722
|
const listing = listings.get(path);
|
|
1708
1723
|
return new Map(listings).set(path, {
|
|
1709
|
-
|
|
1724
|
+
cursor: listing?.cursor,
|
|
1710
1725
|
pagesLoaded: listing?.pagesLoaded ?? 0,
|
|
1711
1726
|
loadingMore: false,
|
|
1712
|
-
error
|
|
1727
|
+
error,
|
|
1728
|
+
entries: listing?.entries ?? []
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
/** Keeps the cursor and pages, so a retry resumes paging instead of starting over. */
|
|
1732
|
+
function clearListingError(listings, path) {
|
|
1733
|
+
const listing = listings.get(path);
|
|
1734
|
+
if (!listing) return listings;
|
|
1735
|
+
return new Map(listings).set(path, {
|
|
1736
|
+
...listing,
|
|
1737
|
+
error: void 0
|
|
1713
1738
|
});
|
|
1714
1739
|
}
|
|
1715
|
-
/** Slash-terminates the prefix: without it, clearing `logs` would also clear
|
|
1716
|
-
* `logs-archive/`. */
|
|
1717
1740
|
function clearListingsUnder(listings, path) {
|
|
1718
|
-
const prefix = path
|
|
1741
|
+
const prefix = ensureTrailingSlash(path);
|
|
1719
1742
|
const next = new Map(listings);
|
|
1720
1743
|
for (const key of listings.keys()) if (key === path || key.startsWith(prefix)) next.delete(key);
|
|
1721
1744
|
return next;
|
|
1722
1745
|
}
|
|
1723
|
-
/** The same prefix rule for the attempt sets that shadow `listings`. They gate
|
|
1724
|
-
* re-listing, so a path left behind here reads as tried-and-empty forever. */
|
|
1725
|
-
function forgetPathsUnder(paths, path) {
|
|
1726
|
-
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
1727
|
-
for (const key of [...paths]) if (key === path || key.startsWith(prefix)) paths.delete(key);
|
|
1728
|
-
}
|
|
1729
1746
|
/** Tree paths whose children should keep arrival order, since sorting them would
|
|
1730
1747
|
* reshuffle rows under the user mid-scroll. */
|
|
1731
1748
|
function incompletePaths(listings) {
|
|
@@ -1734,11 +1751,11 @@ function incompletePaths(listings) {
|
|
|
1734
1751
|
return paths;
|
|
1735
1752
|
}
|
|
1736
1753
|
/** What a deep link needs next. Pure so the walk is table-testable. */
|
|
1737
|
-
function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings, inFlight,
|
|
1754
|
+
function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings, inFlight, pagesPulled }) {
|
|
1738
1755
|
for (const path of ancestors) {
|
|
1739
1756
|
if (!isListableDir(path)) continue;
|
|
1740
1757
|
if (inFlight.has(path)) return { kind: "done" };
|
|
1741
|
-
if (!
|
|
1758
|
+
if (!listings.has(path)) return {
|
|
1742
1759
|
kind: "fetch",
|
|
1743
1760
|
path
|
|
1744
1761
|
};
|
|
@@ -1746,20 +1763,23 @@ function nextHydrationStep({ targetResolved, ancestors, isListableDir, listings,
|
|
|
1746
1763
|
if (targetResolved) return { kind: "done" };
|
|
1747
1764
|
const deepest = [...ancestors].reverse().find(isListableDir);
|
|
1748
1765
|
if (!deepest) return { kind: "done" };
|
|
1749
|
-
if (!hasMore(listings, deepest)) return {
|
|
1750
|
-
|
|
1751
|
-
|
|
1766
|
+
if (!hasMore(listings, deepest)) return {
|
|
1767
|
+
kind: "stuck",
|
|
1768
|
+
path: deepest
|
|
1769
|
+
};
|
|
1770
|
+
if (loadMoreError(listings, deepest)) return {
|
|
1771
|
+
kind: "stuck",
|
|
1772
|
+
path: deepest
|
|
1773
|
+
};
|
|
1774
|
+
if (pagesPulled >= 20) return {
|
|
1775
|
+
kind: "stuck",
|
|
1776
|
+
path: deepest
|
|
1777
|
+
};
|
|
1752
1778
|
return isLoadingMore(listings, deepest) ? { kind: "done" } : {
|
|
1753
1779
|
kind: "more",
|
|
1754
1780
|
path: deepest
|
|
1755
1781
|
};
|
|
1756
1782
|
}
|
|
1757
|
-
/** `replace` drops the directory's previous contents first, which is how a refresh
|
|
1758
|
-
* sheds deleted objects rather than re-adding them. */
|
|
1759
|
-
function nextObjects(previous, page, options) {
|
|
1760
|
-
if (!options.replace) return [...previous, ...page];
|
|
1761
|
-
return [...previous.filter((obj) => !(obj.path.startsWith(options.dirPath) && obj.path !== options.dirPath)), ...page];
|
|
1762
|
-
}
|
|
1763
1783
|
//#endregion
|
|
1764
1784
|
//#region src/file-explorer/FilePreview/StreamedBadge.tsx
|
|
1765
1785
|
function StreamedBadge() {
|
|
@@ -2537,7 +2557,7 @@ function useObjectText(url, { enabled, maxBytes, maxLines, knownSize, cacheKey,
|
|
|
2537
2557
|
}
|
|
2538
2558
|
//#endregion
|
|
2539
2559
|
//#region src/file-explorer/FilePreview/Viewer.tsx
|
|
2540
|
-
var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-
|
|
2560
|
+
var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-4lx3kE-w.js"));
|
|
2541
2561
|
var PREVIEW_MARKDOWN_CONTROLS = {
|
|
2542
2562
|
table: { fullscreen: false },
|
|
2543
2563
|
mermaid: { fullscreen: false }
|
|
@@ -2978,7 +2998,7 @@ function PreviewFetchState({ loading, tooLarge, error, corsError, storage, onCor
|
|
|
2978
2998
|
});
|
|
2979
2999
|
if (tooLarge) return /* @__PURE__ */ jsx(TooLargeInline, { onDownload });
|
|
2980
3000
|
if (error) return /* @__PURE__ */ jsx(PreviewError, { onDownload });
|
|
2981
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3001
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
2982
3002
|
}
|
|
2983
3003
|
function NotPreviewable({ icon, title, message, onDownload }) {
|
|
2984
3004
|
const t = useStrings().fileExplorer;
|
|
@@ -3227,7 +3247,7 @@ function FilePreview({ open, node, siblings, storage, getPresignedUrl, onNavigat
|
|
|
3227
3247
|
onClick: (e) => {
|
|
3228
3248
|
if (e.target === e.currentTarget) onClose();
|
|
3229
3249
|
},
|
|
3230
|
-
children: [isOverlay && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EdgeButton, {
|
|
3250
|
+
children: [isOverlay && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(EdgeButton, {
|
|
3231
3251
|
side: "left",
|
|
3232
3252
|
label: t.preview.previousFile,
|
|
3233
3253
|
disabled: !hasPrev,
|
|
@@ -3572,7 +3592,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
|
|
|
3572
3592
|
const storageIconAlt = useStrings().fileExplorer.chrome.storageIconAlt;
|
|
3573
3593
|
useNestedScrollerWarning(scrollEl, nodes.length);
|
|
3574
3594
|
useRowPitchAudit(scrollEl, nodes, items);
|
|
3575
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3595
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3576
3596
|
padTop > 0 && /* @__PURE__ */ jsx(Spacer, { height: padTop }, "pad-top"),
|
|
3577
3597
|
items.map((item) => {
|
|
3578
3598
|
const node = nodes[item.index];
|
|
@@ -3605,7 +3625,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
|
|
|
3605
3625
|
] });
|
|
3606
3626
|
}
|
|
3607
3627
|
function Spacer({ height }) {
|
|
3608
|
-
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("tr", {
|
|
3628
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx("tr", {
|
|
3609
3629
|
"aria-hidden": "true",
|
|
3610
3630
|
className: "border-0",
|
|
3611
3631
|
children: /* @__PURE__ */ jsx("td", {
|
|
@@ -3638,7 +3658,7 @@ function useRowPitchAudit(scrollEl, nodes, items) {
|
|
|
3638
3658
|
* would land in `<tbody>`, since Children.toArray only flattens arrays. */
|
|
3639
3659
|
function FileTableHeaders({ allSelected, someSelected, onSelectAll }) {
|
|
3640
3660
|
const t = useStrings().fileExplorer;
|
|
3641
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3661
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3642
3662
|
/* @__PURE__ */ jsx(Table.Header, {
|
|
3643
3663
|
caps: false,
|
|
3644
3664
|
className: "py-2",
|
|
@@ -3873,8 +3893,8 @@ function flattenTreeRows(roots, parentToChildrenMap, expandedPaths, options) {
|
|
|
3873
3893
|
isExpanded
|
|
3874
3894
|
});
|
|
3875
3895
|
if (!isExpanded) return;
|
|
3876
|
-
if (options?.loadingPaths?.has(node.path)) {
|
|
3877
|
-
|
|
3896
|
+
if (options?.loadingPaths?.has(node.path) && children.length === 0) {
|
|
3897
|
+
rows.push(statusRow("skeleton", node, depth + 1));
|
|
3878
3898
|
return;
|
|
3879
3899
|
}
|
|
3880
3900
|
const limit = options?.fileLimits?.get(node.path) ?? 50;
|
|
@@ -4080,7 +4100,7 @@ function indentStyle(depth, isDirectory) {
|
|
|
4080
4100
|
}
|
|
4081
4101
|
function IndentGuides({ depth }) {
|
|
4082
4102
|
if (depth === 0) return null;
|
|
4083
|
-
return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: depth }, (_, level) => /* @__PURE__ */ jsx("span", {
|
|
4103
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: Array.from({ length: depth }, (_, level) => /* @__PURE__ */ jsx("span", {
|
|
4084
4104
|
"aria-hidden": "true",
|
|
4085
4105
|
className: "pointer-events-none absolute inset-y-0 w-px bg-(--theme-border)/60",
|
|
4086
4106
|
style: { left: GUIDE_OFFSET + level * INDENT_PER_LEVEL }
|
|
@@ -4092,7 +4112,7 @@ function RowIcons({ isDirectory, isExpanded, customIcon }) {
|
|
|
4092
4112
|
children: customIcon
|
|
4093
4113
|
});
|
|
4094
4114
|
const folder = isExpanded ? /* @__PURE__ */ jsx(FolderOpenIcon, { className: "h-4 w-4 mr-1 text-sky-500" }) : /* @__PURE__ */ jsx(FolderIcon, { className: "h-4 w-4 mr-1 text-sky-500/80" });
|
|
4095
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx("h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform duration-150", isExpanded && "rotate-90") }), customIcon ?? folder] });
|
|
4115
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx("h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform duration-150", isExpanded && "rotate-90") }), customIcon ?? folder] });
|
|
4096
4116
|
}
|
|
4097
4117
|
/** Sized by the slot, not by its contents: react-loading-skeleton wraps each bar
|
|
4098
4118
|
* in an inline span, so a bar occupies a line box taller than its `height` and
|
|
@@ -4276,7 +4296,7 @@ function useTreePitchAudit(scrollEl, rows, items) {
|
|
|
4276
4296
|
//#region src/file-explorer/FileExplorer.tsx
|
|
4277
4297
|
function UriCard({ value }) {
|
|
4278
4298
|
const t = useStrings().fileExplorer;
|
|
4279
|
-
const scheme = value
|
|
4299
|
+
const scheme = getUriScheme(value);
|
|
4280
4300
|
return /* @__PURE__ */ jsxs(CopyToClipboard, {
|
|
4281
4301
|
text: value,
|
|
4282
4302
|
showIcon: false,
|
|
@@ -4344,14 +4364,13 @@ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
|
|
|
4344
4364
|
});
|
|
4345
4365
|
}
|
|
4346
4366
|
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange }) {
|
|
4367
|
+
const copySubmenu = useCopySubmenu();
|
|
4347
4368
|
const t = useStrings().fileExplorer;
|
|
4348
4369
|
const notify = useNotify();
|
|
4349
4370
|
const slots = useSlots();
|
|
4350
4371
|
const fileExplorerContext = useFileExplorer();
|
|
4351
|
-
const [localStorages, setLocalStorages] = useState(storages);
|
|
4352
4372
|
const [listings, setListings] = useState(() => /* @__PURE__ */ new Map());
|
|
4353
|
-
const fetchesInFlightRef = useRef(/* @__PURE__ */ new
|
|
4354
|
-
const listAttemptsRef = useRef(/* @__PURE__ */ new Set());
|
|
4373
|
+
const fetchesInFlightRef = useRef(/* @__PURE__ */ new Map());
|
|
4355
4374
|
const incompleteDirsRef = useRef(/* @__PURE__ */ new Set());
|
|
4356
4375
|
const incompleteDirs = useMemo(() => {
|
|
4357
4376
|
const next = incompletePaths(listings);
|
|
@@ -4360,13 +4379,15 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4360
4379
|
incompleteDirsRef.current = next;
|
|
4361
4380
|
return next;
|
|
4362
4381
|
}, [listings]);
|
|
4363
|
-
const
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
}, [
|
|
4369
|
-
|
|
4382
|
+
const [buildTree] = useState(createTreeBuilder);
|
|
4383
|
+
const { treeMap, parentToChildrenMap, rootNodes } = useMemo(() => buildTree(storages, {
|
|
4384
|
+
listings,
|
|
4385
|
+
forceUserHierarchy: showUserHierarchy,
|
|
4386
|
+
unsortedParents: incompleteDirs
|
|
4387
|
+
}), [
|
|
4388
|
+
buildTree,
|
|
4389
|
+
storages,
|
|
4390
|
+
listings,
|
|
4370
4391
|
showUserHierarchy,
|
|
4371
4392
|
incompleteDirs
|
|
4372
4393
|
]);
|
|
@@ -4427,11 +4448,6 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4427
4448
|
const isUploadSizeExceeded = (size) => {
|
|
4428
4449
|
return size > MAX_UPLOAD_SIZE;
|
|
4429
4450
|
};
|
|
4430
|
-
const rootNodes = useMemo(() => {
|
|
4431
|
-
const childPaths = /* @__PURE__ */ new Set();
|
|
4432
|
-
for (const children of Object.values(parentToChildrenMap)) for (const child of children) childPaths.add(child.path);
|
|
4433
|
-
return Object.values(treeMap).filter((node) => node.type === "directory" && node.path.endsWith("/") && !childPaths.has(node.path));
|
|
4434
|
-
}, [treeMap, parentToChildrenMap]);
|
|
4435
4451
|
const handleToggleExpand = useCallback((path) => {
|
|
4436
4452
|
setExpandedPaths((prev) => {
|
|
4437
4453
|
const next = new Set(prev);
|
|
@@ -4443,7 +4459,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4443
4459
|
setExpandedPaths(new Set([""]));
|
|
4444
4460
|
}, []);
|
|
4445
4461
|
const getStorageProviderAndClient = async (storageId) => {
|
|
4446
|
-
const storage =
|
|
4462
|
+
const storage = storages.find((s) => s.id === storageId);
|
|
4447
4463
|
if (!storage) {
|
|
4448
4464
|
notify.error(t.messages.storageUnavailable);
|
|
4449
4465
|
return;
|
|
@@ -4473,17 +4489,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4473
4489
|
const silent = !more && (options?.silent ?? false);
|
|
4474
4490
|
const nodeStorageId = node.storageId ?? "";
|
|
4475
4491
|
if (!options?.replace && fetchesInFlightRef.current.has(node.path)) return { corsError: false };
|
|
4476
|
-
|
|
4492
|
+
const request = silent ? void 0 : {};
|
|
4493
|
+
if (request) fetchesInFlightRef.current.set(node.path, request);
|
|
4494
|
+
const owns = () => request !== void 0 && fetchesInFlightRef.current.get(node.path) === request;
|
|
4477
4495
|
if (more) setListings((prev) => beginLoadingMore(prev, node.path));
|
|
4478
|
-
else
|
|
4479
|
-
if (options?.replace) {
|
|
4480
|
-
setListings((prev) => clearListingsUnder(prev, node.path));
|
|
4481
|
-
forgetPathsUnder(listAttemptsRef.current, node.path);
|
|
4482
|
-
}
|
|
4483
|
-
if (!silent) setLoadingPaths((prev) => new Set(prev).add(node.path));
|
|
4484
|
-
}
|
|
4496
|
+
else if (request) setLoadingPaths((prev) => new Set(prev).add(node.path));
|
|
4485
4497
|
const cursor = more ? listings.get(node.path)?.cursor : void 0;
|
|
4486
|
-
const pagesBefore = options?.replace ? 0 : listings.get(node.path)?.pagesLoaded ?? 0;
|
|
4487
4498
|
try {
|
|
4488
4499
|
const factory = await getStorageProviderAndClient(node.storageId);
|
|
4489
4500
|
if (!factory) throw new Error("Failed to connect to storage provider.");
|
|
@@ -4493,21 +4504,17 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4493
4504
|
const dir_path = node.root ? "" : node.relativePath ?? "";
|
|
4494
4505
|
const input = provider.listDirectoryInput(storage, dir_path, cursor ? { cursor } : {});
|
|
4495
4506
|
const objects = await client.listDirectory(input);
|
|
4496
|
-
const
|
|
4507
|
+
const entries = provider.convertDataToStorageObjects(objects);
|
|
4497
4508
|
const nextCursor = provider.nextPageCursor?.(objects);
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
};
|
|
4505
|
-
if (!silent) {
|
|
4506
|
-
setLocalStorages((prev) => prev.map((s) => s.id === storage.id ? updatedStorage : s));
|
|
4507
|
-
setListings((prev) => advanceListing(prev, node.path, nextCursor ? { cursor: nextCursor } : {}, pagesBefore));
|
|
4509
|
+
if (owns()) {
|
|
4510
|
+
const page = {
|
|
4511
|
+
entries,
|
|
4512
|
+
cursor: nextCursor
|
|
4513
|
+
};
|
|
4514
|
+
setListings((prev) => options?.replace ? replaceListing(prev, node.path, page) : advanceListing(prev, node.path, page));
|
|
4508
4515
|
}
|
|
4509
4516
|
}
|
|
4510
|
-
if (
|
|
4517
|
+
if (owns()) setHasCorsIssue((prev) => {
|
|
4511
4518
|
if (!prev.has(nodeStorageId)) return prev;
|
|
4512
4519
|
const next = new Map(prev);
|
|
4513
4520
|
next.delete(nodeStorageId);
|
|
@@ -4515,26 +4522,23 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4515
4522
|
});
|
|
4516
4523
|
return { corsError: false };
|
|
4517
4524
|
} catch (error) {
|
|
4518
|
-
const
|
|
4519
|
-
|
|
4520
|
-
if (
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
setHasCorsIssue((prev) => new Map(prev).set(nodeStorageId, true));
|
|
4525
|
-
}
|
|
4526
|
-
return { corsError: true };
|
|
4525
|
+
const isCorsError = error instanceof CorsError;
|
|
4526
|
+
const errorMessage = isCorsError ? error.message || "CORS error" : error instanceof Error ? error.message : "Unknown error";
|
|
4527
|
+
if (owns()) {
|
|
4528
|
+
setListings((prev) => recordListingError(options?.replace ? clearListingsUnder(prev, node.path) : prev, node.path, errorMessage));
|
|
4529
|
+
updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, `Failed to load contents for ${node?.displayName || node?.name}: ${errorMessage}`));
|
|
4530
|
+
if (isCorsError) setHasCorsIssue((prev) => new Map(prev).set(nodeStorageId, true));
|
|
4527
4531
|
}
|
|
4528
|
-
|
|
4529
|
-
updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, message));
|
|
4530
|
-
return { corsError: false };
|
|
4532
|
+
return { corsError: isCorsError };
|
|
4531
4533
|
} finally {
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4534
|
+
if (owns()) {
|
|
4535
|
+
fetchesInFlightRef.current.delete(node.path);
|
|
4536
|
+
if (!more) setLoadingPaths((prev) => {
|
|
4537
|
+
const next = new Set(prev);
|
|
4538
|
+
next.delete(node.path);
|
|
4539
|
+
return next;
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4538
4542
|
}
|
|
4539
4543
|
});
|
|
4540
4544
|
const handleSelectTreeItem = async (path, node) => {
|
|
@@ -4556,8 +4560,8 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4556
4560
|
return next;
|
|
4557
4561
|
});
|
|
4558
4562
|
}, [normalizedSelectedPath]);
|
|
4559
|
-
const [
|
|
4560
|
-
const hydrationStuck =
|
|
4563
|
+
const [stuck, setStuck] = useState(null);
|
|
4564
|
+
const hydrationStuck = stuck?.target === normalizedSelectedPath;
|
|
4561
4565
|
const hydrationPagesRef = useRef({
|
|
4562
4566
|
path: "",
|
|
4563
4567
|
pages: 0
|
|
@@ -4577,18 +4581,19 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4577
4581
|
},
|
|
4578
4582
|
listings,
|
|
4579
4583
|
inFlight: fetchesInFlightRef.current,
|
|
4580
|
-
attempted: listAttemptsRef.current,
|
|
4581
4584
|
pagesPulled: hydrationPagesRef.current.pages
|
|
4582
4585
|
});
|
|
4583
4586
|
if (step.kind === "stuck") {
|
|
4584
|
-
|
|
4587
|
+
setStuck({
|
|
4588
|
+
target: normalizedSelectedPath,
|
|
4589
|
+
deepest: step.path
|
|
4590
|
+
});
|
|
4585
4591
|
return;
|
|
4586
4592
|
}
|
|
4587
4593
|
if (step.kind === "done") return;
|
|
4588
4594
|
const node = treeMap[step.path];
|
|
4589
4595
|
if (!node) return;
|
|
4590
4596
|
if (step.kind === "fetch") {
|
|
4591
|
-
listAttemptsRef.current.add(step.path);
|
|
4592
4597
|
fetchNodeChildren(node);
|
|
4593
4598
|
return;
|
|
4594
4599
|
}
|
|
@@ -4602,8 +4607,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4602
4607
|
useEffect(() => {
|
|
4603
4608
|
for (const path of expandedPaths) {
|
|
4604
4609
|
const node = treeMap[path];
|
|
4605
|
-
if (node?.type === "directory" && node.storageId && !
|
|
4606
|
-
listAttemptsRef.current.add(path);
|
|
4610
|
+
if (node?.type === "directory" && node.storageId && !listings.has(path) && !fetchesInFlightRef.current.has(path)) {
|
|
4607
4611
|
fetchNodeChildren(node);
|
|
4608
4612
|
return;
|
|
4609
4613
|
}
|
|
@@ -4806,7 +4810,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4806
4810
|
};
|
|
4807
4811
|
const openCreateFolder = async (node) => {
|
|
4808
4812
|
if (!isStorageDirectory(node)) return;
|
|
4809
|
-
if (!
|
|
4813
|
+
if (!isListed(listings, node.path)) await fetchNodeChildren(node);
|
|
4810
4814
|
setCreateFolderTarget(node);
|
|
4811
4815
|
};
|
|
4812
4816
|
const getCreateFolderActions = (node, storage) => {
|
|
@@ -4859,15 +4863,11 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4859
4863
|
icon: isFile ? /* @__PURE__ */ jsx(FileTextIcon, {}) : /* @__PURE__ */ jsx(FolderIcon, {}),
|
|
4860
4864
|
onSelect: () => handleSelectTreeItem(node.path, node)
|
|
4861
4865
|
}];
|
|
4862
|
-
const uriExtras = [{
|
|
4863
|
-
label:
|
|
4864
|
-
value
|
|
4866
|
+
const uriExtras = [pwUri, cspUri].filter((uri) => !!uri).map((value) => ({
|
|
4867
|
+
label: `${getUriScheme(value).toUpperCase()} URI`,
|
|
4868
|
+
value,
|
|
4865
4869
|
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
4866
|
-
}
|
|
4867
|
-
label: "S3 URI",
|
|
4868
|
-
value: cspUri,
|
|
4869
|
-
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
4870
|
-
}].filter((u) => u.value);
|
|
4870
|
+
}));
|
|
4871
4871
|
return assembleRowMenu({
|
|
4872
4872
|
lead,
|
|
4873
4873
|
actions,
|
|
@@ -4921,7 +4921,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
4921
4921
|
else if (selectedNode && selectedNode.type === "file") filesToDownload = [selectedNode];
|
|
4922
4922
|
if (filesToDownload.length === 0) return "";
|
|
4923
4923
|
const commands = filesToDownload.map((node) => {
|
|
4924
|
-
const storage =
|
|
4924
|
+
const storage = storages.find((s) => s.id === node.storageId);
|
|
4925
4925
|
if (!storage?.user || !storage.name) return "";
|
|
4926
4926
|
return `pw buckets cp ${shellSingleQuote(getPwUri(storage.user, storage.name, getObjectKeyFromNode(node)))} ${shellSingleQuote(`./${node.name}`)}`;
|
|
4927
4927
|
});
|
|
@@ -5053,7 +5053,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5053
5053
|
notify.error(t.messages.uploadUnsupported);
|
|
5054
5054
|
return;
|
|
5055
5055
|
}
|
|
5056
|
-
const sessionId = fileExplorerContext.addUploadSession(storageName, cleanTargetPath, pendingUpload.uploadNodes, client, provider, targetNode, fetchNodeChildren);
|
|
5056
|
+
const sessionId = fileExplorerContext.addUploadSession(storageName, cleanTargetPath, pendingUpload.uploadNodes, client, provider, targetNode, (node) => fetchNodeChildren(node, { replace: true }));
|
|
5057
5057
|
const sessionQueueIndex = fileExplorerContext.getUploadQueueIndex(sessionId);
|
|
5058
5058
|
if (sessionQueueIndex === -1) {
|
|
5059
5059
|
notify.update(sessionId, {
|
|
@@ -5181,12 +5181,13 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5181
5181
|
children: !selectedNode && normalizedSelectedPath ? hydrationStuck ? /* @__PURE__ */ jsx(NotFoundPreview, {
|
|
5182
5182
|
path: normalizedSelectedPath,
|
|
5183
5183
|
onRetry: () => {
|
|
5184
|
-
|
|
5184
|
+
const deepest = stuck?.deepest;
|
|
5185
5185
|
hydrationPagesRef.current = {
|
|
5186
5186
|
path: "",
|
|
5187
5187
|
pages: 0
|
|
5188
5188
|
};
|
|
5189
|
-
|
|
5189
|
+
setStuck(null);
|
|
5190
|
+
if (deepest) setListings((prev) => hasMore(prev, deepest) ? clearListingError(prev, deepest) : clearListingsUnder(prev, deepest));
|
|
5190
5191
|
}
|
|
5191
5192
|
}) : /* @__PURE__ */ jsx(ListSkeleton, {}) : !selectedNode && showUserHierarchy ? /* @__PURE__ */ jsxs("div", {
|
|
5192
5193
|
className: "overflow-auto h-full",
|
|
@@ -5228,7 +5229,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5228
5229
|
selectedStorage: null,
|
|
5229
5230
|
onRefresh: handleRefresh
|
|
5230
5231
|
})]
|
|
5231
|
-
}) : !selectedNode ? /* @__PURE__ */ jsx(NoFileSelectedPreview, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5232
|
+
}) : !selectedNode ? /* @__PURE__ */ jsx(NoFileSelectedPreview, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5232
5233
|
/* @__PURE__ */ jsxs("div", {
|
|
5233
5234
|
className: "flex items-center justify-between gap-3 px-4 h-12 shrink-0 border-b theme-border",
|
|
5234
5235
|
children: [renderBreadcrumb(), /* @__PURE__ */ jsxs("div", {
|
|
@@ -5320,7 +5321,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5320
5321
|
className: "text-sm rounded-sm hover:theme-hover cursor-pointer w-full items-center px-2 py-1.5",
|
|
5321
5322
|
onClick: handleDownload,
|
|
5322
5323
|
children: "From browser"
|
|
5323
|
-
}) }), selectedNodeSupportsPwUri && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MenuSeparator, { className: "my-0.5 h-px theme-border" }), /* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
5324
|
+
}) }), selectedNodeSupportsPwUri && /* @__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", {
|
|
5324
5325
|
className: "text-sm w-full text-center pointer-events-none px-2 py-1.5",
|
|
5325
5326
|
children: "Using PW CLI"
|
|
5326
5327
|
}), (() => {
|
|
@@ -5391,7 +5392,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5391
5392
|
onSelectAll: handleBatchCheckbox
|
|
5392
5393
|
})
|
|
5393
5394
|
}), /* @__PURE__ */ jsx(NoDataAvailablePreview, {
|
|
5394
|
-
selectedStorage:
|
|
5395
|
+
selectedStorage: storages.find((s) => s.id === selectedNode.storageId) || null,
|
|
5395
5396
|
canWrite: selectedNodeCanWrite,
|
|
5396
5397
|
onRefresh: handleRefresh,
|
|
5397
5398
|
error: hasCorsIssue.get(selectedNode.storageId ?? "") ? new CorsError(connectionIssuesMessage.get(selectedNode.storageId ?? "") || "") : null,
|
|
@@ -5424,7 +5425,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5424
5425
|
})]
|
|
5425
5426
|
}), effectiveTab === "preview" && previewable ? /* @__PURE__ */ jsx(FilePreviewInline, {
|
|
5426
5427
|
node: selectedNode,
|
|
5427
|
-
storage:
|
|
5428
|
+
storage: storages.find((s) => s.id === selectedNode.storageId) ?? null,
|
|
5428
5429
|
getPresignedUrl,
|
|
5429
5430
|
onOpenFull: openPreview,
|
|
5430
5431
|
onDownload: downloadNode,
|
|
@@ -5456,7 +5457,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5456
5457
|
className: "tabular-nums",
|
|
5457
5458
|
children: formatFileSize(selectedNode.size)
|
|
5458
5459
|
}),
|
|
5459
|
-
modifiedDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
5460
|
+
modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
5460
5461
|
"aria-hidden": true,
|
|
5461
5462
|
children: "·"
|
|
5462
5463
|
}), /* @__PURE__ */ jsxs("span", { children: ["Modified ", modifiedDt.toRelative()] })] })
|
|
@@ -5486,21 +5487,21 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5486
5487
|
}), /* @__PURE__ */ jsxs("dl", {
|
|
5487
5488
|
className: "grid grid-cols-[minmax(0,140px)_1fr] gap-x-6 gap-y-2 text-sm",
|
|
5488
5489
|
children: [
|
|
5489
|
-
typeof selectedNode.size === "number" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5490
|
+
typeof selectedNode.size === "number" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5490
5491
|
className: "theme-muted-text",
|
|
5491
5492
|
children: t.chrome.size
|
|
5492
5493
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5493
5494
|
className: "tabular-nums",
|
|
5494
5495
|
children: formatFileSize(selectedNode.size)
|
|
5495
5496
|
})] }),
|
|
5496
|
-
selectedNode.contentType && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5497
|
+
selectedNode.contentType && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5497
5498
|
className: "theme-muted-text",
|
|
5498
5499
|
children: "Content Type"
|
|
5499
5500
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5500
5501
|
className: "font-mono text-xs",
|
|
5501
5502
|
children: selectedNode.contentType
|
|
5502
5503
|
})] }),
|
|
5503
|
-
createdDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5504
|
+
createdDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5504
5505
|
className: "theme-muted-text",
|
|
5505
5506
|
children: t.chrome.created
|
|
5506
5507
|
}), /* @__PURE__ */ jsxs("dd", { children: [
|
|
@@ -5511,7 +5512,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5511
5512
|
children: ["· ", createdDt.toRelative()]
|
|
5512
5513
|
})
|
|
5513
5514
|
] })] }),
|
|
5514
|
-
modifiedDt && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5515
|
+
modifiedDt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5515
5516
|
className: "theme-muted-text",
|
|
5516
5517
|
children: "Last Modified"
|
|
5517
5518
|
}), /* @__PURE__ */ jsxs("dd", { children: [
|
|
@@ -5522,14 +5523,14 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5522
5523
|
children: ["· ", modifiedDt.toRelative()]
|
|
5523
5524
|
})
|
|
5524
5525
|
] })] }),
|
|
5525
|
-
selectedNode.hash && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5526
|
+
selectedNode.hash && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5526
5527
|
className: "theme-muted-text",
|
|
5527
5528
|
children: t.chrome.hash
|
|
5528
5529
|
}), /* @__PURE__ */ jsx("dd", {
|
|
5529
5530
|
className: "font-mono text-xs break-all",
|
|
5530
5531
|
children: selectedNode.hash.replace(/^"|"$/g, "")
|
|
5531
5532
|
})] }),
|
|
5532
|
-
selectedNode.storageClass && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5533
|
+
selectedNode.storageClass && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("dt", {
|
|
5533
5534
|
className: "theme-muted-text",
|
|
5534
5535
|
children: "Storage Class"
|
|
5535
5536
|
}), /* @__PURE__ */ jsx("dd", {
|
|
@@ -5551,7 +5552,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5551
5552
|
open: previewOpen,
|
|
5552
5553
|
node: previewNode,
|
|
5553
5554
|
siblings: previewSiblings,
|
|
5554
|
-
storage:
|
|
5555
|
+
storage: storages.find((s) => s.id === previewNode?.storageId) ?? null,
|
|
5555
5556
|
getPresignedUrl,
|
|
5556
5557
|
onNavigate: setPreviewNode,
|
|
5557
5558
|
onClose: closePreview,
|
|
@@ -5658,7 +5659,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
5658
5659
|
open: accessDrawerOpen,
|
|
5659
5660
|
setOpen: setAccessDrawerOpen,
|
|
5660
5661
|
groups,
|
|
5661
|
-
storage:
|
|
5662
|
+
storage: storages.find((s) => s.id === selectedNode.storageId)
|
|
5662
5663
|
})
|
|
5663
5664
|
]
|
|
5664
5665
|
});
|