convert plugin 'sandbox's UI to use ant.design

Summary:
Bootcamp task:
I’m removing the remaining UI components imported from ‘flipper’ and replacing with new ones from ‘antd’.
I’m also attempting to polish 🇵🇱 the UI and UX of the plugin:

UI:
- Selecting one of the listed sandboxes and custom URL lead to the same action -> align all components together

UX:
- Sometimes loading sandboxes takes time, and it’s not clear for the user what’s happening -> add a loading indicator
- At first the input field was a bit confusing to me -> add more labels to explain the purpose of visible components
- It’s not always clear what happened after requesting a sandbox change -> the result of all actions is now confirmed with the small 'popup' notification.

I don't have any experience with web frontend, so if something is terribly wrong below - don't be afraid to point it out :)

Before/After:

{F745958473}

Reviewed By: mweststrate

Differential Revision: D37343946

fbshipit-source-id: 3dbdd213ffd5540dc7a418c1590eb956ef4c6715
This commit is contained in:
Dawid Cieslak
2022-06-22 11:35:10 -07:00
committed by Facebook GitHub Bot
parent 335dec1a08
commit 8e784f2579

View File

@@ -7,9 +7,15 @@
* @format * @format
*/ */
import {FlexColumn, ButtonGroup, Button, styled, colors} from 'flipper';
import React, {ChangeEvent} from 'react'; import React, {ChangeEvent} from 'react';
import {PluginClient, createState, usePlugin, useValue} from 'flipper-plugin'; import {Button, Input, Typography, Spin, message} from 'antd';
import {
PluginClient,
createState,
usePlugin,
useValue,
Layout,
} from 'flipper-plugin';
export type Sandbox = { export type Sandbox = {
name: string; name: string;
@@ -23,55 +29,24 @@ type ClientMethods = {
type SetSandboxResult = {result: boolean}; type SetSandboxResult = {result: boolean};
const BigButton = styled(Button)({
flexGrow: 1,
fontSize: 24,
padding: 20,
});
const ButtonContainer = styled(FlexColumn)({
alignItems: 'center',
padding: 20,
});
const TextInput = styled.input({
border: `1px solid ${colors.light10}`,
fontSize: '1em',
padding: '0 5px',
borderRight: 0,
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
flexGrow: 1,
});
const FeedbackMessage = styled.span({
fontSize: '1.2em',
paddingTop: '10px',
color: 'green',
});
const TextInputLayout = styled(FlexColumn)({
float: 'left',
justifyContent: 'center',
flexGrow: 1,
borderRadius: 4,
marginRight: 15,
marginTop: 15,
marginLeft: 15,
});
export function plugin(client: PluginClient<{}, ClientMethods>) { export function plugin(client: PluginClient<{}, ClientMethods>) {
const sandboxes = createState<Array<Sandbox>>([]); const sandboxes = createState<Array<Sandbox>>([]);
const customSandbox = createState<string>(''); const customSandbox = createState<string>('');
const showFeedback = createState<boolean>(false); const isLoadingSandboxes = createState<boolean>(false);
client.onConnect(() => { client.onConnect(() => {
isLoadingSandboxes.set(true);
client client
.send('getSandbox', undefined) .send('getSandbox', undefined)
.then((results: Array<Sandbox>) => { .then((results: Array<Sandbox>) => {
sandboxes.set(results); sandboxes.set(results);
isLoadingSandboxes.set(false);
}) })
.catch((e) => console.error('[sandbox] getSandbox call failed:', e)); .catch((e) => {
console.error('[sandbox] getSandbox call failed:', e);
isLoadingSandboxes.set(false);
displayError(e);
});
}); });
const onSendSandboxEnvironment = (sandbox: string) => { const onSendSandboxEnvironment = (sandbox: string) => {
@@ -80,25 +55,35 @@ export function plugin(client: PluginClient<{}, ClientMethods>) {
sandbox, sandbox,
}) })
.then((result: SetSandboxResult) => { .then((result: SetSandboxResult) => {
setTimeout(() => { if (result.result)
showFeedback.set(false); displaySuccess('Update to ' + sandbox + ' successful');
}, 3000); else displayError('Update to ' + sandbox + ' failed');
showFeedback.set(result.result);
}) })
.catch((e) => console.error('[sandbox] setSandbox call failed:', e)); .catch((e) => {
console.error('[sandbox] setSandbox call failed:', e);
displayError(e);
});
}; };
const onChangeSandbox = (e: ChangeEvent<HTMLInputElement>) => { const onChangeSandbox = (e: ChangeEvent<HTMLInputElement>) => {
customSandbox.set(e.target.value); customSandbox.set(e.target.value);
}; };
const displaySuccess = (title: string) => {
message.success(title);
};
const displayError = (title: string) => {
message.error(title);
};
return { return {
client, client,
onChangeSandbox, onChangeSandbox,
onSendSandboxEnvironment, onSendSandboxEnvironment,
customSandbox, customSandbox,
sandboxes, sandboxes,
showFeedback, isLoadingSandboxes,
}; };
} }
@@ -106,16 +91,39 @@ export function Component() {
const instance = usePlugin(plugin); const instance = usePlugin(plugin);
const customSandbox = useValue(instance.customSandbox); const customSandbox = useValue(instance.customSandbox);
const sandboxes = useValue(instance.sandboxes); const sandboxes = useValue(instance.sandboxes);
const showFeedback = useValue(instance.showFeedback); const isLoadingSandboxes = useValue(instance.isLoadingSandboxes);
return ( return (
<FlexColumn> <Layout.Container center pad="medium">
<TextInputLayout> <Layout.Container
<ButtonGroup> center
<TextInput gap
type="text" style={{
placeholder="Sandbox URL (e.g. unixname.sb.facebook.com)" width: '350px',
key="sandbox-url" }}>
<Typography.Text type="secondary">
Select the environment:
</Typography.Text>
<Spin spinning={isLoadingSandboxes} />
{sandboxes.map((sandbox) => (
<Button
key={sandbox.value}
onClick={() => instance.onSendSandboxEnvironment(sandbox.value)}
style={{
width: '100%',
}}>
{sandbox.name}
</Button>
))}
<Typography.Text type="secondary">
Provide custom Sandbox URL
</Typography.Text>
<Input.Group compact>
<Input
style={{
width: 'calc(100% - 80px)',
}}
placeholder="e.g. unixname.sb.facebook.com"
onChange={instance.onChangeSandbox} onChange={instance.onChangeSandbox}
onKeyPress={(event) => { onKeyPress={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -124,24 +132,13 @@ export function Component() {
}} }}
/> />
<Button <Button
key="sandbox-send" type="primary"
icon="download"
onClick={() => instance.onSendSandboxEnvironment(customSandbox)} onClick={() => instance.onSendSandboxEnvironment(customSandbox)}
disabled={customSandbox == null} disabled={customSandbox == null}>
/> Submit
</ButtonGroup> </Button>
<FeedbackMessage hidden={showFeedback == false}> </Input.Group>
Success! </Layout.Container>
</FeedbackMessage> </Layout.Container>
</TextInputLayout>
{sandboxes.map((sandbox) => (
<ButtonContainer key={sandbox.value}>
<BigButton
onClick={() => instance.onSendSandboxEnvironment(sandbox.value)}>
{sandbox.name}
</BigButton>
</ButtonContainer>
))}
</FlexColumn>
); );
} }