@aws-amplify/ui-react-storage 3.14.0 → 3.15.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/dist/browser.js +8 -2
- package/dist/{createStorageBrowser-CotOvK0A.js → createStorageBrowser-CG-6mXiT.js} +392 -148
- package/dist/esm/browser.mjs +1 -0
- package/dist/esm/components/StorageBrowser/ErrorBoundary/ErrorBoundary.mjs +1 -0
- package/dist/esm/components/StorageBrowser/StorageBrowserAmplify.mjs +1 -0
- package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +1 -0
- package/dist/esm/components/StorageBrowser/actions/handlers/defaults.mjs +1 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/listLocations.mjs +7 -2
- package/dist/esm/components/StorageBrowser/actions/handlers/utils.mjs +65 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/zipdownload.mjs +195 -0
- package/dist/esm/components/StorageBrowser/adapters/createAmplifyAuthAdapter/createAmplifyListLocationsHandler.mjs +3 -1
- package/dist/esm/components/StorageBrowser/adapters/createManagedAuthAdapter/createManagedAuthAdapter.mjs +1 -0
- package/dist/esm/components/StorageBrowser/components/ComponentsProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/components/base/preview/DownloadButton.mjs +1 -0
- package/dist/esm/components/StorageBrowser/controls/DataTableControl.mjs +1 -0
- package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +1 -0
- package/dist/esm/components/StorageBrowser/createStorageBrowser/createProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +1 -0
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/downloadView.mjs +2 -0
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/shared.mjs +2 -0
- package/dist/esm/components/StorageBrowser/locationItems/context.mjs +1 -0
- package/dist/esm/components/StorageBrowser/tasks/constants.mjs +2 -0
- package/dist/esm/components/StorageBrowser/tasks/useProcessTasks.mjs +10 -4
- package/dist/esm/components/StorageBrowser/tasks/utils.mjs +4 -1
- package/dist/esm/components/StorageBrowser/useAction/useHandler.mjs +1 -1
- package/dist/esm/components/StorageBrowser/useAction/utils.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyViewProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/FoldersMessageControl.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useCopyView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useFolders.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/CreateFolderView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/useCreateFolderView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/useDeleteView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadViewProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/useLocationDetailView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsViewProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/LocationsView/useLocationsView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/hooks/useFilePreview/useFilePreview.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/constants.mjs +4 -0
- package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/utils.mjs +1 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +2 -1
- package/dist/styles.css +17 -0
- package/dist/types/components/StorageBrowser/actions/handlers/index.d.ts +1 -0
- package/dist/types/components/StorageBrowser/actions/handlers/types.d.ts +4 -2
- package/dist/types/components/StorageBrowser/actions/handlers/utils.d.ts +11 -0
- package/dist/types/components/StorageBrowser/actions/handlers/zipdownload.d.ts +3 -0
- package/dist/types/components/StorageBrowser/displayText/types.d.ts +2 -0
- package/dist/types/components/StorageBrowser/tasks/types.d.ts +3 -3
- package/dist/types/components/StorageBrowser/tasks/useProcessTasks.d.ts +1 -1
- package/dist/types/components/StorageBrowser/useAction/useHandler.d.ts +1 -1
- package/dist/types/components/StorageBrowser/views/utils/tableResolvers/constants.d.ts +4 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +11 -8
- package/dist/esm/components/StorageBrowser/actions/handlers/download.mjs +0 -38
|
@@ -10,8 +10,11 @@ var uiReact = require('@aws-amplify/ui-react');
|
|
|
10
10
|
var elements = require('@aws-amplify/ui-react-core/elements');
|
|
11
11
|
var internal = require('@aws-amplify/ui-react/internal');
|
|
12
12
|
var uiReactCore = require('@aws-amplify/ui-react-core');
|
|
13
|
+
var JSZip = require('jszip');
|
|
13
14
|
var storage = require('aws-amplify/storage');
|
|
14
15
|
|
|
16
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
|
+
|
|
15
18
|
function _interopNamespace(e) {
|
|
16
19
|
if (e && e.__esModule) return e;
|
|
17
20
|
var n = Object.create(null);
|
|
@@ -31,8 +34,185 @@ function _interopNamespace(e) {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
37
|
+
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
38
|
+
|
|
39
|
+
const VERSION = '3.15.0';
|
|
40
|
+
|
|
41
|
+
const DEFAULT_CHECKSUM_ALGORITHM = 'crc-32';
|
|
42
|
+
// 5MiB for multipart upload
|
|
43
|
+
// https://github.com/aws-amplify/amplify-js/blob/1a5366d113c9af4ce994168653df3aadb142c581/packages/storage/src/providers/s3/utils/constants.ts#L16
|
|
44
|
+
const MULTIPART_UPLOAD_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
34
45
|
|
|
35
|
-
const
|
|
46
|
+
const getBucketRegion = (bucketName, fallbackRegion) => {
|
|
47
|
+
try {
|
|
48
|
+
const config = awsAmplify.Amplify.getConfig()?.Storage?.S3;
|
|
49
|
+
if (!config?.buckets || typeof config.buckets !== 'object') {
|
|
50
|
+
return fallbackRegion;
|
|
51
|
+
}
|
|
52
|
+
for (const bucketConfig of Object.values(config.buckets)) {
|
|
53
|
+
if (bucketConfig.bucketName === bucketName && bucketConfig.region) {
|
|
54
|
+
return bucketConfig.region;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return fallbackRegion;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return fallbackRegion;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const constructBucket$1 = ({ bucket: bucketName, region: globalRegion, }) => {
|
|
64
|
+
const bucketRegion = getBucketRegion(bucketName, globalRegion);
|
|
65
|
+
return { bucketName, region: bucketRegion };
|
|
66
|
+
};
|
|
67
|
+
const parseAccessGrantLocation = (location) => {
|
|
68
|
+
const { permission, scope, type } = location;
|
|
69
|
+
if (!scope.startsWith('s3://')) {
|
|
70
|
+
throw new Error(`Invalid scope: ${scope}`);
|
|
71
|
+
}
|
|
72
|
+
const id = crypto.randomUUID();
|
|
73
|
+
// remove default path
|
|
74
|
+
const slicedScope = scope.slice(5);
|
|
75
|
+
let bucket, prefix;
|
|
76
|
+
switch (type) {
|
|
77
|
+
case 'BUCKET': {
|
|
78
|
+
// { scope: 's3://bucket/*', type: 'BUCKET', },
|
|
79
|
+
bucket = slicedScope.slice(0, -2);
|
|
80
|
+
prefix = '';
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'PREFIX': {
|
|
84
|
+
// { scope: 's3://bucket/path/*', type: 'PREFIX', },
|
|
85
|
+
bucket = slicedScope.slice(0, slicedScope.indexOf('/'));
|
|
86
|
+
prefix = `${slicedScope.slice(bucket.length + 1, -1)}`;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'OBJECT': {
|
|
90
|
+
// { scope: 's3://bucket/path/to/object', type: 'OBJECT', },
|
|
91
|
+
bucket = slicedScope.slice(0, slicedScope.indexOf('/'));
|
|
92
|
+
prefix = slicedScope.slice(bucket.length + 1);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
default: {
|
|
96
|
+
throw new Error(`Invalid location type: ${type}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
let permissions;
|
|
100
|
+
switch (permission) {
|
|
101
|
+
case 'READ':
|
|
102
|
+
permissions = ['get', 'list'];
|
|
103
|
+
break;
|
|
104
|
+
case 'READWRITE':
|
|
105
|
+
permissions = ['delete', 'get', 'list', 'write'];
|
|
106
|
+
break;
|
|
107
|
+
case 'WRITE':
|
|
108
|
+
permissions = ['delete', 'write'];
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Invalid location permission: ${permission}`);
|
|
112
|
+
}
|
|
113
|
+
return { bucket, id, permissions: permissions, prefix, type };
|
|
114
|
+
};
|
|
115
|
+
const isSamePermissions = (permissionsToExclude, locationPermissions) => {
|
|
116
|
+
if (permissionsToExclude.length !== locationPermissions.length) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const sortedLocationPermissions = locationPermissions.sort();
|
|
120
|
+
return permissionsToExclude
|
|
121
|
+
.sort()
|
|
122
|
+
.every((permission, index) => permission === sortedLocationPermissions[index]);
|
|
123
|
+
};
|
|
124
|
+
const isSameType = (typeToExclude, locationType) => typeof typeToExclude === 'string'
|
|
125
|
+
? typeToExclude === locationType
|
|
126
|
+
: typeToExclude.includes(locationType);
|
|
127
|
+
const shouldExcludeLocation = ({ permissions, type }, exclude) => {
|
|
128
|
+
const excludedByPermssions = !!(exclude?.exactPermissions &&
|
|
129
|
+
isSamePermissions(exclude.exactPermissions, permissions));
|
|
130
|
+
const excludedByType = !!(exclude?.type && isSameType(exclude.type, type));
|
|
131
|
+
return excludedByPermssions || excludedByType;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Determines if permissions1 is a strict superset (broader) of permissions2.
|
|
135
|
+
* Returns true only if permissions1 contains all permissions from permissions2
|
|
136
|
+
* AND has more permissions.
|
|
137
|
+
*/
|
|
138
|
+
const hasBroaderPermissions = (permissions1, permissions2) => {
|
|
139
|
+
// permissions1 must have more permissions (strict superset, not equal)
|
|
140
|
+
if (permissions1.length <= permissions2.length) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
// permissions1 must contain all permissions from permissions2
|
|
144
|
+
return permissions2.every((perm) => permissions1.includes(perm));
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Deduplicates locations with the same bucket and prefix.
|
|
148
|
+
* Only deduplicates when one location's permissions are a superset of another's.
|
|
149
|
+
* This prevents deduplication of incompatible grants like READ + WRITE.
|
|
150
|
+
*
|
|
151
|
+
* Examples:
|
|
152
|
+
* - READ + READWRITE → Keep READWRITE (superset)
|
|
153
|
+
* - READ + READ → Keep first (identical)
|
|
154
|
+
* - READ + WRITE → Keep both (not superset, need separate locations)
|
|
155
|
+
*/
|
|
156
|
+
const deduplicateLocations = (locations) => {
|
|
157
|
+
// Group locations by bucket:prefix
|
|
158
|
+
const locationGroups = new Map();
|
|
159
|
+
for (const location of locations) {
|
|
160
|
+
const key = `${location.bucket}:${location.prefix}`;
|
|
161
|
+
const group = locationGroups.get(key) ?? [];
|
|
162
|
+
group.push(location);
|
|
163
|
+
locationGroups.set(key, group);
|
|
164
|
+
}
|
|
165
|
+
// For each group, keep only non-redundant locations
|
|
166
|
+
const result = [];
|
|
167
|
+
for (const group of locationGroups.values()) {
|
|
168
|
+
if (group.length === 1) {
|
|
169
|
+
result.push(group[0]);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Find locations that are not subsets of any other location in the group
|
|
173
|
+
const nonRedundant = [];
|
|
174
|
+
for (const location of group) {
|
|
175
|
+
// Check if this location is a subset of any other location
|
|
176
|
+
const isSubsetOfAnother = group.some((other) => {
|
|
177
|
+
if (other === location)
|
|
178
|
+
return false;
|
|
179
|
+
return hasBroaderPermissions(other.permissions, location.permissions);
|
|
180
|
+
});
|
|
181
|
+
if (!isSubsetOfAnother) {
|
|
182
|
+
// Check if we already have an identical permission set
|
|
183
|
+
const isDuplicate = nonRedundant.some((existing) => {
|
|
184
|
+
const sortedNew = [...location.permissions].sort().join(',');
|
|
185
|
+
const sortedExisting = [...existing.permissions].sort().join(',');
|
|
186
|
+
return sortedNew === sortedExisting;
|
|
187
|
+
});
|
|
188
|
+
if (!isDuplicate) {
|
|
189
|
+
nonRedundant.push(location);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
result.push(...nonRedundant);
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
};
|
|
197
|
+
const getFilteredLocations = (locations, exclude) => locations.reduce((filteredLocations, location) => {
|
|
198
|
+
const parsedLocation = parseAccessGrantLocation(location);
|
|
199
|
+
const isNonFolderLikePrefix = !parsedLocation.prefix.endsWith('/') &&
|
|
200
|
+
parsedLocation.type === 'PREFIX';
|
|
201
|
+
if (isNonFolderLikePrefix) {
|
|
202
|
+
return filteredLocations;
|
|
203
|
+
}
|
|
204
|
+
if (!shouldExcludeLocation(parsedLocation, exclude)) {
|
|
205
|
+
filteredLocations.push(parsedLocation);
|
|
206
|
+
}
|
|
207
|
+
return filteredLocations;
|
|
208
|
+
}, []);
|
|
209
|
+
const getFileKey = (key) => key.slice(key.lastIndexOf('/') + 1, key.length);
|
|
210
|
+
const createFileDataItem = (data) => ({
|
|
211
|
+
...data,
|
|
212
|
+
fileKey: getFileKey(data.key),
|
|
213
|
+
});
|
|
214
|
+
const getProgress = ({ totalBytes, transferredBytes, }) => totalBytes ? transferredBytes / totalBytes : undefined;
|
|
215
|
+
const isMultipartUpload = (file) => file.size > MULTIPART_UPLOAD_THRESHOLD_BYTES;
|
|
36
216
|
|
|
37
217
|
const toAccessGrantPermission = (permission) => {
|
|
38
218
|
let result = '';
|
|
@@ -118,7 +298,8 @@ const createAmplifyListLocationsHandler = () => {
|
|
|
118
298
|
id: crypto.randomUUID(),
|
|
119
299
|
};
|
|
120
300
|
});
|
|
121
|
-
|
|
301
|
+
// Deduplicate locations with the same bucket and prefix, keeping broader permissions
|
|
302
|
+
cachedItems = deduplicateLocations(sanitizedItems);
|
|
122
303
|
return getPaginatedLocations({
|
|
123
304
|
items: cachedItems,
|
|
124
305
|
pageSize,
|
|
@@ -163,123 +344,11 @@ const createAmplifyAuthAdapter = () => {
|
|
|
163
344
|
};
|
|
164
345
|
};
|
|
165
346
|
|
|
166
|
-
const DEFAULT_CHECKSUM_ALGORITHM = 'crc-32';
|
|
167
|
-
// 5MiB for multipart upload
|
|
168
|
-
// https://github.com/aws-amplify/amplify-js/blob/1a5366d113c9af4ce994168653df3aadb142c581/packages/storage/src/providers/s3/utils/constants.ts#L16
|
|
169
|
-
const MULTIPART_UPLOAD_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
170
|
-
|
|
171
|
-
const getBucketRegion = (bucketName, fallbackRegion) => {
|
|
172
|
-
try {
|
|
173
|
-
const config = awsAmplify.Amplify.getConfig()?.Storage?.S3;
|
|
174
|
-
if (!config?.buckets || typeof config.buckets !== 'object') {
|
|
175
|
-
return fallbackRegion;
|
|
176
|
-
}
|
|
177
|
-
for (const bucketConfig of Object.values(config.buckets)) {
|
|
178
|
-
if (bucketConfig.bucketName === bucketName && bucketConfig.region) {
|
|
179
|
-
return bucketConfig.region;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return fallbackRegion;
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
return fallbackRegion;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const constructBucket = ({ bucket: bucketName, region: globalRegion, }) => {
|
|
189
|
-
const bucketRegion = getBucketRegion(bucketName, globalRegion);
|
|
190
|
-
return { bucketName, region: bucketRegion };
|
|
191
|
-
};
|
|
192
|
-
const parseAccessGrantLocation = (location) => {
|
|
193
|
-
const { permission, scope, type } = location;
|
|
194
|
-
if (!scope.startsWith('s3://')) {
|
|
195
|
-
throw new Error(`Invalid scope: ${scope}`);
|
|
196
|
-
}
|
|
197
|
-
const id = crypto.randomUUID();
|
|
198
|
-
// remove default path
|
|
199
|
-
const slicedScope = scope.slice(5);
|
|
200
|
-
let bucket, prefix;
|
|
201
|
-
switch (type) {
|
|
202
|
-
case 'BUCKET': {
|
|
203
|
-
// { scope: 's3://bucket/*', type: 'BUCKET', },
|
|
204
|
-
bucket = slicedScope.slice(0, -2);
|
|
205
|
-
prefix = '';
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
case 'PREFIX': {
|
|
209
|
-
// { scope: 's3://bucket/path/*', type: 'PREFIX', },
|
|
210
|
-
bucket = slicedScope.slice(0, slicedScope.indexOf('/'));
|
|
211
|
-
prefix = `${slicedScope.slice(bucket.length + 1, -1)}`;
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
case 'OBJECT': {
|
|
215
|
-
// { scope: 's3://bucket/path/to/object', type: 'OBJECT', },
|
|
216
|
-
bucket = slicedScope.slice(0, slicedScope.indexOf('/'));
|
|
217
|
-
prefix = slicedScope.slice(bucket.length + 1);
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
default: {
|
|
221
|
-
throw new Error(`Invalid location type: ${type}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
let permissions;
|
|
225
|
-
switch (permission) {
|
|
226
|
-
case 'READ':
|
|
227
|
-
permissions = ['get', 'list'];
|
|
228
|
-
break;
|
|
229
|
-
case 'READWRITE':
|
|
230
|
-
permissions = ['delete', 'get', 'list', 'write'];
|
|
231
|
-
break;
|
|
232
|
-
case 'WRITE':
|
|
233
|
-
permissions = ['delete', 'write'];
|
|
234
|
-
break;
|
|
235
|
-
default:
|
|
236
|
-
throw new Error(`Invalid location permission: ${permission}`);
|
|
237
|
-
}
|
|
238
|
-
return { bucket, id, permissions: permissions, prefix, type };
|
|
239
|
-
};
|
|
240
|
-
const isSamePermissions = (permissionsToExclude, locationPermissions) => {
|
|
241
|
-
if (permissionsToExclude.length !== locationPermissions.length) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
const sortedLocationPermissions = locationPermissions.sort();
|
|
245
|
-
return permissionsToExclude
|
|
246
|
-
.sort()
|
|
247
|
-
.every((permission, index) => permission === sortedLocationPermissions[index]);
|
|
248
|
-
};
|
|
249
|
-
const isSameType = (typeToExclude, locationType) => typeof typeToExclude === 'string'
|
|
250
|
-
? typeToExclude === locationType
|
|
251
|
-
: typeToExclude.includes(locationType);
|
|
252
|
-
const shouldExcludeLocation = ({ permissions, type }, exclude) => {
|
|
253
|
-
const excludedByPermssions = !!(exclude?.exactPermissions &&
|
|
254
|
-
isSamePermissions(exclude.exactPermissions, permissions));
|
|
255
|
-
const excludedByType = !!(exclude?.type && isSameType(exclude.type, type));
|
|
256
|
-
return excludedByPermssions || excludedByType;
|
|
257
|
-
};
|
|
258
|
-
const getFilteredLocations = (locations, exclude) => locations.reduce((filteredLocations, location) => {
|
|
259
|
-
const parsedLocation = parseAccessGrantLocation(location);
|
|
260
|
-
const isNonFolderLikePrefix = !parsedLocation.prefix.endsWith('/') &&
|
|
261
|
-
parsedLocation.type === 'PREFIX';
|
|
262
|
-
if (isNonFolderLikePrefix) {
|
|
263
|
-
return filteredLocations;
|
|
264
|
-
}
|
|
265
|
-
if (!shouldExcludeLocation(parsedLocation, exclude)) {
|
|
266
|
-
filteredLocations.push(parsedLocation);
|
|
267
|
-
}
|
|
268
|
-
return filteredLocations;
|
|
269
|
-
}, []);
|
|
270
|
-
const getFileKey = (key) => key.slice(key.lastIndexOf('/') + 1, key.length);
|
|
271
|
-
const createFileDataItem = (data) => ({
|
|
272
|
-
...data,
|
|
273
|
-
fileKey: getFileKey(data.key),
|
|
274
|
-
});
|
|
275
|
-
const getProgress = ({ totalBytes, transferredBytes, }) => totalBytes ? transferredBytes / totalBytes : undefined;
|
|
276
|
-
const isMultipartUpload = (file) => file.size > MULTIPART_UPLOAD_THRESHOLD_BYTES;
|
|
277
|
-
|
|
278
347
|
const copyHandler = (input) => {
|
|
279
348
|
const { config, data } = input;
|
|
280
349
|
const { accountId: expectedBucketOwner, credentials, customEndpoint, } = config;
|
|
281
350
|
const { key, sourceKey, lastModified, eTag } = data;
|
|
282
|
-
const bucket = constructBucket(config);
|
|
351
|
+
const bucket = constructBucket$1(config);
|
|
283
352
|
const source = {
|
|
284
353
|
bucket,
|
|
285
354
|
expectedBucketOwner,
|
|
@@ -320,7 +389,7 @@ const createFolderHandler = (input) => {
|
|
|
320
389
|
const { accountId, credentials, customEndpoint } = config;
|
|
321
390
|
const { onProgress } = options ?? {};
|
|
322
391
|
const { key, preventOverwrite } = data;
|
|
323
|
-
const bucket = constructBucket(config);
|
|
392
|
+
const bucket = constructBucket$1(config);
|
|
324
393
|
const { result } = internals.uploadData({
|
|
325
394
|
path: key,
|
|
326
395
|
data: '',
|
|
@@ -359,7 +428,7 @@ const deleteHandler = ({ config, data, }) => {
|
|
|
359
428
|
const result = internals.remove({
|
|
360
429
|
path: key,
|
|
361
430
|
options: {
|
|
362
|
-
bucket: constructBucket(config),
|
|
431
|
+
bucket: constructBucket$1(config),
|
|
363
432
|
locationCredentialsProvider: credentials,
|
|
364
433
|
expectedBucketOwner: accountId,
|
|
365
434
|
customEndpoint,
|
|
@@ -376,19 +445,115 @@ const deleteHandler = ({ config, data, }) => {
|
|
|
376
445
|
return { result };
|
|
377
446
|
};
|
|
378
447
|
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
448
|
+
const zipProgressManager = ({ dataMap, onZipProgress, }) => {
|
|
449
|
+
const iter = (() => {
|
|
450
|
+
let f;
|
|
451
|
+
let i = 0;
|
|
452
|
+
return (str) => {
|
|
453
|
+
if (!f) {
|
|
454
|
+
f = str;
|
|
455
|
+
}
|
|
456
|
+
else if (str !== f) {
|
|
457
|
+
++i;
|
|
458
|
+
f = str;
|
|
459
|
+
}
|
|
460
|
+
return i;
|
|
461
|
+
};
|
|
462
|
+
})();
|
|
463
|
+
const progressMap = new Map(Array.from(dataMap.keys()).map((k) => [k, 0]));
|
|
464
|
+
const total = dataMap.size;
|
|
465
|
+
dataMap.forEach((data, key) => {
|
|
466
|
+
onZipProgress(key, 0, data);
|
|
467
|
+
});
|
|
468
|
+
return ({ percent, currentFile, }) => {
|
|
469
|
+
if (currentFile) {
|
|
470
|
+
const item = iter(currentFile);
|
|
471
|
+
const sliceSize = 100 / total; // when 3 this is 33.3%
|
|
472
|
+
const start = sliceSize * item; // this is 66.6% for last item of 3;
|
|
473
|
+
// take percent and calculate the percent of the slice
|
|
474
|
+
const progress = percent - start;
|
|
475
|
+
const actualPercent = (progress / sliceSize) * 100;
|
|
476
|
+
progressMap.set(currentFile, Math.max(actualPercent, progressMap.get(currentFile) ?? 0));
|
|
477
|
+
onZipProgress(currentFile, progressMap.get(currentFile), dataMap.get(currentFile));
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
const zipper = (() => {
|
|
482
|
+
let zip = null;
|
|
483
|
+
const dataMap = new Map();
|
|
484
|
+
return {
|
|
485
|
+
addFile: (file, name, data) => {
|
|
486
|
+
if (!zip) {
|
|
487
|
+
zip = new JSZip__default["default"]();
|
|
488
|
+
}
|
|
489
|
+
dataMap.set(name, data);
|
|
490
|
+
return new Promise((ok, no) => {
|
|
491
|
+
try {
|
|
492
|
+
zip?.file(name, file);
|
|
493
|
+
ok();
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
no();
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
getBlobUrl: async (onProgress) => {
|
|
501
|
+
if (!zip) {
|
|
502
|
+
throw new Error('no zip');
|
|
503
|
+
}
|
|
504
|
+
const blob = await zip.generateAsync({
|
|
505
|
+
type: 'blob',
|
|
506
|
+
streamFiles: true,
|
|
507
|
+
compression: 'DEFLATE',
|
|
508
|
+
compressionOptions: {
|
|
509
|
+
level: 5,
|
|
510
|
+
},
|
|
511
|
+
}, zipProgressManager({
|
|
512
|
+
dataMap,
|
|
513
|
+
onZipProgress: (file, progress, data) => {
|
|
514
|
+
if (ui.isFunction(onProgress)) {
|
|
515
|
+
onProgress(progress, file, data);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
}));
|
|
519
|
+
zip = null;
|
|
520
|
+
return URL.createObjectURL(blob);
|
|
521
|
+
},
|
|
522
|
+
destroy: () => {
|
|
523
|
+
dataMap.clear();
|
|
524
|
+
zip = null;
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
})();
|
|
528
|
+
const constructBucket = ({ bucket: bucketName, region, }) => ({ bucketName, region });
|
|
529
|
+
const readBody = async (response, { data, options }) => {
|
|
530
|
+
let loading = true;
|
|
531
|
+
const chunks = [];
|
|
532
|
+
const reader = response.body.getReader();
|
|
533
|
+
const size = +(response.headers.get('content-length') ?? 0);
|
|
534
|
+
let received = 0;
|
|
535
|
+
while (loading) {
|
|
536
|
+
const { value, done } = await reader.read();
|
|
537
|
+
if (done) {
|
|
538
|
+
loading = false;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
chunks.push(value);
|
|
542
|
+
received += value.length;
|
|
543
|
+
if (ui.isFunction(options?.onProgress)) {
|
|
544
|
+
options?.onProgress(data, getProgress({
|
|
545
|
+
totalBytes: size,
|
|
546
|
+
transferredBytes: received,
|
|
547
|
+
}), 'PENDING');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return new Blob(chunks);
|
|
552
|
+
};
|
|
553
|
+
const download = async ({ config, data, all, options }, abortController) => {
|
|
554
|
+
const { customEndpoint, credentials, accountId } = config;
|
|
390
555
|
const { key } = data;
|
|
391
|
-
const
|
|
556
|
+
const { url } = await internals.getUrl({
|
|
392
557
|
path: key,
|
|
393
558
|
options: {
|
|
394
559
|
bucket: constructBucket(config),
|
|
@@ -398,17 +563,76 @@ const downloadHandler = ({ config, data }) => {
|
|
|
398
563
|
contentDisposition: 'attachment',
|
|
399
564
|
expectedBucketOwner: accountId,
|
|
400
565
|
},
|
|
401
|
-
})
|
|
402
|
-
.then(({ url }) => {
|
|
403
|
-
downloadFromUrl(key, url.toString());
|
|
404
|
-
return { status: 'COMPLETE', value: { url } };
|
|
405
|
-
})
|
|
406
|
-
.catch((error) => {
|
|
407
|
-
const { message } = error;
|
|
408
|
-
return { error, message, status: 'FAILED' };
|
|
409
566
|
});
|
|
410
|
-
|
|
411
|
-
|
|
567
|
+
const response = await fetch(url, {
|
|
568
|
+
mode: 'cors',
|
|
569
|
+
signal: abortController.signal,
|
|
570
|
+
});
|
|
571
|
+
const blob = await readBody(response, { data, options });
|
|
572
|
+
const [filename] = key.split('/').reverse();
|
|
573
|
+
await zipper.addFile(blob, filename, data);
|
|
574
|
+
return filename;
|
|
575
|
+
};
|
|
576
|
+
const downloadHandler = (() => {
|
|
577
|
+
const fileDownloadQueue = new Map();
|
|
578
|
+
const handler = ({ config, data, all, options }) => {
|
|
579
|
+
const { key } = data;
|
|
580
|
+
const [, folder] = key.split('/').reverse();
|
|
581
|
+
fileDownloadQueue.set(key, false);
|
|
582
|
+
const abortController = new AbortController();
|
|
583
|
+
return {
|
|
584
|
+
cancel: () => {
|
|
585
|
+
abortController.abort();
|
|
586
|
+
fileDownloadQueue.set(key, true);
|
|
587
|
+
},
|
|
588
|
+
result: download({ config, data, all, options }, abortController)
|
|
589
|
+
.then(() => {
|
|
590
|
+
fileDownloadQueue.set(key, true);
|
|
591
|
+
return {
|
|
592
|
+
status: 'LOADED',
|
|
593
|
+
};
|
|
594
|
+
})
|
|
595
|
+
.catch((e) => {
|
|
596
|
+
const error = e;
|
|
597
|
+
fileDownloadQueue.set(key, true);
|
|
598
|
+
return {
|
|
599
|
+
status: 'FAILED',
|
|
600
|
+
message: error.message,
|
|
601
|
+
error,
|
|
602
|
+
};
|
|
603
|
+
})
|
|
604
|
+
.finally(() => {
|
|
605
|
+
const done = all.every(({ key }) => {
|
|
606
|
+
return fileDownloadQueue.get(key);
|
|
607
|
+
});
|
|
608
|
+
if (done) {
|
|
609
|
+
zipper
|
|
610
|
+
.getBlobUrl((percent, name, _data) => {
|
|
611
|
+
if (ui.isFunction(options?.onProgress)) {
|
|
612
|
+
const progress = percent / 100;
|
|
613
|
+
options?.onProgress(_data, progress, progress === 1 ? 'COMPLETE' : 'FINISHING');
|
|
614
|
+
}
|
|
615
|
+
})
|
|
616
|
+
.then((blobURL) => {
|
|
617
|
+
if (blobURL) {
|
|
618
|
+
zipper.destroy();
|
|
619
|
+
const anchor = document.createElement('a');
|
|
620
|
+
const clickEvent = new MouseEvent('click');
|
|
621
|
+
anchor.href = blobURL;
|
|
622
|
+
anchor.download = `${folder || 'archive'}.zip`;
|
|
623
|
+
anchor.dispatchEvent(clickEvent);
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
.catch(() => {
|
|
627
|
+
// this catch happens, when no zip was created.
|
|
628
|
+
// it is handled by the UI showing "FAILED" for all files
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}),
|
|
632
|
+
};
|
|
633
|
+
};
|
|
634
|
+
return handler;
|
|
635
|
+
})();
|
|
412
636
|
|
|
413
637
|
const DEFAULT_PAGE_SIZE$5 = 1000;
|
|
414
638
|
const parseItems = (items, excludedPath) => items
|
|
@@ -494,7 +718,7 @@ const uploadHandler = ({ config, data, options }) => {
|
|
|
494
718
|
path: key,
|
|
495
719
|
data: file,
|
|
496
720
|
options: {
|
|
497
|
-
bucket: constructBucket(config),
|
|
721
|
+
bucket: constructBucket$1(config),
|
|
498
722
|
expectedBucketOwner: accountId,
|
|
499
723
|
locationCredentialsProvider: credentials,
|
|
500
724
|
onProgress: (event) => {
|
|
@@ -1719,8 +1943,10 @@ const USE_LIST_ERROR_MESSAGE = '`useList` must be called from within `StorageBro
|
|
|
1719
1943
|
const INITIAL_STATUS_COUNTS = {
|
|
1720
1944
|
CANCELED: 0,
|
|
1721
1945
|
COMPLETE: 0,
|
|
1946
|
+
LOADED: 0,
|
|
1722
1947
|
FAILED: 0,
|
|
1723
1948
|
PENDING: 0,
|
|
1949
|
+
FINISHING: 0,
|
|
1724
1950
|
OVERWRITE_PREVENTED: 0,
|
|
1725
1951
|
QUEUED: 0,
|
|
1726
1952
|
TOTAL: 0,
|
|
@@ -1731,7 +1957,10 @@ const isProcessingTasks = (statusCounts) => {
|
|
|
1731
1957
|
if (statusCounts.TOTAL === 0 || statusCounts.TOTAL === statusCounts.QUEUED) {
|
|
1732
1958
|
return false;
|
|
1733
1959
|
}
|
|
1734
|
-
return !(statusCounts.QUEUED === 0 &&
|
|
1960
|
+
return !(statusCounts.QUEUED === 0 &&
|
|
1961
|
+
statusCounts.PENDING === 0 &&
|
|
1962
|
+
statusCounts.LOADED === 0 &&
|
|
1963
|
+
statusCounts.FINISHING === 0);
|
|
1735
1964
|
};
|
|
1736
1965
|
const hasCompletedProcessingTasks = (statusCounts) => {
|
|
1737
1966
|
if (statusCounts.TOTAL === 0 || isProcessingTasks(statusCounts))
|
|
@@ -1829,20 +2058,26 @@ function useProcessTasks(handler, options) {
|
|
|
1829
2058
|
: [...tasksRef.current.values()].find(({ status }) => status === 'QUEUED') ?? {};
|
|
1830
2059
|
if (!data)
|
|
1831
2060
|
return;
|
|
2061
|
+
const all = isSingleTask
|
|
2062
|
+
? [_input.data]
|
|
2063
|
+
: [...tasksRef.current.values()].map(({ data }) => data);
|
|
1832
2064
|
const { onTaskCancel, onTaskComplete, onTaskError, onTaskProgress, onTaskSuccess, } = callbacksRef.current;
|
|
1833
2065
|
const getTask = () => tasksRef.current.get(data.id);
|
|
1834
2066
|
const { options } = _input;
|
|
1835
2067
|
const { onProgress: _onProgress } = options ?? {};
|
|
1836
|
-
const onProgress = ({ id }, progress) => {
|
|
1837
|
-
const task = updateTask(id, {
|
|
2068
|
+
const onProgress = ({ id }, progress, status = 'PENDING') => {
|
|
2069
|
+
const task = updateTask(id, {
|
|
2070
|
+
progress,
|
|
2071
|
+
status,
|
|
2072
|
+
});
|
|
1838
2073
|
if (task && ui.isFunction(onTaskProgress)) {
|
|
1839
2074
|
onTaskProgress(task, progress);
|
|
1840
2075
|
}
|
|
1841
2076
|
if (task && ui.isFunction(_onProgress)) {
|
|
1842
|
-
_onProgress(data, progress);
|
|
2077
|
+
_onProgress(data, progress, status);
|
|
1843
2078
|
}
|
|
1844
2079
|
};
|
|
1845
|
-
const input = { ..._input, data, options: { ...options, onProgress } };
|
|
2080
|
+
const input = { ..._input, data, all, options: { ...options, onProgress } };
|
|
1846
2081
|
const { cancel: _cancel, result } = handler(input);
|
|
1847
2082
|
const cancel = !_cancel
|
|
1848
2083
|
? undefined
|
|
@@ -1927,7 +2162,7 @@ function useHandler(handler, options) {
|
|
|
1927
2162
|
handleProcessing({
|
|
1928
2163
|
config,
|
|
1929
2164
|
...(hasData
|
|
1930
|
-
? { data: input.data }
|
|
2165
|
+
? { data: input.data, all: [input.data] }
|
|
1931
2166
|
: // if no `data` provided, provide `concurrency` to `options`
|
|
1932
2167
|
{ options: { concurrency: DEFAULT_ACTION_CONCURRENCY } }),
|
|
1933
2168
|
});
|
|
@@ -2036,8 +2271,10 @@ const DEFAULT_ACTION_VIEW_DISPLAY_TEXT = {
|
|
|
2036
2271
|
actionDestinationLabel: 'Destination',
|
|
2037
2272
|
statusDisplayCanceledLabel: 'Canceled',
|
|
2038
2273
|
statusDisplayCompletedLabel: 'Completed',
|
|
2274
|
+
statusDisplayLoadedLabel: 'Completed',
|
|
2039
2275
|
statusDisplayFailedLabel: 'Failed',
|
|
2040
2276
|
statusDisplayInProgressLabel: 'In progress',
|
|
2277
|
+
statusDisplayFinishingLabel: 'In progress',
|
|
2041
2278
|
statusDisplayTotalLabel: 'Total',
|
|
2042
2279
|
statusDisplayQueuedLabel: 'Not started',
|
|
2043
2280
|
// empty by default
|
|
@@ -2390,6 +2627,8 @@ const DEFAULT_DOWNLOAD_VIEW_DISPLAY_TEXT = {
|
|
|
2390
2627
|
...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
|
|
2391
2628
|
title: 'Download',
|
|
2392
2629
|
actionStartLabel: 'Download',
|
|
2630
|
+
statusDisplayFinishingLabel: 'Zipping',
|
|
2631
|
+
statusDisplayLoadedLabel: 'Loaded',
|
|
2393
2632
|
getActionCompleteMessage: (data) => {
|
|
2394
2633
|
const { counts } = data ?? {};
|
|
2395
2634
|
const { COMPLETE, FAILED, TOTAL } = counts ?? {};
|
|
@@ -3746,15 +3985,19 @@ function useResolveTableData(keys, { getCell, getHeader, getRowKey }, { items, p
|
|
|
3746
3985
|
|
|
3747
3986
|
const STATUS_LABELS = {
|
|
3748
3987
|
PENDING: 'statusDisplayInProgressLabel',
|
|
3988
|
+
FINISHING: 'statusDisplayFinishingLabel',
|
|
3749
3989
|
CANCELED: 'statusDisplayCanceledLabel',
|
|
3750
3990
|
COMPLETE: 'statusDisplayCompletedLabel',
|
|
3991
|
+
LOADED: 'statusDisplayLoadedLabel',
|
|
3751
3992
|
FAILED: 'statusDisplayFailedLabel',
|
|
3752
3993
|
QUEUED: 'statusDisplayQueuedLabel',
|
|
3753
3994
|
OVERWRITE_PREVENTED: 'statusDisplayOverwritePreventedLabel',
|
|
3754
3995
|
};
|
|
3755
3996
|
const STATUS_ICONS = {
|
|
3756
3997
|
PENDING: 'action-progress',
|
|
3998
|
+
FINISHING: 'action-progress',
|
|
3757
3999
|
COMPLETE: 'action-success',
|
|
4000
|
+
LOADED: 'action-success',
|
|
3758
4001
|
FAILED: 'action-error',
|
|
3759
4002
|
OVERWRITE_PREVENTED: 'action-info',
|
|
3760
4003
|
CANCELED: 'action-canceled',
|
|
@@ -3795,7 +4038,7 @@ const getUploadCellProgress = ({ progress, status, }) => {
|
|
|
3795
4038
|
};
|
|
3796
4039
|
const getDownloadCellProgress = ({ progress, status, }) => {
|
|
3797
4040
|
// prefer `progress` if available, 1 if status is complete, default 0
|
|
3798
|
-
const value = progress ?? (status === 'COMPLETE' ? 1 : 0);
|
|
4041
|
+
const value = progress ?? (status === 'LOADED' || status === 'COMPLETE' ? 1 : 0);
|
|
3799
4042
|
const displayValue = `${Math.round(value * 100)}%`;
|
|
3800
4043
|
return { displayValue, value };
|
|
3801
4044
|
};
|
|
@@ -5536,7 +5779,7 @@ function useFilePreview({ activeFile, }) {
|
|
|
5536
5779
|
const config = getConfig(location.current);
|
|
5537
5780
|
const { accountId, customEndpoint, credentials } = config;
|
|
5538
5781
|
const sharedOptions = {
|
|
5539
|
-
bucket: constructBucket(config),
|
|
5782
|
+
bucket: constructBucket$1(config),
|
|
5540
5783
|
expectedBucketOwner: accountId,
|
|
5541
5784
|
};
|
|
5542
5785
|
try {
|
|
@@ -6368,6 +6611,7 @@ exports.VERSION = VERSION;
|
|
|
6368
6611
|
exports.componentsDefault = componentsDefault;
|
|
6369
6612
|
exports.createAmplifyAuthAdapter = createAmplifyAuthAdapter;
|
|
6370
6613
|
exports.createStorageBrowser = createStorageBrowser;
|
|
6614
|
+
exports.deduplicateLocations = deduplicateLocations;
|
|
6371
6615
|
exports.defaultActionConfigs = defaultActionConfigs;
|
|
6372
6616
|
exports.defaultHandlers = defaultHandlers;
|
|
6373
6617
|
exports.getFilteredLocations = getFilteredLocations;
|