revamp UI + show oncall/group

Summary:
Redesigns the bug reporting dialog:
- show information from `package.json`'s `bugs` field, where we can link to support groups or name oncalls.
- adds show/hide animation
- uses new button style

Reviewed By: jknoxville

Differential Revision: D13417287

fbshipit-source-id: 2948794e9b1f42bbd895981d5e4b0578a9b8ee2e
This commit is contained in:
Daniel Büchele
2018-12-18 09:32:07 -08:00
committed by Facebook Github Bot
parent c540fe5529
commit c9b982b182
7 changed files with 276 additions and 79 deletions

View File

@@ -0,0 +1,68 @@
// flow-typed signature: 896bbb51b1a943fefff583786cd4d0c0
// flow-typed version: b6c24caf38/react-transition-group_v2.x.x/flow_>=v0.60.x
// @flow
declare module 'react-transition-group' {
declare export type CSSTransitionClassNames = {
appear?: string,
appearActive?: string,
enter?: string,
enterActive?: string,
enterDone?: string,
exit?: string,
exitActive?: string,
exitDone?: string,
};
declare export type TransitionStatus = 'entering' | 'entered' | 'exiting' | 'exited' | 'unmounted';
declare export type EndHandler = (node: HTMLElement, done: () => void) => void;
declare export type EnterHandler = (node: HTMLElement, isAppearing: boolean) => void;
declare export type ExitHandler = (node: HTMLElement) => void;
declare type TransitionActions = {
appear?: boolean;
enter?: boolean;
exit?: boolean;
}
declare type TransitionProps = TransitionActions & {
mountOnEnter?: boolean,
unmountOnExit?: boolean,
onEnter?: EnterHandler,
onEntering?: EnterHandler,
onEntered?: EnterHandler,
onExit?: ExitHandler,
onExiting?: ExitHandler,
onExited?: ExitHandler,
} & ({
timeout: number | { enter?: number, exit?: number },
addEndListener?: null,
} | {
timeout?: number | { enter?: number, exit?: number },
addEndListener: EndHandler,
})
declare export class Transition extends React$Component<TransitionProps & {
in?: boolean,
children: ((status: TransitionStatus) => React$Node) | React$Node,
}> {}
declare export class TransitionGroup extends React$Component<TransitionActions & {
component?: React$ElementType | null,
children?: React$Node,
childFactory?: (child: React$Node) => React$Node,
}> {}
declare export class ReplaceTransition extends React$Component<TransitionProps & {
in: boolean,
children: React$Node,
}> {}
declare export class CSSTransition extends React$Component<TransitionProps & {
in?: boolean,
classNames: string | CSSTransitionClassNames,
children?: ((status: TransitionStatus) => React$Node) | React$Node,
}> {}
}

View File

@@ -80,6 +80,7 @@
"react-emotion": "^9.2.6", "react-emotion": "^9.2.6",
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"react-test-renderer": "^16.5.2", "react-test-renderer": "^16.5.2",
"react-transition-group": "^2.5.1",
"react-virtualized-auto-sizer": "^1.0.2", "react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.3.1", "react-window": "^1.3.1",
"redux": "^4.0.0", "redux": "^4.0.0",

View File

@@ -8,7 +8,6 @@
import React from 'react'; import React from 'react';
import {FlexColumn, FlexRow} from 'flipper'; import {FlexColumn, FlexRow} from 'flipper';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {toggleBugDialogVisible} from './reducers/application.js';
import WelcomeScreen from './chrome/WelcomeScreen.js'; import WelcomeScreen from './chrome/WelcomeScreen.js';
import TitleBar from './chrome/TitleBar.js'; import TitleBar from './chrome/TitleBar.js';
import MainSidebar from './chrome/MainSidebar.js'; import MainSidebar from './chrome/MainSidebar.js';
@@ -25,11 +24,9 @@ type Props = {
logger: Logger, logger: Logger,
bugReporter: BugReporter, bugReporter: BugReporter,
leftSidebarVisible: boolean, leftSidebarVisible: boolean,
bugDialogVisible: boolean,
pluginManagerVisible: boolean, pluginManagerVisible: boolean,
selectedDevice: ?BaseDevice, selectedDevice: ?BaseDevice,
error: ?string, error: ?string,
toggleBugDialogVisible: (visible?: boolean) => any,
}; };
export class App extends React.Component<Props> { export class App extends React.Component<Props> {
@@ -51,14 +48,7 @@ export class App extends React.Component<Props> {
return ( return (
<FlexColumn grow={true}> <FlexColumn grow={true}>
<TitleBar /> <TitleBar />
{this.props.bugDialogVisible && ( <BugReporterDialog bugReporter={this.props.bugReporter} />
<BugReporterDialog
bugReporter={this.props.bugReporter}
close={() => {
this.props.toggleBugDialogVisible(false);
}}
/>
)}
<FlexRow grow={true}> <FlexRow grow={true}>
{this.props.leftSidebarVisible && <MainSidebar />} {this.props.leftSidebarVisible && <MainSidebar />}
{this.props.selectedDevice ? ( {this.props.selectedDevice ? (
@@ -78,16 +68,12 @@ export class App extends React.Component<Props> {
* run Flow. */ * run Flow. */
export default connect( export default connect(
({ ({
application: {pluginManagerVisible, bugDialogVisible, leftSidebarVisible}, application: {pluginManagerVisible, leftSidebarVisible},
connections: {selectedDevice, error}, connections: {selectedDevice, error},
}) => ({ }) => ({
pluginManagerVisible, pluginManagerVisible,
bugDialogVisible,
leftSidebarVisible, leftSidebarVisible,
selectedDevice, selectedDevice,
error, error,
}), }),
{
toggleBugDialogVisible,
},
)(App); )(App);

View File

@@ -5,7 +5,7 @@ exports[`Empty app state matches snapshot 1`] = `
className="css-1si6n3e" className="css-1si6n3e"
> >
<div <div
className="toolbar css-1u64wvw" className="toolbar css-1cn9bxd"
> >
<div <div
className="css-q7gju5" className="css-q7gju5"

View File

@@ -6,7 +6,11 @@
*/ */
import type BugReporter from '../fb-stubs/BugReporter.js'; import type BugReporter from '../fb-stubs/BugReporter.js';
import type {FlipperDevicePlugin, FlipperPlugin} from '../plugin';
import {toggleBugDialogVisible} from '../reducers/application.js';
import {Component} from 'react'; import {Component} from 'react';
import {Transition} from 'react-transition-group';
import {connect} from 'react-redux';
import { import {
Button, Button,
colors, colors,
@@ -16,6 +20,7 @@ import {
FlexRow, FlexRow,
Textarea, Textarea,
Text, Text,
Glyph,
FlexCenter, FlexCenter,
styled, styled,
} from 'flipper'; } from 'flipper';
@@ -24,26 +29,49 @@ const Container = styled(FlexColumn)({
padding: 10, padding: 10,
}); });
const Icon = styled(Glyph)({
marginRight: 8,
marginLeft: 3,
});
const Center = styled(Text)({
textAlign: 'center',
lineHeight: '130%',
paddingLeft: 20,
paddingRight: 20,
});
const Title = styled('div')({
fontWeight: '500',
marginTop: 8,
marginLeft: 2,
marginBottom: 8,
});
const textareaStyle = { const textareaStyle = {
margin: 0, margin: 0,
marginBottom: 10, marginBottom: 10,
}; };
const DialogContainer = styled('div')({ const DialogContainer = styled('div')(({state}) => ({
transform: `translateY(${
state === 'entering' || state === 'exiting' ? '-110' : ''
}%)`,
transition: '.3s transform',
width: 400, width: 400,
height: 300, height: 300,
position: 'absolute', position: 'absolute',
left: '50%', left: '50%',
marginLeft: -200, marginLeft: -200,
top: 40, top: 38,
zIndex: 999999, zIndex: 2,
backgroundColor: '#fff', backgroundColor: '#EFEEEF',
border: '1px solid #ddd', border: '1px solid #C6C6C6',
borderTop: 'none', borderTop: 'none',
borderBottomLeftRadius: 5, borderBottomLeftRadius: 2,
borderBottomRightRadius: 5, borderBottomRightRadius: 2,
boxShadow: '0 1px 10px rgba(0, 0, 0, 0.1)', boxShadow: '0 5px 13px rgba(0, 0, 0, 0.2)',
}); }));
const TitleInput = styled(Input)({ const TitleInput = styled(Input)({
...textareaStyle, ...textareaStyle,
@@ -64,47 +92,56 @@ const Footer = styled(FlexRow)({
}); });
const CloseDoneButton = styled(Button)({ const CloseDoneButton = styled(Button)({
width: 50, marginTop: 20,
margin: '10px auto', marginLeft: 'auto !important',
marginRight: 'auto',
}); });
type State = { const InfoBox = styled(FlexRow)({
marginBottom: 20,
lineHeight: '130%',
});
type State = {|
description: string, description: string,
title: string, title: string,
submitting: boolean, submitting: boolean,
success: false | number, // false if not created, id of bug if it's been created success: ?number,
error: ?string, error: ?string,
}; |};
type Props = { type Props = {|
bugReporter: BugReporter, bugReporter: BugReporter,
close: () => void, toggleBugDialogVisible: (visible: boolean) => mixed,
}; activePlugin: ?Class<FlipperPlugin<> | FlipperDevicePlugin<>>,
bugDialogVisible: boolean,
|};
const DEFAULT_DESCRIPTION = `Thanks for taking the time to provide feedback! class BugReporterDialog extends Component<Props, State> {
Please fill out the following information to make addressing your issue easier. state = {
description: '',
What device platform are you using? ios/android title: '',
What sort of device are you using? emulator/physical submitting: false,
What app are you trying to use? wilde, fb4a, lite etc success: null,
Describe your problem in as much detail as possible: `; error: null,
};
export default class BugReporterDialog extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
description: DEFAULT_DESCRIPTION,
title: '',
submitting: false,
success: false,
error: null,
};
}
titleRef: HTMLElement; titleRef: HTMLElement;
descriptionRef: HTMLElement; descriptionRef: HTMLElement;
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}
onKeyDown = (e: KeyboardEvent) => {
if (this.props.bugDialogVisible && e.key === 'Escape') {
this.onCancel();
}
};
onDescriptionChange = (e: SyntheticInputEvent<HTMLInputElement>) => { onDescriptionChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
this.setState({description: e.target.value}); this.setState({description: e.target.value});
}; };
@@ -173,67 +210,142 @@ export default class BugReporterDialog extends Component<Props, State> {
}; };
onCancel = () => { onCancel = () => {
this.props.close(); this.setState({
error: null,
title: '',
description: '',
});
this.props.toggleBugDialogVisible(false);
}; };
render() { render() {
let content; let content;
const {title, success, error, description, submitting} = this.state;
const {title, success, error, description} = this.state;
if (success) { if (success) {
content = ( content = (
<FlexCenter grow={true}> <FlexCenter grow={true}>
<FlexColumn> <FlexColumn>
<Text> <Center>
<Text>Bug </Text> <Glyph
name="checkmark-circle"
<Text bold={true}> size={24}
<Link variant="outline"
href={`https://our.intern.facebook.com/intern/bug/${success}`}> color={colors.light30}
{success} />
</Link> <br />
</Text> <Title>Bug Report created</Title>
The bug report{' '}
<Text> created. Thank you for the report!</Text> <Link
</Text> href={`https://our.intern.facebook.com/intern/bug/${success}`}>
{success}
<CloseDoneButton onClick={this.onCancel}>Close</CloseDoneButton> </Link>{' '}
was successfully created. Thank you for your help making Flipper
better!
</Center>
<CloseDoneButton onClick={this.onCancel} compact type="primary">
Close
</CloseDoneButton>
</FlexColumn> </FlexColumn>
</FlexCenter> </FlexCenter>
); );
} else { } else {
content = ( content = (
<Container grow={true}> <Container grow={true}>
<Title>Report a bug...</Title>
<TitleInput <TitleInput
placeholder="Title..." placeholder="Title..."
value={title} value={title}
innerRef={this.setTitleRef} innerRef={this.setTitleRef}
onChange={this.onTitleChange} onChange={this.onTitleChange}
disabled={submitting}
/> />
<DescriptionTextarea <DescriptionTextarea
placeholder="Description..." placeholder="Describe your problem in as much detail as possible..."
value={description} value={description}
innerRef={this.setDescriptionRef} innerRef={this.setDescriptionRef}
onChange={this.onDescriptionChange} onChange={this.onDescriptionChange}
disabled={submitting}
/> />
{this.props.activePlugin?.bugs && (
<InfoBox>
<Icon color={colors.light50} name="info-circle" />
<span>
If you bug is related to the{' '}
<strong>
{this.props.activePlugin?.title ||
this.props.activePlugin?.id}{' '}
plugin
</strong>
{this.props.activePlugin?.bugs?.url && (
<span>
, you might find useful information about it here:{' '}
<Link href={this.props.activePlugin?.bugs?.url || ''}>
{this.props.activePlugin?.bugs?.url}
</Link>
</span>
)}
{this.props.activePlugin?.bugs?.email && (
<span>
, you might also want contact{' '}
<Link
href={
'mailto:' + String(this.props.activePlugin?.bugs?.email)
}>
{this.props.activePlugin?.bugs?.email}
</Link>, the author/oncall of this plugin, directly
</span>
)}
.
</span>
</InfoBox>
)}
<Footer> <Footer>
{error != null && <Text color={colors.red}>{error}</Text>} {error != null && <Text color={colors.red}>{error}</Text>}
<SubmitButtonContainer> <SubmitButtonContainer>
<Button type="primary" onClick={this.onSubmit}> <Button
Submit report onClick={this.onCancel}
</Button> disabled={submitting}
<Button type="danger" onClick={this.onCancel}> compact
padded>
Cancel Cancel
</Button> </Button>
<Button
type="primary"
onClick={this.onSubmit}
disabled={submitting}
compact
padded>
Submit Report
</Button>
</SubmitButtonContainer> </SubmitButtonContainer>
</Footer> </Footer>
</Container> </Container>
); );
} }
return <DialogContainer>{content}</DialogContainer>; return (
<Transition in={this.props.bugDialogVisible} timeout={300} unmountOnExit>
{state => <DialogContainer state={state}>{content}</DialogContainer>}
</Transition>
);
} }
} }
// $FlowFixMe
export default connect(
({
plugins: {devicePlugins, clientPlugins},
connections: {selectedPlugin},
application: {bugDialogVisible},
}) => ({
bugDialogVisible,
activePlugin:
devicePlugins.get(selectedPlugin) || clientPlugins.get(selectedPlugin),
}),
{
toggleBugDialogVisible,
},
)(BugReporterDialog);

View File

@@ -44,6 +44,7 @@ const AppTitleBar = styled(FlexRow)(({focused}) => ({
paddingRight: 10, paddingRight: 10,
justifyContent: 'space-between', justifyContent: 'space-between',
WebkitAppRegion: 'drag', WebkitAppRegion: 'drag',
zIndex: 3,
})); }));
type Props = {| type Props = {|

View File

@@ -62,7 +62,7 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/runtime@^7.0.0": "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2":
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f"
integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg== integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==
@@ -2054,6 +2054,13 @@ doctrine@^2.0.2, doctrine@^2.1.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
dom-helpers@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
dependencies:
"@babel/runtime" "^7.1.2"
domexception@^1.0.1: domexception@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -4021,7 +4028,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -4292,6 +4299,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
dependencies: dependencies:
js-tokens "^3.0.0" js-tokens "^3.0.0"
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
loud-rejection@^1.0.0: loud-rejection@^1.0.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -5283,6 +5297,11 @@ react-is@^16.5.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ== integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-redux@^5.0.7: react-redux@^5.0.7:
version "5.0.7" version "5.0.7"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
@@ -5304,6 +5323,16 @@ react-test-renderer@^16.5.2:
react-is "^16.5.2" react-is "^16.5.2"
schedule "^0.5.0" schedule "^0.5.0"
react-transition-group@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.1.tgz#67fbd8d30ebb1c57a149d554dbb82eabefa61f0d"
integrity sha512-8x/CxUL9SjYFmUdzsBPTgtKeCxt7QArjNSte0wwiLtF/Ix/o1nWNJooNy5o9XbHIKS31pz7J5VF2l41TwlvbHQ==
dependencies:
dom-helpers "^3.3.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
react-virtualized-auto-sizer@^1.0.2: react-virtualized-auto-sizer@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd" resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"