Skip to content

Commit

Permalink
feat(storage-browser): add custom actions (#6210)
Browse files Browse the repository at this point in the history
* feat(storage-browser): add custom actions

* Fix circular dependency

* Remove unused variables from example apps

* Update unit tests

* Update unit tests. Fix types

* Bump coverage down

* Remove 'as const' from permissions in example

* Address feedback

* Fix copy regression

* Fix issue with custom action view filtering

* fix: task data are not being refreshed when it's updated

---------

Co-authored-by: Chris Fang <[email protected]>
Co-authored-by: Hui Zhao <[email protected]>
  • Loading branch information
3 people authored Nov 27, 2024
1 parent e9b219f commit 3e9f95d
Show file tree
Hide file tree
Showing 114 changed files with 2,265 additions and 2,484 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Auth } from '../managedAuthAdapter';
import { Button, Flex, Breadcrumbs } from '@aws-amplify/ui-react';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

const components: CreateStorageBrowserInput['components'] = {
Navigation: ({ items }) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React from 'react';

import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';

import { Flex } from '@aws-amplify/ui-react';

import '@aws-amplify/ui-react-storage/styles.css';

const { StorageBrowser } = createStorageBrowser({
actions: {
default: {
copy: {
actionListItem: {
icon: 'copy-file',
label: 'Override Copy',
},
handler: ({ data }) => {
const { key } = data;
return {
result: Promise.resolve({ status: 'COMPLETE', value: { key } }),
};
},
viewName: 'CopyView',
},
createFolder: {
actionListItem: {
icon: 'create-folder',
label: 'Override Create Folder',
},
handler: ({ data }) => {
const { key } = data;
return {
result: Promise.resolve({ status: 'COMPLETE', value: { key } }),
};
},
viewName: 'CreateFolderView',
},
delete: {
actionListItem: {
icon: 'delete-file',
label: 'Override Delete',
},
handler: ({ data }) => {
const { key } = data;
return {
result: Promise.resolve({ status: 'COMPLETE', value: { key } }),
};
},
viewName: 'DeleteView',
},
download: () => {
return {
result: Promise.resolve({
status: 'COMPLETE',
value: { url: new URL('') },
}),
};
},
upload: {
actionListItem: {
icon: 'upload-file',
label: 'Override Upload',
},
handler: ({ data }) => {
const { key } = data;
return {
result: Promise.resolve({ status: 'COMPLETE', value: { key } }),
};
},
viewName: 'UploadView',
},
listLocationItems: () =>
Promise.resolve({
items: [
{
id: 'jaskjkaska',
key: 'item-key',
lastModified: new Date(),
size: 1008,
type: 'FILE' as const,
},
],
nextToken: undefined,
}),
},
},
config: {
getLocationCredentials: () =>
Promise.resolve({
credentials: {
accessKeyId: '',
expiration: new Date(),
secretAccessKey: '',
sessionToken: '',
},
}),
region: '',
registerAuthListener: () => null,
listLocations: () =>
Promise.resolve({
items: [
{
bucket: 'my-bucket',
id: crypto.randomUUID(),
permissions: ['delete', 'get', 'list', 'write'],
prefix: 'my-prefix',
type: 'PREFIX',
},
],
nextToken: undefined,
}),
},
});

function Example() {
return (
<Flex
direction="column"
width="100vw"
height="100vh"
overflow="hidden"
padding="xl"
>
<StorageBrowser />
</Flex>
);
}

export default Example;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import { StorageBrowser } from '@aws-amplify/ui-react-storage';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

import config from './aws-exports';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
createAmplifyAuthAdapter,
createStorageBrowser,
} from '@aws-amplify/ui-react-storage/browser';
import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

import config from './aws-exports';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Button, Flex } from '@aws-amplify/ui-react';

import { StorageBrowser } from '../../StorageBrowser';

import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
import '@aws-amplify/ui-react-storage/styles.css';

export default function Page() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Button, Flex } from '@aws-amplify/ui-react';

import { StorageBrowser } from '../StorageBrowser';

import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
import '@aws-amplify/ui-react-storage/styles.css';

function Locations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import useIsSignedIn from './useIsSignedIn';
import { Authenticator } from '@aws-amplify/ui-react';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

function Example() {
const router = useRouter();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,104 @@
import React from 'react';
import { getUrl } from '@aws-amplify/storage/internals';

import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';
import {
ActionViewConfig,
ActionHandler,
createStorageBrowser,
} from '@aws-amplify/ui-react-storage/browser';

import { managedAuthAdapter } from '../managedAuthAdapter';
import { SignIn, SignOutButton } from './routed/components';

import { Flex, View } from '@aws-amplify/ui-react';
import {
Button,
Flex,
Link,
StepperField,
Text,
View,
} from '@aws-amplify/ui-react';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

const { StorageBrowser } = createStorageBrowser({
type GetLink = ActionHandler<{ duration: number; fileKey: string }, string>;

const getLink: GetLink = ({ data, config }) => {
const result = getUrl({
path: data.key,
options: {
bucket: { bucketName: config.bucket, region: config.region },
locationCredentialsProvider: config.credentials,
expiresIn: data.duration * 60,
validateObjectExistence: true,
},
}).then((res) => ({
status: 'COMPLETE' as const,
value: res.url.toString(),
}));

return { result };
};

const generateLink: ActionViewConfig<GetLink, 'LinkActionView'> = {
handler: getLink,
viewName: 'LinkActionView',
actionListItem: {
icon: 'download',
label: 'Generate Download Links',
disable: (selected) => !selected?.length,
},
};

const { StorageBrowser, useAction, useView } = createStorageBrowser({
actions: { custom: { generateLink } },
config: managedAuthAdapter,
});

const LinkActionView = () => {
const [duration, setDuration] = React.useState(60);

const locationDetailState = useView('LocationDetail');
const { onActionExit, fileDataItems } = locationDetailState;

const items = React.useMemo(
() =>
!fileDataItems
? []
: fileDataItems.map((item) => ({ ...item, duration })),
[fileDataItems, duration]
);

const [{ tasks }, handleCreate] = useAction('generateLink', { items });

return (
<Flex direction="column">
<Button onClick={onActionExit}>Exit</Button>
<StepperField
label="Duration"
step={15}
value={duration}
min={15}
max={300}
onStepChange={(value) => {
setDuration(value);
}}
/>
<Button onClick={() => handleCreate()}>Start</Button>
{!tasks
? null
: tasks.map(({ data, status, value }) => {
return (
<Flex direction="row" key={data.fileKey}>
<Text>{data.fileKey}</Text>
{value ? <Link href={value}>link</Link> : null}
<Text>{status}</Text>
</Flex>
);
})}
</Flex>
);
};

function Example() {
const [showSignIn, setShowSignIn] = React.useState(false);

Expand All @@ -29,9 +114,8 @@ function Example() {
>
<SignOutButton onSignOut={() => setShowSignIn(false)} />
<View flex="1" overflow="hidden">
<StorageBrowser
displayText={{ LocationsView: { title: 'Home - Managed Auth' } }}
/>
<StorageBrowser views={{ LinkActionView }} />
<StorageBrowser.LocationActionView />
</View>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { SignOutButton } from '../../components';
import { StorageBrowser } from '../../StorageBrowser';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

export default function Page() {
const { back, query, pathname, replace } = useRouter();
Expand Down Expand Up @@ -50,7 +49,10 @@ export default function Page() {
}}
/>
{typeof query.actionType === 'string' ? (
<dialog open={!!query.actionType}>
<dialog
open={!!query.actionType}
style={{ width: '50vw', position: 'absolute', zIndex: 1000 }}
>
<StorageBrowser.LocationActionView
onExit={() => {
replace({ query: { ...query, actionType: undefined } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { SignOutButton } from '../components';
import { StorageBrowser } from '../StorageBrowser';

import '@aws-amplify/ui-react-storage/styles.css';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';

function Locations() {
const router = useRouter();
Expand Down
4 changes: 2 additions & 2 deletions packages/react-storage/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const config: Config = {
// functions: 90,
// lines: 95,
// statements: 95,
branches: 84,
functions: 88,
branches: 82,
functions: 86,
lines: 94,
statements: 94,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React from 'react';
import { createStorageBrowser } from './createStorageBrowser';
import { StorageBrowserProps as StorageBrowserPropsBase } from './types';
import { createAmplifyAuthAdapter } from './adapters';
import { componentsDefault } from './componentsDefault';

export interface StorageBrowserProps extends StorageBrowserPropsBase {}

Expand All @@ -12,10 +11,7 @@ export const StorageBrowser = ({
displayText,
}: StorageBrowserProps): React.JSX.Element => {
const { StorageBrowser } = React.useRef(
createStorageBrowser({
components: componentsDefault,
config: createAmplifyAuthAdapter(),
})
createStorageBrowser({ config: createAmplifyAuthAdapter() })
).current;

return <StorageBrowser views={views} displayText={displayText} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { useViews } from './views';
import { useViews } from './views/context';
import { useStore } from './providers/store';

/**
Expand All @@ -10,7 +10,8 @@ import { useStore } from './providers/store';
* - render `ActionView` on action selection
*/
export function StorageBrowserDefault(): React.JSX.Element {
const { LocationActionView, LocationDetailView, LocationsView } = useViews();
const { primary } = useViews();
const { LocationActionView, LocationDetailView, LocationsView } = primary;

const [{ actionType, location }] = useStore();
const { current } = location;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ describe('StorageBrowser', () => {

expect(createStorageBrowserSpy).toHaveBeenCalledTimes(1);
expect(createStorageBrowserSpy).toHaveBeenCalledWith({
components: expect.anything(),
config: expect.anything(),
});

Expand Down
Loading

0 comments on commit 3e9f95d

Please sign in to comment.