@aws-amplify/ui-react-storage 3.2.1 → 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/index.mjs +1 -0
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +721 -7
- 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/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
|
@@ -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 };
|