Skip to content

Commit

Permalink
feat(ui-react-storage): Added upload actions and option to upload fil…
Browse files Browse the repository at this point in the history
…es on click (#4367)
  • Loading branch information
ioanabrooks committed Aug 23, 2023
1 parent 14e402b commit e1fc611
Show file tree
Hide file tree
Showing 34 changed files with 396 additions and 64 deletions.
15 changes: 15 additions & 0 deletions .changeset/long-sloths-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@aws-amplify/ui-react-storage': minor
'@aws-amplify/ui-react': patch
'@aws-amplify/ui': patch
---

feat(ui-react-storage): Added upload actions and option to upload files on click. Usage:

```
<StorageManager
acceptedFileTypes={['image/*']}
accessLevel="public"
autoUpload={false}
/>
```
22 changes: 1 addition & 21 deletions docs/__tests__/__snapshots__/cssvars-table.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4542,29 +4542,9 @@ exports[`CSS Variables Table 1`] = `
\\"variable\\": \\"--amplify-components-storagemanager-previewer-border-width\\",
\\"value\\": \\"var(--amplify-border-widths-small)\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-border-color\\",
\\"value\\": \\"var(--amplify-colors-border-secondary)\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-border-style\\",
\\"value\\": \\"solid\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-border-width\\",
\\"value\\": \\"var(--amplify-border-widths-small)\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-justify-content\\",
\\"value\\": \\"space-between\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-padding-block\\",
\\"value\\": \\"var(--amplify-space-medium)\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-footer-padding-inline\\",
\\"value\\": \\"var(--amplify-space-medium)\\"
\\"value\\": \\"flex-end\\"
},
{
\\"variable\\": \\"--amplify-components-storagemanager-previewer-max-height\\",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { StorageManager } from '@aws-amplify/ui-react-storage';

export const StorageManagerUploadActionsExample = () => {
return (
<StorageManager
acceptedFileTypes={['image/*']}
accessLevel="public"
autoUpload={false}
maxFileCount={1}
isResumable
provider="slow" // IGNORE
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { StorageManagerMetadataExample } from './StorageManagerMetadataExample';
export { StorageManagerResumableExample } from './StorageManagerResumableExample';
export { StorageManagerThemeExample } from './StorageManagerThemeExample';
export { StorageManagerHandleExample } from './StorageManagerHandleExample';
export { StorageManagerUploadActionsExample } from './StorageManagerUploadActionsExample';
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,23 @@ export const STORAGE_MANAGER = [
'Access level for files in Storage. See https://docs.amplify.aws/lib/storage/configureaccess/q/platform/js/',
type: `'public' | 'private' | 'protected'`,
},
{
name: `maxFileCount`,
description: '',
type: 'integer',
},
{
name: `acceptedFileTypes?`,
description:
"List of accepted file types, values of `['*']` or undefined allow any files",
type: `string[]`,
},
{
name: `autoUpload?`,
description:
'Determines if the upload will automatically start after a file is selected. The default value is `true`',
type: 'boolean',
},
{
name: `maxFileCount`,
description: '',
type: 'integer',
},
{
name: `maxFileSize?`,
description: '',
Expand Down Expand Up @@ -107,6 +113,11 @@ export const STORAGE_MANAGER = [
description: 'The heading above the list of files',
type: `React.ComponentType<FileListHeaderProps>`,
},
{
name: `components?.FileListFooter?`,
description: 'The footer below the list of files',
type: `React.ComponentType<FileListFooterProps>`,
},
{
name: `ref?`,
description: 'Forward ref prop exposing StorageManager imperative methods.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
StorageManagerResumableExample,
StorageManagerThemeExample,
StorageManagerHandleExample,
StorageManagerUploadActionsExample,
} from './examples'

<DefaultStorageManagerExample />
Expand Down Expand Up @@ -63,6 +64,18 @@ At a minimum you must include the `accessLevel` and `maxFileCount` props. `acces

<ReactPropsTable props={STORAGE_MANAGER} />

## Automatically Upload

The default behavior of the Storage Manager component is to automatically start the upload after a file is selected. If you wish to change that, set the value of the `autoUpload` prop to false.

<Example>
<StorageManagerUploadActionsExample />
<ExampleCode>
```jsx file=./examples/StorageManagerUploadActionsExample.tsx
```
</ExampleCode>
</Example>

## Setting Limits

You can limit what users upload with these 3 props:
Expand Down Expand Up @@ -177,7 +190,7 @@ You can use the `displayText` prop to also support different languages. Use an o

### Component overrides

Don't like how things look? Use your own components inside the StorageManager! You can pass your own components with the `components` prop. The available components to override are: `Container`, `FileList`, `FileListHeader`, `DropZone`, and `FilePicker`.
Don't like how things look? Use your own components inside the StorageManager! You can pass your own components with the `components` prop. The available components to override are: `Container`, `FileList`, `FileListHeader`, `FileListFooter`, `DropZone`, and `FilePicker`.

_You can even use a completely different UI kit like MUI, Chakra, or your own design system!_

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@environments/storage/file-uploader/src/aws-exports';
export default awsExports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Amplify } from 'aws-amplify';
import { StorageManager } from '@aws-amplify/ui-react-storage';
import '@aws-amplify/ui-react/styles.css';
import awsExports from './aws-exports';
Amplify.configure(awsExports);

export function StorageManagerExample() {
return (
<>
<StorageManager
acceptedFileTypes={['image/*']}
accessLevel="public"
autoUpload={false}
maxFileCount={1}
showThumbnails
/>
</>
);
}
export default StorageManagerExample;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Feature: Storage Manager with footer and upload actions

Background:
Given I'm running the example "ui/components/storage/storage-manager/upload-actions"

@react
Scenario: I select a file and upload it on click
Then I see "Browse files"
Then I select a file with file name "test.jpg"
Then I see "test.jpg"
Then I see "Upload 1 file"
Then I see "Clear all"
Given I intercept '{ "method": "POST", "url": "**/test.jpg?uploads" }' with fixture "Storage.public-uploads.xml"
Given I intercept '{ "method": "PUT", "url": "**/test.jpg?partNumber=1**" }' with fixture "Storage.public-upload-part.xml" and add header "Etag" with value "&quot;abc123&quot;"
Given I intercept '{ "method": "POST", "url": "**/test.jpg?uploadId**" }' with fixture "Storage.public-upload-complete-multipart.xml"
Given I intercept '{ "method": "GET", "url": "**/?list-type=2**" }' with fixture "Storage.public-uploads-list.xml"
Then I see "1 file selected"
Then I click the "Upload 1 file" button
Then I see "Uploaded"
Then I see "1 file uploaded"

@react
Scenario: I can clear all selected files
Then I see "Browse files"
Then I select a file with file name "test.jpg"
Then I see "test.jpg"
Then I see "Upload 1 file"
Then I see "Clear all"
Given I intercept '{ "method": "POST", "url": "**/test.jpg?uploads" }' with fixture "Storage.public-uploads.xml"
Given I intercept '{ "method": "PUT", "url": "**/test.jpg?partNumber=1**" }' with fixture "Storage.public-upload-part.xml" and add header "Etag" with value "&quot;abc123&quot;"
Given I intercept '{ "method": "POST", "url": "**/test.jpg?uploadId**" }' with fixture "Storage.public-upload-complete-multipart.xml"
Given I intercept '{ "method": "GET", "url": "**/?list-type=2**" }' with fixture "Storage.public-uploads-list.xml"
Then I click the "Clear all" button
Then I see "Browse files"
Then I don't see "Uploaded"
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DropZone,
FileList,
FileListHeader,
FileListFooter,
FilePicker,
} from './ui';
import {
Expand All @@ -25,6 +26,7 @@ function StorageManagerBase(
{
acceptedFileTypes = [],
accessLevel,
autoUpload = true,
defaultFiles,
displayText: overrideDisplayText,
isResumable = false,
Expand Down Expand Up @@ -52,6 +54,7 @@ function StorageManagerBase(
FileList,
FilePicker,
FileListHeader,
FileListFooter,
...components,
};

Expand Down Expand Up @@ -79,6 +82,7 @@ function StorageManagerBase(
clearFiles,
files,
removeUpload,
queueFiles,
setUploadingFile,
setUploadPaused,
setUploadProgress,
Expand All @@ -101,6 +105,7 @@ function StorageManagerBase(
);
addFiles({
files: filteredFiles,
status: autoUpload ? FileStatus.QUEUED : FileStatus.ADDED,
getFileErrorMessage: getMaxFileSizeErrorMessage,
});
},
Expand Down Expand Up @@ -130,10 +135,19 @@ function StorageManagerBase(

addFiles({
files: Array.from(files),
status: autoUpload ? FileStatus.QUEUED : FileStatus.ADDED,
getFileErrorMessage: getMaxFileSizeErrorMessage,
});
};

const onClearAll = () => {
clearFiles();
};

const onUploadAll = () => {
queueFiles();
};

const onPauseUpload = ({
id,
uploadTask,
Expand Down Expand Up @@ -199,8 +213,13 @@ function StorageManagerBase(

const remainingFilesCount = files.length - uploadedFilesLength;

// number of files selected for upload when autoUpload is turned off
const selectedFilesCount = autoUpload ? 0 : remainingFilesCount;

const hasFiles = files.length > 0;

const hasUploadActions = !autoUpload && remainingFilesCount > 0;

const hiddenInput = React.useRef<HTMLInputElement>(null);
function handleClick() {
if (hiddenInput.current) {
Expand Down Expand Up @@ -238,6 +257,7 @@ function StorageManagerBase(
displayText={displayText}
fileCount={files.length}
remainingFilesCount={remainingFilesCount}
selectedFilesCount={selectedFilesCount}
/>
) : null}
<Components.FileList
Expand All @@ -252,6 +272,14 @@ function StorageManagerBase(
hasMaxFilesError={hasMaxFilesError}
maxFileCount={maxFileCount}
/>
{hasUploadActions ? (
<Components.FileListFooter
displayText={displayText}
remainingFilesCount={remainingFilesCount}
onClearAll={onClearAll}
onUploadAll={onUploadAll}
/>
) : null}
</Components.Container>
);
}
Expand All @@ -265,6 +293,7 @@ const StorageManager = Object.assign(
DropZone,
FileList,
FileListHeader,
FileListFooter,
FilePicker,
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { Storage } from 'aws-amplify';

import * as StorageHooks from '../hooks';
import { StorageManager } from '../StorageManager';
import { StorageManagerProps, StorageManagerHandle } from '../types';
import {
StorageManagerProps,
StorageManagerHandle,
FileStatus,
} from '../types';
import { defaultStorageManagerDisplayText } from '../utils';

const storageSpy = jest
Expand Down Expand Up @@ -65,6 +69,25 @@ describe('StorageManager', () => {
expect(warnSpy).not.toHaveBeenCalled();
});

it('renders as expected with autoUpload turned off', () => {
const { getByText } = render(
<StorageManager {...storeManagerProps} autoUpload={false} />
);
const hiddenInput = document.querySelector(
'input[type="file"]'
) as HTMLInputElement;

const mockFile = new File(['hello'], 'hello.png', { type: 'image/png' });
fireEvent.change(hiddenInput, { target: { files: [mockFile] } });

expect(
getByText(defaultStorageManagerDisplayText.clearAllButtonText)
).toBeVisible();
expect(
getByText(defaultStorageManagerDisplayText.getSelectedFilesText(1))
).toBeVisible();
});

it('renders as expected with override display text', () => {
const displayText = {
...defaultStorageManagerDisplayText,
Expand Down Expand Up @@ -191,6 +214,7 @@ describe('StorageManager', () => {
jest.spyOn(StorageHooks, 'useStorageManager').mockReturnValue({
addFiles: mockAddFiles,
files: [],
status: FileStatus.QUEUED,
} as unknown as StorageHooks.UseStorageManager);

const { findByRole } = render(<StorageManager {...storeManagerProps} />);
Expand All @@ -216,6 +240,7 @@ describe('StorageManager', () => {
expect(mockAddFiles).toHaveBeenCalledTimes(1);
expect(mockAddFiles).toHaveBeenCalledWith({
files: [mockFile],
status: FileStatus.QUEUED,
getFileErrorMessage: expect.any(Function),
});
});
Expand Down Expand Up @@ -244,6 +269,7 @@ describe('StorageManager', () => {
expect(mockAddFiles).toHaveBeenCalledTimes(1);
expect(mockAddFiles).toHaveBeenCalledWith({
files: [file],
status: FileStatus.QUEUED,
getFileErrorMessage: expect.any(Function),
});

Expand Down
Loading

0 comments on commit e1fc611

Please sign in to comment.