Support screenshot buttons

Summary: Added Sandy styling to `CaptureButtons`, and converted to hooks while at it

Reviewed By: cekkaewnumchai

Differential Revision: D24538953

fbshipit-source-id: cfb9a9b856ada40eb96b77d2e5e6ea780971d0ce
This commit is contained in:
Michel Weststrate
2020-10-27 05:16:10 -07:00
committed by Facebook GitHub Bot
parent 9f3df3406d
commit 82604020ab
2 changed files with 108 additions and 123 deletions

View File

@@ -7,28 +7,14 @@
* @format * @format
*/ */
import {Button as AntButton, message} from 'antd';
import {Button, ButtonGroup} from '../ui'; import {Button, ButtonGroup} from '../ui';
import React, {Component} from 'react'; import React, {useState, useEffect, useCallback} from 'react';
import {connect} from 'react-redux';
import path from 'path'; import path from 'path';
import BaseDevice from '../devices/BaseDevice';
import {State as Store} from '../reducers';
import open from 'open'; import open from 'open';
import {capture, CAPTURE_LOCATION, getFileName} from '../utils/screenshot'; import {capture, CAPTURE_LOCATION, getFileName} from '../utils/screenshot';
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
type OwnProps = {}; import {useStore} from '../utils/useStore';
type StateFromProps = {
selectedDevice: BaseDevice | null | undefined;
};
type DispatchFromProps = {};
type State = {
recording: boolean;
recordingEnabled: boolean;
capturingScreenshot: boolean;
};
export async function openFile(path: string | null) { export async function openFile(path: string | null) {
if (!path) { if (!path) {
@@ -42,111 +28,101 @@ export async function openFile(path: string | null) {
} }
} }
type Props = OwnProps & StateFromProps & DispatchFromProps; export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
class ScreenCaptureButtons extends Component<Props, State> { const selectedDevice = useStore((state) => state.connections.selectedDevice);
videoPath: string | null | undefined; const [isTakingScreenshot, setIsTakingScreenshot] = useState(false);
const [isRecordingAvailable, setIsRecordingAvailable] = useState(false);
const [isRecording, setIsRecording] = useState(false);
state = { useEffect(() => {
recording: false, let cancelled = false;
recordingEnabled: false, selectedDevice?.screenCaptureAvailable().then((result) => {
capturingScreenshot: false, if (!cancelled) {
}; setIsRecordingAvailable(result);
}
componentDidMount() {
this.checkIfRecordingIsAvailable();
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.selectedDevice !== this.props.selectedDevice) {
this.checkIfRecordingIsAvailable(nextProps);
}
}
checkIfRecordingIsAvailable = async (props: Props = this.props) => {
const {selectedDevice} = props;
const recordingEnabled = selectedDevice
? await selectedDevice.screenCaptureAvailable()
: false;
this.setState({recordingEnabled});
};
captureScreenshot: Promise<void> | any = async () => {
const {selectedDevice} = this.props;
if (selectedDevice != null) {
await capture(selectedDevice)
.then(openFile)
.catch((e) => console.error('Taking screenshot failed:', e));
}
};
startRecording = async () => {
const {selectedDevice} = this.props;
if (!selectedDevice) {
return;
}
const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4'));
return selectedDevice.startScreenCapture(videoPath);
};
stopRecording = async () => {
const {selectedDevice} = this.props;
if (!selectedDevice) {
return;
}
const path = await selectedDevice.stopScreenCapture().catch((e) => {
console.error(e);
}); });
path ? openFile(path) : 0; return () => {
}; cancelled = true;
};
}, [selectedDevice]);
onRecordingClicked = () => { const handleScreenshot = useCallback(() => {
if (this.state.recording) { setIsTakingScreenshot(true);
this.stopRecording(); capture(selectedDevice!)
this.setState({ .then(openFile)
recording: false, .catch((e) => {
console.error('Taking screenshot failed:', e);
message.error('Taking screenshot failed:' + e);
})
.finally(() => {
setIsTakingScreenshot(false);
});
}, [selectedDevice]);
const handleRecording = useCallback(() => {
if (!selectedDevice) {
return;
}
if (!isRecording) {
setIsRecording(true);
const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4'));
selectedDevice.startScreenCapture(videoPath).catch((e) => {
console.error('Failed to start recording', e);
message.error('Failed to start recording' + e);
setIsRecording(false);
}); });
} else { } else {
this.setState({ selectedDevice
recording: true, .stopScreenCapture()
}); .then((path) => {
this.startRecording().catch((e) => { path && openFile(path);
this.setState({ })
recording: false, .catch((e) => {
console.error('Failed to start recording', e);
message.error('Failed to start recording' + e);
})
.finally(() => {
setIsRecording(false);
}); });
console.error(e);
});
} }
}; }, [selectedDevice, isRecording]);
render() { return useSandy ? (
const {recordingEnabled} = this.state; <>
const {selectedDevice} = this.props; <AntButton
icon={<CameraOutlined />}
return ( title="Take Screenshot"
<ButtonGroup> type="ghost"
<Button onClick={handleScreenshot}
compact={true} disabled={!selectedDevice}
onClick={this.captureScreenshot} loading={isTakingScreenshot}
icon="camera" />
title="Take Screenshot" <AntButton
disabled={!selectedDevice} icon={<VideoCameraOutlined />}
/> title="Make Screen Recording"
<Button type={isRecording ? 'primary' : 'ghost'}
compact={true} onClick={handleRecording}
onClick={this.onRecordingClicked} disabled={!selectedDevice || !isRecordingAvailable}
icon={this.state.recording ? 'stop-playback' : 'camcorder'} danger={isRecording}
pulse={this.state.recording} />
selected={this.state.recording} </>
title="Make Screen Recording" ) : (
disabled={!selectedDevice || !recordingEnabled} <ButtonGroup>
/> <Button
</ButtonGroup> compact={true}
); onClick={handleScreenshot}
} icon="camera"
title="Take Screenshot"
disabled={!selectedDevice}
/>
<Button
compact={true}
onClick={handleRecording}
icon={isRecording ? 'stop-playback' : 'camcorder'}
pulse={isRecording}
selected={isRecording}
title="Make Screen Recording"
disabled={!selectedDevice || !isRecordingAvailable}
/>
</ButtonGroup>
);
} }
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({connections: {selectedDevice}}) => ({
selectedDevice,
}),
)(ScreenCaptureButtons);

View File

@@ -11,13 +11,14 @@ import React from 'react';
import {Alert, Button, Input} from 'antd'; import {Alert, Button, Input} from 'antd';
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar'; import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
import {SettingOutlined, RocketOutlined} from '@ant-design/icons'; import {SettingOutlined, RocketOutlined} from '@ant-design/icons';
import {Layout, Link} from '../../ui'; import {Layout, Link, styled} from '../../ui';
import {theme} from 'flipper-plugin'; import {theme} from 'flipper-plugin';
import {useStore as useReduxStore} from 'react-redux'; import {useStore as useReduxStore} from 'react-redux';
import {showEmulatorLauncher} from './LaunchEmulator'; import {showEmulatorLauncher} from './LaunchEmulator';
import {AppSelector} from './AppSelector'; import {AppSelector} from './AppSelector';
import {useStore} from '../../utils/useStore'; import {useStore} from '../../utils/useStore';
import {PluginList} from './PluginList'; import {PluginList} from './PluginList';
import ScreenCaptureButtons from '../../chrome/ScreenCaptureButtons';
const appTooltip = ( const appTooltip = (
<> <>
@@ -33,6 +34,7 @@ const appTooltip = (
export function AppInspect() { export function AppInspect() {
const store = useReduxStore(); const store = useReduxStore();
const selectedDevice = useStore((state) => state.connections.selectedDevice); const selectedDevice = useStore((state) => state.connections.selectedDevice);
return ( return (
<LeftSidebar> <LeftSidebar>
<Layout.Top> <Layout.Top>
@@ -43,18 +45,19 @@ export function AppInspect() {
<Layout.Container padv="small" padh="medium" gap={theme.space.large}> <Layout.Container padv="small" padh="medium" gap={theme.space.large}>
<AppSelector /> <AppSelector />
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" /> <Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
<Layout.Horizontal gap> <Toolbar gap>
<Button icon={<SettingOutlined />} type="link" /> <Button icon={<SettingOutlined />} type="ghost" />
<Button icon={<SettingOutlined />} type="link" /> <Button icon={<SettingOutlined />} type="ghost" />
<Button <Button
icon={<RocketOutlined />} icon={<RocketOutlined />}
type="link" type="ghost"
title="Start Emulator / Simulator..." title="Start Emulator / Simulator..."
onClick={() => { onClick={() => {
showEmulatorLauncher(store); showEmulatorLauncher(store);
}} }}
/> />
</Layout.Horizontal> <ScreenCaptureButtons useSandy />
</Toolbar>
</Layout.Container> </Layout.Container>
</Layout.Container> </Layout.Container>
<Layout.ScrollContainer vertical padv={theme.space.large}> <Layout.ScrollContainer vertical padv={theme.space.large}>
@@ -68,3 +71,9 @@ export function AppInspect() {
</LeftSidebar> </LeftSidebar>
); );
} }
const Toolbar = styled(Layout.Horizontal)({
'.ant-btn': {
border: 'none',
},
});