Screenshot in titlebar
Summary: Now, that we always have one device selected, we can put the buttons for screenshot and screen capture to Sonar's titlebar. This diff also adds support for iOS devices. Reviewed By: jknoxville Differential Revision: D8732632 fbshipit-source-id: 56271fbba7b4a2c10c2742c5c457dbb4c3c16777
This commit is contained in:
committed by
Facebook Github Bot
parent
de353a7ed0
commit
03a8e696a9
281
src/chrome/ScreenCaptureButtons.js
Normal file
281
src/chrome/ScreenCaptureButtons.js
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Button, ButtonGroup, Component} from 'sonar';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import AndroidDevice from '../devices/AndroidDevice';
|
||||||
|
import IOSDevice from '../devices/IOSDevice';
|
||||||
|
import os from 'os';
|
||||||
|
import fs from 'fs';
|
||||||
|
import adb from 'adbkit-fb';
|
||||||
|
import path from 'path';
|
||||||
|
import {exec} from 'child_process';
|
||||||
|
|
||||||
|
const SCREENSHOT_FILE_NAME = 'screen.png';
|
||||||
|
const VIDEO_FILE_NAME = 'video.mp4';
|
||||||
|
const SCREENSHOT_PATH = path.join(
|
||||||
|
os.homedir(),
|
||||||
|
'/.sonar/',
|
||||||
|
SCREENSHOT_FILE_NAME,
|
||||||
|
);
|
||||||
|
const VIDEO_PATH = path.join(os.homedir(), '.sonar', VIDEO_FILE_NAME);
|
||||||
|
|
||||||
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
|
|
||||||
|
type PullTransfer = any;
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
devices: Array<BaseDevice>,
|
||||||
|
selectedDeviceIndex: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
type State = {|
|
||||||
|
pullingData: boolean,
|
||||||
|
recording: boolean,
|
||||||
|
recordingEnabled: boolean,
|
||||||
|
capturingScreenshot: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
function openFile(path: string): Promise<*> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(`${getOpenCommand()} ${path}`, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenCommand(): string {
|
||||||
|
//TODO: TESTED ONLY ON MAC!
|
||||||
|
switch (os.platform()) {
|
||||||
|
case 'win32':
|
||||||
|
return 'start';
|
||||||
|
case 'linux':
|
||||||
|
return 'xdg-open';
|
||||||
|
default:
|
||||||
|
return 'open';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePngStreamToFile(stream: PullTransfer): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
stream.on('end', () => {
|
||||||
|
resolve(SCREENSHOT_PATH);
|
||||||
|
});
|
||||||
|
stream.on('error', reject);
|
||||||
|
stream.pipe(fs.createWriteStream(SCREENSHOT_PATH));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScreenCaptureButtons extends Component<Props, State> {
|
||||||
|
iOSRecorder: ?any;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
pullingData: false,
|
||||||
|
recording: false,
|
||||||
|
recordingEnabled: false,
|
||||||
|
capturingScreenshot: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.checkIfRecordingIsAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
|
this.checkIfRecordingIsAvailable(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIfRecordingIsAvailable = (props: Props = this.props): void => {
|
||||||
|
const {devices, selectedDeviceIndex} = props;
|
||||||
|
const device: BaseDevice = devices[selectedDeviceIndex];
|
||||||
|
|
||||||
|
if (device instanceof AndroidDevice) {
|
||||||
|
this.executeShell(
|
||||||
|
device,
|
||||||
|
`[ ! -f /system/bin/screenrecord ] && echo "File does not exist"`,
|
||||||
|
).then(output =>
|
||||||
|
this.setState({
|
||||||
|
recordingEnabled: !output,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
device instanceof IOSDevice &&
|
||||||
|
device.deviceType === 'emulator'
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
recordingEnabled: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
recordingEnabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
captureScreenshot = () => {
|
||||||
|
const {devices, selectedDeviceIndex} = this.props;
|
||||||
|
const device: BaseDevice = devices[selectedDeviceIndex];
|
||||||
|
|
||||||
|
if (device instanceof AndroidDevice) {
|
||||||
|
return device.adb
|
||||||
|
.screencap(device.serial)
|
||||||
|
.then(writePngStreamToFile)
|
||||||
|
.then(openFile)
|
||||||
|
.catch(console.error);
|
||||||
|
} else if (device instanceof IOSDevice) {
|
||||||
|
exec(
|
||||||
|
`xcrun simctl io booted screenshot ${SCREENSHOT_PATH}`,
|
||||||
|
(err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
openFile(SCREENSHOT_PATH);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
startRecording = () => {
|
||||||
|
const {devices, selectedDeviceIndex} = this.props;
|
||||||
|
const device: BaseDevice = devices[selectedDeviceIndex];
|
||||||
|
|
||||||
|
if (device instanceof AndroidDevice) {
|
||||||
|
this.setState({
|
||||||
|
recording: true,
|
||||||
|
});
|
||||||
|
this.executeShell(
|
||||||
|
device,
|
||||||
|
`screenrecord --bugreport /sdcard/${VIDEO_FILE_NAME}`,
|
||||||
|
)
|
||||||
|
.then(output => {
|
||||||
|
if (output) {
|
||||||
|
throw output;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setState({
|
||||||
|
recording: false,
|
||||||
|
pullingData: true,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(): Promise<string> => {
|
||||||
|
return this.pullFromDevice(
|
||||||
|
device,
|
||||||
|
`/sdcard/${VIDEO_FILE_NAME}`,
|
||||||
|
VIDEO_PATH,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(openFile)
|
||||||
|
.then(() => {
|
||||||
|
this.executeShell(device, `rm /sdcard/${VIDEO_FILE_NAME}`);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setState({
|
||||||
|
pullingData: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`unable to capture video: ${error}`);
|
||||||
|
this.setState({
|
||||||
|
recording: false,
|
||||||
|
pullingData: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (device instanceof IOSDevice) {
|
||||||
|
this.setState({
|
||||||
|
recording: true,
|
||||||
|
});
|
||||||
|
this.iOSRecorder = exec(
|
||||||
|
`xcrun simctl io booted recordVideo ${VIDEO_PATH}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pullFromDevice = (
|
||||||
|
device: AndroidDevice,
|
||||||
|
src: string,
|
||||||
|
dst: string,
|
||||||
|
): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return device.adb.pull(device.serial, src).then(stream => {
|
||||||
|
stream.on('end', () => {
|
||||||
|
resolve(dst);
|
||||||
|
});
|
||||||
|
stream.on('error', reject);
|
||||||
|
stream.pipe(fs.createWriteStream(dst));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
stopRecording = () => {
|
||||||
|
const {devices, selectedDeviceIndex} = this.props;
|
||||||
|
const device: BaseDevice = devices[selectedDeviceIndex];
|
||||||
|
if (device instanceof AndroidDevice) {
|
||||||
|
this.executeShell(device, `pgrep 'screenrecord' -L 2`);
|
||||||
|
} else if (this.iOSRecorder) {
|
||||||
|
this.iOSRecorder.kill();
|
||||||
|
this.setState({
|
||||||
|
recording: false,
|
||||||
|
});
|
||||||
|
openFile(VIDEO_PATH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeShell = (device: AndroidDevice, command: string): Promise<string> => {
|
||||||
|
return device.adb
|
||||||
|
.shell(device.serial, command)
|
||||||
|
.then(adb.util.readAll)
|
||||||
|
.then(output => output.toString().trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
onRecordingClicked = () => {
|
||||||
|
if (this.state.recording) {
|
||||||
|
this.stopRecording();
|
||||||
|
} else {
|
||||||
|
this.startRecording();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {recordingEnabled} = this.state;
|
||||||
|
const {devices, selectedDeviceIndex} = this.props;
|
||||||
|
const device: ?BaseDevice =
|
||||||
|
selectedDeviceIndex > -1 ? devices[selectedDeviceIndex] : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
compact={true}
|
||||||
|
onClick={this.captureScreenshot}
|
||||||
|
icon="camera"
|
||||||
|
title="Take Screenshot"
|
||||||
|
disabled={!device}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
compact={true}
|
||||||
|
onClick={this.onRecordingClicked}
|
||||||
|
icon={this.state.recording ? 'stop-playback' : 'camcorder'}
|
||||||
|
pulse={this.state.recording}
|
||||||
|
selected={this.state.recording}
|
||||||
|
title="Make Screen Recording"
|
||||||
|
disabled={!device || !recordingEnabled}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(({connections: {devices, selectedDeviceIndex}}) => ({
|
||||||
|
devices,
|
||||||
|
selectedDeviceIndex,
|
||||||
|
}))(ScreenCaptureButtons);
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
togglePluginManagerVisible,
|
togglePluginManagerVisible,
|
||||||
} from '../reducers/application.js';
|
} from '../reducers/application.js';
|
||||||
import DevicesButton from './DevicesButton.js';
|
import DevicesButton from './DevicesButton.js';
|
||||||
|
import ScreenCaptureButtons from './ScreenCaptureButtons.js';
|
||||||
import AutoUpdateVersion from './AutoUpdateVersion.js';
|
import AutoUpdateVersion from './AutoUpdateVersion.js';
|
||||||
import config from '../fb-stubs/config.js';
|
import config from '../fb-stubs/config.js';
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ class SonarTitleBar extends Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<TitleBar focused={this.props.windowIsFocused} className="toolbar">
|
<TitleBar focused={this.props.windowIsFocused} className="toolbar">
|
||||||
<DevicesButton />
|
<DevicesButton />
|
||||||
|
<ScreenCaptureButtons />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{process.platform === 'darwin' ? <AutoUpdateVersion /> : null}
|
{process.platform === 'darwin' ? <AutoUpdateVersion /> : null}
|
||||||
{config.bugReportButtonVisible && (
|
{config.bugReportButtonVisible && (
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type {SonarDevicePlugin} from '../plugin.js';
|
|||||||
import {GK} from 'sonar';
|
import {GK} from 'sonar';
|
||||||
import logs from './logs/index.js';
|
import logs from './logs/index.js';
|
||||||
import cpu from './cpu/index.js';
|
import cpu from './cpu/index.js';
|
||||||
import screen from './screen/index.js';
|
|
||||||
|
|
||||||
const plugins: Array<Class<SonarDevicePlugin<any>>> = [logs];
|
const plugins: Array<Class<SonarDevicePlugin<any>>> = [logs];
|
||||||
|
|
||||||
@@ -18,8 +17,4 @@ if (GK.get('sonar_uiperf')) {
|
|||||||
plugins.push(cpu);
|
plugins.push(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GK.get('sonar_screen_plugin')) {
|
|
||||||
plugins.push(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const devicePlugins = plugins;
|
export const devicePlugins = plugins;
|
||||||
|
|||||||
@@ -1,291 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2018-present Facebook.
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {SonarDevicePlugin} from 'sonar';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
FlexColumn,
|
|
||||||
FlexRow,
|
|
||||||
LoadingIndicator,
|
|
||||||
styled,
|
|
||||||
colors,
|
|
||||||
Component,
|
|
||||||
} from 'sonar';
|
|
||||||
|
|
||||||
const os = require('os');
|
|
||||||
const fs = require('fs');
|
|
||||||
const adb = require('adbkit-fb');
|
|
||||||
const path = require('path');
|
|
||||||
const exec = require('child_process').exec;
|
|
||||||
const SCREENSHOT_FILE_NAME = 'screen.png';
|
|
||||||
const VIDEO_FILE_NAME = 'video.mp4';
|
|
||||||
const SCREENSHOT_PATH = path.join(
|
|
||||||
os.homedir(),
|
|
||||||
'/.sonar/',
|
|
||||||
SCREENSHOT_FILE_NAME,
|
|
||||||
);
|
|
||||||
const VIDEO_PATH = path.join(os.homedir(), '.sonar', VIDEO_FILE_NAME);
|
|
||||||
|
|
||||||
type AndroidDevice = any;
|
|
||||||
type AdbClient = any;
|
|
||||||
type PullTransfer = any;
|
|
||||||
|
|
||||||
type State = {|
|
|
||||||
pullingData: boolean,
|
|
||||||
recording: boolean,
|
|
||||||
recordingEnabled: boolean,
|
|
||||||
capturingScreenshot: boolean,
|
|
||||||
|};
|
|
||||||
|
|
||||||
const BigButton = Button.extends({
|
|
||||||
height: 200,
|
|
||||||
width: 200,
|
|
||||||
flexGrow: 1,
|
|
||||||
fontSize: 24,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ButtonContainer = FlexRow.extends({
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
padding: 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
const LoadingSpinnerContainer = FlexRow.extends({
|
|
||||||
flexGrow: 1,
|
|
||||||
padding: 24,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
const LoadingSpinnerText = styled.text({
|
|
||||||
fontSize: 24,
|
|
||||||
marginLeft: 12,
|
|
||||||
color: colors.grey,
|
|
||||||
});
|
|
||||||
|
|
||||||
class LoadingSpinner extends Component<{}, {}> {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<LoadingSpinnerContainer>
|
|
||||||
<LoadingIndicator />
|
|
||||||
<LoadingSpinnerText>Pulling files from device...</LoadingSpinnerText>
|
|
||||||
</LoadingSpinnerContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFile(path: string): Promise<*> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec(`${getOpenCommand()} ${path}`, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOpenCommand(): string {
|
|
||||||
//TODO: TESTED ONLY ON MAC!
|
|
||||||
switch (os.platform()) {
|
|
||||||
case 'win32':
|
|
||||||
return 'start';
|
|
||||||
case 'linux':
|
|
||||||
return 'xdg-open';
|
|
||||||
default:
|
|
||||||
return 'open';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function writePngStreamToFile(stream: PullTransfer): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stream.on('end', () => {
|
|
||||||
resolve(SCREENSHOT_PATH);
|
|
||||||
});
|
|
||||||
stream.on('error', reject);
|
|
||||||
stream.pipe(fs.createWriteStream(SCREENSHOT_PATH));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ScreenPlugin extends SonarDevicePlugin<State> {
|
|
||||||
static id = 'DeviceScreen';
|
|
||||||
static title = 'Screen';
|
|
||||||
static icon = 'mobile';
|
|
||||||
|
|
||||||
device: AndroidDevice;
|
|
||||||
adbClient: AdbClient;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
pullingData: false,
|
|
||||||
recording: false,
|
|
||||||
recordingEnabled: false,
|
|
||||||
capturingScreenshot: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.adbClient = this.device.adb;
|
|
||||||
|
|
||||||
this.executeShell(
|
|
||||||
`[ ! -f /system/bin/screenrecord ] && echo "File does not exist"`,
|
|
||||||
).then(output => {
|
|
||||||
if (output) {
|
|
||||||
console.error(
|
|
||||||
'screenrecord util does not exist. Most likely it is an emulator which does not support screen recording via adb',
|
|
||||||
);
|
|
||||||
this.setState({
|
|
||||||
recordingEnabled: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
recordingEnabled: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
captureScreenshot = () => {
|
|
||||||
return this.adbClient
|
|
||||||
.screencap(this.device.serial)
|
|
||||||
.then(writePngStreamToFile)
|
|
||||||
.then(openFile)
|
|
||||||
.catch(error => {
|
|
||||||
//TODO: proper logging?
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
pullFromDevice = (src: string, dst: string): Promise<string> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
return this.adbClient.pull(this.device.serial, src).then(stream => {
|
|
||||||
stream.on('end', () => {
|
|
||||||
resolve(dst);
|
|
||||||
});
|
|
||||||
stream.on('error', reject);
|
|
||||||
stream.pipe(fs.createWriteStream(dst));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onRecordingClicked = () => {
|
|
||||||
if (this.state.recording) {
|
|
||||||
this.stopRecording();
|
|
||||||
} else {
|
|
||||||
this.startRecording();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onScreenshotClicked = () => {
|
|
||||||
var self = this;
|
|
||||||
this.setState({
|
|
||||||
capturingScreenshot: true,
|
|
||||||
});
|
|
||||||
this.captureScreenshot().then(() => {
|
|
||||||
self.setState({
|
|
||||||
capturingScreenshot: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
startRecording = () => {
|
|
||||||
const self = this;
|
|
||||||
this.setState({
|
|
||||||
recording: true,
|
|
||||||
});
|
|
||||||
this.executeShell(`screenrecord --bugreport /sdcard/${VIDEO_FILE_NAME}`)
|
|
||||||
.then(output => {
|
|
||||||
if (output) {
|
|
||||||
throw output;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
self.setState({
|
|
||||||
recording: false,
|
|
||||||
pullingData: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(): Promise<string> => {
|
|
||||||
return self.pullFromDevice(`/sdcard/${VIDEO_FILE_NAME}`, VIDEO_PATH);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(openFile)
|
|
||||||
.then(() => {
|
|
||||||
self.executeShell(`rm /sdcard/${VIDEO_FILE_NAME}`);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
self.setState({
|
|
||||||
pullingData: false,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(`unable to capture video: ${error}`);
|
|
||||||
self.setState({
|
|
||||||
recording: false,
|
|
||||||
pullingData: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
stopRecording = () => {
|
|
||||||
this.executeShell(`pgrep 'screenrecord' -L 2`);
|
|
||||||
};
|
|
||||||
|
|
||||||
executeShell = (command: string): Promise<string> => {
|
|
||||||
return this.adbClient
|
|
||||||
.shell(this.device.serial, command)
|
|
||||||
.then(adb.util.readAll)
|
|
||||||
.then(output => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
resolve(output.toString().trim());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getLoadingSpinner = () => {
|
|
||||||
return this.state.pullingData ? <LoadingSpinner /> : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const recordingEnabled =
|
|
||||||
this.state.recordingEnabled &&
|
|
||||||
!this.state.capturingScreenshot &&
|
|
||||||
!this.state.pullingData;
|
|
||||||
const screenshotEnabled =
|
|
||||||
!this.state.recording &&
|
|
||||||
!this.state.capturingScreenshot &&
|
|
||||||
!this.state.pullingData;
|
|
||||||
return (
|
|
||||||
<FlexColumn>
|
|
||||||
<ButtonContainer>
|
|
||||||
<BigButton
|
|
||||||
key="video_btn"
|
|
||||||
onClick={!recordingEnabled ? null : this.onRecordingClicked}
|
|
||||||
icon={this.state.recording ? 'stop' : 'camcorder'}
|
|
||||||
disabled={!recordingEnabled}
|
|
||||||
selected={true}
|
|
||||||
pulse={this.state.recording}
|
|
||||||
iconSize={24}>
|
|
||||||
{!this.state.recording ? 'Record screen' : 'Stop recording'}
|
|
||||||
</BigButton>
|
|
||||||
<BigButton
|
|
||||||
key="screenshot_btn"
|
|
||||||
icon="camera"
|
|
||||||
selected={true}
|
|
||||||
onClick={!screenshotEnabled ? null : this.onScreenshotClicked}
|
|
||||||
iconSize={24}
|
|
||||||
pulse={this.state.capturingScreenshot}
|
|
||||||
disabled={!screenshotEnabled}>
|
|
||||||
Take screenshot
|
|
||||||
</BigButton>
|
|
||||||
</ButtonContainer>
|
|
||||||
{this.getLoadingSpinner()}
|
|
||||||
</FlexColumn>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user