@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.
Files changed (101) hide show
  1. package/dist/browser.js +8 -2
  2. package/dist/{createStorageBrowser-CotOvK0A.js → createStorageBrowser-B-J76Lyp.js} +1012 -249
  3. package/dist/esm/browser.mjs +1 -0
  4. package/dist/esm/components/StorageBrowser/ErrorBoundary/ErrorBoundary.mjs +0 -3
  5. package/dist/esm/components/StorageBrowser/StorageBrowserAmplify.mjs +1 -0
  6. package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +14 -3
  7. package/dist/esm/components/StorageBrowser/actions/handlers/defaults.mjs +1 -1
  8. package/dist/esm/components/StorageBrowser/actions/handlers/delete.mjs +39 -8
  9. package/dist/esm/components/StorageBrowser/actions/handlers/listLocations.mjs +7 -2
  10. package/dist/esm/components/StorageBrowser/actions/handlers/utils.mjs +65 -1
  11. package/dist/esm/components/StorageBrowser/actions/handlers/zipdownload.mjs +195 -0
  12. package/dist/esm/components/StorageBrowser/adapters/createAmplifyAuthAdapter/createAmplifyListLocationsHandler.mjs +3 -1
  13. package/dist/esm/components/StorageBrowser/adapters/createManagedAuthAdapter/createManagedAuthAdapter.mjs +1 -0
  14. package/dist/esm/components/StorageBrowser/components/ComponentsProvider.mjs +0 -3
  15. package/dist/esm/components/StorageBrowser/components/base/preview/DownloadButton.mjs +0 -3
  16. package/dist/esm/components/StorageBrowser/components/composables/ActionConfirmationModal.mjs +34 -0
  17. package/dist/esm/components/StorageBrowser/components/composables/defaults.mjs +2 -0
  18. package/dist/esm/components/StorageBrowser/components/elements/definitions.mjs +2 -2
  19. package/dist/esm/components/StorageBrowser/controls/ActionConfirmationModalControl.mjs +12 -0
  20. package/dist/esm/components/StorageBrowser/controls/DataTableControl.mjs +0 -3
  21. package/dist/esm/components/StorageBrowser/controls/hooks/useActionConfirmationModal.mjs +17 -0
  22. package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +8 -3
  23. package/dist/esm/components/StorageBrowser/createStorageBrowser/createProvider.mjs +1 -0
  24. package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +9 -4
  25. package/dist/esm/components/StorageBrowser/displayText/libraries/en/deleteView.mjs +117 -5
  26. package/dist/esm/components/StorageBrowser/displayText/libraries/en/downloadView.mjs +2 -0
  27. package/dist/esm/components/StorageBrowser/displayText/libraries/en/locationDetailView.mjs +1 -0
  28. package/dist/esm/components/StorageBrowser/displayText/libraries/en/shared.mjs +3 -0
  29. package/dist/esm/components/StorageBrowser/locationItems/context.mjs +18 -14
  30. package/dist/esm/components/StorageBrowser/locationItems/utils.mjs +38 -0
  31. package/dist/esm/components/StorageBrowser/store/validateStoreProps.mjs +1 -1
  32. package/dist/esm/components/StorageBrowser/tasks/constants.mjs +2 -0
  33. package/dist/esm/components/StorageBrowser/tasks/useProcessTasks.mjs +14 -5
  34. package/dist/esm/components/StorageBrowser/tasks/utils.mjs +4 -1
  35. package/dist/esm/components/StorageBrowser/useAction/useHandler.mjs +1 -1
  36. package/dist/esm/components/StorageBrowser/useAction/utils.mjs +0 -4
  37. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyView.mjs +0 -3
  38. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyViewProvider.mjs +4 -3
  39. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/FoldersMessageControl.mjs +0 -3
  40. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useCopyView.mjs +0 -3
  41. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useFolders.mjs +0 -3
  42. package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/CreateFolderView.mjs +0 -3
  43. package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/useCreateFolderView.mjs +0 -3
  44. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteView.mjs +5 -5
  45. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteViewProvider.mjs +7 -6
  46. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/useDeleteView.mjs +69 -6
  47. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/utils.mjs +87 -0
  48. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadView.mjs +0 -3
  49. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadViewProvider.mjs +9 -0
  50. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +0 -3
  51. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.mjs +1 -4
  52. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadViewProvider.mjs +4 -0
  53. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.mjs +0 -3
  54. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailView.mjs +0 -3
  55. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailViewProvider.mjs +2 -1
  56. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.mjs +38 -27
  57. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.mjs +9 -1
  58. package/dist/esm/components/StorageBrowser/views/LocationDetailView/useLocationDetailView.mjs +11 -9
  59. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsView.mjs +0 -3
  60. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsViewProvider.mjs +0 -3
  61. package/dist/esm/components/StorageBrowser/views/LocationsView/useLocationsView.mjs +1 -0
  62. package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +7 -3
  63. package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +8 -3
  64. package/dist/esm/components/StorageBrowser/views/hooks/useFilePreview/useFilePreview.mjs +1 -0
  65. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/constants.mjs +14 -1
  66. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.mjs +123 -13
  67. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/utils.mjs +4 -4
  68. package/dist/esm/version.mjs +1 -1
  69. package/dist/index.js +2 -1
  70. package/dist/styles.css +100 -1
  71. package/dist/types/components/StorageBrowser/actions/handlers/delete.d.ts +5 -3
  72. package/dist/types/components/StorageBrowser/actions/handlers/index.d.ts +1 -0
  73. package/dist/types/components/StorageBrowser/actions/handlers/types.d.ts +18 -2
  74. package/dist/types/components/StorageBrowser/actions/handlers/utils.d.ts +11 -0
  75. package/dist/types/components/StorageBrowser/actions/handlers/zipdownload.d.ts +3 -0
  76. package/dist/types/components/StorageBrowser/components/composables/ActionConfirmationModal.d.ts +12 -0
  77. package/dist/types/components/StorageBrowser/components/composables/types.d.ts +2 -0
  78. package/dist/types/components/StorageBrowser/controls/ActionConfirmationModalControl.d.ts +2 -0
  79. package/dist/types/components/StorageBrowser/controls/hooks/useActionConfirmationModal.d.ts +2 -0
  80. package/dist/types/components/StorageBrowser/controls/index.d.ts +1 -0
  81. package/dist/types/components/StorageBrowser/controls/types.d.ts +4 -0
  82. package/dist/types/components/StorageBrowser/displayText/types.d.ts +9 -0
  83. package/dist/types/components/StorageBrowser/locationItems/context.d.ts +12 -3
  84. package/dist/types/components/StorageBrowser/locationItems/index.d.ts +1 -0
  85. package/dist/types/components/StorageBrowser/locationItems/utils.d.ts +27 -0
  86. package/dist/types/components/StorageBrowser/tasks/types.d.ts +10 -5
  87. package/dist/types/components/StorageBrowser/tasks/useProcessTasks.d.ts +1 -1
  88. package/dist/types/components/StorageBrowser/useAction/useHandler.d.ts +1 -1
  89. package/dist/types/components/StorageBrowser/views/LocationActionView/DeleteView/types.d.ts +5 -0
  90. package/dist/types/components/StorageBrowser/views/LocationActionView/DeleteView/utils.d.ts +26 -0
  91. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.d.ts +4 -1
  92. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.d.ts +3 -2
  93. package/dist/types/components/StorageBrowser/views/LocationDetailView/types.d.ts +8 -1
  94. package/dist/types/components/StorageBrowser/views/utils/index.d.ts +1 -1
  95. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/constants.d.ts +5 -0
  96. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.d.ts +5 -2
  97. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/index.d.ts +1 -1
  98. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/types.d.ts +4 -0
  99. package/dist/types/version.d.ts +1 -1
  100. package/package.json +12 -9
  101. package/dist/esm/components/StorageBrowser/actions/handlers/download.mjs +0 -38
@@ -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) => !selected || selected.length === 0,
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) => !selected || selected.length === 0,
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) => !selected || selected.length === 0,
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 './download.mjs';
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 result = remove({
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
- .then(({ path }) => ({
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
- return fetchLocations([], nextToken);
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
- cachedItems = sanitizedItems;
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';