@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.
- package/bin/copy-serviceworker.js +36 -0
- package/dist/browser.js +23 -23
- package/dist/{createStorageBrowser-CtfBkr62.js → createStorageBrowser-S_PgkSVv.js} +1123 -264
- package/dist/download-sw.js +70 -0
- package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +3 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/composedDownloadHandler.mjs +2 -2
- package/dist/esm/components/StorageBrowser/actions/handlers/download.mjs +1 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/utils.mjs +23 -1
- package/dist/esm/components/StorageBrowser/actions/handlers/zipdownload.mjs +342 -118
- package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +3 -1
- package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +1 -1
- package/dist/esm/components/StorageBrowser/displayText/libraries/en/downloadView.mjs +4 -0
- package/dist/esm/components/StorageBrowser/service-worker/useServiceWorkerRegistration.mjs +25 -0
- package/dist/esm/components/StorageBrowser/useAction/useHandler.mjs +6 -2
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadViewProvider.mjs +60 -8
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +376 -5
- package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/utils.mjs +172 -0
- package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +1 -0
- package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +1 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/styles.css +144 -114
- package/dist/types/components/StorageBrowser/actions/handlers/download.d.ts +15 -0
- package/dist/types/components/StorageBrowser/actions/handlers/utils.d.ts +12 -0
- package/dist/types/components/StorageBrowser/actions/handlers/zipdownload.d.ts +2 -2
- package/dist/types/components/StorageBrowser/actions/index.d.ts +1 -1
- package/dist/types/components/StorageBrowser/displayText/types.d.ts +21 -0
- package/dist/types/components/StorageBrowser/service-worker/download-sw.d.ts +2 -0
- package/dist/types/components/StorageBrowser/service-worker/useServiceWorkerRegistration.d.ts +2 -0
- package/dist/types/components/StorageBrowser/useAction/types.d.ts +1 -0
- package/dist/types/components/StorageBrowser/views/LocationActionView/DownloadView/types.d.ts +40 -0
- package/dist/types/components/StorageBrowser/views/LocationActionView/DownloadView/utils.d.ts +99 -0
- package/dist/types/components/StorageBrowser/views/LocationActionView/index.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- 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
|
|
package/dist/esm/version.mjs
CHANGED
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-
|
|
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');
|