@aws-amplify/ui-react-storage 3.17.2 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/copy-serviceworker.js +36 -0
  2. package/dist/browser.js +23 -23
  3. package/dist/{createStorageBrowser-CtfBkr62.js → createStorageBrowser-S_PgkSVv.js} +1123 -264
  4. package/dist/download-sw.js +70 -0
  5. package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +3 -1
  6. package/dist/esm/components/StorageBrowser/actions/handlers/composedDownloadHandler.mjs +2 -2
  7. package/dist/esm/components/StorageBrowser/actions/handlers/download.mjs +1 -1
  8. package/dist/esm/components/StorageBrowser/actions/handlers/utils.mjs +23 -1
  9. package/dist/esm/components/StorageBrowser/actions/handlers/zipdownload.mjs +342 -118
  10. package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +3 -1
  11. package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +1 -1
  12. package/dist/esm/components/StorageBrowser/displayText/libraries/en/downloadView.mjs +4 -0
  13. package/dist/esm/components/StorageBrowser/service-worker/useServiceWorkerRegistration.mjs +25 -0
  14. package/dist/esm/components/StorageBrowser/useAction/useHandler.mjs +6 -2
  15. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadViewProvider.mjs +60 -8
  16. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +376 -5
  17. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/utils.mjs +172 -0
  18. package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +1 -0
  19. package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +1 -1
  20. package/dist/esm/version.mjs +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/styles.css +144 -114
  23. package/dist/types/components/StorageBrowser/actions/handlers/download.d.ts +15 -0
  24. package/dist/types/components/StorageBrowser/actions/handlers/utils.d.ts +12 -0
  25. package/dist/types/components/StorageBrowser/actions/handlers/zipdownload.d.ts +2 -2
  26. package/dist/types/components/StorageBrowser/actions/index.d.ts +1 -1
  27. package/dist/types/components/StorageBrowser/displayText/types.d.ts +21 -0
  28. package/dist/types/components/StorageBrowser/service-worker/download-sw.d.ts +2 -0
  29. package/dist/types/components/StorageBrowser/service-worker/useServiceWorkerRegistration.d.ts +2 -0
  30. package/dist/types/components/StorageBrowser/useAction/types.d.ts +1 -0
  31. package/dist/types/components/StorageBrowser/views/LocationActionView/DownloadView/types.d.ts +40 -0
  32. package/dist/types/components/StorageBrowser/views/LocationActionView/DownloadView/utils.d.ts +99 -0
  33. package/dist/types/components/StorageBrowser/views/LocationActionView/index.d.ts +1 -1
  34. package/dist/types/version.d.ts +1 -1
  35. package/package.json +12 -8
@@ -0,0 +1,172 @@
1
+ import { list } from '@aws-amplify/storage/internals';
2
+ import { constructBucket, createDownloadItem } from '../../../actions/handlers/utils.mjs';
3
+ import '@aws-amplify/ui';
4
+ import '@zip.js/zip.js';
5
+ import 'aws-amplify/storage';
6
+ import '../../../actions/configs/context.mjs';
7
+ import '../../../actions/configs/defaults.mjs';
8
+
9
+ /**
10
+ * Page size for the recursive `list()` used to expand a folder into its files.
11
+ * Mirrors the value used by DeleteView's `countFilesInFolder`.
12
+ */
13
+ const LIST_PAGE_SIZE = 1000;
14
+ /**
15
+ * Hard cap on the combined number of files a single download may contain,
16
+ * counted across every folder (and loose file) in the selection. Folder
17
+ * expansion stops paginating as soon as the running total would exceed this
18
+ * value and throws {@link FileLimitError}; the view then surfaces a blocked
19
+ * state instead of downloading a truncated set (silent truncation would be
20
+ * data loss). Mirrors the 5000 threshold DeleteView uses for its file count.
21
+ */
22
+ const LARGE_DOWNLOAD_FILE_COUNT = 5000;
23
+ /**
24
+ * Thrown by {@link expandFolderToFiles} when the combined expanded file count
25
+ * of the selection exceeds {@link LARGE_DOWNLOAD_FILE_COUNT}. Callers use it
26
+ * to distinguish the over-limit state from enumeration failures.
27
+ */
28
+ class FileLimitError extends Error {
29
+ constructor() {
30
+ super(`Download selection exceeds the maximum of ${LARGE_DOWNLOAD_FILE_COUNT} files`);
31
+ this.name = 'FileLimitError';
32
+ }
33
+ }
34
+ /**
35
+ * Computes the base name for a multi-file zip archive from the flat list of
36
+ * file keys being zipped. Used for file-only, multi-item, and mixed
37
+ * selections; a selection of exactly one folder is named after that folder
38
+ * instead (see {@link resolveArchiveName}).
39
+ *
40
+ * Rule: the name is the last path segment of the LONGEST COMMON ANCESTOR
41
+ * DIRECTORY of all files. For each key the directory segments are
42
+ * `key.split('/')` without the final basename; the longest common prefix of
43
+ * those segment arrays is taken, and its last element is the name. When the
44
+ * common prefix is empty (root-level files or files with no shared ancestor)
45
+ * this falls back to `'download'`.
46
+ *
47
+ * Examples:
48
+ * - ['public/nested/one/pic.jpg', 'public/nested/two/pic.jpg'] -> 'nested'
49
+ * - ['public/nested/a.jpg', 'public/images/b.jpg', 'public/c.jpg'] -> 'public'
50
+ * - ['photos/a.jpg', 'photos/b.jpg'] -> 'photos'
51
+ * - ['a.jpg', 'b.jpg'] -> 'download'
52
+ * - ['public/x.jpg', 'other/y.jpg'] -> 'download'
53
+ * - [] -> 'download'
54
+ *
55
+ * Pure/synchronous; the base name only — the handler appends `.zip`.
56
+ */
57
+ const getArchiveName = (fileKeys) => {
58
+ if (fileKeys.length === 0) {
59
+ return 'download';
60
+ }
61
+ // Directory segments per file = path split without the final basename.
62
+ const dirSegments = fileKeys.map((key) => key.split('/').slice(0, -1));
63
+ // Longest common prefix across every file's directory segment array.
64
+ const [first, ...rest] = dirSegments;
65
+ let commonLength = first.length;
66
+ for (const segments of rest) {
67
+ let i = 0;
68
+ while (i < commonLength &&
69
+ i < segments.length &&
70
+ segments[i] === first[i]) {
71
+ i += 1;
72
+ }
73
+ commonLength = i;
74
+ if (commonLength === 0) {
75
+ break;
76
+ }
77
+ }
78
+ // Last segment of the common ancestor dir; empty prefix OR an empty segment
79
+ // (e.g. a key like 'a//b.jpg') -> 'download'.
80
+ const name = commonLength === 0 ? '' : first[commonLength - 1];
81
+ return name === '' ? 'download' : name;
82
+ };
83
+ /**
84
+ * Resolves the zip archive base name for the current SELECTION.
85
+ *
86
+ * When the selection consists of exactly ONE FOLDER (no other items), the
87
+ * archive is named after that folder (basename of `folder.key`, trailing
88
+ * slash stripped): the download was initiated from that folder, so selecting
89
+ * `photos/` yields `photos.zip` even when every file lives in a deeper
90
+ * subfolder like `photos/vacation/`. Every other selection shape (file-only,
91
+ * multi-folder, mixed) falls back to the longest-common-ancestor rule of
92
+ * {@link getArchiveName}.
93
+ */
94
+ const resolveArchiveName = (dataItems, fileKeys) => {
95
+ if (dataItems.length === 1 && dataItems[0].type === 'FOLDER') {
96
+ const basename = dataItems[0].key.replace(/\/$/, '').split('/').pop();
97
+ if (basename) {
98
+ return basename;
99
+ }
100
+ }
101
+ return getArchiveName(fileKeys);
102
+ };
103
+ /**
104
+ * Recursively expands a folder into the flat list of downloadable files it
105
+ * contains, preserving each file's folder-relative zip path.
106
+ *
107
+ * Clones the pagination loop from DeleteView's `countFilesInFolder`, but:
108
+ * - collects {@link DownloadHandlerData} items instead of counting,
109
+ * - stops paginating and throws {@link FileLimitError} once the shared
110
+ * `fileCounter` would exceed {@link LARGE_DOWNLOAD_FILE_COUNT} (the caller
111
+ * surfaces a blocked state; a truncated zip is never produced),
112
+ * - filters out directory markers (keys ending in `'/'`),
113
+ * - is cancellable via `signal`, checked between `list()` pages.
114
+ */
115
+ const expandFolderToFiles = async ({ folderKey, config, locationPrefix, signal, fileCounter = { count: 0 }, }) => {
116
+ const { accountId, credentials, customEndpoint } = config;
117
+ const bucket = constructBucket(config);
118
+ const files = [];
119
+ let nextToken;
120
+ do {
121
+ // Abort is checked between pages (and before the first page) so a cancel
122
+ // during enumeration stops promptly without emitting a partial result.
123
+ if (signal?.aborted) {
124
+ throw new DOMException('Folder expansion aborted', 'AbortError');
125
+ }
126
+ // Short-circuit between pages when the SHARED counter already exceeds the
127
+ // cap (e.g. a sibling folder's expansion pushed it over while this one
128
+ // awaited `list()`): no point paginating a selection that is blocked.
129
+ if (fileCounter.count > LARGE_DOWNLOAD_FILE_COUNT) {
130
+ throw new FileLimitError();
131
+ }
132
+ const { items, nextToken: listNextToken } = await list({
133
+ path: folderKey,
134
+ options: {
135
+ bucket,
136
+ locationCredentialsProvider: credentials,
137
+ expectedBucketOwner: accountId,
138
+ customEndpoint,
139
+ pageSize: LIST_PAGE_SIZE,
140
+ nextToken,
141
+ },
142
+ });
143
+ for (const item of items) {
144
+ // Skip directory markers (zero-byte keys ending in '/') — only real files
145
+ // become zip entries.
146
+ if (item.path.endsWith('/')) {
147
+ continue;
148
+ }
149
+ // Enforce the cap per file so the loop stops as soon as the combined
150
+ // selection exceeds it, mid-page included.
151
+ fileCounter.count += 1;
152
+ if (fileCounter.count > LARGE_DOWNLOAD_FILE_COUNT) {
153
+ throw new FileLimitError();
154
+ }
155
+ const fileData = {
156
+ key: item.path,
157
+ id: crypto.randomUUID(),
158
+ // `list()` may omit size/lastModified; fall back defensively instead
159
+ // of asserting non-null.
160
+ size: item.size ?? 0,
161
+ lastModified: item.lastModified ?? new Date(0),
162
+ eTag: item.eTag,
163
+ type: 'FILE',
164
+ };
165
+ files.push(createDownloadItem(fileData, locationPrefix));
166
+ }
167
+ nextToken = listNextToken;
168
+ } while (nextToken);
169
+ return files;
170
+ };
171
+
172
+ export { FileLimitError, LARGE_DOWNLOAD_FILE_COUNT, expandFolderToFiles, getArchiveName, resolveArchiveName };
@@ -23,6 +23,7 @@ import 'aws-amplify';
23
23
  import '@zip.js/zip.js';
24
24
  import 'aws-amplify/storage';
25
25
  import { DownloadView } from '../LocationActionView/DownloadView/DownloadView.mjs';
26
+ import '../../actions/configs/context.mjs';
26
27
 
27
28
  const DEFAULT_ACTION_VIEWS = {
28
29
  createFolder: CreateFolderView,
@@ -21,11 +21,11 @@ import 'aws-amplify';
21
21
  import '@zip.js/zip.js';
22
22
  import 'aws-amplify/storage';
23
23
  import '../LocationActionView/DownloadView/DownloadView.mjs';
24
+ import '../../actions/configs/context.mjs';
24
25
  import { LocationActionView } from '../LocationActionView/LocationActionView.mjs';
25
26
  import '../LocationActionView/UploadView/UploadView.mjs';
26
27
  import '../../fileItems/context.mjs';
27
28
  import { LocationDetailView } from '../LocationDetailView/LocationDetailView.mjs';
28
- import '../../actions/configs/context.mjs';
29
29
  import '../../filePreview/context.mjs';
30
30
  import { LocationsView } from '../LocationsView/LocationsView.mjs';
31
31
 
@@ -1,3 +1,3 @@
1
- const VERSION = '3.17.2';
1
+ const VERSION = '3.18.0';
2
2
 
3
3
  export { VERSION };
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var uiReactCore = require('@aws-amplify/ui-react-core');
9
9
  var auth = require('aws-amplify/auth');
10
10
  var storage = require('aws-amplify/storage');
11
11
  var internal = require('@aws-amplify/ui-react/internal');
12
- var createStorageBrowser = require('./createStorageBrowser-CtfBkr62.js');
12
+ var createStorageBrowser = require('./createStorageBrowser-S_PgkSVv.js');
13
13
  require('@aws-amplify/storage/internals');
14
14
  require('aws-amplify');
15
15
  require('@zip.js/zip.js');