@aws-amplify/ui-react-storage 3.14.0 → 3.16.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-B-J76Lyp.js} +1012 -249
- package/dist/esm/browser.mjs +1 -0
- package/dist/esm/components/StorageBrowser/ErrorBoundary/ErrorBoundary.mjs +0 -3
- package/dist/esm/components/StorageBrowser/StorageBrowserAmplify.mjs +1 -0
- package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +14 -3
- package/dist/esm/components/StorageBrowser/actions/handlers/defaults.mjs +1 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/delete.mjs +39 -8
- 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 +0 -3
- package/dist/esm/components/StorageBrowser/components/base/preview/DownloadButton.mjs +0 -3
- package/dist/esm/components/StorageBrowser/components/composables/ActionConfirmationModal.mjs +34 -0
- package/dist/esm/components/StorageBrowser/components/composables/defaults.mjs +2 -0
- package/dist/esm/components/StorageBrowser/components/elements/definitions.mjs +2 -2
- package/dist/esm/components/StorageBrowser/controls/ActionConfirmationModalControl.mjs +12 -0
- package/dist/esm/components/StorageBrowser/controls/DataTableControl.mjs +0 -3
- package/dist/esm/components/StorageBrowser/controls/hooks/useActionConfirmationModal.mjs +17 -0
- package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +8 -3
- package/dist/esm/components/StorageBrowser/createStorageBrowser/createProvider.mjs +1 -0
- package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +9 -4
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/deleteView.mjs +117 -5
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/downloadView.mjs +2 -0
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/locationDetailView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/shared.mjs +3 -0
- package/dist/esm/components/StorageBrowser/locationItems/context.mjs +18 -14
- package/dist/esm/components/StorageBrowser/locationItems/utils.mjs +38 -0
- package/dist/esm/components/StorageBrowser/store/validateStoreProps.mjs +1 -1
- package/dist/esm/components/StorageBrowser/tasks/constants.mjs +2 -0
- package/dist/esm/components/StorageBrowser/tasks/useProcessTasks.mjs +14 -5
- 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 +0 -4
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyViewProvider.mjs +4 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/FoldersMessageControl.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useCopyView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useFolders.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/CreateFolderView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/useCreateFolderView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteView.mjs +5 -5
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteViewProvider.mjs +7 -6
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/useDeleteView.mjs +69 -6
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/utils.mjs +87 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadViewProvider.mjs +9 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.mjs +1 -4
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadViewProvider.mjs +4 -0
- package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailViewProvider.mjs +2 -1
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.mjs +38 -27
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.mjs +9 -1
- package/dist/esm/components/StorageBrowser/views/LocationDetailView/useLocationDetailView.mjs +11 -9
- package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsView.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsViewProvider.mjs +0 -3
- package/dist/esm/components/StorageBrowser/views/LocationsView/useLocationsView.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +7 -3
- package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +8 -3
- package/dist/esm/components/StorageBrowser/views/hooks/useFilePreview/useFilePreview.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/constants.mjs +14 -1
- package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.mjs +123 -13
- package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/utils.mjs +4 -4
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +2 -1
- package/dist/styles.css +100 -1
- package/dist/types/components/StorageBrowser/actions/handlers/delete.d.ts +5 -3
- package/dist/types/components/StorageBrowser/actions/handlers/index.d.ts +1 -0
- package/dist/types/components/StorageBrowser/actions/handlers/types.d.ts +18 -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/components/composables/ActionConfirmationModal.d.ts +12 -0
- package/dist/types/components/StorageBrowser/components/composables/types.d.ts +2 -0
- package/dist/types/components/StorageBrowser/controls/ActionConfirmationModalControl.d.ts +2 -0
- package/dist/types/components/StorageBrowser/controls/hooks/useActionConfirmationModal.d.ts +2 -0
- package/dist/types/components/StorageBrowser/controls/index.d.ts +1 -0
- package/dist/types/components/StorageBrowser/controls/types.d.ts +4 -0
- package/dist/types/components/StorageBrowser/displayText/types.d.ts +9 -0
- package/dist/types/components/StorageBrowser/locationItems/context.d.ts +12 -3
- package/dist/types/components/StorageBrowser/locationItems/index.d.ts +1 -0
- package/dist/types/components/StorageBrowser/locationItems/utils.d.ts +27 -0
- package/dist/types/components/StorageBrowser/tasks/types.d.ts +10 -5
- 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/LocationActionView/DeleteView/types.d.ts +5 -0
- package/dist/types/components/StorageBrowser/views/LocationActionView/DeleteView/utils.d.ts +26 -0
- package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.d.ts +4 -1
- package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.d.ts +3 -2
- package/dist/types/components/StorageBrowser/views/LocationDetailView/types.d.ts +8 -1
- package/dist/types/components/StorageBrowser/views/utils/index.d.ts +1 -1
- package/dist/types/components/StorageBrowser/views/utils/tableResolvers/constants.d.ts +5 -0
- package/dist/types/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.d.ts +5 -2
- package/dist/types/components/StorageBrowser/views/utils/tableResolvers/index.d.ts +1 -1
- package/dist/types/components/StorageBrowser/views/utils/tableResolvers/types.d.ts +4 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +12 -9
- package/dist/esm/components/StorageBrowser/actions/handlers/download.mjs +0 -38
package/dist/esm/browser.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import '@aws-amplify/storage/internals';
|
|
|
2
2
|
import 'aws-amplify';
|
|
3
3
|
import '@aws-amplify/ui';
|
|
4
4
|
export { defaultHandlers } from './components/StorageBrowser/actions/handlers/defaults.mjs';
|
|
5
|
+
import 'jszip';
|
|
5
6
|
import 'aws-amplify/storage';
|
|
6
7
|
import './components/StorageBrowser/actions/configs/context.mjs';
|
|
7
8
|
export { defaultActionConfigs } from './components/StorageBrowser/actions/configs/defaults.mjs';
|
|
@@ -11,9 +11,6 @@ import '@aws-amplify/ui-react-core/elements';
|
|
|
11
11
|
import '../credentials/context.mjs';
|
|
12
12
|
import '@aws-amplify/storage/internals';
|
|
13
13
|
import '../configuration/context.mjs';
|
|
14
|
-
import 'aws-amplify';
|
|
15
|
-
import 'aws-amplify/storage';
|
|
16
|
-
import '../actions/configs/context.mjs';
|
|
17
14
|
import '../actions/configs/defaults.mjs';
|
|
18
15
|
import '../displayText/context.mjs';
|
|
19
16
|
import '../filePreview/context.mjs';
|
|
@@ -3,6 +3,7 @@ import { createAmplifyAuthAdapter } from './adapters/createAmplifyAuthAdapter/cr
|
|
|
3
3
|
import '@aws-amplify/storage/internals';
|
|
4
4
|
import 'aws-amplify';
|
|
5
5
|
import '@aws-amplify/ui';
|
|
6
|
+
import 'jszip';
|
|
6
7
|
import 'aws-amplify/storage';
|
|
7
8
|
import './actions/configs/context.mjs';
|
|
8
9
|
import './actions/configs/defaults.mjs';
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { hasSelectedFolders } from '../../locationItems/utils.mjs';
|
|
1
2
|
import '@aws-amplify/storage/internals';
|
|
2
3
|
import 'aws-amplify';
|
|
3
4
|
import '@aws-amplify/ui';
|
|
4
5
|
import { defaultHandlers } from '../handlers/defaults.mjs';
|
|
6
|
+
import 'jszip';
|
|
5
7
|
import 'aws-amplify/storage';
|
|
6
8
|
|
|
7
9
|
const copyActionConfig = {
|
|
8
10
|
viewName: 'CopyView',
|
|
9
11
|
actionListItem: {
|
|
10
|
-
disable: (selected) =>
|
|
12
|
+
disable: (selected) => {
|
|
13
|
+
const hasNoSelection = !selected || selected.length === 0;
|
|
14
|
+
return hasNoSelection || hasSelectedFolders(selected);
|
|
15
|
+
},
|
|
11
16
|
hide: (permissions) => !permissions.includes('write'),
|
|
12
17
|
icon: 'copy-file',
|
|
13
18
|
label: 'Copy',
|
|
@@ -17,7 +22,10 @@ const copyActionConfig = {
|
|
|
17
22
|
const deleteActionConfig = {
|
|
18
23
|
viewName: 'DeleteView',
|
|
19
24
|
actionListItem: {
|
|
20
|
-
disable: (selected) =>
|
|
25
|
+
disable: (selected) => {
|
|
26
|
+
const hasNoSelection = !selected || selected.length === 0;
|
|
27
|
+
return hasNoSelection;
|
|
28
|
+
},
|
|
21
29
|
hide: (permissions) => !permissions.includes('delete'),
|
|
22
30
|
icon: 'delete-file',
|
|
23
31
|
label: 'Delete',
|
|
@@ -45,7 +53,10 @@ const uploadActionConfig = {
|
|
|
45
53
|
const downloadActionConfig = {
|
|
46
54
|
viewName: 'DownloadView',
|
|
47
55
|
actionListItem: {
|
|
48
|
-
disable: (selected) =>
|
|
56
|
+
disable: (selected) => {
|
|
57
|
+
const hasNoSelection = !selected || selected.length === 0;
|
|
58
|
+
return hasNoSelection || hasSelectedFolders(selected);
|
|
59
|
+
},
|
|
49
60
|
hide: (permissions) => !permissions.includes('get'),
|
|
50
61
|
icon: 'download',
|
|
51
62
|
label: 'Download',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyHandler } from './copy.mjs';
|
|
2
2
|
import { createFolderHandler } from './createFolder.mjs';
|
|
3
3
|
import { deleteHandler } from './delete.mjs';
|
|
4
|
-
import { downloadHandler } from './
|
|
4
|
+
import { zipDownloadHandler as downloadHandler } from './zipdownload.mjs';
|
|
5
5
|
import { listLocationItemsHandler } from './listLocationItems.mjs';
|
|
6
6
|
import { uploadHandler } from './upload.mjs';
|
|
7
7
|
|
|
@@ -1,27 +1,58 @@
|
|
|
1
1
|
import { remove } from '@aws-amplify/storage/internals';
|
|
2
2
|
import { constructBucket } from './utils.mjs';
|
|
3
3
|
|
|
4
|
-
const deleteHandler = ({ config, data, }) => {
|
|
4
|
+
const deleteHandler = ({ config, data, options, }) => {
|
|
5
5
|
const { key } = data;
|
|
6
6
|
const { accountId, credentials, customEndpoint } = config;
|
|
7
|
-
const
|
|
7
|
+
const { onProgress } = options ?? {};
|
|
8
|
+
let cumulativeSuccessCount = 0;
|
|
9
|
+
let cumulativeFailureCount = 0;
|
|
10
|
+
let operationCancel = () => {
|
|
11
|
+
// noop
|
|
12
|
+
};
|
|
13
|
+
const cancel = () => {
|
|
14
|
+
operationCancel?.();
|
|
15
|
+
};
|
|
16
|
+
const operation = remove({
|
|
8
17
|
path: key,
|
|
9
18
|
options: {
|
|
10
19
|
bucket: constructBucket(config),
|
|
11
20
|
locationCredentialsProvider: credentials,
|
|
12
21
|
expectedBucketOwner: accountId,
|
|
13
22
|
customEndpoint,
|
|
23
|
+
onProgress: (progress) => {
|
|
24
|
+
if (!progress) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const batchSuccessCount = progress?.deleted?.length ?? 0;
|
|
28
|
+
const batchFailureCount = progress?.failed?.length ?? 0;
|
|
29
|
+
cumulativeSuccessCount += batchSuccessCount;
|
|
30
|
+
cumulativeFailureCount += batchFailureCount;
|
|
31
|
+
onProgress?.(data, {
|
|
32
|
+
successCount: cumulativeSuccessCount,
|
|
33
|
+
failureCount: cumulativeFailureCount,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
14
36
|
},
|
|
37
|
+
});
|
|
38
|
+
operationCancel = operation?.cancel ? operation?.cancel : () => { };
|
|
39
|
+
const operationPromise = operation?.result ?? operation;
|
|
40
|
+
const result = operationPromise
|
|
41
|
+
?.then?.(({ path }) => {
|
|
42
|
+
return {
|
|
43
|
+
status: 'COMPLETE',
|
|
44
|
+
value: {
|
|
45
|
+
key: path,
|
|
46
|
+
successCount: cumulativeSuccessCount,
|
|
47
|
+
failureCount: cumulativeFailureCount,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
15
50
|
})
|
|
16
|
-
|
|
17
|
-
status: 'COMPLETE',
|
|
18
|
-
value: { key: path },
|
|
19
|
-
}))
|
|
20
|
-
.catch((error) => {
|
|
51
|
+
?.catch?.((error) => {
|
|
21
52
|
const { message } = error;
|
|
22
53
|
return { error, message, status: 'FAILED' };
|
|
23
54
|
});
|
|
24
|
-
return { result };
|
|
55
|
+
return { result, cancel };
|
|
25
56
|
};
|
|
26
57
|
|
|
27
58
|
export { deleteHandler };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { listCallerAccessGrants } from '@aws-amplify/storage/internals';
|
|
2
2
|
import { assertAccountId } from '../../validators/assertAccountId.mjs';
|
|
3
3
|
import '@aws-amplify/ui';
|
|
4
|
-
import { getFilteredLocations } from './utils.mjs';
|
|
4
|
+
import { deduplicateLocations, getFilteredLocations } from './utils.mjs';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_PAGE_SIZE = 1000;
|
|
7
7
|
const listLocationsHandler = async (input) => {
|
|
@@ -26,7 +26,12 @@ const listLocationsHandler = async (input) => {
|
|
|
26
26
|
}
|
|
27
27
|
return { items, nextToken: output.nextToken };
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
const result = await fetchLocations([], nextToken);
|
|
30
|
+
// Deduplicate locations with the same bucket and prefix, keeping broader permissions
|
|
31
|
+
return {
|
|
32
|
+
items: deduplicateLocations(result.items),
|
|
33
|
+
nextToken: result.nextToken,
|
|
34
|
+
};
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
export { listLocationsHandler };
|
|
@@ -88,6 +88,70 @@ const shouldExcludeLocation = ({ permissions, type }, exclude) => {
|
|
|
88
88
|
const excludedByType = !!(exclude?.type && isSameType(exclude.type, type));
|
|
89
89
|
return excludedByPermssions || excludedByType;
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Determines if permissions1 is a strict superset (broader) of permissions2.
|
|
93
|
+
* Returns true only if permissions1 contains all permissions from permissions2
|
|
94
|
+
* AND has more permissions.
|
|
95
|
+
*/
|
|
96
|
+
const hasBroaderPermissions = (permissions1, permissions2) => {
|
|
97
|
+
// permissions1 must have more permissions (strict superset, not equal)
|
|
98
|
+
if (permissions1.length <= permissions2.length) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
// permissions1 must contain all permissions from permissions2
|
|
102
|
+
return permissions2.every((perm) => permissions1.includes(perm));
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Deduplicates locations with the same bucket and prefix.
|
|
106
|
+
* Only deduplicates when one location's permissions are a superset of another's.
|
|
107
|
+
* This prevents deduplication of incompatible grants like READ + WRITE.
|
|
108
|
+
*
|
|
109
|
+
* Examples:
|
|
110
|
+
* - READ + READWRITE → Keep READWRITE (superset)
|
|
111
|
+
* - READ + READ → Keep first (identical)
|
|
112
|
+
* - READ + WRITE → Keep both (not superset, need separate locations)
|
|
113
|
+
*/
|
|
114
|
+
const deduplicateLocations = (locations) => {
|
|
115
|
+
// Group locations by bucket:prefix
|
|
116
|
+
const locationGroups = new Map();
|
|
117
|
+
for (const location of locations) {
|
|
118
|
+
const key = `${location.bucket}:${location.prefix}`;
|
|
119
|
+
const group = locationGroups.get(key) ?? [];
|
|
120
|
+
group.push(location);
|
|
121
|
+
locationGroups.set(key, group);
|
|
122
|
+
}
|
|
123
|
+
// For each group, keep only non-redundant locations
|
|
124
|
+
const result = [];
|
|
125
|
+
for (const group of locationGroups.values()) {
|
|
126
|
+
if (group.length === 1) {
|
|
127
|
+
result.push(group[0]);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Find locations that are not subsets of any other location in the group
|
|
131
|
+
const nonRedundant = [];
|
|
132
|
+
for (const location of group) {
|
|
133
|
+
// Check if this location is a subset of any other location
|
|
134
|
+
const isSubsetOfAnother = group.some((other) => {
|
|
135
|
+
if (other === location)
|
|
136
|
+
return false;
|
|
137
|
+
return hasBroaderPermissions(other.permissions, location.permissions);
|
|
138
|
+
});
|
|
139
|
+
if (!isSubsetOfAnother) {
|
|
140
|
+
// Check if we already have an identical permission set
|
|
141
|
+
const isDuplicate = nonRedundant.some((existing) => {
|
|
142
|
+
const sortedNew = [...location.permissions].sort().join(',');
|
|
143
|
+
const sortedExisting = [...existing.permissions].sort().join(',');
|
|
144
|
+
return sortedNew === sortedExisting;
|
|
145
|
+
});
|
|
146
|
+
if (!isDuplicate) {
|
|
147
|
+
nonRedundant.push(location);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
result.push(...nonRedundant);
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
};
|
|
91
155
|
const getFilteredLocations = (locations, exclude) => locations.reduce((filteredLocations, location) => {
|
|
92
156
|
const parsedLocation = parseAccessGrantLocation(location);
|
|
93
157
|
const isNonFolderLikePrefix = !parsedLocation.prefix.endsWith('/') &&
|
|
@@ -108,4 +172,4 @@ const createFileDataItem = (data) => ({
|
|
|
108
172
|
const getProgress = ({ totalBytes, transferredBytes, }) => totalBytes ? transferredBytes / totalBytes : undefined;
|
|
109
173
|
const isMultipartUpload = (file) => file.size > MULTIPART_UPLOAD_THRESHOLD_BYTES;
|
|
110
174
|
|
|
111
|
-
export { constructBucket, createFileDataItem, getBucketRegion, getFileKey, getFilteredLocations, getProgress, isMultipartUpload, parseAccessGrantLocation, shouldExcludeLocation };
|
|
175
|
+
export { constructBucket, createFileDataItem, deduplicateLocations, getBucketRegion, getFileKey, getFilteredLocations, getProgress, isMultipartUpload, parseAccessGrantLocation, shouldExcludeLocation };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { getUrl } from '@aws-amplify/storage/internals';
|
|
2
|
+
import { isFunction } from '@aws-amplify/ui';
|
|
3
|
+
import { getProgress } from './utils.mjs';
|
|
4
|
+
import JSZip from 'jszip';
|
|
5
|
+
|
|
6
|
+
const zipProgressManager = ({ dataMap, onZipProgress, }) => {
|
|
7
|
+
const iter = (() => {
|
|
8
|
+
let f;
|
|
9
|
+
let i = 0;
|
|
10
|
+
return (str) => {
|
|
11
|
+
if (!f) {
|
|
12
|
+
f = str;
|
|
13
|
+
}
|
|
14
|
+
else if (str !== f) {
|
|
15
|
+
++i;
|
|
16
|
+
f = str;
|
|
17
|
+
}
|
|
18
|
+
return i;
|
|
19
|
+
};
|
|
20
|
+
})();
|
|
21
|
+
const progressMap = new Map(Array.from(dataMap.keys()).map((k) => [k, 0]));
|
|
22
|
+
const total = dataMap.size;
|
|
23
|
+
dataMap.forEach((data, key) => {
|
|
24
|
+
onZipProgress(key, 0, data);
|
|
25
|
+
});
|
|
26
|
+
return ({ percent, currentFile, }) => {
|
|
27
|
+
if (currentFile) {
|
|
28
|
+
const item = iter(currentFile);
|
|
29
|
+
const sliceSize = 100 / total; // when 3 this is 33.3%
|
|
30
|
+
const start = sliceSize * item; // this is 66.6% for last item of 3;
|
|
31
|
+
// take percent and calculate the percent of the slice
|
|
32
|
+
const progress = percent - start;
|
|
33
|
+
const actualPercent = (progress / sliceSize) * 100;
|
|
34
|
+
progressMap.set(currentFile, Math.max(actualPercent, progressMap.get(currentFile) ?? 0));
|
|
35
|
+
onZipProgress(currentFile, progressMap.get(currentFile), dataMap.get(currentFile));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const zipper = (() => {
|
|
40
|
+
let zip = null;
|
|
41
|
+
const dataMap = new Map();
|
|
42
|
+
return {
|
|
43
|
+
addFile: (file, name, data) => {
|
|
44
|
+
if (!zip) {
|
|
45
|
+
zip = new JSZip();
|
|
46
|
+
}
|
|
47
|
+
dataMap.set(name, data);
|
|
48
|
+
return new Promise((ok, no) => {
|
|
49
|
+
try {
|
|
50
|
+
zip?.file(name, file);
|
|
51
|
+
ok();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
no();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
getBlobUrl: async (onProgress) => {
|
|
59
|
+
if (!zip) {
|
|
60
|
+
throw new Error('no zip');
|
|
61
|
+
}
|
|
62
|
+
const blob = await zip.generateAsync({
|
|
63
|
+
type: 'blob',
|
|
64
|
+
streamFiles: true,
|
|
65
|
+
compression: 'DEFLATE',
|
|
66
|
+
compressionOptions: {
|
|
67
|
+
level: 5,
|
|
68
|
+
},
|
|
69
|
+
}, zipProgressManager({
|
|
70
|
+
dataMap,
|
|
71
|
+
onZipProgress: (file, progress, data) => {
|
|
72
|
+
if (isFunction(onProgress)) {
|
|
73
|
+
onProgress(progress, file, data);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
zip = null;
|
|
78
|
+
return URL.createObjectURL(blob);
|
|
79
|
+
},
|
|
80
|
+
destroy: () => {
|
|
81
|
+
dataMap.clear();
|
|
82
|
+
zip = null;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
})();
|
|
86
|
+
const constructBucket = ({ bucket: bucketName, region, }) => ({ bucketName, region });
|
|
87
|
+
const readBody = async (response, { data, options }) => {
|
|
88
|
+
let loading = true;
|
|
89
|
+
const chunks = [];
|
|
90
|
+
const reader = response.body.getReader();
|
|
91
|
+
const size = +(response.headers.get('content-length') ?? 0);
|
|
92
|
+
let received = 0;
|
|
93
|
+
while (loading) {
|
|
94
|
+
const { value, done } = await reader.read();
|
|
95
|
+
if (done) {
|
|
96
|
+
loading = false;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
chunks.push(value);
|
|
100
|
+
received += value.length;
|
|
101
|
+
if (isFunction(options?.onProgress)) {
|
|
102
|
+
options?.onProgress(data, getProgress({
|
|
103
|
+
totalBytes: size,
|
|
104
|
+
transferredBytes: received,
|
|
105
|
+
}), 'PENDING');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return new Blob(chunks);
|
|
110
|
+
};
|
|
111
|
+
const download = async ({ config, data, all, options }, abortController) => {
|
|
112
|
+
const { customEndpoint, credentials, accountId } = config;
|
|
113
|
+
const { key } = data;
|
|
114
|
+
const { url } = await getUrl({
|
|
115
|
+
path: key,
|
|
116
|
+
options: {
|
|
117
|
+
bucket: constructBucket(config),
|
|
118
|
+
customEndpoint,
|
|
119
|
+
locationCredentialsProvider: credentials,
|
|
120
|
+
validateObjectExistence: true,
|
|
121
|
+
contentDisposition: 'attachment',
|
|
122
|
+
expectedBucketOwner: accountId,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const response = await fetch(url, {
|
|
126
|
+
mode: 'cors',
|
|
127
|
+
signal: abortController.signal,
|
|
128
|
+
});
|
|
129
|
+
const blob = await readBody(response, { data, options });
|
|
130
|
+
const [filename] = key.split('/').reverse();
|
|
131
|
+
await zipper.addFile(blob, filename, data);
|
|
132
|
+
return filename;
|
|
133
|
+
};
|
|
134
|
+
const downloadHandler = (() => {
|
|
135
|
+
const fileDownloadQueue = new Map();
|
|
136
|
+
const handler = ({ config, data, all, options }) => {
|
|
137
|
+
const { key } = data;
|
|
138
|
+
const [, folder] = key.split('/').reverse();
|
|
139
|
+
fileDownloadQueue.set(key, false);
|
|
140
|
+
const abortController = new AbortController();
|
|
141
|
+
return {
|
|
142
|
+
cancel: () => {
|
|
143
|
+
abortController.abort();
|
|
144
|
+
fileDownloadQueue.set(key, true);
|
|
145
|
+
},
|
|
146
|
+
result: download({ config, data, all, options }, abortController)
|
|
147
|
+
.then(() => {
|
|
148
|
+
fileDownloadQueue.set(key, true);
|
|
149
|
+
return {
|
|
150
|
+
status: 'LOADED',
|
|
151
|
+
};
|
|
152
|
+
})
|
|
153
|
+
.catch((e) => {
|
|
154
|
+
const error = e;
|
|
155
|
+
fileDownloadQueue.set(key, true);
|
|
156
|
+
return {
|
|
157
|
+
status: 'FAILED',
|
|
158
|
+
message: error.message,
|
|
159
|
+
error,
|
|
160
|
+
};
|
|
161
|
+
})
|
|
162
|
+
.finally(() => {
|
|
163
|
+
const done = all.every(({ key }) => {
|
|
164
|
+
return fileDownloadQueue.get(key);
|
|
165
|
+
});
|
|
166
|
+
if (done) {
|
|
167
|
+
zipper
|
|
168
|
+
.getBlobUrl((percent, name, _data) => {
|
|
169
|
+
if (isFunction(options?.onProgress)) {
|
|
170
|
+
const progress = percent / 100;
|
|
171
|
+
options?.onProgress(_data, progress, progress === 1 ? 'COMPLETE' : 'FINISHING');
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.then((blobURL) => {
|
|
175
|
+
if (blobURL) {
|
|
176
|
+
zipper.destroy();
|
|
177
|
+
const anchor = document.createElement('a');
|
|
178
|
+
const clickEvent = new MouseEvent('click');
|
|
179
|
+
anchor.href = blobURL;
|
|
180
|
+
anchor.download = `${folder || 'archive'}.zip`;
|
|
181
|
+
anchor.dispatchEvent(clickEvent);
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.catch(() => {
|
|
185
|
+
// this catch happens, when no zip was created.
|
|
186
|
+
// it is handled by the UI showing "FAILED" for all files
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}),
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
return handler;
|
|
193
|
+
})();
|
|
194
|
+
|
|
195
|
+
export { downloadHandler as zipDownloadHandler };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listPaths } from '@aws-amplify/storage/internals';
|
|
2
|
+
import { deduplicateLocations } from '../../actions/handlers/utils.mjs';
|
|
2
3
|
import { parseAmplifyAuthPermission } from '../permissionParsers.mjs';
|
|
3
4
|
import { getPaginatedLocations } from './getPaginatedLocations.mjs';
|
|
4
5
|
|
|
@@ -24,7 +25,8 @@ const createAmplifyListLocationsHandler = () => {
|
|
|
24
25
|
id: crypto.randomUUID(),
|
|
25
26
|
};
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
+
// Deduplicate locations with the same bucket and prefix, keeping broader permissions
|
|
29
|
+
cachedItems = deduplicateLocations(sanitizedItems);
|
|
28
30
|
return getPaginatedLocations({
|
|
29
31
|
items: cachedItems,
|
|
30
32
|
pageSize,
|
|
@@ -2,6 +2,7 @@ import { createLocationCredentialsHandler } from './createLocationCredentialsHan
|
|
|
2
2
|
import '@aws-amplify/storage/internals';
|
|
3
3
|
import 'aws-amplify';
|
|
4
4
|
import '@aws-amplify/ui';
|
|
5
|
+
import 'jszip';
|
|
5
6
|
import 'aws-amplify/storage';
|
|
6
7
|
import { listLocationsHandler } from '../../actions/handlers/listLocations.mjs';
|
|
7
8
|
import '../../actions/configs/context.mjs';
|
|
@@ -11,9 +11,6 @@ import '../useAction/context.mjs';
|
|
|
11
11
|
import '../credentials/context.mjs';
|
|
12
12
|
import '@aws-amplify/storage/internals';
|
|
13
13
|
import '../configuration/context.mjs';
|
|
14
|
-
import 'aws-amplify';
|
|
15
|
-
import 'aws-amplify/storage';
|
|
16
|
-
import '../actions/configs/context.mjs';
|
|
17
14
|
import '../actions/configs/defaults.mjs';
|
|
18
15
|
import '../displayText/context.mjs';
|
|
19
16
|
import '../filePreview/context.mjs';
|
|
@@ -12,9 +12,6 @@ import '../../../credentials/context.mjs';
|
|
|
12
12
|
import '@aws-amplify/storage/internals';
|
|
13
13
|
import '../../../configuration/context.mjs';
|
|
14
14
|
import '@aws-amplify/ui';
|
|
15
|
-
import 'aws-amplify';
|
|
16
|
-
import 'aws-amplify/storage';
|
|
17
|
-
import '../../../actions/configs/context.mjs';
|
|
18
15
|
import '../../../actions/configs/defaults.mjs';
|
|
19
16
|
import { useDisplayText } from '../../../displayText/context.mjs';
|
|
20
17
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import '@aws-amplify/ui-react';
|
|
3
|
+
import { ViewElement, HeadingElement, TextElement, ButtonElement } from '../elements/definitions.mjs';
|
|
4
|
+
import '../elements/IconElement.mjs';
|
|
5
|
+
|
|
6
|
+
const ActionConfirmationModal = ({ isOpen = false, title, message, content, onConfirm, onCancel, confirmLabel, cancelLabel, }) => {
|
|
7
|
+
const modalRef = React__default.useRef(null);
|
|
8
|
+
React__default.useEffect(() => {
|
|
9
|
+
if (isOpen && modalRef.current) {
|
|
10
|
+
modalRef.current.focus();
|
|
11
|
+
}
|
|
12
|
+
}, [isOpen]);
|
|
13
|
+
const handleKeyDown = (event) => {
|
|
14
|
+
if (event.key === 'Escape') {
|
|
15
|
+
onCancel?.();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (!isOpen)
|
|
19
|
+
return null;
|
|
20
|
+
return (React__default.createElement("div", { ref: modalRef, className: "amplify-modal__overlay", role: "dialog", "aria-modal": "true", tabIndex: -1, onKeyDown: handleKeyDown, onClick: (e) => {
|
|
21
|
+
if (e.target === e.currentTarget) {
|
|
22
|
+
onCancel?.();
|
|
23
|
+
}
|
|
24
|
+
} },
|
|
25
|
+
React__default.createElement(ViewElement, { className: "amplify-modal__content", role: "document" },
|
|
26
|
+
React__default.createElement(HeadingElement, { className: "amplify-modal__title" }, title),
|
|
27
|
+
message && (React__default.createElement(TextElement, { className: "amplify-modal__body" }, message)),
|
|
28
|
+
content && (React__default.createElement(ViewElement, { className: "amplify-modal__body" }, content)),
|
|
29
|
+
React__default.createElement(ViewElement, { className: "amplify-modal__footer" },
|
|
30
|
+
React__default.createElement(ButtonElement, { className: "amplify-modal__cancel", onClick: onCancel, variant: "cancel" }, cancelLabel),
|
|
31
|
+
React__default.createElement(ButtonElement, { className: "amplify-modal__confirm", onClick: onConfirm, variant: "primary" }, confirmLabel)))));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { ActionConfirmationModal };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActionCancel } from './ActionCancel.mjs';
|
|
2
|
+
import { ActionConfirmationModal } from './ActionConfirmationModal.mjs';
|
|
2
3
|
import { ActionDestination } from './ActionDestination.mjs';
|
|
3
4
|
import { ActionExit } from './ActionExit.mjs';
|
|
4
5
|
import { ActionStart } from './ActionStart.mjs';
|
|
@@ -22,6 +23,7 @@ import { Title } from './Title.mjs';
|
|
|
22
23
|
|
|
23
24
|
const DEFAULT_COMPOSABLES = {
|
|
24
25
|
ActionCancel,
|
|
26
|
+
ActionConfirmationModal,
|
|
25
27
|
ActionDestination,
|
|
26
28
|
ActionExit,
|
|
27
29
|
ActionStart,
|
|
@@ -29,7 +29,7 @@ const OrderedListElement = defineBaseElement({
|
|
|
29
29
|
type: 'ol',
|
|
30
30
|
displayName: 'OrderedList',
|
|
31
31
|
});
|
|
32
|
-
defineBaseElement({
|
|
32
|
+
const UnorderedListElement = defineBaseElement({
|
|
33
33
|
type: 'ul',
|
|
34
34
|
displayName: 'UnorderedList',
|
|
35
35
|
});
|
|
@@ -83,4 +83,4 @@ const SpanElement = defineBaseElement({
|
|
|
83
83
|
displayName: 'Span',
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
export { ButtonElement, DescriptionDetailsElement, DescriptionListElement, DescriptionTermElement, HeadingElement, InputElement, LabelElement, ListItemElement, NavElement, OrderedListElement, SpanElement, TableBodyElement, TableDataCellElement, TableElement, TableHeadElement, TableHeaderElement, TableRowElement, TextElement, ViewElement };
|
|
86
|
+
export { ButtonElement, DescriptionDetailsElement, DescriptionListElement, DescriptionTermElement, HeadingElement, InputElement, LabelElement, ListItemElement, NavElement, OrderedListElement, SpanElement, TableBodyElement, TableDataCellElement, TableElement, TableHeadElement, TableHeaderElement, TableRowElement, TextElement, UnorderedListElement, ViewElement };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ActionConfirmationModal } from '../components/composables/ActionConfirmationModal.mjs';
|
|
3
|
+
import { useResolvedComposable } from './hooks/useResolvedComposable.mjs';
|
|
4
|
+
import { useActionConfirmationModal } from './hooks/useActionConfirmationModal.mjs';
|
|
5
|
+
|
|
6
|
+
const ActionConfirmationModalControl = () => {
|
|
7
|
+
const props = useActionConfirmationModal();
|
|
8
|
+
const Resolved = useResolvedComposable(ActionConfirmationModal, 'ActionConfirmationModal');
|
|
9
|
+
return React__default.createElement(Resolved, { ...props });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { ActionConfirmationModalControl };
|
|
@@ -11,9 +11,6 @@ import '@aws-amplify/ui-react-core/elements';
|
|
|
11
11
|
import '../credentials/context.mjs';
|
|
12
12
|
import '@aws-amplify/storage/internals';
|
|
13
13
|
import '../configuration/context.mjs';
|
|
14
|
-
import 'aws-amplify';
|
|
15
|
-
import 'aws-amplify/storage';
|
|
16
|
-
import '../actions/configs/context.mjs';
|
|
17
14
|
import '../actions/configs/defaults.mjs';
|
|
18
15
|
import '../displayText/context.mjs';
|
|
19
16
|
import '../filePreview/context.mjs';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useControlsContext } from '../context.mjs';
|
|
2
|
+
|
|
3
|
+
const useActionConfirmationModal = () => {
|
|
4
|
+
const { data: { confirmationModal = {} }, onConfirmationModalConfirm = () => { }, onConfirmationModalCancel = () => { }, } = useControlsContext();
|
|
5
|
+
return {
|
|
6
|
+
isOpen: false,
|
|
7
|
+
title: 'Confirm Action',
|
|
8
|
+
message: '',
|
|
9
|
+
confirmLabel: 'Confirm',
|
|
10
|
+
cancelLabel: 'Cancel',
|
|
11
|
+
...confirmationModal,
|
|
12
|
+
onConfirm: onConfirmationModalConfirm,
|
|
13
|
+
onCancel: onConfirmationModalCancel,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { useActionConfirmationModal };
|
|
@@ -10,17 +10,22 @@ import '../credentials/context.mjs';
|
|
|
10
10
|
import '@aws-amplify/storage/internals';
|
|
11
11
|
import '../configuration/context.mjs';
|
|
12
12
|
import '@aws-amplify/ui-react-core';
|
|
13
|
-
import 'aws-amplify';
|
|
14
|
-
import 'aws-amplify/storage';
|
|
15
|
-
import '../actions/configs/context.mjs';
|
|
16
13
|
import '../actions/configs/defaults.mjs';
|
|
17
14
|
import '../views/LocationActionView/CreateFolderView/CreateFolderView.mjs';
|
|
18
15
|
import '../views/LocationActionView/DeleteView/DeleteView.mjs';
|
|
16
|
+
import '../displayText/context.mjs';
|
|
17
|
+
import '@aws-amplify/ui-react';
|
|
18
|
+
import '../components/elements/definitions.mjs';
|
|
19
|
+
import '../components/elements/IconElement.mjs';
|
|
20
|
+
import 'aws-amplify';
|
|
21
|
+
import 'jszip';
|
|
22
|
+
import 'aws-amplify/storage';
|
|
19
23
|
import '../views/LocationActionView/DownloadView/DownloadView.mjs';
|
|
20
24
|
import '../views/context/actionViews.mjs';
|
|
21
25
|
import '../views/LocationActionView/UploadView/UploadView.mjs';
|
|
22
26
|
import '../fileItems/context.mjs';
|
|
23
27
|
import '../views/LocationDetailView/LocationDetailView.mjs';
|
|
28
|
+
import '../actions/configs/context.mjs';
|
|
24
29
|
import '../filePreview/context.mjs';
|
|
25
30
|
import '../views/LocationsView/LocationsView.mjs';
|
|
26
31
|
|
|
@@ -2,6 +2,7 @@ import React__default from 'react';
|
|
|
2
2
|
import '@aws-amplify/storage/internals';
|
|
3
3
|
import 'aws-amplify';
|
|
4
4
|
import '@aws-amplify/ui';
|
|
5
|
+
import 'jszip';
|
|
5
6
|
import 'aws-amplify/storage';
|
|
6
7
|
import { ActionConfigsProvider } from '../actions/configs/context.mjs';
|
|
7
8
|
import { defaultActionConfigs } from '../actions/configs/defaults.mjs';
|