@aws-amplify/ui-react-storage 3.2.0 → 3.3.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/esm/components/FileUploader/FileUploader.mjs +185 -0
- package/dist/esm/components/FileUploader/hooks/useFileUploader/actions.mjs +39 -0
- package/dist/esm/components/FileUploader/hooks/useFileUploader/reducer.mjs +93 -0
- package/dist/esm/components/FileUploader/hooks/useFileUploader/types.mjs +13 -0
- package/dist/esm/components/FileUploader/hooks/useFileUploader/useFileUploader.mjs +62 -0
- package/dist/esm/components/FileUploader/hooks/useUploadFiles/useUploadFiles.mjs +79 -0
- package/dist/esm/components/FileUploader/types.mjs +11 -0
- package/dist/esm/components/FileUploader/ui/Container/Container.mjs +8 -0
- package/dist/esm/components/FileUploader/ui/DropZone/DropZone.mjs +16 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileControl.mjs +23 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileDetails.mjs +12 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileList.mjs +44 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileRemoveButton.mjs +12 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileStatusMessage.mjs +28 -0
- package/dist/esm/components/FileUploader/ui/FileList/FileThumbnail.mjs +12 -0
- package/dist/esm/components/FileUploader/ui/FileListFooter/FileListFooter.mjs +13 -0
- package/dist/esm/components/FileUploader/ui/FileListHeader/FileListHeader.mjs +14 -0
- package/dist/esm/components/FileUploader/ui/FilePicker/FilePicker.mjs +9 -0
- package/dist/esm/components/FileUploader/utils/checkMaxFileSize.mjs +12 -0
- package/dist/esm/components/FileUploader/utils/displayText.mjs +39 -0
- package/dist/esm/components/FileUploader/utils/filterAllowedFiles.mjs +27 -0
- package/dist/esm/components/FileUploader/utils/getInput.mjs +39 -0
- package/dist/esm/components/FileUploader/utils/resolveFile.mjs +20 -0
- package/dist/esm/components/FileUploader/utils/uploadFile.mjs +26 -0
- package/dist/esm/components/StorageManager/StorageManager.mjs +4 -0
- package/dist/esm/components/StorageManager/ui/FileList/FileDetails.mjs +1 -4
- package/dist/esm/components/StorageManager/utils/checkMaxFileSize.mjs +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +723 -37
- package/dist/styles.css +23 -42
- package/dist/types/components/FileUploader/FileUploader.d.ts +15 -0
- package/dist/types/components/FileUploader/hooks/index.d.ts +2 -0
- package/dist/types/components/FileUploader/hooks/useFileUploader/actions.d.ts +22 -0
- package/dist/types/components/FileUploader/hooks/useFileUploader/index.d.ts +1 -0
- package/dist/types/components/FileUploader/hooks/useFileUploader/reducer.d.ts +2 -0
- package/dist/types/components/FileUploader/hooks/useFileUploader/types.d.ts +50 -0
- package/dist/types/components/FileUploader/hooks/useFileUploader/useFileUploader.d.ts +35 -0
- package/dist/types/components/FileUploader/hooks/useUploadFiles/index.d.ts +1 -0
- package/dist/types/components/FileUploader/hooks/useUploadFiles/useUploadFiles.d.ts +12 -0
- package/dist/types/components/FileUploader/index.d.ts +3 -0
- package/dist/types/components/FileUploader/types.d.ts +129 -0
- package/dist/types/components/FileUploader/ui/Container/Container.d.ts +6 -0
- package/dist/types/components/FileUploader/ui/Container/index.d.ts +1 -0
- package/dist/types/components/FileUploader/ui/DropZone/DropZone.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/DropZone/index.d.ts +2 -0
- package/dist/types/components/FileUploader/ui/DropZone/types.d.ts +13 -0
- package/dist/types/components/FileUploader/ui/FileList/FileControl.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/FileDetails.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/FileList.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/FileRemoveButton.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/FileStatusMessage.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/FileThumbnail.d.ts +3 -0
- package/dist/types/components/FileUploader/ui/FileList/index.d.ts +2 -0
- package/dist/types/components/FileUploader/ui/FileList/types.d.ts +51 -0
- package/dist/types/components/FileUploader/ui/FileListFooter/FileListFooter.d.ts +9 -0
- package/dist/types/components/FileUploader/ui/FileListFooter/index.d.ts +1 -0
- package/dist/types/components/FileUploader/ui/FileListHeader/FileListHeader.d.ts +10 -0
- package/dist/types/components/FileUploader/ui/FileListHeader/index.d.ts +1 -0
- package/dist/types/components/FileUploader/ui/FilePicker/FilePicker.d.ts +4 -0
- package/dist/types/components/FileUploader/ui/FilePicker/index.d.ts +1 -0
- package/dist/types/components/FileUploader/ui/index.d.ts +6 -0
- package/dist/types/components/FileUploader/utils/checkMaxFileSize.d.ts +5 -0
- package/dist/types/components/FileUploader/utils/displayText.d.ts +22 -0
- package/dist/types/components/FileUploader/utils/filterAllowedFiles.d.ts +1 -0
- package/dist/types/components/FileUploader/utils/getInput.d.ts +17 -0
- package/dist/types/components/FileUploader/utils/index.d.ts +5 -0
- package/dist/types/components/FileUploader/utils/resolveFile.d.ts +9 -0
- package/dist/types/components/FileUploader/utils/uploadFile.d.ts +32 -0
- package/dist/types/components/StorageManager/utils/index.d.ts +0 -1
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/dist/esm/components/StorageManager/utils/humanFileSize.mjs +0 -29
- package/dist/types/components/StorageManager/utils/humanFileSize.d.ts +0 -11
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { getLogger, ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { VisuallyHidden } from '@aws-amplify/ui-react';
|
|
4
|
+
import { useDeprecationWarning, useSetUserAgent } from '@aws-amplify/ui-react-core';
|
|
5
|
+
import { useDropZone } from '@aws-amplify/ui-react/internal';
|
|
6
|
+
import { useFileUploader } from './hooks/useFileUploader/useFileUploader.mjs';
|
|
7
|
+
import { useUploadFiles } from './hooks/useUploadFiles/useUploadFiles.mjs';
|
|
8
|
+
import { FileStatus } from './types.mjs';
|
|
9
|
+
import { Container } from './ui/Container/Container.mjs';
|
|
10
|
+
import { DropZone } from './ui/DropZone/DropZone.mjs';
|
|
11
|
+
import { FileList } from './ui/FileList/FileList.mjs';
|
|
12
|
+
import { FileListHeader } from './ui/FileListHeader/FileListHeader.mjs';
|
|
13
|
+
import { FileListFooter } from './ui/FileListFooter/FileListFooter.mjs';
|
|
14
|
+
import { FilePicker } from './ui/FilePicker/FilePicker.mjs';
|
|
15
|
+
import { checkMaxFileSize } from './utils/checkMaxFileSize.mjs';
|
|
16
|
+
import { defaultFileUploaderDisplayText } from './utils/displayText.mjs';
|
|
17
|
+
import { filterAllowedFiles } from './utils/filterAllowedFiles.mjs';
|
|
18
|
+
import 'aws-amplify/auth';
|
|
19
|
+
import 'aws-amplify/storage';
|
|
20
|
+
import { VERSION } from '../../version.mjs';
|
|
21
|
+
|
|
22
|
+
const logger = getLogger('Storage');
|
|
23
|
+
const MISSING_REQUIRED_PROPS_MESSAGE = '`FileUploader` requires a `maxFileCount` prop to be provided.';
|
|
24
|
+
const ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE = '`FileUploader` does not allow usage of a `path` callback prop with an `accessLevel` prop.';
|
|
25
|
+
const ACCESS_LEVEL_DEPRECATION_MESSAGE = '`accessLevel` has been deprecated and will be removed in a future major version. See migration notes at https://ui.docs.amplify.aws/react/connected-components/storage/FileUploader';
|
|
26
|
+
const FileUploaderBase = React.forwardRef(function FileUploader({ acceptedFileTypes = [], accessLevel, autoUpload = true, components, defaultFiles, displayText: overrideDisplayText, isResumable = false, maxFileCount, maxFileSize, onFileRemove, onUploadError, onUploadStart, onUploadSuccess, path, processFile, showThumbnails = true, useAccelerateEndpoint, }, ref) {
|
|
27
|
+
if (!maxFileCount) {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.warn(MISSING_REQUIRED_PROPS_MESSAGE);
|
|
30
|
+
}
|
|
31
|
+
if (accessLevel && typeof path === 'function') {
|
|
32
|
+
throw new Error(ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE);
|
|
33
|
+
}
|
|
34
|
+
useDeprecationWarning({
|
|
35
|
+
message: ACCESS_LEVEL_DEPRECATION_MESSAGE,
|
|
36
|
+
shouldWarn: !!accessLevel,
|
|
37
|
+
});
|
|
38
|
+
const Components = {
|
|
39
|
+
Container,
|
|
40
|
+
DropZone,
|
|
41
|
+
FileList,
|
|
42
|
+
FilePicker,
|
|
43
|
+
FileListHeader,
|
|
44
|
+
FileListFooter,
|
|
45
|
+
...components,
|
|
46
|
+
};
|
|
47
|
+
const allowMultipleFiles = maxFileCount === undefined ||
|
|
48
|
+
(typeof maxFileCount === 'number' && maxFileCount > 1);
|
|
49
|
+
const displayText = {
|
|
50
|
+
...defaultFileUploaderDisplayText,
|
|
51
|
+
...overrideDisplayText,
|
|
52
|
+
};
|
|
53
|
+
const { getFileSizeErrorText } = displayText;
|
|
54
|
+
const getMaxFileSizeErrorMessage = (file) => {
|
|
55
|
+
return checkMaxFileSize({
|
|
56
|
+
file,
|
|
57
|
+
maxFileSize,
|
|
58
|
+
getFileSizeErrorText,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const { addFiles, clearFiles, files, removeUpload, queueFiles, setProcessedKey, setUploadingFile, setUploadPaused, setUploadProgress, setUploadSuccess, setUploadResumed, } = useFileUploader(defaultFiles);
|
|
62
|
+
React.useImperativeHandle(ref, () => ({ clearFiles }));
|
|
63
|
+
const { dragState, ...dropZoneProps } = useDropZone({
|
|
64
|
+
acceptedFileTypes,
|
|
65
|
+
onDropComplete: ({ acceptedFiles, rejectedFiles }) => {
|
|
66
|
+
if (rejectedFiles && rejectedFiles.length > 0) {
|
|
67
|
+
logger.warn('Rejected files: ', rejectedFiles);
|
|
68
|
+
}
|
|
69
|
+
// We need to filter out files by extension here,
|
|
70
|
+
// we don't get filenames on the drag event, only on drop
|
|
71
|
+
const _acceptedFiles = filterAllowedFiles(acceptedFiles, acceptedFileTypes);
|
|
72
|
+
addFiles({
|
|
73
|
+
files: _acceptedFiles,
|
|
74
|
+
status: autoUpload ? FileStatus.QUEUED : FileStatus.ADDED,
|
|
75
|
+
getFileErrorMessage: getMaxFileSizeErrorMessage,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
useUploadFiles({
|
|
80
|
+
accessLevel,
|
|
81
|
+
files,
|
|
82
|
+
isResumable,
|
|
83
|
+
maxFileCount,
|
|
84
|
+
onUploadError,
|
|
85
|
+
onUploadSuccess,
|
|
86
|
+
onUploadStart,
|
|
87
|
+
onProcessFileSuccess: setProcessedKey,
|
|
88
|
+
setUploadingFile,
|
|
89
|
+
setUploadProgress,
|
|
90
|
+
setUploadSuccess,
|
|
91
|
+
processFile,
|
|
92
|
+
path,
|
|
93
|
+
useAccelerateEndpoint,
|
|
94
|
+
});
|
|
95
|
+
const onFilePickerChange = (event) => {
|
|
96
|
+
const { files } = event.target;
|
|
97
|
+
if (!files || files.length === 0) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
addFiles({
|
|
101
|
+
files: Array.from(files),
|
|
102
|
+
status: autoUpload ? FileStatus.QUEUED : FileStatus.ADDED,
|
|
103
|
+
getFileErrorMessage: getMaxFileSizeErrorMessage,
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const onClearAll = () => {
|
|
107
|
+
clearFiles();
|
|
108
|
+
};
|
|
109
|
+
const onUploadAll = () => {
|
|
110
|
+
queueFiles();
|
|
111
|
+
};
|
|
112
|
+
const onPauseUpload = ({ id, uploadTask }) => {
|
|
113
|
+
uploadTask.pause();
|
|
114
|
+
setUploadPaused({ id });
|
|
115
|
+
};
|
|
116
|
+
const onResumeUpload = ({ id, uploadTask }) => {
|
|
117
|
+
uploadTask.resume();
|
|
118
|
+
setUploadResumed({ id });
|
|
119
|
+
};
|
|
120
|
+
const onCancelUpload = ({ id, uploadTask }) => {
|
|
121
|
+
// At this time we don't know if the delete
|
|
122
|
+
// permissions are enabled (required to cancel upload),
|
|
123
|
+
// so we do a pause instead and remove from files
|
|
124
|
+
uploadTask.pause();
|
|
125
|
+
removeUpload({ id });
|
|
126
|
+
};
|
|
127
|
+
const onDeleteUpload = ({ id }) => {
|
|
128
|
+
// At this time we don't know if the delete
|
|
129
|
+
// permissions are enabled, so we do a soft delete
|
|
130
|
+
// from file list, but don't remove from storage
|
|
131
|
+
removeUpload({ id });
|
|
132
|
+
if (typeof onFileRemove === 'function') {
|
|
133
|
+
const file = files.find((file) => file.id === id);
|
|
134
|
+
if (file) {
|
|
135
|
+
// return `processedKey` if available and `processFile` is provided
|
|
136
|
+
const key = (processFile && file?.processedKey) ?? file.key;
|
|
137
|
+
onFileRemove({ key });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
// checks if all downloads completed to 100%
|
|
142
|
+
const allUploadsSuccessful = files.length === 0
|
|
143
|
+
? false
|
|
144
|
+
: files.every((file) => file?.status === FileStatus.UPLOADED);
|
|
145
|
+
// Displays if over max files
|
|
146
|
+
const hasMaxFilesError = files.filter((file) => file.progress < 100).length > maxFileCount;
|
|
147
|
+
const uploadedFilesLength = files.filter((file) => file?.status === FileStatus.UPLOADED).length;
|
|
148
|
+
const remainingFilesCount = files.length - uploadedFilesLength;
|
|
149
|
+
// number of files selected for upload when autoUpload is turned off
|
|
150
|
+
const selectedFilesCount = autoUpload ? 0 : remainingFilesCount;
|
|
151
|
+
const hasFiles = files.length > 0;
|
|
152
|
+
const hasUploadActions = !autoUpload && remainingFilesCount > 0;
|
|
153
|
+
const hiddenInput = React.useRef(null);
|
|
154
|
+
function handleClick() {
|
|
155
|
+
if (hiddenInput.current) {
|
|
156
|
+
hiddenInput.current.click();
|
|
157
|
+
hiddenInput.current.value = '';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
useSetUserAgent({
|
|
161
|
+
componentName: 'FileUploader',
|
|
162
|
+
packageName: 'react-storage',
|
|
163
|
+
version: VERSION,
|
|
164
|
+
});
|
|
165
|
+
return (React.createElement(Components.Container, { className: `${ComponentClassName.FileUploader} ${hasFiles ? ComponentClassName.FileUploaderPreviewer : ''}` },
|
|
166
|
+
React.createElement(Components.DropZone, { inDropZone: dragState !== 'inactive', ...dropZoneProps, displayText: displayText },
|
|
167
|
+
React.createElement(React.Fragment, null,
|
|
168
|
+
React.createElement(Components.FilePicker, { onClick: handleClick }, displayText.browseFilesText),
|
|
169
|
+
React.createElement(VisuallyHidden, null,
|
|
170
|
+
React.createElement("input", { type: "file", tabIndex: -1, ref: hiddenInput, onChange: onFilePickerChange, multiple: allowMultipleFiles, accept: acceptedFileTypes.join(',') })))),
|
|
171
|
+
hasFiles ? (React.createElement(Components.FileListHeader, { allUploadsSuccessful: allUploadsSuccessful, displayText: displayText, fileCount: files.length, remainingFilesCount: remainingFilesCount, selectedFilesCount: selectedFilesCount })) : null,
|
|
172
|
+
React.createElement(Components.FileList, { displayText: displayText, files: files, isResumable: isResumable, onCancelUpload: onCancelUpload, onDeleteUpload: onDeleteUpload, onResume: onResumeUpload, onPause: onPauseUpload, showThumbnails: showThumbnails, hasMaxFilesError: hasMaxFilesError, maxFileCount: maxFileCount }),
|
|
173
|
+
hasUploadActions ? (React.createElement(Components.FileListFooter, { displayText: displayText, remainingFilesCount: remainingFilesCount, onClearAll: onClearAll, onUploadAll: onUploadAll })) : null));
|
|
174
|
+
});
|
|
175
|
+
// pass an empty object as first param to avoid destructive action on `FileUploaderBase`
|
|
176
|
+
const FileUploader = Object.assign({}, FileUploaderBase, {
|
|
177
|
+
Container,
|
|
178
|
+
DropZone,
|
|
179
|
+
FileList,
|
|
180
|
+
FileListHeader,
|
|
181
|
+
FileListFooter,
|
|
182
|
+
FilePicker,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export { ACCESS_LEVEL_DEPRECATION_MESSAGE, ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE, FileUploader, MISSING_REQUIRED_PROPS_MESSAGE };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FileUploaderActionTypes } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
const addFilesAction = ({ files, status, getFileErrorMessage, }) => ({
|
|
4
|
+
type: FileUploaderActionTypes.ADD_FILES,
|
|
5
|
+
files,
|
|
6
|
+
status,
|
|
7
|
+
getFileErrorMessage,
|
|
8
|
+
});
|
|
9
|
+
const clearFilesAction = () => ({
|
|
10
|
+
type: FileUploaderActionTypes.CLEAR_FILES,
|
|
11
|
+
});
|
|
12
|
+
const queueFilesAction = () => ({
|
|
13
|
+
type: FileUploaderActionTypes.QUEUE_FILES,
|
|
14
|
+
});
|
|
15
|
+
const setProcessedKeyAction = (input) => ({
|
|
16
|
+
...input,
|
|
17
|
+
type: FileUploaderActionTypes.SET_PROCESSED_FILE_KEY,
|
|
18
|
+
});
|
|
19
|
+
const setUploadingFileAction = ({ id, uploadTask, }) => ({
|
|
20
|
+
type: FileUploaderActionTypes.SET_STATUS_UPLOADING,
|
|
21
|
+
id,
|
|
22
|
+
uploadTask,
|
|
23
|
+
});
|
|
24
|
+
const setUploadProgressAction = ({ id, progress, }) => ({
|
|
25
|
+
type: FileUploaderActionTypes.SET_UPLOAD_PROGRESS,
|
|
26
|
+
id,
|
|
27
|
+
progress,
|
|
28
|
+
});
|
|
29
|
+
const setUploadStatusAction = ({ id, status, }) => ({
|
|
30
|
+
type: FileUploaderActionTypes.SET_STATUS,
|
|
31
|
+
id,
|
|
32
|
+
status,
|
|
33
|
+
});
|
|
34
|
+
const removeUploadAction = ({ id }) => ({
|
|
35
|
+
type: FileUploaderActionTypes.REMOVE_UPLOAD,
|
|
36
|
+
id,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export { addFilesAction, clearFilesAction, queueFilesAction, removeUploadAction, setProcessedKeyAction, setUploadProgressAction, setUploadStatusAction, setUploadingFileAction };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { FileStatus } from '../../types.mjs';
|
|
2
|
+
import { FileUploaderActionTypes } from './types.mjs';
|
|
3
|
+
|
|
4
|
+
const updateFiles = (files, nextFileData) => files.reduce((files, currentFile) => {
|
|
5
|
+
if (currentFile.id === nextFileData.id) {
|
|
6
|
+
return [...files, { ...currentFile, ...nextFileData }];
|
|
7
|
+
}
|
|
8
|
+
return [...files, currentFile];
|
|
9
|
+
}, []);
|
|
10
|
+
function fileUploaderStateReducer(state, action) {
|
|
11
|
+
switch (action.type) {
|
|
12
|
+
case FileUploaderActionTypes.ADD_FILES: {
|
|
13
|
+
const { files, status } = action;
|
|
14
|
+
const newUploads = files.map((file) => {
|
|
15
|
+
const errorText = action.getFileErrorMessage(file);
|
|
16
|
+
return {
|
|
17
|
+
// make sure id is unique,
|
|
18
|
+
// we only use it internally and don't send it to Storage
|
|
19
|
+
id: `${Date.now()}-${file.name}`,
|
|
20
|
+
file,
|
|
21
|
+
error: errorText,
|
|
22
|
+
key: file.name,
|
|
23
|
+
status: errorText ? FileStatus.ERROR : status,
|
|
24
|
+
isImage: file.type.startsWith('image/'),
|
|
25
|
+
progress: -1,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
const newFiles = [...state.files, ...newUploads];
|
|
29
|
+
return { ...state, files: newFiles };
|
|
30
|
+
}
|
|
31
|
+
case FileUploaderActionTypes.CLEAR_FILES: {
|
|
32
|
+
return { ...state, files: [] };
|
|
33
|
+
}
|
|
34
|
+
case FileUploaderActionTypes.QUEUE_FILES: {
|
|
35
|
+
const { files } = state;
|
|
36
|
+
const newFiles = files.reduce((files, currentFile) => {
|
|
37
|
+
return [
|
|
38
|
+
...files,
|
|
39
|
+
{
|
|
40
|
+
...currentFile,
|
|
41
|
+
...(currentFile.status === FileStatus.ADDED
|
|
42
|
+
? { status: FileStatus.QUEUED }
|
|
43
|
+
: {}),
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
}, []);
|
|
47
|
+
return {
|
|
48
|
+
...state,
|
|
49
|
+
files: newFiles,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
case FileUploaderActionTypes.SET_STATUS_UPLOADING: {
|
|
53
|
+
const { id, uploadTask } = action;
|
|
54
|
+
const status = FileStatus.UPLOADING;
|
|
55
|
+
const progress = 0;
|
|
56
|
+
const nextFileData = { status, progress, id, uploadTask };
|
|
57
|
+
const files = updateFiles(state.files, nextFileData);
|
|
58
|
+
return { ...state, files };
|
|
59
|
+
}
|
|
60
|
+
case FileUploaderActionTypes.SET_PROCESSED_FILE_KEY: {
|
|
61
|
+
const { processedKey, id } = action;
|
|
62
|
+
const files = updateFiles(state.files, { processedKey, id });
|
|
63
|
+
return { files };
|
|
64
|
+
}
|
|
65
|
+
case FileUploaderActionTypes.SET_UPLOAD_PROGRESS: {
|
|
66
|
+
const { id, progress } = action;
|
|
67
|
+
const files = updateFiles(state.files, { id, progress });
|
|
68
|
+
return { ...state, files };
|
|
69
|
+
}
|
|
70
|
+
case FileUploaderActionTypes.SET_STATUS: {
|
|
71
|
+
const { id, status } = action;
|
|
72
|
+
const files = updateFiles(state.files, { id, status });
|
|
73
|
+
return { ...state, files };
|
|
74
|
+
}
|
|
75
|
+
case FileUploaderActionTypes.REMOVE_UPLOAD: {
|
|
76
|
+
const { id } = action;
|
|
77
|
+
const { files } = state;
|
|
78
|
+
const newFiles = files.reduce((files, currentFile) => {
|
|
79
|
+
if (currentFile.id === id) {
|
|
80
|
+
// remove by not returning currentFile
|
|
81
|
+
return [...files];
|
|
82
|
+
}
|
|
83
|
+
return [...files, currentFile];
|
|
84
|
+
}, []);
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
files: newFiles,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { fileUploaderStateReducer };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
var FileUploaderActionTypes;
|
|
2
|
+
(function (FileUploaderActionTypes) {
|
|
3
|
+
FileUploaderActionTypes["ADD_FILES"] = "ADD_FILES";
|
|
4
|
+
FileUploaderActionTypes["CLEAR_FILES"] = "CLEAR_FILES";
|
|
5
|
+
FileUploaderActionTypes["QUEUE_FILES"] = "QUEUE_FILES";
|
|
6
|
+
FileUploaderActionTypes["SET_STATUS"] = "SET_STATUS";
|
|
7
|
+
FileUploaderActionTypes["SET_PROCESSED_FILE_KEY"] = "SET_PROCESSED_FILE_KEY";
|
|
8
|
+
FileUploaderActionTypes["SET_STATUS_UPLOADING"] = "SET_STATUS_UPLOADING";
|
|
9
|
+
FileUploaderActionTypes["SET_UPLOAD_PROGRESS"] = "SET_UPLOAD_PROGRESS";
|
|
10
|
+
FileUploaderActionTypes["REMOVE_UPLOAD"] = "REMOVE_UPLOAD";
|
|
11
|
+
})(FileUploaderActionTypes || (FileUploaderActionTypes = {}));
|
|
12
|
+
|
|
13
|
+
export { FileUploaderActionTypes };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { isObject } from '@aws-amplify/ui';
|
|
3
|
+
import { FileStatus } from '../../types.mjs';
|
|
4
|
+
import { fileUploaderStateReducer } from './reducer.mjs';
|
|
5
|
+
import { addFilesAction, clearFilesAction, queueFilesAction, setUploadingFileAction, setProcessedKeyAction, setUploadProgressAction, setUploadStatusAction, removeUploadAction } from './actions.mjs';
|
|
6
|
+
|
|
7
|
+
const isDefaultFile = (file) => !!(isObject(file) && file.key);
|
|
8
|
+
const createFileFromDefault = (file) => isDefaultFile(file)
|
|
9
|
+
? { ...file, id: file.key, status: FileStatus.UPLOADED }
|
|
10
|
+
: undefined;
|
|
11
|
+
function useFileUploader(defaultFiles = []) {
|
|
12
|
+
const [{ files }, dispatch] = React__default.useReducer(fileUploaderStateReducer, {
|
|
13
|
+
files: (Array.isArray(defaultFiles)
|
|
14
|
+
? defaultFiles.map(createFileFromDefault).filter((file) => !!file)
|
|
15
|
+
: []),
|
|
16
|
+
});
|
|
17
|
+
const addFiles = ({ files, status, getFileErrorMessage, }) => {
|
|
18
|
+
dispatch(addFilesAction({ files, status, getFileErrorMessage }));
|
|
19
|
+
};
|
|
20
|
+
const clearFiles = () => {
|
|
21
|
+
dispatch(clearFilesAction());
|
|
22
|
+
};
|
|
23
|
+
const queueFiles = () => {
|
|
24
|
+
dispatch(queueFilesAction());
|
|
25
|
+
};
|
|
26
|
+
const setUploadingFile = ({ uploadTask, id, }) => {
|
|
27
|
+
dispatch(setUploadingFileAction({ id, uploadTask }));
|
|
28
|
+
};
|
|
29
|
+
const setProcessedKey = (input) => {
|
|
30
|
+
dispatch(setProcessedKeyAction(input));
|
|
31
|
+
};
|
|
32
|
+
const setUploadProgress = ({ progress, id, }) => {
|
|
33
|
+
dispatch(setUploadProgressAction({ id, progress }));
|
|
34
|
+
};
|
|
35
|
+
const setUploadSuccess = ({ id }) => {
|
|
36
|
+
dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADED }));
|
|
37
|
+
};
|
|
38
|
+
const setUploadPaused = ({ id }) => {
|
|
39
|
+
dispatch(setUploadStatusAction({ id, status: FileStatus.PAUSED }));
|
|
40
|
+
};
|
|
41
|
+
const setUploadResumed = ({ id }) => {
|
|
42
|
+
dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADING }));
|
|
43
|
+
};
|
|
44
|
+
const removeUpload = ({ id }) => {
|
|
45
|
+
dispatch(removeUploadAction({ id }));
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
removeUpload,
|
|
49
|
+
setProcessedKey,
|
|
50
|
+
setUploadPaused,
|
|
51
|
+
setUploadProgress,
|
|
52
|
+
setUploadResumed,
|
|
53
|
+
setUploadSuccess,
|
|
54
|
+
setUploadingFile,
|
|
55
|
+
queueFiles,
|
|
56
|
+
addFiles,
|
|
57
|
+
clearFiles,
|
|
58
|
+
files,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { useFileUploader };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { isFunction } from '@aws-amplify/ui';
|
|
3
|
+
import { getInput } from '../../utils/getInput.mjs';
|
|
4
|
+
import { uploadFile } from '../../utils/uploadFile.mjs';
|
|
5
|
+
import { FileStatus } from '../../types.mjs';
|
|
6
|
+
|
|
7
|
+
function useUploadFiles({ accessLevel, files, isResumable, maxFileCount, onProcessFileSuccess, onUploadError, onUploadStart, onUploadSuccess, path, processFile, setUploadingFile, setUploadProgress, setUploadSuccess, useAccelerateEndpoint, }) {
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const filesReadyToUpload = files.filter((file) => file.status === FileStatus.QUEUED);
|
|
10
|
+
if (filesReadyToUpload.length > maxFileCount) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const { file, key, id } of filesReadyToUpload) {
|
|
14
|
+
const onProgress = (event) => {
|
|
15
|
+
/**
|
|
16
|
+
* When a file is zero bytes, the progress.total will equal zero.
|
|
17
|
+
* Therefore, this will prevent a divide by zero error.
|
|
18
|
+
*/
|
|
19
|
+
const progress = event.totalBytes === undefined || event.totalBytes === 0
|
|
20
|
+
? 100
|
|
21
|
+
: Math.floor((event.transferredBytes / event.totalBytes) * 100);
|
|
22
|
+
setUploadProgress({ id, progress });
|
|
23
|
+
};
|
|
24
|
+
if (file) {
|
|
25
|
+
const handleProcessFileSuccess = (input) => onProcessFileSuccess({ id, ...input });
|
|
26
|
+
const input = getInput({
|
|
27
|
+
accessLevel,
|
|
28
|
+
file,
|
|
29
|
+
key,
|
|
30
|
+
onProcessFileSuccess: handleProcessFileSuccess,
|
|
31
|
+
onProgress,
|
|
32
|
+
path,
|
|
33
|
+
processFile,
|
|
34
|
+
useAccelerateEndpoint,
|
|
35
|
+
});
|
|
36
|
+
uploadFile({
|
|
37
|
+
input,
|
|
38
|
+
onComplete: (event) => {
|
|
39
|
+
if (isFunction(onUploadSuccess)) {
|
|
40
|
+
onUploadSuccess({
|
|
41
|
+
key: event.key ??
|
|
42
|
+
event.path,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
setUploadSuccess({ id });
|
|
46
|
+
},
|
|
47
|
+
onError: ({ key, error }) => {
|
|
48
|
+
if (isFunction(onUploadError)) {
|
|
49
|
+
onUploadError(error.message, { key });
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
onStart: ({ key, uploadTask }) => {
|
|
53
|
+
if (isFunction(onUploadStart)) {
|
|
54
|
+
onUploadStart({ key });
|
|
55
|
+
}
|
|
56
|
+
setUploadingFile({ id, uploadTask });
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}, [
|
|
62
|
+
files,
|
|
63
|
+
accessLevel,
|
|
64
|
+
isResumable,
|
|
65
|
+
setUploadProgress,
|
|
66
|
+
setUploadingFile,
|
|
67
|
+
onUploadError,
|
|
68
|
+
onProcessFileSuccess,
|
|
69
|
+
onUploadSuccess,
|
|
70
|
+
onUploadStart,
|
|
71
|
+
maxFileCount,
|
|
72
|
+
setUploadSuccess,
|
|
73
|
+
processFile,
|
|
74
|
+
path,
|
|
75
|
+
useAccelerateEndpoint,
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { useUploadFiles };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
var FileStatus;
|
|
2
|
+
(function (FileStatus) {
|
|
3
|
+
FileStatus["ADDED"] = "added";
|
|
4
|
+
FileStatus["QUEUED"] = "queued";
|
|
5
|
+
FileStatus["UPLOADING"] = "uploading";
|
|
6
|
+
FileStatus["PAUSED"] = "paused";
|
|
7
|
+
FileStatus["ERROR"] = "error";
|
|
8
|
+
FileStatus["UPLOADED"] = "uploaded";
|
|
9
|
+
})(FileStatus || (FileStatus = {}));
|
|
10
|
+
|
|
11
|
+
export { FileStatus };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { classNames, classNameModifier, ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { View, Text } from '@aws-amplify/ui-react';
|
|
4
|
+
import { useIcons, IconUpload } from '@aws-amplify/ui-react/internal';
|
|
5
|
+
|
|
6
|
+
function DropZone({ children, displayText, inDropZone, onDragEnter, onDragLeave, onDragOver, onDragStart, onDrop, testId, }) {
|
|
7
|
+
const { dropFilesText } = displayText;
|
|
8
|
+
const icons = useIcons('storageManager');
|
|
9
|
+
return (React__default.createElement(View, { className: classNames(inDropZone &&
|
|
10
|
+
classNameModifier(ComponentClassName.FileUploaderDropZone, 'active'), ComponentClassName.FileUploaderDropZone), "data-testid": testId, onDragStart: onDragStart, onDragEnter: onDragEnter, onDragLeave: onDragLeave, onDrop: onDrop, onDragOver: onDragOver },
|
|
11
|
+
React__default.createElement(View, { as: "span", "aria-hidden": true, className: ComponentClassName.FileUploaderDropZoneIcon }, icons?.upload ?? React__default.createElement(IconUpload, null)),
|
|
12
|
+
React__default.createElement(Text, { className: ComponentClassName.FileUploaderDropZoneText }, dropFilesText),
|
|
13
|
+
children));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { DropZone };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { View, Loader, Button } from '@aws-amplify/ui-react';
|
|
4
|
+
import { FileStatus } from '../../types.mjs';
|
|
5
|
+
import { FileStatusMessage } from './FileStatusMessage.mjs';
|
|
6
|
+
import { FileRemoveButton } from './FileRemoveButton.mjs';
|
|
7
|
+
import { UploadDetails } from './FileDetails.mjs';
|
|
8
|
+
import { FileThumbnail } from './FileThumbnail.mjs';
|
|
9
|
+
|
|
10
|
+
function FileControl({ onPause, onResume, displayName, errorMessage, isImage, isResumable, loaderIsDeterminate, onRemove, progress, showThumbnails = true, size, status, displayText, thumbnailUrl, }) {
|
|
11
|
+
const { getPausedText, getUploadingText, uploadSuccessfulText, pauseButtonText, resumeButtonText, } = displayText;
|
|
12
|
+
return (React__default.createElement(View, { className: ComponentClassName.FileUploaderFile },
|
|
13
|
+
React__default.createElement(View, { className: ComponentClassName.FileUploaderFileWrapper },
|
|
14
|
+
showThumbnails ? (React__default.createElement(FileThumbnail, { isImage: isImage, fileName: displayName, url: thumbnailUrl })) : null,
|
|
15
|
+
React__default.createElement(UploadDetails, { displayName: displayName, fileSize: size }),
|
|
16
|
+
status === FileStatus.UPLOADING ? (React__default.createElement(Loader, { className: ComponentClassName.FileUploaderLoader, variation: "linear", percentage: progress, isDeterminate: loaderIsDeterminate, isPercentageTextHidden: true })) : null,
|
|
17
|
+
isResumable &&
|
|
18
|
+
(status === FileStatus.UPLOADING || status === FileStatus.PAUSED) ? (status === FileStatus.PAUSED ? (React__default.createElement(Button, { onClick: onResume, size: "small", variation: "link" }, resumeButtonText)) : (React__default.createElement(Button, { onClick: onPause, size: "small", variation: "link" }, pauseButtonText))) : null,
|
|
19
|
+
React__default.createElement(FileRemoveButton, { altText: `Remove file ${displayName}`, onClick: onRemove })),
|
|
20
|
+
React__default.createElement(FileStatusMessage, { uploadSuccessfulText: uploadSuccessfulText, getUploadingText: getUploadingText, getPausedText: getPausedText, status: status, errorMessage: errorMessage, percentage: progress })));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { FileControl };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ComponentClassName, humanFileSize } from '@aws-amplify/ui';
|
|
3
|
+
import { View, Text } from '@aws-amplify/ui-react';
|
|
4
|
+
|
|
5
|
+
const UploadDetails = ({ displayName, fileSize, }) => {
|
|
6
|
+
return (React__default.createElement(React__default.Fragment, null,
|
|
7
|
+
React__default.createElement(View, { className: ComponentClassName.FileUploaderFileMain },
|
|
8
|
+
React__default.createElement(Text, { className: ComponentClassName.FileUploaderFileName }, displayName)),
|
|
9
|
+
React__default.createElement(Text, { as: "span", className: ComponentClassName.FileUploaderFileSize }, fileSize ? humanFileSize(fileSize, true) : '')));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { UploadDetails };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { View, Alert } from '@aws-amplify/ui-react';
|
|
4
|
+
import { FileStatus } from '../../types.mjs';
|
|
5
|
+
import { FileControl } from './FileControl.mjs';
|
|
6
|
+
|
|
7
|
+
function FileList({ displayText, files, hasMaxFilesError, isResumable, onCancelUpload, onDeleteUpload, onResume, onPause, showThumbnails, maxFileCount, }) {
|
|
8
|
+
if (files.length < 1) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const { getMaxFilesErrorText } = displayText;
|
|
12
|
+
const headingMaxFiles = getMaxFilesErrorText(maxFileCount);
|
|
13
|
+
return (React__default.createElement(View, { className: ComponentClassName.FileUploaderFileList },
|
|
14
|
+
files.map((storageFile) => {
|
|
15
|
+
const { file, status, progress, error, key, isImage, id, uploadTask } = storageFile;
|
|
16
|
+
const thumbnailUrl = file && isImage ? URL.createObjectURL(file) : '';
|
|
17
|
+
const loaderIsDeterminate = isResumable ? progress > 0 : true;
|
|
18
|
+
const isUploading = status === FileStatus.UPLOADING;
|
|
19
|
+
const onRemove = () => {
|
|
20
|
+
if (isResumable &&
|
|
21
|
+
(status === FileStatus.UPLOADING || status === FileStatus.PAUSED) &&
|
|
22
|
+
uploadTask) {
|
|
23
|
+
onCancelUpload({ id, uploadTask });
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
onDeleteUpload({ id });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const handlePauseUpload = () => {
|
|
30
|
+
if (uploadTask) {
|
|
31
|
+
onPause({ id, uploadTask });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const handleResumeUpload = () => {
|
|
35
|
+
if (uploadTask) {
|
|
36
|
+
onResume({ id, uploadTask });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return (React__default.createElement(FileControl, { displayName: key, errorMessage: error, displayText: displayText, isImage: isImage, isUploading: isUploading, isResumable: isResumable, key: id, loaderIsDeterminate: loaderIsDeterminate, onRemove: onRemove, onPause: handlePauseUpload, onResume: handleResumeUpload, progress: progress, showThumbnails: showThumbnails, size: file?.size, status: status, thumbnailUrl: thumbnailUrl }));
|
|
40
|
+
}),
|
|
41
|
+
hasMaxFilesError && (React__default.createElement(Alert, { variation: "error", heading: headingMaxFiles }))));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { FileList };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { useIcons, IconClose } from '@aws-amplify/ui-react/internal';
|
|
3
|
+
import { Button, VisuallyHidden, View } from '@aws-amplify/ui-react';
|
|
4
|
+
|
|
5
|
+
const FileRemoveButton = ({ altText, onClick, }) => {
|
|
6
|
+
const icons = useIcons('storageManager');
|
|
7
|
+
return (React__default.createElement(Button, { size: "small", onClick: onClick, testId: "storage-manager-remove-button" },
|
|
8
|
+
React__default.createElement(VisuallyHidden, null, altText),
|
|
9
|
+
React__default.createElement(View, { as: "span", "aria-hidden": true, fontSize: "medium" }, icons?.remove ?? React__default.createElement(IconClose, null))));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { FileRemoveButton };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { classNames, ComponentClassName, classNameModifier } from '@aws-amplify/ui';
|
|
3
|
+
import { Text, View } from '@aws-amplify/ui-react';
|
|
4
|
+
import { useIcons, IconError, IconCheck } from '@aws-amplify/ui-react/internal';
|
|
5
|
+
import { FileStatus } from '../../types.mjs';
|
|
6
|
+
|
|
7
|
+
const FileStatusMessage = ({ errorMessage, getPausedText, getUploadingText, percentage, status, uploadSuccessfulText, }) => {
|
|
8
|
+
const icons = useIcons('storageManager');
|
|
9
|
+
switch (status) {
|
|
10
|
+
case FileStatus.UPLOADING: {
|
|
11
|
+
return (React__default.createElement(Text, { className: ComponentClassName.FileUploaderFileStatus }, getUploadingText(percentage)));
|
|
12
|
+
}
|
|
13
|
+
case FileStatus.PAUSED:
|
|
14
|
+
return (React__default.createElement(Text, { className: ComponentClassName.FileUploaderFileStatus }, getPausedText(percentage)));
|
|
15
|
+
case FileStatus.UPLOADED:
|
|
16
|
+
return (React__default.createElement(Text, { className: classNames(ComponentClassName.FileUploaderFileStatus, classNameModifier(ComponentClassName.FileUploaderFileStatus, 'success')) },
|
|
17
|
+
React__default.createElement(View, { as: "span", fontSize: "xl" }, icons?.success ?? React__default.createElement(IconCheck, null)),
|
|
18
|
+
uploadSuccessfulText));
|
|
19
|
+
case FileStatus.ERROR:
|
|
20
|
+
return (React__default.createElement(Text, { className: classNames(ComponentClassName.FileUploaderFileStatus, classNameModifier(ComponentClassName.FileUploaderFileStatus, 'error')) },
|
|
21
|
+
React__default.createElement(View, { as: "span", fontSize: "xl" }, icons?.error ?? React__default.createElement(IconError, null)),
|
|
22
|
+
errorMessage));
|
|
23
|
+
default:
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { FileStatusMessage };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { ComponentClassName } from '@aws-amplify/ui';
|
|
3
|
+
import { Image, View } from '@aws-amplify/ui-react';
|
|
4
|
+
import { useIcons, IconFile } from '@aws-amplify/ui-react/internal';
|
|
5
|
+
|
|
6
|
+
const FileThumbnail = ({ fileName, isImage, url, }) => {
|
|
7
|
+
const icons = useIcons('storageManager');
|
|
8
|
+
const thumbnail = isImage ? (React__default.createElement(Image, { alt: fileName, src: url })) : (icons?.file ?? React__default.createElement(IconFile, null));
|
|
9
|
+
return (React__default.createElement(View, { className: ComponentClassName.FileUploaderFileImage }, thumbnail));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { FileThumbnail };
|