Remove non-Sandy UI
Summary: This removes the Non-Sandy UI from the Flipper codebase. It is a pretty rough scan for unused components, over time when converting more advanced components to Ant design probably even more code can be removed. Partially used `npx ts-purge` to reveal never imported source files. Changelog: It is no longer possible to opt out of the new Sandy UI Reviewed By: jknoxville Differential Revision: D25825282 fbshipit-source-id: 9041dbc7e03bce0760c9a0a34f1877851b5f06cf
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ba74b074c2
commit
12e59afdc6
@@ -14,7 +14,6 @@ import {
|
||||
FlipperDevicePlugin,
|
||||
} from './plugin';
|
||||
import BaseDevice, {OS} from './devices/BaseDevice';
|
||||
import {LegacyApp} from './chrome/LegacyApp';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import {Store} from './reducers/index';
|
||||
import {setPluginState} from './reducers/pluginStates';
|
||||
@@ -124,7 +123,6 @@ export interface FlipperClientConnection<D, M> {
|
||||
}
|
||||
|
||||
export default class Client extends EventEmitter {
|
||||
app: LegacyApp | undefined;
|
||||
connected: boolean;
|
||||
id: string;
|
||||
query: ClientQuery;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {useEffect} from 'react';
|
||||
import LegacyApp from './LegacyApp';
|
||||
import fbConfig from '../fb-stubs/config';
|
||||
import {isFBEmployee} from '../utils/fbEmployee';
|
||||
import {Logger} from '../fb-interfaces/Logger';
|
||||
import isSandyEnabled from '../utils/isSandyEnabled';
|
||||
import {SandyApp} from '../sandy-chrome/SandyApp';
|
||||
import {notification} from 'antd';
|
||||
import isProduction from '../utils/isProduction';
|
||||
|
||||
type Props = {logger: Logger};
|
||||
|
||||
export default function App(props: Props) {
|
||||
useEffect(() => {
|
||||
if (fbConfig.warnFBEmployees && isProduction()) {
|
||||
isFBEmployee().then((isEmployee) => {
|
||||
if (isEmployee) {
|
||||
notification.warning({
|
||||
placement: 'bottomLeft',
|
||||
message: 'Please use Flipper@FB',
|
||||
description: (
|
||||
<>
|
||||
You are using the open-source version of Flipper. Install the
|
||||
internal build from Managed Software Center to get access to
|
||||
more plugins.
|
||||
</>
|
||||
),
|
||||
duration: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
return isSandyEnabled() ? <SandyApp /> : <LegacyApp logger={props.logger} />;
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import type {Styles} from 'console-feed/lib/definitions/Styles';
|
||||
import {createState, useValue} from 'flipper-plugin';
|
||||
import {useLocalStorage} from '../utils/useLocalStorage';
|
||||
import {theme} from 'flipper-plugin';
|
||||
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
||||
import {useIsDarkMode} from '../utils/useIsDarkMode';
|
||||
|
||||
const MAX_LOG_ITEMS = 1000;
|
||||
@@ -65,7 +64,6 @@ const allLogLevels: Methods[] = [
|
||||
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
|
||||
|
||||
export function ConsoleLogs() {
|
||||
const isSandy = useIsSandy();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const logs = useValue(logsAtom);
|
||||
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
|
||||
@@ -90,7 +88,7 @@ export function ConsoleLogs() {
|
||||
);
|
||||
}, [logLevels, setLogLevels]);
|
||||
|
||||
const styles = useMemo(() => buildTheme(isSandy), [isSandy]);
|
||||
const styles = useMemo(buildTheme, []);
|
||||
|
||||
return (
|
||||
<Layout.Top>
|
||||
@@ -106,7 +104,7 @@ export function ConsoleLogs() {
|
||||
<Console
|
||||
logs={logs}
|
||||
filter={logLevels}
|
||||
variant={isDarkMode || !isSandy ? 'dark' : 'light'}
|
||||
variant={isDarkMode ? 'dark' : 'light'}
|
||||
styles={styles}
|
||||
/>
|
||||
</Layout.ScrollContainer>
|
||||
@@ -114,15 +112,7 @@ export function ConsoleLogs() {
|
||||
);
|
||||
}
|
||||
|
||||
function buildTheme(isSandy: boolean): Styles {
|
||||
if (!isSandy) {
|
||||
const bg = '#333';
|
||||
return {
|
||||
BASE_BACKGROUND_COLOR: bg,
|
||||
BASE_COLOR: 'white',
|
||||
LOG_BACKGROUND: bg,
|
||||
};
|
||||
}
|
||||
function buildTheme(): Styles {
|
||||
return {
|
||||
// See: https://github.com/samdenty/console-feed/blob/master/src/definitions/Styles.d.ts
|
||||
BASE_BACKGROUND_COLOR: 'transparent',
|
||||
|
||||
@@ -12,7 +12,6 @@ import ReactDOM from 'react-dom';
|
||||
import Sidebar from '../ui/components/Sidebar';
|
||||
import {toggleRightSidebarAvailable} from '../reducers/application';
|
||||
import {useDispatch, useStore} from '../utils/useStore';
|
||||
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
||||
import {ContentContainer} from '../sandy-chrome/ContentContainer';
|
||||
import {Layout} from '../ui';
|
||||
|
||||
@@ -33,7 +32,6 @@ export default function DetailSidebar({children, width, minWidth}: OwnProps) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
const isSandy = useIsSandy();
|
||||
const dispatch = useDispatch();
|
||||
const {rightSidebarAvailable, rightSidebarVisible} = useStore((state) => {
|
||||
const {rightSidebarAvailable, rightSidebarVisible} = state.application;
|
||||
@@ -72,16 +70,10 @@ export default function DetailSidebar({children, width, minWidth}: OwnProps) {
|
||||
minWidth={minWidth}
|
||||
width={width || 300}
|
||||
position="right"
|
||||
gutter={isSandy}>
|
||||
{isSandy ? (
|
||||
gutter>
|
||||
<ContentContainer>
|
||||
<Layout.ScrollContainer vertical>
|
||||
{children}
|
||||
</Layout.ScrollContainer>
|
||||
<Layout.ScrollContainer vertical>{children}</Layout.ScrollContainer>
|
||||
</ContentContainer>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Sidebar>,
|
||||
domNode,
|
||||
)) ||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* 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, styled} from '../ui';
|
||||
import {connect, ReactReduxContext} from 'react-redux';
|
||||
|
||||
import {selectDevice, preferDevice} from '../reducers/connections';
|
||||
import {
|
||||
setActiveSheet,
|
||||
ActiveSheet,
|
||||
ACTIVE_SHEET_JS_EMULATOR_LAUNCHER,
|
||||
} from '../reducers/application';
|
||||
import {showOpenDialog} from '../utils/exportData';
|
||||
import BaseDevice from '../devices/BaseDevice';
|
||||
import React, {Component} from 'react';
|
||||
import {State} from '../reducers';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import {launchEmulator} from '../devices/AndroidDevice';
|
||||
|
||||
type StateFromProps = {
|
||||
selectedDevice: BaseDevice | null | undefined;
|
||||
androidEmulators: Array<string>;
|
||||
devices: Array<BaseDevice>;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
selectDevice: (device: BaseDevice) => void;
|
||||
preferDevice: (device: string) => void;
|
||||
setActiveSheet: (sheet: ActiveSheet) => void;
|
||||
};
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
const DropdownButton = styled(Button)({
|
||||
fontSize: 11,
|
||||
});
|
||||
|
||||
class DevicesButton extends Component<Props> {
|
||||
launchEmulator = async (name: string) => {
|
||||
await launchEmulator(name);
|
||||
this.props.preferDevice(name);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
devices,
|
||||
androidEmulators,
|
||||
selectedDevice,
|
||||
selectDevice,
|
||||
} = this.props;
|
||||
let buttonLabel = 'No device selected';
|
||||
let icon = 'minus-circle';
|
||||
|
||||
if (selectedDevice && selectedDevice.isArchived) {
|
||||
buttonLabel = `${selectedDevice.displayTitle() || 'Unknown device'}`;
|
||||
icon = 'box';
|
||||
} else if (selectedDevice && selectedDevice.deviceType === 'physical') {
|
||||
buttonLabel = selectedDevice.displayTitle() || 'Unknown device';
|
||||
icon = 'mobile';
|
||||
} else if (selectedDevice && selectedDevice.deviceType === 'emulator') {
|
||||
buttonLabel = selectedDevice.displayTitle() || 'Unknown emulator';
|
||||
icon = 'desktop';
|
||||
}
|
||||
|
||||
const dropdown: any[] = [];
|
||||
|
||||
// Physical devices
|
||||
const connectedDevices = [
|
||||
{
|
||||
label: 'Connected Devices',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter((device) => device.deviceType === 'physical')
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: `📱 ${device.displayTitle()}`,
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (connectedDevices.length > 1) {
|
||||
dropdown.push(...connectedDevices);
|
||||
}
|
||||
// Emulators
|
||||
const runningEmulators = [
|
||||
{
|
||||
label: 'Running Emulators',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter((device) => device.deviceType === 'emulator')
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: device.displayTitle(),
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (runningEmulators.length > 1) {
|
||||
dropdown.push(...runningEmulators);
|
||||
}
|
||||
// Archived
|
||||
const importedFiles = [
|
||||
{
|
||||
label: 'Disconnected Devices',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter((device) => device.isArchived)
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: `📦 ${device.displayTitle()}`,
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (importedFiles.length > 1) {
|
||||
dropdown.push(...importedFiles);
|
||||
}
|
||||
// Launch JS emulator
|
||||
if (GK.get('flipper_js_client_emulator')) {
|
||||
dropdown.push(
|
||||
{type: 'separator' as 'separator'},
|
||||
{
|
||||
label: 'Launch JS Web App',
|
||||
click: () =>
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_JS_EMULATOR_LAUNCHER),
|
||||
},
|
||||
);
|
||||
}
|
||||
// Launch Android emulators
|
||||
if (androidEmulators.length > 0) {
|
||||
const emulators = Array.from(androidEmulators)
|
||||
.filter(
|
||||
(name: string) =>
|
||||
devices.findIndex(
|
||||
(device: BaseDevice) =>
|
||||
device.title === name && !device.isArchived,
|
||||
) === -1,
|
||||
)
|
||||
.map((name: string) => ({
|
||||
label: name,
|
||||
click: () => this.launchEmulator(name),
|
||||
}));
|
||||
|
||||
if (emulators.length > 0) {
|
||||
dropdown.push(
|
||||
{type: 'separator' as 'separator'},
|
||||
{
|
||||
label: 'Launch Android emulators',
|
||||
enabled: false,
|
||||
},
|
||||
...emulators,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dropdown.length > 0) {
|
||||
dropdown.push({type: 'separator' as 'separator'});
|
||||
}
|
||||
return (
|
||||
<ReactReduxContext.Consumer>
|
||||
{({store}) => {
|
||||
dropdown.push({
|
||||
label: 'Open File...',
|
||||
click: () => {
|
||||
showOpenDialog(store);
|
||||
},
|
||||
});
|
||||
return (
|
||||
<DropdownButton compact={true} icon={icon} dropdown={dropdown}>
|
||||
{buttonLabel}
|
||||
</DropdownButton>
|
||||
);
|
||||
}}
|
||||
</ReactReduxContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
({connections: {devices, androidEmulators, selectedDevice}}) => ({
|
||||
devices,
|
||||
androidEmulators,
|
||||
selectedDevice,
|
||||
}),
|
||||
{
|
||||
selectDevice,
|
||||
preferDevice,
|
||||
setActiveSheet,
|
||||
},
|
||||
)(DevicesButton);
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {styled, colors} from '../ui';
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
setActiveSheet,
|
||||
ActiveSheet,
|
||||
ACTIVE_SHEET_DOCTOR,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
} from '../reducers/application';
|
||||
import {State as Store} from '../reducers/index';
|
||||
import {ButtonGroup, Button} from '../ui';
|
||||
import {FlexColumn, FlexRow} from '../ui';
|
||||
import runHealthchecks, {
|
||||
HealthcheckSettings,
|
||||
HealthcheckEventsHandler,
|
||||
} from '../utils/runHealthchecks';
|
||||
import {
|
||||
updateHealthcheckResult,
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
HealthcheckReport,
|
||||
HealthcheckResult,
|
||||
} from '../reducers/healthchecks';
|
||||
|
||||
import {reportUsage} from '../utils/metrics';
|
||||
|
||||
type StateFromProps = {
|
||||
healthcheckReport: HealthcheckReport;
|
||||
} & HealthcheckSettings;
|
||||
|
||||
type DispatchFromProps = {
|
||||
setActiveSheet: (payload: ActiveSheet) => void;
|
||||
} & HealthcheckEventsHandler;
|
||||
|
||||
type State = {visible: boolean; message: string; showSettingsButton: boolean};
|
||||
|
||||
type Props = DispatchFromProps & StateFromProps;
|
||||
class DoctorBar extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
message: '',
|
||||
showSettingsButton: false,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.showMessageIfChecksFailed();
|
||||
}
|
||||
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||
const failedCategories = Object.values(
|
||||
props.healthcheckReport.categories,
|
||||
).filter((cat) => hasProblems(cat.result));
|
||||
if (failedCategories.length == 1) {
|
||||
const failedCat = failedCategories[0];
|
||||
if (failedCat.key === 'ios' || failedCat.key === 'android') {
|
||||
return {
|
||||
...state,
|
||||
message: `Doctor has discovered problems with your ${failedCat.label} setup. If you are not interested in ${failedCat.label} development you can disable it in Settings.`,
|
||||
showSettingsButton: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (failedCategories.length) {
|
||||
return {
|
||||
...state,
|
||||
message: 'Doctor has discovered problems with your installation.',
|
||||
showSettingsButton: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async showMessageIfChecksFailed() {
|
||||
await runHealthchecks(this.props);
|
||||
const result = this.props.healthcheckReport.result;
|
||||
if (hasProblems(result)) {
|
||||
if (result.isAcknowledged) {
|
||||
reportUsage('doctor:warning:suppressed');
|
||||
} else {
|
||||
this.setVisible(true);
|
||||
reportUsage('doctor:warning:shown');
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
this.state.visible && (
|
||||
<Container>
|
||||
<WarningContainer>
|
||||
<FlexRow style={{flexDirection: 'row-reverse'}}>
|
||||
<ButtonSection>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => {
|
||||
reportUsage('doctor:report:opened:fromWarningBar');
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR);
|
||||
this.setVisible(false);
|
||||
}}>
|
||||
Show Problems
|
||||
</Button>
|
||||
{this.state.showSettingsButton && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
reportUsage('settings:opened:fromWarningBar');
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS);
|
||||
}}>
|
||||
Show Settings
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => this.setVisible(false)}>
|
||||
Dismiss
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonSection>
|
||||
<FlexColumn style={{flexGrow: 1}}>
|
||||
{this.state.message}
|
||||
</FlexColumn>
|
||||
</FlexRow>
|
||||
</WarningContainer>
|
||||
</Container>
|
||||
)
|
||||
);
|
||||
}
|
||||
setVisible(visible: boolean) {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
...prevState,
|
||||
visible,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, {}, Store>(
|
||||
({settingsState, healthchecks: {healthcheckReport}}) => ({
|
||||
healthcheckReport,
|
||||
settings: settingsState,
|
||||
}),
|
||||
{
|
||||
setActiveSheet,
|
||||
updateHealthcheckResult,
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
},
|
||||
)(DoctorBar);
|
||||
|
||||
const Container = styled.div({
|
||||
boxShadow: '2px 2px 2px #ccc',
|
||||
userSelect: 'text',
|
||||
});
|
||||
|
||||
const WarningContainer = styled.div({
|
||||
backgroundColor: colors.orange,
|
||||
color: '#fff',
|
||||
maxHeight: '600px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
transition: 'max-height 0.3s ease',
|
||||
'&.collapsed': {
|
||||
maxHeight: '0px',
|
||||
},
|
||||
padding: '4px 12px',
|
||||
borderBottom: '1px solid ' + colors.orangeDark3,
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '28px',
|
||||
});
|
||||
|
||||
const ButtonSection = styled(FlexColumn)({
|
||||
marginLeft: '8px',
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
});
|
||||
|
||||
function hasProblems(result: HealthcheckResult) {
|
||||
return result.status === 'WARNING' || result.status === 'FAILED';
|
||||
}
|
||||
@@ -222,7 +222,7 @@ function hasNewProblems(result: HealthcheckResult) {
|
||||
return hasProblems(result) && !result.isAcknowledged;
|
||||
}
|
||||
|
||||
export type State = {
|
||||
type State = {
|
||||
acknowledgeCheckboxVisible: boolean;
|
||||
acknowledgeOnClose?: boolean;
|
||||
selectedCheckKey?: string;
|
||||
|
||||
@@ -7,16 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
FlexColumn,
|
||||
Button,
|
||||
styled,
|
||||
Text,
|
||||
FlexRow,
|
||||
Spacer,
|
||||
Input,
|
||||
Label,
|
||||
} from '../ui';
|
||||
import {Button} from '../ui';
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {State as Store} from '../reducers';
|
||||
@@ -26,31 +17,8 @@ import {Settings} from '../reducers/settings';
|
||||
import {Collapse, Form, Input as AntInput} from 'antd';
|
||||
import {Html5Outlined} from '@ant-design/icons';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 20,
|
||||
width: 800,
|
||||
});
|
||||
|
||||
const Title = styled(Text)({
|
||||
marginBottom: 18,
|
||||
marginRight: 10,
|
||||
fontWeight: 100,
|
||||
fontSize: '40px',
|
||||
});
|
||||
|
||||
const textareaStyle = {
|
||||
margin: 0,
|
||||
marginBottom: 10,
|
||||
};
|
||||
|
||||
const TitleInput = styled(Input)({
|
||||
...textareaStyle,
|
||||
height: 30,
|
||||
});
|
||||
|
||||
type OwnProps = {
|
||||
onHide: () => void;
|
||||
useSandy?: boolean;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
@@ -96,7 +64,7 @@ class JSEmulatorLauncherSheet extends Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const {url, height, width} = this.state;
|
||||
return this.props.useSandy ? (
|
||||
return (
|
||||
<Form labelCol={{span: 4}}>
|
||||
<Form.Item label="Url">
|
||||
<AntInput value={url} onChange={this.onUrlChange} />
|
||||
@@ -113,27 +81,6 @@ class JSEmulatorLauncherSheet extends Component<Props, State> {
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : (
|
||||
<Container>
|
||||
<Title>Launch Web App</Title>
|
||||
<Label>Url</Label>
|
||||
<TitleInput value={url} onChange={this.onUrlChange} />
|
||||
<Label>Height</Label>
|
||||
<TitleInput value={height} onChange={this.onHeightChange} />
|
||||
<Label>Width</Label>
|
||||
<TitleInput value={width} onChange={this.onWidthChange} />
|
||||
|
||||
<br />
|
||||
<FlexRow>
|
||||
<Spacer />
|
||||
<Button compact padded onClick={this.props.onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" compact padded onClick={this.applyChanges}>
|
||||
Launch
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -154,7 +101,7 @@ export function JSEmulatorLauncherSheetSandy({onClose}: {onClose(): void}) {
|
||||
extra={<Html5Outlined />}
|
||||
header="Launch JS Web App"
|
||||
key="launch-js-web-app">
|
||||
<Launcher onHide={onClose} useSandy />
|
||||
<Launcher onHide={onClose} />
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
);
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {FlexRow, styled, Layout} from '../ui';
|
||||
import {connect} from 'react-redux';
|
||||
import TitleBar from './TitleBar';
|
||||
import MainSidebar2 from './mainsidebar/MainSidebar2';
|
||||
import DoctorBar from './DoctorBar';
|
||||
import PluginContainer from '../PluginContainer';
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
import {
|
||||
ACTIVE_SHEET_CHANGELOG_RECENT_ONLY,
|
||||
setActiveSheet,
|
||||
} from '../reducers/application';
|
||||
import {Logger} from '../fb-interfaces/Logger';
|
||||
import {State as Store} from '../reducers/index';
|
||||
import {StaticView} from '../reducers/connections';
|
||||
import StatusBar from './StatusBar';
|
||||
import {hasNewChangesToShow} from './ChangelogSheet';
|
||||
import QPL, {QuickLogActionType, FLIPPER_QPL_EVENTS} from '../fb-stubs/QPL';
|
||||
import {SheetRenderer} from './SheetRenderer';
|
||||
|
||||
const version = remote.app.getVersion();
|
||||
|
||||
type OwnProps = {
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
leftSidebarVisible: boolean;
|
||||
staticView: StaticView;
|
||||
};
|
||||
|
||||
type DispatchProps = {
|
||||
setActiveSheet: typeof setActiveSheet;
|
||||
};
|
||||
|
||||
/**
|
||||
* This wrapper is only needed for hacky plugins that place contents out of
|
||||
* contents, like hermes debugger
|
||||
*/
|
||||
const PluginContent = styled(FlexRow)({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
});
|
||||
PluginContent.displayName = 'App:PluginContent';
|
||||
type Props = StateFromProps & OwnProps & DispatchProps;
|
||||
|
||||
export function registerStartupTime(logger: Logger) {
|
||||
// track time since launch
|
||||
const [s, ns] = process.hrtime();
|
||||
const launchEndTime = s * 1e3 + ns / 1e6;
|
||||
ipcRenderer.on('getLaunchTime', (_: any, launchStartTime: number) => {
|
||||
logger.track('performance', 'launchTime', launchEndTime - launchStartTime);
|
||||
|
||||
QPL.markerStart(FLIPPER_QPL_EVENTS.STARTUP, 0, launchStartTime);
|
||||
QPL.markerEnd(
|
||||
FLIPPER_QPL_EVENTS.STARTUP,
|
||||
QuickLogActionType.SUCCESS,
|
||||
0,
|
||||
launchEndTime,
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.send('getLaunchTime');
|
||||
ipcRenderer.send('componentDidMount');
|
||||
}
|
||||
|
||||
export class LegacyApp extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
registerStartupTime(this.props.logger);
|
||||
if (hasNewChangesToShow(window.localStorage)) {
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_CHANGELOG_RECENT_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout.Top>
|
||||
<Layout.Top>
|
||||
<>
|
||||
<TitleBar version={version} />
|
||||
<DoctorBar />
|
||||
</>
|
||||
<>
|
||||
<SheetRenderer logger={this.props.logger} />
|
||||
</>
|
||||
</Layout.Top>
|
||||
<Layout.Bottom>
|
||||
<Layout.Left>
|
||||
{this.props.leftSidebarVisible && <MainSidebar2 />}
|
||||
<PluginContent>
|
||||
{this.props.staticView != null ? (
|
||||
React.createElement(this.props.staticView, {
|
||||
logger: this.props.logger,
|
||||
})
|
||||
) : (
|
||||
<PluginContainer logger={this.props.logger} />
|
||||
)}
|
||||
<div>
|
||||
<div
|
||||
id="flipper-out-of-contents-container"
|
||||
style={{
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</PluginContent>
|
||||
</Layout.Left>
|
||||
<StatusBar />
|
||||
</Layout.Bottom>
|
||||
</Layout.Top>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchProps, OwnProps, Store>(
|
||||
({application: {leftSidebarVisible}, connections: {staticView}}) => ({
|
||||
leftSidebarVisible,
|
||||
staticView,
|
||||
}),
|
||||
{
|
||||
setActiveSheet,
|
||||
},
|
||||
)(LegacyApp);
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {platform} from 'os';
|
||||
import {useValue} from 'flipper-plugin';
|
||||
import {Button, styled} from '../ui';
|
||||
import {useStore} from '../utils/useStore';
|
||||
import {useMemoize} from '../utils/useMemoize';
|
||||
import {State} from '../reducers';
|
||||
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
import type {NavigationPlugin} from '../../../plugins/navigation/index';
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
import type {Bookmark} from '../../../plugins/navigation/types';
|
||||
|
||||
const DropdownButton = styled(Button)({
|
||||
fontSize: 11,
|
||||
});
|
||||
|
||||
const shortenText = (text: string, MAX_CHARACTERS = 30): string => {
|
||||
if (text.length <= MAX_CHARACTERS) {
|
||||
return text;
|
||||
} else {
|
||||
return text.split('').slice(0, MAX_CHARACTERS).join('') + '...';
|
||||
}
|
||||
};
|
||||
|
||||
const NAVIGATION_PLUGIN_ID = 'Navigation';
|
||||
|
||||
export function LocationsButton() {
|
||||
const navPlugin = useStore(navPluginStateSelector);
|
||||
return navPlugin ? (
|
||||
<ActiveLocationsButton navPlugin={navPlugin} />
|
||||
) : (
|
||||
<DropdownButton compact={true}>(none)</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
function ActiveLocationsButton({navPlugin}: {navPlugin: NavigationPlugin}) {
|
||||
const currentURI = useValue(navPlugin.currentURI);
|
||||
const bookmarks = useValue(navPlugin.bookmarks);
|
||||
|
||||
const keyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (
|
||||
((platform() === 'darwin' && e.metaKey) ||
|
||||
(platform() !== 'darwin' && e.ctrlKey)) &&
|
||||
/^\d$/.test(e.key) &&
|
||||
bookmarks.size >= parseInt(e.key, 10)
|
||||
) {
|
||||
navPlugin.navigateTo(
|
||||
Array.from(bookmarks.values())[parseInt(e.key, 10) - 1].uri,
|
||||
);
|
||||
}
|
||||
},
|
||||
[bookmarks, navPlugin],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', keyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDown);
|
||||
};
|
||||
}, [keyDown]);
|
||||
|
||||
const dropdown = useMemoize(computeBookmarkDropdown, [
|
||||
navPlugin,
|
||||
bookmarks,
|
||||
currentURI,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownButton compact={true} dropdown={dropdown}>
|
||||
{(currentURI && shortenText(currentURI)) || '(none)'}
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function navPluginStateSelector(state: State) {
|
||||
const {selectedApp, clients} = state.connections;
|
||||
if (!selectedApp) return undefined;
|
||||
const client = clients.find((client) => client.id === selectedApp);
|
||||
if (!client) return undefined;
|
||||
return client.sandyPluginStates.get(NAVIGATION_PLUGIN_ID)?.instanceApi as
|
||||
| undefined
|
||||
| NavigationPlugin;
|
||||
}
|
||||
|
||||
function computeBookmarkDropdown(
|
||||
navPlugin: NavigationPlugin,
|
||||
bookmarks: Map<string, Bookmark>,
|
||||
currentURI: string,
|
||||
) {
|
||||
const dropdown: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'Bookmarks',
|
||||
enabled: false,
|
||||
},
|
||||
...Array.from(bookmarks.values()).map((bookmark, i) => {
|
||||
return {
|
||||
click: () => {
|
||||
navPlugin.navigateTo(bookmark.uri);
|
||||
},
|
||||
accelerator: i < 9 ? `CmdOrCtrl+${i + 1}` : undefined,
|
||||
label: shortenText(
|
||||
(bookmark.commonName ? bookmark.commonName + ' - ' : '') +
|
||||
bookmark.uri,
|
||||
100,
|
||||
),
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
||||
if (currentURI) {
|
||||
dropdown.push(
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: 'Bookmark Current Location',
|
||||
click: () => {
|
||||
navPlugin.addBookmark({
|
||||
uri: currentURI,
|
||||
commonName: null,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
return dropdown;
|
||||
}
|
||||
@@ -8,53 +8,14 @@
|
||||
*/
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {Button, ButtonGroup, colors} from '../ui';
|
||||
import MetroDevice, {MetroReportableEvent} from '../devices/MetroDevice';
|
||||
import styled from '@emotion/styled';
|
||||
import {useStore} from '../utils/useStore';
|
||||
import {Button as AntButton} from 'antd';
|
||||
import {MenuOutlined, ReloadOutlined} from '@ant-design/icons';
|
||||
|
||||
type LogEntry = {};
|
||||
|
||||
export type PersistedState = {
|
||||
logs: LogEntry[];
|
||||
};
|
||||
|
||||
function ProgressBar({
|
||||
progress,
|
||||
width,
|
||||
color,
|
||||
}: {
|
||||
progress: number;
|
||||
width: number;
|
||||
color: string;
|
||||
}) {
|
||||
return (
|
||||
<ProgressBarContainer width={width} color={color}>
|
||||
<ProgressBarBar progress={progress} color={color} />
|
||||
</ProgressBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const ProgressBarContainer = styled.div<{width: number; color: string}>(
|
||||
({width, color}) => ({
|
||||
border: `1px solid ${color}`,
|
||||
borderRadius: 4,
|
||||
height: 6,
|
||||
width: width,
|
||||
}),
|
||||
);
|
||||
|
||||
const ProgressBarBar = styled.div<{progress: number; color: string}>(
|
||||
({progress, color}) => ({
|
||||
background: color,
|
||||
width: `${Math.min(100, Math.round(progress * 100))}%`,
|
||||
height: 4,
|
||||
}),
|
||||
);
|
||||
|
||||
export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
||||
export default function MetroButton() {
|
||||
const device = useStore((state) =>
|
||||
state.connections.devices.find(
|
||||
(device) => device.os === 'Metro' && !device.isArchived,
|
||||
@@ -68,7 +29,7 @@ export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
||||
[device],
|
||||
);
|
||||
const [progress, setProgress] = useState(1);
|
||||
const [hasBuildError, setHasBuildError] = useState(false);
|
||||
const [_hasBuildError, setHasBuildError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!device) {
|
||||
@@ -98,7 +59,7 @@ export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return useSandy ? (
|
||||
return (
|
||||
<>
|
||||
<AntButton
|
||||
icon={<ReloadOutlined />}
|
||||
@@ -118,31 +79,5 @@ export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
title="Reload React Native App"
|
||||
icon="arrows-circle"
|
||||
compact
|
||||
onClick={() => {
|
||||
sendCommand('reload');
|
||||
}}>
|
||||
{progress < 1 ? (
|
||||
<ProgressBar
|
||||
progress={100 * progress}
|
||||
color={hasBuildError ? colors.red : colors.cyan}
|
||||
width={20}
|
||||
/>
|
||||
) : null}
|
||||
</Button>
|
||||
<Button
|
||||
title="Open the React Native Dev Menu on the device"
|
||||
icon="navicon"
|
||||
compact
|
||||
onClick={() => {
|
||||
sendCommand('devMenu');
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
FlexBox,
|
||||
Text,
|
||||
Button,
|
||||
styled,
|
||||
colors,
|
||||
} from '../ui';
|
||||
import React, {PureComponent} from 'react';
|
||||
|
||||
const Anchor = styled.img({
|
||||
zIndex: 6,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, calc(100% + 2px))',
|
||||
});
|
||||
|
||||
const PopoverContainer = styled(FlexColumn)({
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 7,
|
||||
border: '1px solid rgba(0,0,0,0.3)',
|
||||
boxShadow: '0 2px 10px 0 rgba(0,0,0,0.3)',
|
||||
position: 'absolute',
|
||||
zIndex: 5,
|
||||
minWidth: 240,
|
||||
bottom: 0,
|
||||
marginTop: 15,
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, calc(100% + 15px))',
|
||||
overflow: 'hidden',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
height: 13,
|
||||
top: -13,
|
||||
width: 26,
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
});
|
||||
|
||||
const Heading = styled(Text)({
|
||||
display: 'block',
|
||||
backgroundColor: colors.white,
|
||||
color: colors.light30,
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
lineHeight: '21px',
|
||||
padding: '4px 8px 0',
|
||||
});
|
||||
|
||||
const PopoverItem = styled(FlexRow)({
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid ${colors.light05}`,
|
||||
height: 50,
|
||||
'&:last-child': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const ItemTitle = styled(Text)({
|
||||
display: 'block',
|
||||
fontSize: 14,
|
||||
fontWeight: 400,
|
||||
lineHeight: '120%',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
marginBottom: 1,
|
||||
});
|
||||
|
||||
const ItemSubtitle = styled(Text)({
|
||||
display: 'block',
|
||||
fontWeight: 400,
|
||||
fontSize: 11,
|
||||
color: colors.light30,
|
||||
lineHeight: '14px',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
const ItemImage = styled(FlexBox)({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 40,
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
const ItemContent = styled.div({
|
||||
minWidth: 0,
|
||||
paddingRight: 5,
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
const Section = styled.div({
|
||||
borderBottom: `1px solid ${colors.light05}`,
|
||||
'&:last-child': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const Action = styled(Button)({
|
||||
border: `1px solid ${colors.macOSTitleBarButtonBorder}`,
|
||||
background: 'transparent',
|
||||
color: colors.macOSTitleBarIconSelected,
|
||||
marginRight: 8,
|
||||
lineHeight: '22px',
|
||||
'&:hover': {
|
||||
background: 'transparent',
|
||||
},
|
||||
'&:active': {
|
||||
background: 'transparent',
|
||||
border: `1px solid ${colors.macOSTitleBarButtonBorder}`,
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
sections: Array<{
|
||||
title: string;
|
||||
items: Array<{
|
||||
title: string;
|
||||
subtitle: string;
|
||||
onClick: (() => void) | null | undefined;
|
||||
icon: Element | null | undefined;
|
||||
}>;
|
||||
}>;
|
||||
onDismiss: Function;
|
||||
};
|
||||
|
||||
export default class Popover extends PureComponent<Props> {
|
||||
_ref?: Element | null;
|
||||
|
||||
componentDidMount() {
|
||||
window.document.addEventListener('click', this.handleClick);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.document.addEventListener('click', this.handleClick);
|
||||
}
|
||||
|
||||
handleClick = (e: MouseEvent) => {
|
||||
if (this._ref && !this._ref.contains(e.target as HTMLElement)) {
|
||||
this.props.onDismiss();
|
||||
}
|
||||
};
|
||||
|
||||
_setRef = (ref: Element | null) => {
|
||||
this._ref = ref;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Anchor src="./anchor.svg" key="anchor" />
|
||||
<PopoverContainer ref={this._setRef} key="popup">
|
||||
{this.props.sections.map((section) => {
|
||||
if (section.items.length > 0) {
|
||||
return (
|
||||
<Section key={section.title}>
|
||||
<Heading>{section.title}</Heading>
|
||||
{section.items.map((item) => (
|
||||
<PopoverItem key={item.title}>
|
||||
<ItemImage>{item.icon}</ItemImage>
|
||||
<ItemContent>
|
||||
<ItemTitle>{item.title}</ItemTitle>
|
||||
<ItemSubtitle>{item.subtitle}</ItemSubtitle>
|
||||
</ItemContent>
|
||||
{item.onClick && (
|
||||
<Action onClick={item.onClick} compact={true}>
|
||||
Run
|
||||
</Action>
|
||||
)}
|
||||
</PopoverItem>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</PopoverContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,6 @@ import {LeftRailButton} from '../sandy-chrome/LeftRail';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
||||
import {FeedbackPrompt} from '../fb-stubs/UserFeedback';
|
||||
import {connect} from 'react-redux';
|
||||
import {State as Store} from '../reducers';
|
||||
import {StarOutlined} from '@ant-design/icons';
|
||||
import {Popover, Rate} from 'antd';
|
||||
import {useStore} from '../utils/useStore';
|
||||
@@ -458,7 +456,3 @@ export function SandyRatingButton() {
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect<{sessionId: string | null}, null, {}, Store>(
|
||||
({application: {sessionId}}) => ({sessionId}),
|
||||
)(RatingButton);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import {Button as AntButton, message} from 'antd';
|
||||
import {Button, ButtonGroup} from '../ui';
|
||||
import React, {useState, useEffect, useCallback} from 'react';
|
||||
import path from 'path';
|
||||
import open from 'open';
|
||||
@@ -16,7 +15,7 @@ import {capture, CAPTURE_LOCATION, getFileName} from '../utils/screenshot';
|
||||
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
|
||||
import {useStore} from '../utils/useStore';
|
||||
|
||||
export async function openFile(path: string | null) {
|
||||
async function openFile(path: string | null) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
@@ -28,7 +27,7 @@ export async function openFile(path: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
|
||||
export default function ScreenCaptureButtons() {
|
||||
const selectedDevice = useStore((state) => state.connections.selectedDevice);
|
||||
const [isTakingScreenshot, setIsTakingScreenshot] = useState(false);
|
||||
const [isRecordingAvailable, setIsRecordingAvailable] = useState(false);
|
||||
@@ -84,7 +83,7 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
|
||||
}
|
||||
}, [selectedDevice, isRecording]);
|
||||
|
||||
return useSandy ? (
|
||||
return (
|
||||
<>
|
||||
<AntButton
|
||||
icon={<CameraOutlined />}
|
||||
@@ -103,24 +102,5 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
|
||||
danger={isRecording}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui';
|
||||
import {FlexColumn, Button} from '../ui';
|
||||
import React, {Component, useContext} from 'react';
|
||||
import {updateSettings, Action} from '../reducers/settings';
|
||||
import {
|
||||
@@ -29,22 +29,10 @@ import {reportUsage} from '../utils/metrics';
|
||||
import {Modal, message} from 'antd';
|
||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 20,
|
||||
width: 800,
|
||||
});
|
||||
|
||||
const Title = styled(Text)({
|
||||
marginBottom: 18,
|
||||
marginRight: 10,
|
||||
fontWeight: 100,
|
||||
fontSize: '40px',
|
||||
});
|
||||
|
||||
type OwnProps = {
|
||||
useSandy?: boolean;
|
||||
onHide: () => void;
|
||||
platform: NodeJS.Platform;
|
||||
noModal?: boolean;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
@@ -114,23 +102,6 @@ class SettingsSheet extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderNativeContainer(
|
||||
contents: React.ReactElement,
|
||||
footer: React.ReactElement,
|
||||
) {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Settings</Title>
|
||||
{contents}
|
||||
<br />
|
||||
<FlexRow>
|
||||
<Spacer />
|
||||
{footer}
|
||||
</FlexRow>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
enableAndroid,
|
||||
@@ -140,12 +111,9 @@ class SettingsSheet extends Component<Props, State> {
|
||||
enablePrefetching,
|
||||
idbPath,
|
||||
reactNative,
|
||||
disableSandy,
|
||||
darkMode,
|
||||
} = this.state.updatedSettings;
|
||||
|
||||
const {useSandy} = this.props;
|
||||
|
||||
const settingsPristine =
|
||||
isEqual(this.props.settings, this.state.updatedSettings) &&
|
||||
isEqual(this.props.launcherSettings, this.state.updatedLauncherSettings);
|
||||
@@ -264,29 +232,6 @@ class SettingsSheet extends Component<Props, State> {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ToggledSection
|
||||
label="Disable Sandy UI"
|
||||
toggled={this.state.updatedSettings.disableSandy}
|
||||
onChange={(v) => {
|
||||
this.setState({
|
||||
updatedSettings: {
|
||||
...this.state.updatedSettings,
|
||||
disableSandy: v,
|
||||
},
|
||||
forcedRestartSettings: {
|
||||
...this.state.forcedRestartSettings,
|
||||
disableSandy: v,
|
||||
},
|
||||
});
|
||||
}}>
|
||||
{' '}
|
||||
<ConfigText
|
||||
content={
|
||||
'If disabled, Flipper will fall back to the classic design.'
|
||||
}
|
||||
/>
|
||||
</ToggledSection>
|
||||
{!disableSandy && (
|
||||
<ToggledSection
|
||||
label="Enable dark theme (experimental)"
|
||||
toggled={darkMode}
|
||||
@@ -299,7 +244,6 @@ class SettingsSheet extends Component<Props, State> {
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ToggledSection
|
||||
label="React Native keyboard shortcuts"
|
||||
toggled={reactNative.shortcuts.enabled}
|
||||
@@ -388,9 +332,14 @@ class SettingsSheet extends Component<Props, State> {
|
||||
</>
|
||||
);
|
||||
|
||||
return useSandy
|
||||
? this.renderSandyContainer(contents, footer)
|
||||
: this.renderNativeContainer(contents, footer);
|
||||
return this.props.noModal ? (
|
||||
<>
|
||||
{contents}
|
||||
{footer}
|
||||
</>
|
||||
) : (
|
||||
this.renderSandyContainer(contents, footer)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
FlexColumn,
|
||||
Button,
|
||||
styled,
|
||||
Text,
|
||||
FlexRow,
|
||||
Spacer,
|
||||
Input,
|
||||
Link,
|
||||
colors,
|
||||
} from '../ui';
|
||||
import {FlexColumn, Button, styled, Text, Input, Link, colors} from '../ui';
|
||||
import React, {Component} from 'react';
|
||||
import {writeKeychain, getUser} from '../fb-stubs/user';
|
||||
import {login} from '../reducers/user';
|
||||
@@ -29,11 +19,6 @@ import {reportPlatformFailures} from '../utils/metrics';
|
||||
import {Modal} from 'antd';
|
||||
import {TrackingScope} from 'flipper-plugin';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 20,
|
||||
width: 500,
|
||||
});
|
||||
|
||||
const Title = styled(Text)({
|
||||
marginBottom: 6,
|
||||
fontWeight: 600,
|
||||
@@ -49,7 +34,6 @@ const TokenInput = styled(Input)({
|
||||
});
|
||||
|
||||
type OwnProps = {
|
||||
useSandy?: boolean;
|
||||
onHide: () => any;
|
||||
};
|
||||
|
||||
@@ -150,22 +134,6 @@ class SignInSheet extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderNativeContainer(
|
||||
contents: React.ReactElement,
|
||||
footer: React.ReactElement,
|
||||
) {
|
||||
return (
|
||||
<Container>
|
||||
{contents}
|
||||
<br />
|
||||
<FlexRow>
|
||||
<Spacer />
|
||||
{footer}
|
||||
</FlexRow>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = (
|
||||
<>
|
||||
@@ -212,9 +180,7 @@ class SignInSheet extends Component<Props, State> {
|
||||
</>
|
||||
);
|
||||
|
||||
return this.props.useSandy
|
||||
? this.renderSandyContainer(content, footer)
|
||||
: this.renderNativeContainer(content, footer);
|
||||
return this.renderSandyContainer(content, footer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {colors} from '../ui/components/colors';
|
||||
import {styled} from '../ui';
|
||||
import {connect} from 'react-redux';
|
||||
import {State} from '../reducers';
|
||||
import React, {ReactElement} from 'react';
|
||||
import Text from '../ui/components/Text';
|
||||
|
||||
const StatusBarContainer = styled(Text)({
|
||||
backgroundColor: colors.macOSTitleBarBackgroundBlur,
|
||||
borderTop: '1px solid #b3b3b3',
|
||||
lineHeight: '26px',
|
||||
padding: '0 10px',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
type Props = {
|
||||
statusMessage: React.ReactNode | string | null;
|
||||
};
|
||||
|
||||
export function statusBarView(props: Props): ReactElement | null {
|
||||
const {statusMessage} = props;
|
||||
if (statusMessage) {
|
||||
return (
|
||||
<StatusBarContainer whiteSpace="nowrap">
|
||||
{statusMessage}
|
||||
</StatusBarContainer>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<Props, void, {}, State>((state: State) => {
|
||||
const {
|
||||
application: {statusMessages},
|
||||
} = state;
|
||||
if (statusMessages.length > 0) {
|
||||
return {statusMessage: statusMessages[statusMessages.length - 1]};
|
||||
}
|
||||
return {
|
||||
statusMessage: null,
|
||||
};
|
||||
})(statusBarView);
|
||||
@@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
ActiveSheet,
|
||||
LauncherMsg,
|
||||
ShareType,
|
||||
setActiveSheet,
|
||||
toggleLeftSidebarVisible,
|
||||
toggleRightSidebarVisible,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
ACTIVE_SHEET_DOCTOR,
|
||||
} from '../reducers/application';
|
||||
import {
|
||||
colors,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupChain,
|
||||
FlexRow,
|
||||
Spacer,
|
||||
styled,
|
||||
Text,
|
||||
LoadingIndicator,
|
||||
} from '../ui';
|
||||
import {connect} from 'react-redux';
|
||||
import RatingButton from './RatingButton';
|
||||
import DevicesButton from './DevicesButton';
|
||||
import {LocationsButton} from './LocationsButton';
|
||||
import ScreenCaptureButtons from './ScreenCaptureButtons';
|
||||
import UpdateIndicator from './UpdateIndicator';
|
||||
import config from '../fb-stubs/config';
|
||||
import {clipboard} from 'electron';
|
||||
import React from 'react';
|
||||
import {State} from '../reducers';
|
||||
import {reportUsage} from '../utils/metrics';
|
||||
import MetroButton from './MetroButton';
|
||||
import {navPluginStateSelector} from './LocationsButton';
|
||||
import {getVersionString} from '../utils/versionString';
|
||||
|
||||
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
|
||||
userSelect: 'none',
|
||||
background: focused
|
||||
? `linear-gradient(to bottom, ${colors.macOSTitleBarBackgroundTop} 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)`
|
||||
: colors.macOSTitleBarBackgroundBlur,
|
||||
borderBottom: `1px solid ${
|
||||
focused ? colors.macOSTitleBarBorder : colors.macOSTitleBarBorderBlur
|
||||
}`,
|
||||
height: 38,
|
||||
flexShrink: 0,
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 80,
|
||||
paddingRight: 10,
|
||||
justifyContent: 'space-between',
|
||||
WebkitAppRegion: 'drag',
|
||||
zIndex: 6,
|
||||
}));
|
||||
|
||||
type OwnProps = {
|
||||
version: string;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
toggleLeftSidebarVisible: (visible?: boolean) => void;
|
||||
toggleRightSidebarVisible: (visible?: boolean) => void;
|
||||
setActiveSheet: (sheet: ActiveSheet) => void;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
windowIsFocused: boolean;
|
||||
leftSidebarVisible: boolean;
|
||||
rightSidebarVisible: boolean;
|
||||
rightSidebarAvailable: boolean;
|
||||
downloadingImportData: boolean;
|
||||
launcherMsg: LauncherMsg;
|
||||
share: ShareType | null | undefined;
|
||||
navPluginIsActive: boolean;
|
||||
};
|
||||
|
||||
const VersionText = styled(Text)({
|
||||
color: colors.light50,
|
||||
marginLeft: 4,
|
||||
marginTop: 2,
|
||||
cursor: 'pointer',
|
||||
display: 'block',
|
||||
padding: '4px 10px',
|
||||
'&:hover': {
|
||||
backgroundColor: `rgba(0,0,0,0.05)`,
|
||||
borderRadius: '999em',
|
||||
},
|
||||
});
|
||||
|
||||
export class Version extends React.Component<
|
||||
{children: string},
|
||||
{copied: boolean}
|
||||
> {
|
||||
state = {
|
||||
copied: false,
|
||||
};
|
||||
_onClick = () => {
|
||||
clipboard.writeText(this.props.children);
|
||||
this.setState({copied: true});
|
||||
setTimeout(() => this.setState({copied: false}), 1000);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<VersionText onClick={this._onClick}>
|
||||
{this.state.copied ? 'Copied' : this.props.children}
|
||||
</VersionText>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Importing = styled(FlexRow)({
|
||||
color: colors.light50,
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
});
|
||||
|
||||
function statusMessageComponent(
|
||||
downloadingImportData: boolean,
|
||||
statusComponent?: React.ReactNode | undefined,
|
||||
) {
|
||||
if (downloadingImportData) {
|
||||
return (
|
||||
<Importing>
|
||||
<LoadingIndicator size={16} />
|
||||
Importing data...
|
||||
</Importing>
|
||||
);
|
||||
}
|
||||
if (statusComponent) {
|
||||
return statusComponent;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
type Props = OwnProps & DispatchFromProps & StateFromProps;
|
||||
class TitleBar extends React.Component<Props, StateFromProps> {
|
||||
render() {
|
||||
const {navPluginIsActive, share} = this.props;
|
||||
return (
|
||||
<AppTitleBar focused={this.props.windowIsFocused} className="toolbar">
|
||||
{navPluginIsActive ? (
|
||||
<ButtonGroupChain iconSize={12}>
|
||||
<DevicesButton />
|
||||
<LocationsButton />
|
||||
</ButtonGroupChain>
|
||||
) : (
|
||||
<DevicesButton />
|
||||
)}
|
||||
|
||||
<MetroButton />
|
||||
|
||||
<ScreenCaptureButtons />
|
||||
{statusMessageComponent(
|
||||
this.props.downloadingImportData,
|
||||
share != null ? share.statusComponent : undefined,
|
||||
)}
|
||||
<Spacer />
|
||||
|
||||
{config.showFlipperRating ? <RatingButton /> : null}
|
||||
<Version>{getVersionString()}</Version>
|
||||
|
||||
<UpdateIndicator />
|
||||
|
||||
<Button
|
||||
icon="settings"
|
||||
title="Settings"
|
||||
compact={true}
|
||||
onClick={() => {
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS);
|
||||
reportUsage('settings:opened:fromTitleBar');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon="first-aid"
|
||||
title="Doctor"
|
||||
compact={true}
|
||||
onClick={() => {
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR);
|
||||
reportUsage('doctor:report:opened:fromTitleBar');
|
||||
}}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
compact={true}
|
||||
selected={this.props.leftSidebarVisible}
|
||||
onClick={() => this.props.toggleLeftSidebarVisible()}
|
||||
icon="icons/sidebar_left.svg"
|
||||
iconSize={20}
|
||||
title="Toggle Plugins"
|
||||
/>
|
||||
<Button
|
||||
compact={true}
|
||||
selected={this.props.rightSidebarVisible}
|
||||
onClick={() => this.props.toggleRightSidebarVisible()}
|
||||
icon="icons/sidebar_right.svg"
|
||||
iconSize={20}
|
||||
title="Toggle Details"
|
||||
disabled={!this.props.rightSidebarAvailable}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</AppTitleBar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
(state) => {
|
||||
const {
|
||||
application: {
|
||||
windowIsFocused,
|
||||
leftSidebarVisible,
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
downloadingImportData,
|
||||
launcherMsg,
|
||||
share,
|
||||
},
|
||||
} = state;
|
||||
const navPluginIsActive = !!navPluginStateSelector(state);
|
||||
|
||||
return {
|
||||
windowIsFocused,
|
||||
leftSidebarVisible,
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
downloadingImportData,
|
||||
launcherMsg,
|
||||
share,
|
||||
navPluginIsActive,
|
||||
};
|
||||
},
|
||||
{
|
||||
setActiveSheet,
|
||||
toggleLeftSidebarVisible,
|
||||
toggleRightSidebarVisible,
|
||||
},
|
||||
)(TitleBar);
|
||||
@@ -1,137 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {User, USER_UNAUTHORIZED, USER_NOT_SIGNEDIN} from '../reducers/user';
|
||||
import {ActiveSheet} from '../reducers/application';
|
||||
|
||||
import {styled, FlexRow, Glyph, Text, colors} from '../ui';
|
||||
import {logout} from '../reducers/user';
|
||||
import {setActiveSheet, ACTIVE_SHEET_SIGN_IN} from '../reducers/application';
|
||||
import {connect} from 'react-redux';
|
||||
import electron from 'electron';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {getUser} from '../fb-stubs/user';
|
||||
import config from '../fb-stubs/config';
|
||||
import {State as Store} from '../reducers';
|
||||
|
||||
const Container = styled(FlexRow)({
|
||||
alignItems: 'center',
|
||||
padding: '5px 10px',
|
||||
borderTop: `1px solid ${colors.blackAlpha10}`,
|
||||
fontWeight: 500,
|
||||
flexShrink: 0,
|
||||
minHeight: 36,
|
||||
color: colors.blackAlpha80,
|
||||
});
|
||||
|
||||
const ProfilePic = styled.img({
|
||||
borderRadius: '999em',
|
||||
flexShrink: 0,
|
||||
width: 24,
|
||||
marginRight: 6,
|
||||
});
|
||||
|
||||
const UserName = styled(Text)({
|
||||
flexGrow: 1,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
marginRight: 6,
|
||||
textOverflow: 'ellipsis',
|
||||
});
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type DispatchFromProps = {
|
||||
logout: () => void;
|
||||
setActiveSheet: (activeSheet: ActiveSheet) => void;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
type Props = OwnProps & DispatchFromProps & StateFromProps;
|
||||
class UserAccount extends PureComponent<Props> {
|
||||
_ref: Element | null | undefined;
|
||||
|
||||
setRef = (ref: HTMLDivElement | null) => {
|
||||
const element = findDOMNode(ref);
|
||||
if (element instanceof HTMLElement) {
|
||||
this._ref = element;
|
||||
}
|
||||
};
|
||||
|
||||
showDetails = () => {
|
||||
const menuTemplate: Array<Electron.MenuItemConstructorOptions> = [
|
||||
{
|
||||
label: 'Sign Out',
|
||||
click: this.props.logout,
|
||||
},
|
||||
];
|
||||
|
||||
const menu = electron.remote.Menu.buildFromTemplate(menuTemplate);
|
||||
const {bottom = null, left = null} = this._ref
|
||||
? this._ref.getBoundingClientRect()
|
||||
: {};
|
||||
menu.popup({
|
||||
window: electron.remote.getCurrentWindow(),
|
||||
// @ts-ignore async is not part of public api in electron menu popup
|
||||
async: true,
|
||||
x: left || 10,
|
||||
y: (bottom || 10) + 8,
|
||||
});
|
||||
};
|
||||
|
||||
openLogin = () => this.props.setActiveSheet(ACTIVE_SHEET_SIGN_IN);
|
||||
|
||||
componentDidMount() {
|
||||
if (config.showLogin) {
|
||||
getUser().catch((error) => {
|
||||
if (error === USER_UNAUTHORIZED || error === USER_NOT_SIGNEDIN) {
|
||||
this.openLogin();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {user} = this.props;
|
||||
const name = user ? user.name : null;
|
||||
return name ? (
|
||||
<Container ref={this.setRef} onClick={this.showDetails}>
|
||||
<ProfilePic
|
||||
src={user.profile_picture ? user.profile_picture.uri : undefined}
|
||||
/>
|
||||
<UserName>{this.props.user.name}</UserName>
|
||||
<Glyph name="chevron-down" size={10} variant="outline" />
|
||||
</Container>
|
||||
) : (
|
||||
<Container onClick={this.openLogin}>
|
||||
<Glyph
|
||||
name="profile-circle"
|
||||
size={16}
|
||||
variant="outline"
|
||||
color={colors.blackAlpha50}
|
||||
/>
|
||||
Sign In...
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({user}) => ({
|
||||
user,
|
||||
}),
|
||||
{
|
||||
logout,
|
||||
setActiveSheet,
|
||||
},
|
||||
)(UserAccount);
|
||||
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {styled} from '../ui/index';
|
||||
import FlexColumn from '../ui/components/FlexColumn';
|
||||
import FlexRow from '../ui/components/FlexRow';
|
||||
import Text from '../ui/components/FlexRow';
|
||||
import Glyph from '../ui/components/Glyph';
|
||||
import {colors, brandColors} from '../ui/components/colors';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import constants from '../fb-stubs/constants';
|
||||
import {shell, remote} from 'electron';
|
||||
import {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import {Logger, Tracked, TrackingScope} from 'flipper-plugin';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.light02,
|
||||
});
|
||||
|
||||
const Welcome = styled(FlexColumn)<{isMounted?: boolean}>(({isMounted}) => ({
|
||||
width: 460,
|
||||
background: colors.white,
|
||||
borderRadius: 10,
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
||||
overflow: 'hidden',
|
||||
opacity: isMounted ? 1 : 0,
|
||||
transform: `translateY(${isMounted ? 0 : 20}px)`,
|
||||
transition: '0.6s all ease-out',
|
||||
}));
|
||||
|
||||
const Title = styled(Text)({
|
||||
fontSize: 24,
|
||||
fontWeight: 300,
|
||||
textAlign: 'center',
|
||||
color: colors.light50,
|
||||
marginBottom: 16,
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const Version = styled(Text)({
|
||||
textAlign: 'center',
|
||||
fontSize: 11,
|
||||
fontWeight: 300,
|
||||
color: colors.light30,
|
||||
marginBottom: 60,
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const Item = styled(FlexRow)({
|
||||
padding: 10,
|
||||
cursor: 'pointer',
|
||||
alignItems: 'center',
|
||||
borderTop: `1px solid ${colors.light10}`,
|
||||
'&:hover, &:focus, &:active': {
|
||||
backgroundColor: colors.light02,
|
||||
textDecoration: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const ItemTitle = styled(Text)({
|
||||
color: colors.light50,
|
||||
fontSize: 15,
|
||||
});
|
||||
|
||||
const ItemSubTitle = styled(Text)({
|
||||
color: colors.light30,
|
||||
fontSize: 11,
|
||||
marginTop: 2,
|
||||
});
|
||||
|
||||
const Icon = styled(Glyph)({
|
||||
marginRight: 11,
|
||||
marginLeft: 6,
|
||||
});
|
||||
|
||||
const Logo = styled.img({
|
||||
width: 128,
|
||||
height: 128,
|
||||
alignSelf: 'center',
|
||||
marginTop: 50,
|
||||
marginBottom: 20,
|
||||
});
|
||||
|
||||
type Props = {
|
||||
logger: Logger;
|
||||
};
|
||||
type State = {
|
||||
isMounted: boolean;
|
||||
};
|
||||
|
||||
export default class WelcomeScreen extends PureComponent<Props, State> {
|
||||
state = {
|
||||
isMounted: false,
|
||||
};
|
||||
|
||||
timer: NodeJS.Timeout | null | undefined;
|
||||
|
||||
componentDidMount() {
|
||||
// waiting sometime before showing the welcome screen to allow Flipper to
|
||||
// connect to devices, if there are any
|
||||
this.timer = setTimeout(
|
||||
() =>
|
||||
this.setState({
|
||||
isMounted: true,
|
||||
}),
|
||||
2000,
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<TrackingScope scope="welcomescreen">
|
||||
<Welcome isMounted={this.state.isMounted}>
|
||||
<Logo src="./icon.png" />
|
||||
<Title>Welcome to Flipper</Title>
|
||||
<Version>
|
||||
{isProduction() && remote
|
||||
? `Version ${remote.app.getVersion()}`
|
||||
: 'Development Mode'}
|
||||
</Version>
|
||||
<Tracked>
|
||||
<Item
|
||||
onClick={() => {
|
||||
shell &&
|
||||
shell.openExternal(
|
||||
'https://fbflipper.com/docs/features/index',
|
||||
);
|
||||
}}>
|
||||
<Icon size={20} name="rocket" color={brandColors.Flipper} />
|
||||
<FlexColumn>
|
||||
<ItemTitle>Using Flipper</ItemTitle>
|
||||
<ItemSubTitle>
|
||||
Learn how Flipper can help you debug your App
|
||||
</ItemSubTitle>
|
||||
</FlexColumn>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() =>
|
||||
shell &&
|
||||
shell.openExternal(
|
||||
'https://fbflipper.com/docs/tutorial/intro',
|
||||
)
|
||||
}>
|
||||
<Icon size={20} name="magic-wand" color={brandColors.Flipper} />
|
||||
<FlexColumn>
|
||||
<ItemTitle>Create your own plugin</ItemTitle>
|
||||
<ItemSubTitle>Get started with these pointers</ItemSubTitle>
|
||||
</FlexColumn>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() =>
|
||||
shell &&
|
||||
shell.openExternal(
|
||||
'https://fbflipper.com/docs/getting-started/index',
|
||||
)
|
||||
}>
|
||||
<Icon size={20} name="tools" color={brandColors.Flipper} />
|
||||
<FlexColumn>
|
||||
<ItemTitle>Add Flipper support to your app</ItemTitle>
|
||||
<ItemSubTitle>Get started with these pointers</ItemSubTitle>
|
||||
</FlexColumn>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() =>
|
||||
shell && shell.openExternal(constants.FEEDBACK_GROUP_LINK)
|
||||
}>
|
||||
<Icon size={20} name="posts" color={brandColors.Flipper} />
|
||||
<FlexColumn>
|
||||
<ItemTitle>Contributing and Feedback</ItemTitle>
|
||||
<ItemSubTitle>
|
||||
Report issues and help us improve Flipper
|
||||
</ItemSubTitle>
|
||||
</FlexColumn>
|
||||
</Item>
|
||||
</Tracked>
|
||||
</Welcome>
|
||||
</TrackingScope>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {statusBarView} from '../StatusBar';
|
||||
|
||||
test('statusBarView returns null for empty status messages', () => {
|
||||
const view = statusBarView({statusMessage: null});
|
||||
expect(view).toBeNull();
|
||||
});
|
||||
|
||||
test('statusBarView returns non null view when the list of messages is non empty', () => {
|
||||
const view = statusBarView({statusMessage: 'Last Message'});
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
@@ -114,30 +114,31 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
|
||||
<div
|
||||
className="css-12n892b"
|
||||
>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-default"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Close
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="css-auhar3-TooltipContainer e1abcqbd0"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-ia8nd7-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-primary"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,30 +260,31 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
||||
<div
|
||||
className="css-12n892b"
|
||||
>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-default"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Close
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="css-auhar3-TooltipContainer e1abcqbd0"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,23 +29,24 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
|
||||
<div
|
||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-default"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="ant-btn ant-btn-primary"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Run In Background
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -79,23 +80,24 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
|
||||
<div
|
||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
<button
|
||||
className="ant-btn ant-btn-default"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
className="css-xing9h-StyledButton enfqd41"
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="ant-btn ant-btn-primary"
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Run In Background
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,625 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import BaseDevice from '../../devices/BaseDevice';
|
||||
import Client from '../../Client';
|
||||
import {UninitializedClient} from '../../UninitializedClient';
|
||||
import {sortPluginsByName} from '../../utils/pluginUtils';
|
||||
import {PluginNotification} from '../../reducers/notifications';
|
||||
import {ActiveSheet} from '../../reducers/application';
|
||||
import {State as Store} from '../../reducers';
|
||||
import {
|
||||
Sidebar,
|
||||
colors,
|
||||
Glyph,
|
||||
styled,
|
||||
SmallText,
|
||||
Info,
|
||||
HBox,
|
||||
LoadingIndicator,
|
||||
} from '../../ui';
|
||||
import React, {
|
||||
PureComponent,
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import NotificationScreen from '../NotificationScreen';
|
||||
import {
|
||||
selectPlugin,
|
||||
starPlugin as starPluginAction,
|
||||
StaticView,
|
||||
setStaticView,
|
||||
getAvailableClients,
|
||||
canBeDefaultDevice,
|
||||
} from '../../reducers/connections';
|
||||
import {setActiveSheet} from '../../reducers/application';
|
||||
import {connect} from 'react-redux';
|
||||
import SupportRequestDetails from '../../fb-stubs/SupportRequestDetails';
|
||||
import MainSidebarUtilsSection from './MainSidebarUtilsSection';
|
||||
import {
|
||||
ListItem,
|
||||
PluginName,
|
||||
Plugins,
|
||||
CategoryName,
|
||||
PluginIcon,
|
||||
PluginSidebarListItem,
|
||||
NoDevices,
|
||||
getColorByApp,
|
||||
getFavoritePlugins,
|
||||
isStaticViewActive,
|
||||
} from './sidebarUtils';
|
||||
import {useLocalStorage} from '../../utils/useLocalStorage';
|
||||
import {PluginDefinition, ClientPluginMap, DevicePluginMap} from '../../plugin';
|
||||
import GK from '../../fb-stubs/GK';
|
||||
import ArchivedDevice from '../../devices/ArchivedDevice';
|
||||
|
||||
type FlipperPlugins = PluginDefinition[];
|
||||
type PluginsByCategoryType = [string, FlipperPlugins][];
|
||||
|
||||
type SectionLevel = 1 | 2 | 3;
|
||||
|
||||
const ShowMoreButton = styled('div')<{collapsed: boolean}>(({collapsed}) => ({
|
||||
border: `1px solid ${colors.macOSTitleBarIconBlur}`,
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 16,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
lineHeight: '12px',
|
||||
transform: collapsed ? 'rotate(0deg)' : 'rotate(-180deg)',
|
||||
transition: `transform 0.3s ease`,
|
||||
}));
|
||||
|
||||
const SidebarSectionButton = styled('button')<{
|
||||
level: SectionLevel;
|
||||
color: string;
|
||||
collapsed: boolean;
|
||||
}>(({level, color}) => ({
|
||||
fontWeight: level === 3 ? 'normal' : 'bold',
|
||||
borderRadius: 0,
|
||||
border: 'none',
|
||||
background: level === 1 ? colors.sectionHeaderBorder : 'transparent',
|
||||
textAlign: level === 3 ? 'center' : 'left',
|
||||
width: '100%',
|
||||
fontSize: level === 3 ? 11 : 14,
|
||||
color,
|
||||
padding: `${level === 3 ? 0 : 8}px 10px 8px 9px`,
|
||||
textTransform: 'capitalize',
|
||||
fontVariantCaps: level === 2 ? 'all-small-caps' : 'normal',
|
||||
}));
|
||||
|
||||
const SidebarSectionBody = styled('div')<{
|
||||
level: SectionLevel;
|
||||
collapsed: boolean;
|
||||
}>(({collapsed, level}) => ({
|
||||
userSelect: 'none',
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
maxHeight: collapsed ? 0 : 2000, // might need increase if too many plugins...
|
||||
transition: collapsed
|
||||
? 'max-height 0.3s ease-out'
|
||||
: 'max-height 0.5s ease-in',
|
||||
borderBottom:
|
||||
level === 2 ? `1px solid ${colors.sectionHeaderBorder}` : undefined,
|
||||
}));
|
||||
|
||||
const SidebarSectionButtonGlyph = styled(Glyph)<{collapsed: boolean}>(
|
||||
({collapsed}) => ({
|
||||
transform: collapsed ? 'rotate(90deg)' : 'rotate(180deg)',
|
||||
transition: `transform 0.3s ease`,
|
||||
}),
|
||||
);
|
||||
|
||||
const SidebarSection: React.FC<{
|
||||
defaultCollapsed?: boolean;
|
||||
title: string | React.ReactNode | ((collapsed: boolean) => React.ReactNode);
|
||||
level: SectionLevel;
|
||||
color?: string;
|
||||
storageKey: string;
|
||||
}> = ({children, title, level, color, defaultCollapsed, storageKey}) => {
|
||||
const hasMounted = useRef(false);
|
||||
const [collapsed, setCollapsed] = useLocalStorage(
|
||||
storageKey,
|
||||
!!defaultCollapsed,
|
||||
);
|
||||
color = color || colors.macOSTitleBarIconActive;
|
||||
|
||||
useEffect(() => {
|
||||
// if default collapsed changed to false after mounting, propagate that
|
||||
if (hasMounted.current && !defaultCollapsed && collapsed) {
|
||||
setCollapsed(!collapsed);
|
||||
}
|
||||
hasMounted.current = true;
|
||||
}, [defaultCollapsed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarSectionButton
|
||||
onClick={() => setCollapsed((s) => !s)}
|
||||
level={level}
|
||||
color={color}
|
||||
collapsed={collapsed}>
|
||||
<HBox grow="left">
|
||||
{typeof title === 'function' ? title(collapsed) : title}
|
||||
{level < 3 && children && (
|
||||
<SidebarSectionButtonGlyph
|
||||
name="chevron-up"
|
||||
size={12}
|
||||
color={color}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
)}
|
||||
</HBox>
|
||||
</SidebarSectionButton>
|
||||
<SidebarSectionBody level={level} collapsed={collapsed}>
|
||||
{children}
|
||||
</SidebarSectionBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type StateFromProps = {
|
||||
numNotifications: number;
|
||||
windowIsFocused: boolean;
|
||||
devices: BaseDevice[];
|
||||
selectedDevice: BaseDevice | null | undefined;
|
||||
staticView: StaticView;
|
||||
selectedPlugin: string | null | undefined;
|
||||
selectedApp: string | null | undefined;
|
||||
userStarredPlugins: Store['connections']['userStarredPlugins'];
|
||||
clients: Array<Client>;
|
||||
uninitializedClients: Array<{
|
||||
client: UninitializedClient;
|
||||
deviceId?: string;
|
||||
errorMessage?: string;
|
||||
}>;
|
||||
devicePlugins: DevicePluginMap;
|
||||
clientPlugins: ClientPluginMap;
|
||||
};
|
||||
|
||||
type SelectPlugin = (payload: {
|
||||
selectedPlugin: string | null;
|
||||
selectedApp?: string | null;
|
||||
deepLinkPayload: unknown;
|
||||
selectedDevice: BaseDevice;
|
||||
}) => void;
|
||||
|
||||
type DispatchFromProps = {
|
||||
selectPlugin: SelectPlugin;
|
||||
setActiveSheet: (activeSheet: ActiveSheet) => void;
|
||||
setStaticView: (payload: StaticView) => void;
|
||||
starPlugin: typeof starPluginAction;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
type State = {
|
||||
showWatchDebugRoot: boolean;
|
||||
showAllPlugins: boolean;
|
||||
};
|
||||
|
||||
class MainSidebar2 extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
showWatchDebugRoot: GK.get('watch_team_flipper_clientless_access'),
|
||||
showAllPlugins: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {devices} = this.props;
|
||||
return (
|
||||
<Sidebar position="left" width={250} backgroundColor={colors.light02}>
|
||||
<Plugins>
|
||||
{devices.length ? (
|
||||
devices.map((device) => this.renderDevice(device))
|
||||
) : (
|
||||
<NoDevices />
|
||||
)}
|
||||
{this.renderUnitializedClients()}
|
||||
</Plugins>
|
||||
<MainSidebarUtilsSection />
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
renderDevice(device: BaseDevice) {
|
||||
const {
|
||||
selectedPlugin,
|
||||
selectPlugin,
|
||||
clientPlugins,
|
||||
starPlugin,
|
||||
userStarredPlugins,
|
||||
selectedApp,
|
||||
selectedDevice,
|
||||
} = this.props;
|
||||
const clients = getAvailableClients(device, this.props.clients);
|
||||
const devicePluginsItems = device.devicePlugins
|
||||
.map((pluginName) => this.props.devicePlugins.get(pluginName)!)
|
||||
.sort(sortPluginsByName)
|
||||
.map((plugin) => (
|
||||
<PluginSidebarListItem
|
||||
key={plugin.id}
|
||||
isActive={plugin.id === selectedPlugin && selectedDevice === device}
|
||||
onClick={() =>
|
||||
selectPlugin({
|
||||
selectedPlugin: plugin.id,
|
||||
selectedApp: null,
|
||||
deepLinkPayload: null,
|
||||
selectedDevice: device,
|
||||
})
|
||||
}
|
||||
plugin={plugin}
|
||||
/>
|
||||
));
|
||||
const wrapDevicePlugins =
|
||||
clients.length > 0 && device.devicePlugins.length > 1 && !device.source;
|
||||
|
||||
return (
|
||||
<SidebarSection
|
||||
title={device.displayTitle()}
|
||||
key={device.serial}
|
||||
storageKey={device.serial}
|
||||
level={1}
|
||||
defaultCollapsed={!canBeDefaultDevice(device)}>
|
||||
{this.showArchivedDeviceDetails(device)}
|
||||
{wrapDevicePlugins ? (
|
||||
<SidebarSection
|
||||
level={2}
|
||||
title="Device Plugins"
|
||||
storageKey={device.serial + ':device-plugins'}
|
||||
defaultCollapsed={false}>
|
||||
{devicePluginsItems}
|
||||
</SidebarSection>
|
||||
) : (
|
||||
<div style={{marginTop: 6}}>{devicePluginsItems}</div>
|
||||
)}
|
||||
{clients.map((client) => (
|
||||
<PluginList
|
||||
device={device}
|
||||
key={client.id}
|
||||
client={client}
|
||||
clientPlugins={clientPlugins}
|
||||
starPlugin={starPlugin}
|
||||
userStarredPlugins={userStarredPlugins}
|
||||
selectedPlugin={selectedPlugin}
|
||||
selectedApp={selectedApp}
|
||||
selectPlugin={selectPlugin}
|
||||
/>
|
||||
))}
|
||||
</SidebarSection>
|
||||
);
|
||||
}
|
||||
|
||||
renderUnitializedClients() {
|
||||
const {uninitializedClients} = this.props;
|
||||
return uninitializedClients.length > 0 ? (
|
||||
<SidebarSection
|
||||
title="Connecting..."
|
||||
key="unitializedClients"
|
||||
level={1}
|
||||
storageKey="unitializedClients">
|
||||
{uninitializedClients.map((entry) => (
|
||||
<SidebarSection
|
||||
color={getColorByApp(entry.client.appName)}
|
||||
storageKey={'unitializedClients:' + JSON.stringify(entry.client)}
|
||||
key={JSON.stringify(entry.client)}
|
||||
title={
|
||||
<HBox grow="left">
|
||||
{entry.client.appName}
|
||||
{entry.errorMessage ? (
|
||||
<Glyph name={'mobile-cross'} size={16} />
|
||||
) : (
|
||||
<LoadingIndicator size={16} />
|
||||
)}
|
||||
</HBox>
|
||||
}
|
||||
level={2}></SidebarSection>
|
||||
))}
|
||||
</SidebarSection>
|
||||
) : null;
|
||||
}
|
||||
|
||||
showArchivedDeviceDetails(device: BaseDevice) {
|
||||
if (!device.isArchived || !device.source) {
|
||||
return null;
|
||||
}
|
||||
const {staticView, setStaticView} = this.props;
|
||||
const supportRequestDetailsactive = isStaticViewActive(
|
||||
staticView,
|
||||
SupportRequestDetails,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ListItem style={{marginTop: 8}}>
|
||||
<Info type="warning" small>
|
||||
{device.source ? 'Imported device' : 'Archived device'}
|
||||
</Info>
|
||||
</ListItem>
|
||||
{(device as ArchivedDevice).supportRequestDetails && (
|
||||
<ListItem
|
||||
active={supportRequestDetailsactive}
|
||||
onClick={() => setStaticView(SupportRequestDetails)}>
|
||||
<PluginIcon
|
||||
color={colors.light50}
|
||||
name={'app-dailies'}
|
||||
isActive={supportRequestDetailsactive}
|
||||
/>
|
||||
<PluginName isActive={supportRequestDetailsactive}>
|
||||
Support Request Details
|
||||
</PluginName>
|
||||
</ListItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderNotificationsEntry() {
|
||||
if (GK.get('flipper_disable_notifications')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const active = isStaticViewActive(
|
||||
this.props.staticView,
|
||||
NotificationScreen,
|
||||
);
|
||||
return (
|
||||
<ListItem
|
||||
active={active}
|
||||
onClick={() => this.props.setStaticView(NotificationScreen)}
|
||||
style={{
|
||||
borderTop: `1px solid ${colors.blackAlpha10}`,
|
||||
}}>
|
||||
<PluginIcon
|
||||
color={colors.light50}
|
||||
name={this.props.numNotifications > 0 ? 'bell' : 'bell-null'}
|
||||
isActive={active}
|
||||
/>
|
||||
<PluginName count={this.props.numNotifications} isActive={active}>
|
||||
Notifications
|
||||
</PluginName>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function groupPluginsByCategory(
|
||||
plugins: FlipperPlugins,
|
||||
): PluginsByCategoryType {
|
||||
const sortedPlugins = plugins.slice().sort(sortPluginsByName);
|
||||
const byCategory: {[cat: string]: FlipperPlugins} = {};
|
||||
const res: PluginsByCategoryType = [];
|
||||
sortedPlugins.forEach((plugin) => {
|
||||
const category = plugin.category || '';
|
||||
(byCategory[category] || (byCategory[category] = [])).push(plugin);
|
||||
});
|
||||
// Sort categories
|
||||
Object.keys(byCategory)
|
||||
.sort()
|
||||
.forEach((category) => {
|
||||
res.push([category, byCategory[category]]);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({
|
||||
application: {windowIsFocused},
|
||||
connections: {
|
||||
devices,
|
||||
selectedDevice,
|
||||
selectedPlugin,
|
||||
selectedApp,
|
||||
userStarredPlugins,
|
||||
clients,
|
||||
uninitializedClients,
|
||||
staticView,
|
||||
},
|
||||
notifications: {activeNotifications, blocklistedPlugins},
|
||||
plugins: {devicePlugins, clientPlugins},
|
||||
}) => ({
|
||||
numNotifications: (() => {
|
||||
const blocklist = new Set(blocklistedPlugins);
|
||||
return activeNotifications.filter(
|
||||
(n: PluginNotification) => !blocklist.has(n.pluginId),
|
||||
).length;
|
||||
})(),
|
||||
windowIsFocused,
|
||||
devices,
|
||||
selectedDevice,
|
||||
staticView,
|
||||
selectedPlugin,
|
||||
selectedApp,
|
||||
userStarredPlugins,
|
||||
clients,
|
||||
uninitializedClients,
|
||||
devicePlugins,
|
||||
clientPlugins,
|
||||
}),
|
||||
{
|
||||
selectPlugin,
|
||||
setStaticView,
|
||||
setActiveSheet,
|
||||
starPlugin: starPluginAction,
|
||||
},
|
||||
)(MainSidebar2);
|
||||
|
||||
const PluginList = memo(function PluginList({
|
||||
client,
|
||||
device,
|
||||
clientPlugins,
|
||||
starPlugin,
|
||||
userStarredPlugins,
|
||||
selectedPlugin,
|
||||
selectedApp,
|
||||
selectPlugin,
|
||||
}: {
|
||||
client: Client;
|
||||
device: BaseDevice;
|
||||
clientPlugins: ClientPluginMap;
|
||||
starPlugin: typeof starPluginAction;
|
||||
userStarredPlugins: Store['connections']['userStarredPlugins'];
|
||||
selectedPlugin?: null | string;
|
||||
selectPlugin: SelectPlugin;
|
||||
selectedApp?: null | string;
|
||||
}) {
|
||||
// client is a mutable structure, so we need the event emitter to detect the addition of plugins....
|
||||
const [, setPluginsChanged] = useState(0);
|
||||
useEffect(() => {
|
||||
const listener = () => setPluginsChanged((v) => v + 1);
|
||||
client.on('plugins-change', listener);
|
||||
return () => {
|
||||
client.off('plugins-change', listener);
|
||||
};
|
||||
}, [client]);
|
||||
|
||||
const onFavorite = useCallback(
|
||||
(plugin: PluginDefinition) => {
|
||||
starPlugin({
|
||||
selectedApp: client.query.app,
|
||||
plugin,
|
||||
});
|
||||
},
|
||||
[client, starPlugin],
|
||||
);
|
||||
|
||||
const allPlugins = Array.from(clientPlugins.values()).filter(
|
||||
(p) => client.plugins.indexOf(p.id) > -1,
|
||||
);
|
||||
const favoritePlugins: FlipperPlugins = getFavoritePlugins(
|
||||
device,
|
||||
client,
|
||||
allPlugins,
|
||||
userStarredPlugins[client.query.app],
|
||||
true,
|
||||
);
|
||||
const selectedNonFavoritePlugin =
|
||||
selectedApp === client.id &&
|
||||
client.plugins.includes(selectedPlugin!) &&
|
||||
!favoritePlugins.find((plugin) => plugin.id === selectedPlugin);
|
||||
const allPluginsStarred = favoritePlugins.length === allPlugins.length;
|
||||
|
||||
return (
|
||||
<SidebarSection
|
||||
level={2}
|
||||
key={client.id}
|
||||
title={client.query.app}
|
||||
storageKey={`${device.serial}:${client.query.app}`}
|
||||
color={getColorByApp(client.query.app)}>
|
||||
{favoritePlugins.length === 0 ? (
|
||||
<ListItem>
|
||||
<SmallText center>No plugins enabled</SmallText>
|
||||
</ListItem>
|
||||
) : (
|
||||
<PluginsByCategory
|
||||
client={client}
|
||||
device={device}
|
||||
plugins={favoritePlugins}
|
||||
starred={true}
|
||||
onFavorite={onFavorite}
|
||||
selectedPlugin={selectedPlugin}
|
||||
selectedApp={selectedApp}
|
||||
selectPlugin={selectPlugin}
|
||||
/>
|
||||
)}
|
||||
{!allPluginsStarred && (
|
||||
<SidebarSection
|
||||
level={3}
|
||||
color={colors.macOSTitleBarIconBlur}
|
||||
storageKey={`${device.serial}:${client.query.app}:disabled-plugins`}
|
||||
defaultCollapsed={
|
||||
favoritePlugins.length > 0 && !selectedNonFavoritePlugin
|
||||
}
|
||||
title={(collapsed) => (
|
||||
<ShowMoreButton collapsed={collapsed}>
|
||||
<Glyph
|
||||
color={colors.macOSTitleBarIconBlur}
|
||||
size={8}
|
||||
name="chevron-down"
|
||||
/>
|
||||
</ShowMoreButton>
|
||||
)}>
|
||||
<PluginsByCategory
|
||||
client={client}
|
||||
device={device}
|
||||
plugins={getFavoritePlugins(
|
||||
device,
|
||||
client,
|
||||
allPlugins,
|
||||
userStarredPlugins[client.query.app],
|
||||
false,
|
||||
)}
|
||||
starred={false}
|
||||
onFavorite={onFavorite}
|
||||
selectedPlugin={selectedPlugin}
|
||||
selectedApp={selectedApp}
|
||||
selectPlugin={selectPlugin}
|
||||
/>
|
||||
</SidebarSection>
|
||||
)}
|
||||
</SidebarSection>
|
||||
);
|
||||
});
|
||||
|
||||
const PluginsByCategory = memo(function PluginsByCategory({
|
||||
client,
|
||||
plugins,
|
||||
starred,
|
||||
onFavorite,
|
||||
selectedPlugin,
|
||||
selectedApp,
|
||||
selectPlugin,
|
||||
device,
|
||||
}: {
|
||||
client: Client;
|
||||
device: BaseDevice;
|
||||
plugins: FlipperPlugins;
|
||||
starred: boolean;
|
||||
selectedPlugin?: null | string;
|
||||
selectedApp?: null | string;
|
||||
onFavorite: (plugin: PluginDefinition) => void;
|
||||
selectPlugin: SelectPlugin;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{groupPluginsByCategory(plugins).map(([category, plugins]) => (
|
||||
<Fragment key={category}>
|
||||
{category && (
|
||||
<ListItem>
|
||||
<CategoryName>{category}</CategoryName>
|
||||
</ListItem>
|
||||
)}
|
||||
{plugins.map((plugin) => (
|
||||
<PluginSidebarListItem
|
||||
key={plugin.id}
|
||||
isActive={
|
||||
plugin.id === selectedPlugin && selectedApp === client.id
|
||||
}
|
||||
onClick={() =>
|
||||
selectPlugin({
|
||||
selectedPlugin: plugin.id,
|
||||
selectedApp: client.id,
|
||||
deepLinkPayload: null,
|
||||
selectedDevice: device,
|
||||
})
|
||||
}
|
||||
plugin={plugin}
|
||||
app={client.query.app}
|
||||
onFavorite={() => onFavorite(plugin)}
|
||||
starred={device.isArchived ? undefined : starred}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import config from '../../fb-stubs/config';
|
||||
import {PluginNotification} from '../../reducers/notifications';
|
||||
import {ActiveSheet, ACTIVE_SHEET_PLUGINS} from '../../reducers/application';
|
||||
import {State as Store} from '../../reducers';
|
||||
import NotificationScreen from '../NotificationScreen';
|
||||
import {StaticView, setStaticView} from '../../reducers/connections';
|
||||
import {setActiveSheet} from '../../reducers/application';
|
||||
import UserAccount from '../UserAccount';
|
||||
import SupportRequestFormV2 from '../../fb-stubs/SupportRequestFormV2';
|
||||
import {
|
||||
isStaticViewActive,
|
||||
PluginIcon,
|
||||
PluginName,
|
||||
ListItem,
|
||||
} from './sidebarUtils';
|
||||
import {Group} from '../../reducers/supportForm';
|
||||
import {getInstance} from '../../fb-stubs/Logger';
|
||||
import {ConsoleLogs, errorCounterAtom} from '../ConsoleLogs';
|
||||
import {useValue} from 'flipper-plugin';
|
||||
import {colors} from '../../ui';
|
||||
import GK from '../../fb-stubs/GK';
|
||||
import WatchTools from '../../fb-stubs/WatchTools';
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type StateFromProps = {
|
||||
staticView: StaticView;
|
||||
selectedGroup: Group;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
setActiveSheet: (activeSheet: ActiveSheet) => void;
|
||||
setStaticView: (payload: StaticView) => void;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
|
||||
function MainSidebarUtilsSection({
|
||||
staticView,
|
||||
selectedGroup,
|
||||
setActiveSheet,
|
||||
setStaticView,
|
||||
}: Props) {
|
||||
const showWatchDebugRoot = GK.get('watch_team_flipper_clientless_access');
|
||||
|
||||
return (
|
||||
<div style={{flexShrink: 0, borderTop: `1px solid ${colors.blackAlpha10}`}}>
|
||||
{showWatchDebugRoot &&
|
||||
(function () {
|
||||
const active = isStaticViewActive(staticView, WatchTools);
|
||||
return (
|
||||
<ListItem
|
||||
active={active}
|
||||
style={{
|
||||
borderTop: `1px solid ${colors.blackAlpha10}`,
|
||||
}}
|
||||
onClick={() => setStaticView(WatchTools)}>
|
||||
<PluginIcon
|
||||
color={colors.light50}
|
||||
name={'watch-tv'}
|
||||
isActive={active}
|
||||
/>
|
||||
<PluginName isActive={active}>Watch</PluginName>
|
||||
</ListItem>
|
||||
);
|
||||
})()}
|
||||
<RenderNotificationsEntry />
|
||||
{(function () {
|
||||
const active = isStaticViewActive(staticView, SupportRequestFormV2);
|
||||
return (
|
||||
<ListItem
|
||||
active={active}
|
||||
onClick={() => {
|
||||
getInstance().track('usage', 'support-form-source', {
|
||||
source: 'sidebar',
|
||||
group: selectedGroup.name,
|
||||
});
|
||||
setStaticView(SupportRequestFormV2);
|
||||
}}>
|
||||
<PluginIcon
|
||||
color={colors.light50}
|
||||
name={'app-dailies'}
|
||||
isActive={active}
|
||||
/>
|
||||
<PluginName isActive={active}>Support Requests</PluginName>
|
||||
</ListItem>
|
||||
);
|
||||
})()}
|
||||
<ListItem onClick={() => setActiveSheet(ACTIVE_SHEET_PLUGINS)}>
|
||||
<PluginIcon
|
||||
name="question-circle"
|
||||
color={colors.light50}
|
||||
isActive={false}
|
||||
/>
|
||||
Manage Plugins
|
||||
</ListItem>
|
||||
<DebugLogsEntry staticView={staticView} setStaticView={setStaticView} />
|
||||
{config.showLogin && <UserAccount />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({connections: {staticView}, supportForm: {supportFormV2}}) => ({
|
||||
staticView,
|
||||
selectedGroup: supportFormV2.selectedGroup,
|
||||
}),
|
||||
{
|
||||
setStaticView,
|
||||
setActiveSheet,
|
||||
},
|
||||
)(MainSidebarUtilsSection);
|
||||
|
||||
type RenderNotificationsEntryProps = {
|
||||
numNotifications: number;
|
||||
staticView: StaticView;
|
||||
};
|
||||
|
||||
type RenderNotificationsEntryDispatchFromProps = {
|
||||
setStaticView: (payload: StaticView) => void;
|
||||
};
|
||||
|
||||
type RenderEntryProps = RenderNotificationsEntryProps &
|
||||
RenderNotificationsEntryDispatchFromProps;
|
||||
|
||||
const RenderNotificationsEntry = connect<
|
||||
RenderNotificationsEntryProps,
|
||||
RenderNotificationsEntryDispatchFromProps,
|
||||
{},
|
||||
Store
|
||||
>(
|
||||
({
|
||||
connections: {staticView},
|
||||
notifications: {activeNotifications, blocklistedPlugins},
|
||||
}) => ({
|
||||
numNotifications: (() => {
|
||||
const blocklist = new Set(blocklistedPlugins);
|
||||
return activeNotifications.filter(
|
||||
(n: PluginNotification) => !blocklist.has(n.pluginId),
|
||||
).length;
|
||||
})(),
|
||||
staticView,
|
||||
}),
|
||||
{
|
||||
setStaticView,
|
||||
},
|
||||
)(({staticView, setStaticView, numNotifications}: RenderEntryProps) => {
|
||||
if (GK.get('flipper_disable_notifications')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const active = isStaticViewActive(staticView, NotificationScreen);
|
||||
return (
|
||||
<ListItem active={active} onClick={() => setStaticView(NotificationScreen)}>
|
||||
<PluginIcon
|
||||
color={colors.light50}
|
||||
name={numNotifications > 0 ? 'bell' : 'bell-null'}
|
||||
isActive={active}
|
||||
/>
|
||||
<PluginName count={numNotifications} isActive={active}>
|
||||
Notifications
|
||||
</PluginName>
|
||||
</ListItem>
|
||||
);
|
||||
});
|
||||
|
||||
function DebugLogsEntry({
|
||||
staticView,
|
||||
setStaticView,
|
||||
}: {
|
||||
staticView: StaticView;
|
||||
setStaticView: (payload: StaticView) => void;
|
||||
}) {
|
||||
const active = isStaticViewActive(staticView, ConsoleLogs);
|
||||
const errorCount = useValue(errorCounterAtom);
|
||||
return (
|
||||
<ListItem onClick={() => setStaticView(ConsoleLogs)} active={active}>
|
||||
<PluginIcon
|
||||
name="caution-octagon"
|
||||
color={colors.light50}
|
||||
isActive={active}
|
||||
/>
|
||||
<PluginName count={errorCount} isActive={active}>
|
||||
Debug Logs
|
||||
</PluginName>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React, {useRef, useEffect} from 'react';
|
||||
import {
|
||||
FlexBox,
|
||||
colors,
|
||||
Text,
|
||||
Glyph,
|
||||
styled,
|
||||
FlexColumn,
|
||||
ToggleButton,
|
||||
brandColors,
|
||||
Spacer,
|
||||
Heading,
|
||||
} from '../../ui';
|
||||
import {Property} from 'csstype';
|
||||
import {getPluginTitle} from '../../utils/pluginUtils';
|
||||
import {PluginDefinition} from '../../plugin';
|
||||
import {StaticView} from '../../reducers/connections';
|
||||
import BaseDevice from '../../devices/BaseDevice';
|
||||
import Client from '../../Client';
|
||||
|
||||
export type FlipperPlugins = PluginDefinition[];
|
||||
export type PluginsByCategory = [string, FlipperPlugins][];
|
||||
|
||||
export const ListItem = styled.div<{active?: boolean; disabled?: boolean}>(
|
||||
({active, disabled}) => ({
|
||||
paddingLeft: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: 6,
|
||||
flexShrink: 0,
|
||||
backgroundColor: active ? colors.macOSTitleBarIconSelected : 'none',
|
||||
color: disabled
|
||||
? 'rgba(0, 0, 0, 0.5)'
|
||||
: active
|
||||
? colors.white
|
||||
: colors.macOSSidebarSectionItem,
|
||||
lineHeight: '25px',
|
||||
padding: '0 10px',
|
||||
'&[disabled]': {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function PluginIcon({
|
||||
isActive,
|
||||
backgroundColor,
|
||||
name,
|
||||
color,
|
||||
}: {
|
||||
isActive: boolean;
|
||||
backgroundColor?: string;
|
||||
name: string;
|
||||
color: string;
|
||||
}) {
|
||||
return (
|
||||
<PluginShape backgroundColor={backgroundColor}>
|
||||
<Glyph size={12} name={name} color={isActive ? colors.white : color} />
|
||||
</PluginShape>
|
||||
);
|
||||
}
|
||||
|
||||
const PluginShape = styled(FlexBox)<{
|
||||
backgroundColor?: Property.BackgroundColor;
|
||||
}>(({backgroundColor}) => ({
|
||||
marginRight: 8,
|
||||
backgroundColor,
|
||||
borderRadius: 3,
|
||||
flexShrink: 0,
|
||||
width: 18,
|
||||
height: 18,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
top: '-1px',
|
||||
}));
|
||||
|
||||
export const PluginName = styled(Text)<{isActive?: boolean; count?: number}>(
|
||||
(props) => ({
|
||||
cursor: 'default',
|
||||
minWidth: 0,
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
flexGrow: 1,
|
||||
'::after': {
|
||||
fontSize: 12,
|
||||
display: props.count ? 'inline-block' : 'none',
|
||||
padding: '0 8px',
|
||||
lineHeight: '17px',
|
||||
height: 17,
|
||||
alignSelf: 'center',
|
||||
content: `"${props.count}"`,
|
||||
borderRadius: '999em',
|
||||
color: props.isActive ? colors.macOSTitleBarIconSelected : colors.white,
|
||||
backgroundColor: props.isActive
|
||||
? colors.white
|
||||
: colors.macOSTitleBarIconSelected,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export function isStaticViewActive(
|
||||
current: StaticView,
|
||||
selected: StaticView,
|
||||
): boolean {
|
||||
return Boolean(current && selected && current === selected);
|
||||
}
|
||||
|
||||
export const CategoryName = styled(PluginName)({
|
||||
color: colors.macOSSidebarSectionTitle,
|
||||
textTransform: 'uppercase',
|
||||
fontSize: '0.9em',
|
||||
});
|
||||
|
||||
export const Plugins = styled(FlexColumn)({
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
});
|
||||
|
||||
export const PluginSidebarListItem: React.FC<{
|
||||
onClick: () => void;
|
||||
isActive: boolean;
|
||||
plugin: PluginDefinition;
|
||||
app?: string | null | undefined;
|
||||
helpRef?: any;
|
||||
provided?: any;
|
||||
onFavorite?: () => void;
|
||||
starred?: boolean; // undefined means: not starrable
|
||||
}> = function (props) {
|
||||
const {isActive, plugin, onFavorite, starred} = props;
|
||||
const iconColor = getColorByApp(props.app);
|
||||
const domRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = domRef.current;
|
||||
if (isActive && node) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
if (rect.top < 0 || rect.bottom > document.documentElement.clientHeight) {
|
||||
node.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
ref={domRef}
|
||||
active={isActive}
|
||||
onClick={props.onClick}
|
||||
disabled={starred === false}>
|
||||
<PluginIcon
|
||||
isActive={isActive}
|
||||
name={plugin.icon || 'apps'}
|
||||
backgroundColor={starred === false ? colors.light20 : iconColor}
|
||||
color={colors.white}
|
||||
/>
|
||||
<PluginName
|
||||
title={`${getPluginTitle(plugin)} ${plugin.version} ${
|
||||
plugin.details?.description ? '- ' + plugin.details?.description : ''
|
||||
}`}>
|
||||
{getPluginTitle(plugin)}
|
||||
</PluginName>
|
||||
{starred !== undefined && (!starred || isActive) && (
|
||||
<ToggleButton
|
||||
onClick={onFavorite}
|
||||
toggled={starred}
|
||||
tooltip="Click to enable / disable this plugin"
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export function getColorByApp(app?: string | null): string {
|
||||
let iconColor: string | undefined = (brandColors as any)[app!];
|
||||
|
||||
if (!iconColor) {
|
||||
if (!app) {
|
||||
// Device plugin
|
||||
iconColor = colors.macOSTitleBarIconBlur;
|
||||
} else {
|
||||
const pluginColors = [
|
||||
colors.seaFoam,
|
||||
colors.teal,
|
||||
colors.lime,
|
||||
colors.lemon,
|
||||
colors.orange,
|
||||
colors.tomato,
|
||||
colors.cherry,
|
||||
colors.pink,
|
||||
colors.grape,
|
||||
];
|
||||
|
||||
iconColor = pluginColors[parseInt(app, 36) % pluginColors.length];
|
||||
}
|
||||
}
|
||||
return iconColor;
|
||||
}
|
||||
|
||||
export const NoDevices = () => (
|
||||
<ListItem
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: 50,
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<Glyph name="mobile" size={32} color={colors.red}></Glyph>
|
||||
<Spacer style={{height: 20}} />
|
||||
<Heading>Select a device to get started</Heading>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
export const NoClients = () => (
|
||||
<ListItem style={{marginTop: 8}}>
|
||||
<Glyph
|
||||
name="mobile-engagement"
|
||||
size={16}
|
||||
color={colors.red}
|
||||
style={{marginRight: 10}}
|
||||
/>
|
||||
No clients connected
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
export function getFavoritePlugins(
|
||||
device: BaseDevice,
|
||||
client: Client,
|
||||
allPlugins: FlipperPlugins,
|
||||
starredPlugins: undefined | string[],
|
||||
returnFavoredPlugins: boolean, // if false, unfavoried plugins are returned
|
||||
): FlipperPlugins {
|
||||
if (device.isArchived) {
|
||||
if (!returnFavoredPlugins) {
|
||||
return [];
|
||||
}
|
||||
// for archived plugins, all stored plugins are enabled
|
||||
return allPlugins.filter(
|
||||
(plugin) => client.plugins.indexOf(plugin.id) !== -1,
|
||||
);
|
||||
}
|
||||
if (!starredPlugins || !starredPlugins.length) {
|
||||
return returnFavoredPlugins ? [] : allPlugins;
|
||||
}
|
||||
return allPlugins.filter((plugin) => {
|
||||
const idx = starredPlugins.indexOf(plugin.id);
|
||||
return idx === -1 ? !returnFavoredPlugins : returnFavoredPlugins;
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,7 @@ exports[`load PluginInstaller list 1`] = `
|
||||
class="css-9dawc5-View-FlexBox-FlexColumn"
|
||||
>
|
||||
<div
|
||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
||||
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||
>
|
||||
<div
|
||||
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
||||
@@ -139,8 +139,9 @@ exports[`load PluginInstaller list 1`] = `
|
||||
<div
|
||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<span
|
||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||
<a
|
||||
class="ant-typography"
|
||||
href="https://yarnpkg.com/en/package/flipper-plugin-hello"
|
||||
>
|
||||
<div
|
||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||
@@ -148,7 +149,7 @@ exports[`load PluginInstaller list 1`] = `
|
||||
size="16"
|
||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -156,12 +157,14 @@ exports[`load PluginInstaller list 1`] = `
|
||||
title=""
|
||||
width="15%"
|
||||
>
|
||||
<div
|
||||
class="css-iby3qx-StyledButton enfqd41"
|
||||
type="primary"
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Install
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -206,8 +209,9 @@ exports[`load PluginInstaller list 1`] = `
|
||||
<div
|
||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<span
|
||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||
<a
|
||||
class="ant-typography"
|
||||
href="https://yarnpkg.com/en/package/flipper-plugin-world"
|
||||
>
|
||||
<div
|
||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||
@@ -215,7 +219,7 @@ exports[`load PluginInstaller list 1`] = `
|
||||
size="16"
|
||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -223,19 +227,21 @@ exports[`load PluginInstaller list 1`] = `
|
||||
title=""
|
||||
width="15%"
|
||||
>
|
||||
<div
|
||||
class="css-iby3qx-StyledButton enfqd41"
|
||||
type="primary"
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Install
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
||||
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||
>
|
||||
<div
|
||||
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
||||
@@ -278,14 +284,15 @@ exports[`load PluginInstaller list 1`] = `
|
||||
<div
|
||||
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
||||
>
|
||||
<div
|
||||
class="css-1rxpsn8-StyledButton enfqd41"
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
disabled=""
|
||||
title="Cannot install plugin package by the specified path"
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Install
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="css-170i4ha-View-FlexBox-FlexRow-ErrorGlyphContainer evd5j490"
|
||||
/>
|
||||
@@ -301,7 +308,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
class="css-9dawc5-View-FlexBox-FlexColumn"
|
||||
>
|
||||
<div
|
||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
||||
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||
>
|
||||
<div
|
||||
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
||||
@@ -434,8 +441,9 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
<div
|
||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<span
|
||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||
<a
|
||||
class="ant-typography"
|
||||
href="https://yarnpkg.com/en/package/flipper-plugin-hello"
|
||||
>
|
||||
<div
|
||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||
@@ -443,7 +451,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
size="16"
|
||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -451,11 +459,14 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
title=""
|
||||
width="15%"
|
||||
>
|
||||
<div
|
||||
class="css-iby3qx-StyledButton enfqd41"
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Remove
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -500,8 +511,9 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
<div
|
||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||
/>
|
||||
<span
|
||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
||||
<a
|
||||
class="ant-typography"
|
||||
href="https://yarnpkg.com/en/package/flipper-plugin-world"
|
||||
>
|
||||
<div
|
||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||
@@ -509,7 +521,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
size="16"
|
||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -517,19 +529,21 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
title=""
|
||||
width="15%"
|
||||
>
|
||||
<div
|
||||
class="css-iby3qx-StyledButton enfqd41"
|
||||
type="primary"
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Install
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
||||
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||
>
|
||||
<div
|
||||
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
||||
@@ -572,14 +586,15 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
||||
<div
|
||||
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
||||
>
|
||||
<div
|
||||
class="css-1rxpsn8-StyledButton enfqd41"
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
disabled=""
|
||||
title="Cannot install plugin package by the specified path"
|
||||
type="primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Install
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="css-170i4ha-View-FlexBox-FlexRow-ErrorGlyphContainer evd5j490"
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
InstalledPluginDetails,
|
||||
installPluginFromFile,
|
||||
} from 'flipper-plugin-lib';
|
||||
import {Actions, State, Store} from '../reducers/index';
|
||||
import {State, Store} from '../reducers/index';
|
||||
import {
|
||||
PluginDownloadStatus,
|
||||
pluginDownloadStarted,
|
||||
@@ -26,17 +26,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import tmp from 'tmp';
|
||||
import {promisify} from 'util';
|
||||
import {requirePlugin} from './plugins';
|
||||
import {registerPluginUpdate, selectPlugin} from '../reducers/connections';
|
||||
import {Button} from 'antd';
|
||||
import React from 'react';
|
||||
import {reportPlatformFailures, reportUsage} from '../utils/metrics';
|
||||
import {addNotification, removeNotification} from '../reducers/notifications';
|
||||
import reloadFlipper from '../utils/reloadFlipper';
|
||||
import {activatePlugin, pluginInstalled} from '../reducers/pluginManager';
|
||||
import {Dispatch} from 'redux';
|
||||
import {showErrorNotification} from '../utils/notifications';
|
||||
import isSandyEnabled from '../utils/isSandyEnabled';
|
||||
|
||||
// Adapter which forces node.js implementation for axios instead of browser implementation
|
||||
// used by default in Electron. Node.js implementation is better, because it
|
||||
@@ -144,8 +136,6 @@ async function handlePluginDownload(
|
||||
notifyIfFailed: startedByUser,
|
||||
}),
|
||||
);
|
||||
} else if (!isSandyEnabled()) {
|
||||
notifyAboutUpdatedPluginNonSandy(installedPlugin, store.dispatch);
|
||||
}
|
||||
console.log(
|
||||
`Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`,
|
||||
@@ -178,59 +168,3 @@ function pluginIsDisabledForAllConnectedClients(
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function notifyAboutUpdatedPluginNonSandy(
|
||||
plugin: InstalledPluginDetails,
|
||||
dispatch: Dispatch<Actions>,
|
||||
) {
|
||||
const {name, version, title, id} = plugin;
|
||||
const reloadPluginAndRemoveNotification = () => {
|
||||
reportUsage('plugin-auto-update:notification:reloadClicked', undefined, id);
|
||||
dispatch(
|
||||
registerPluginUpdate({
|
||||
plugin: requirePlugin(plugin),
|
||||
enablePlugin: false,
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
removeNotification({
|
||||
pluginId: 'plugin-auto-update',
|
||||
client: null,
|
||||
notificationId: `auto-update.${name}.${version}`,
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: id,
|
||||
deepLinkPayload: null,
|
||||
}),
|
||||
);
|
||||
};
|
||||
const reloadAll = () => {
|
||||
reportUsage('plugin-auto-update:notification:reloadAllClicked');
|
||||
reloadFlipper();
|
||||
};
|
||||
dispatch(
|
||||
addNotification({
|
||||
pluginId: 'plugin-auto-update',
|
||||
client: null,
|
||||
notification: {
|
||||
id: `auto-update.${name}.${version}`,
|
||||
title: `${title} ${version} is ready to install`,
|
||||
message: (
|
||||
<div>
|
||||
{title} {version} has been downloaded. Reload is required to apply
|
||||
the update.{' '}
|
||||
<Button onClick={reloadPluginAndRemoveNotification}>
|
||||
Reload Plugin
|
||||
</Button>
|
||||
<Button onClick={reloadAll}>Reload Flipper</Button>
|
||||
</div>
|
||||
),
|
||||
severity: 'warning',
|
||||
timestamp: Date.now(),
|
||||
category: `Plugin Auto Update`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import ReactDOM from 'react-dom';
|
||||
import ContextMenuProvider from './ui/components/ContextMenuProvider';
|
||||
import GK from './fb-stubs/GK';
|
||||
import {init as initLogger} from './fb-stubs/Logger';
|
||||
import App from './chrome/AppWrapper';
|
||||
import {SandyApp} from './sandy-chrome/SandyApp';
|
||||
import setupPrefetcher from './fb-stubs/Prefetcher';
|
||||
import {persistStore} from 'redux-persist';
|
||||
import {Store} from './reducers/index';
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
_LoggerContext,
|
||||
} from 'flipper-plugin';
|
||||
import isProduction from './utils/isProduction';
|
||||
import isSandyEnabled from './utils/isSandyEnabled';
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
||||
// By default Node.JS has its internal certificate storage and doesn't use
|
||||
@@ -67,7 +66,7 @@ const AppFrame = ({logger}: {logger: Logger}) => (
|
||||
<PopoverProvider>
|
||||
<ContextMenuProvider>
|
||||
<_NuxManagerContext.Provider value={_createNuxManager()}>
|
||||
<App logger={logger} />
|
||||
<SandyApp />
|
||||
</_NuxManagerContext.Provider>
|
||||
</ContextMenuProvider>
|
||||
</PopoverProvider>
|
||||
@@ -124,18 +123,14 @@ function init() {
|
||||
store,
|
||||
{name: 'loadTheme', fireImmediately: true, throttleMs: 500},
|
||||
(state) => ({
|
||||
sandy: isSandyEnabled(),
|
||||
dark: state.settingsState.darkMode,
|
||||
}),
|
||||
(theme) => {
|
||||
(document.getElementById(
|
||||
'flipper-theme-import',
|
||||
) as HTMLLinkElement).href = `themes/${
|
||||
theme.sandy && theme.dark ? 'dark' : 'light'
|
||||
theme.dark ? 'dark' : 'light'
|
||||
}.css`;
|
||||
document
|
||||
.getElementById('root')
|
||||
?.classList.toggle('flipperlegacy_design', !theme.sandy);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ export type Settings = {
|
||||
openDevMenu: string;
|
||||
};
|
||||
};
|
||||
disableSandy: boolean;
|
||||
darkMode: boolean;
|
||||
showWelcomeAtStartup: boolean;
|
||||
};
|
||||
@@ -78,7 +77,6 @@ const initialState: Settings = {
|
||||
openDevMenu: 'Alt+Shift+D',
|
||||
},
|
||||
},
|
||||
disableSandy: false,
|
||||
darkMode: false,
|
||||
showWelcomeAtStartup: true,
|
||||
};
|
||||
|
||||
@@ -48,9 +48,8 @@ import config from '../fb-stubs/config';
|
||||
import styled from '@emotion/styled';
|
||||
import {showEmulatorLauncher} from './appinspect/LaunchEmulator';
|
||||
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
|
||||
import {setStaticView} from '../reducers/connections';
|
||||
import {setStaticView, StaticView} from '../reducers/connections';
|
||||
import {getInstance} from '../fb-stubs/Logger';
|
||||
import {isStaticViewActive} from '../chrome/mainsidebar/sidebarUtils';
|
||||
import {getUser} from '../fb-stubs/user';
|
||||
import {SandyRatingButton} from '../chrome/RatingButton';
|
||||
import {filterNotifications} from './notification/notificationUtils';
|
||||
@@ -310,7 +309,7 @@ function ShowSettingsButton() {
|
||||
selected={showSettings}
|
||||
/>
|
||||
{showSettings && (
|
||||
<SettingsSheet platform={process.platform} onHide={onClose} useSandy />
|
||||
<SettingsSheet platform={process.platform} onHide={onClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -417,7 +416,14 @@ function LoginButton() {
|
||||
title="Log In"
|
||||
onClick={() => setShowLogin(true)}
|
||||
/>
|
||||
{showLogin && <SignInSheet onHide={onClose} useSandy />}
|
||||
{showLogin && <SignInSheet onHide={onClose} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function isStaticViewActive(
|
||||
current: StaticView,
|
||||
selected: StaticView,
|
||||
): boolean {
|
||||
return Boolean(current && selected && current === selected);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import {TrackingScope, useLogger} from 'flipper-plugin';
|
||||
import {styled} from '../ui';
|
||||
import {Layout, Sidebar} from '../ui';
|
||||
import {theme} from 'flipper-plugin';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {Logger} from '../fb-interfaces/Logger';
|
||||
|
||||
import {LeftRail} from './LeftRail';
|
||||
import {registerStartupTime} from '../chrome/LegacyApp';
|
||||
import {useStore, useDispatch} from '../utils/useStore';
|
||||
import {SandyContext} from './SandyContext';
|
||||
import {ConsoleLogs} from '../chrome/ConsoleLogs';
|
||||
import {setStaticView} from '../reducers/connections';
|
||||
import {
|
||||
@@ -30,10 +30,15 @@ import {ContentContainer} from './ContentContainer';
|
||||
import {Notification} from './notification/Notification';
|
||||
import {SheetRenderer} from '../chrome/SheetRenderer';
|
||||
import {hasNewChangesToShow} from '../chrome/ChangelogSheet';
|
||||
import {SandyWelcomScreen} from './SandyWelcomeScreen';
|
||||
import {SandyWelcomeScreen} from './SandyWelcomeScreen';
|
||||
import {getVersionString} from '../utils/versionString';
|
||||
import config from '../fb-stubs/config';
|
||||
import {WelcomeScreenStaticView} from './WelcomeScreen';
|
||||
import QPL, {QuickLogActionType, FLIPPER_QPL_EVENTS} from '../fb-stubs/QPL';
|
||||
import fbConfig from '../fb-stubs/config';
|
||||
import {isFBEmployee} from '../utils/fbEmployee';
|
||||
import {notification} from 'antd';
|
||||
import isProduction from '../utils/isProduction';
|
||||
|
||||
export type ToplevelNavItem =
|
||||
| 'appinspect'
|
||||
@@ -99,6 +104,27 @@ export function SandyApp() {
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (fbConfig.warnFBEmployees && isProduction()) {
|
||||
isFBEmployee().then((isEmployee) => {
|
||||
if (isEmployee) {
|
||||
notification.warning({
|
||||
placement: 'bottomLeft',
|
||||
message: 'Please use Flipper@FB',
|
||||
description: (
|
||||
<>
|
||||
You are using the open-source version of Flipper. Install the
|
||||
internal build from Managed Software Center to get access to
|
||||
more plugins.
|
||||
</>
|
||||
),
|
||||
duration: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const leftMenuContent = !leftSidebarVisible ? null : toplevelSelection ===
|
||||
'appinspect' ? (
|
||||
<AppInspect />
|
||||
@@ -107,11 +133,10 @@ export function SandyApp() {
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<SandyContext.Provider value={true}>
|
||||
<Layout.Top>
|
||||
<>
|
||||
<SheetRenderer logger={logger} />
|
||||
<SandyWelcomScreen />
|
||||
<SandyWelcomeScreen />
|
||||
</>
|
||||
<Layout.Left>
|
||||
<Layout.Horizontal>
|
||||
@@ -153,7 +178,6 @@ export function SandyApp() {
|
||||
</MainContainer>
|
||||
</Layout.Left>
|
||||
</Layout.Top>
|
||||
</SandyContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,3 +207,23 @@ const MainContainer = styled(Layout.Container)({
|
||||
background: theme.backgroundWash,
|
||||
padding: `${theme.space.large}px ${theme.space.large}px ${theme.space.large}px 0`,
|
||||
});
|
||||
|
||||
function registerStartupTime(logger: Logger) {
|
||||
// track time since launch
|
||||
const [s, ns] = process.hrtime();
|
||||
const launchEndTime = s * 1e3 + ns / 1e6;
|
||||
ipcRenderer.on('getLaunchTime', (_: any, launchStartTime: number) => {
|
||||
logger.track('performance', 'launchTime', launchEndTime - launchStartTime);
|
||||
|
||||
QPL.markerStart(FLIPPER_QPL_EVENTS.STARTUP, 0, launchStartTime);
|
||||
QPL.markerEnd(
|
||||
FLIPPER_QPL_EVENTS.STARTUP,
|
||||
QuickLogActionType.SUCCESS,
|
||||
0,
|
||||
launchEndTime,
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.send('getLaunchTime');
|
||||
ipcRenderer.send('componentDidMount');
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {createContext, useContext} from 'react';
|
||||
|
||||
export const SandyContext = createContext(false);
|
||||
|
||||
export function useIsSandy(): boolean {
|
||||
return useContext(SandyContext);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {useLocalStorage} from '../utils/useLocalStorage';
|
||||
|
||||
const {Title, Text, Link} = Typography;
|
||||
|
||||
export function SandyWelcomScreen() {
|
||||
export function SandyWelcomeScreen() {
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const [showWelcomeScreen, setShowWelcomeScreen] = useLocalStorage(
|
||||
'flipper-sandy-show-welcome-screen',
|
||||
@@ -63,10 +63,6 @@ export function SandyWelcomScreen() {
|
||||
to find new or improved features.
|
||||
</Text>
|
||||
<Text>
|
||||
It is possible to enable the experimental dark mode, or switch back to
|
||||
the old design, by using the{' '}
|
||||
<Button icon={<SettingOutlined />} disabled size="small" /> settings
|
||||
button.{' '}
|
||||
{constants.IS_PUBLIC_BUILD ? (
|
||||
<>
|
||||
Feel free let us know your thoughts about the new experience on{' '}
|
||||
|
||||
@@ -73,8 +73,8 @@ export function AppInspect() {
|
||||
)}
|
||||
{!isArchived && activeDevice && (
|
||||
<Toolbar gap>
|
||||
<MetroButton useSandy />
|
||||
<ScreenCaptureButtons useSandy />
|
||||
<MetroButton />
|
||||
<ScreenCaptureButtons />
|
||||
</Toolbar>
|
||||
)}
|
||||
</Layout.Container>
|
||||
|
||||
@@ -27,10 +27,9 @@ import {
|
||||
selectDevice,
|
||||
} from '../../reducers/connections';
|
||||
import BaseDevice, {OS} from '../../devices/BaseDevice';
|
||||
import {getColorByApp} from '../../chrome/mainsidebar/sidebarUtils';
|
||||
import Client from '../../Client';
|
||||
import {State} from '../../reducers';
|
||||
import {brandIcons} from '../../ui/components/colors';
|
||||
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
|
||||
import {showEmulatorLauncher} from './LaunchEmulator';
|
||||
|
||||
const {Text, Link, Title} = Typography;
|
||||
@@ -251,3 +250,29 @@ function NoDevices() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getColorByApp(app?: string | null): string {
|
||||
let iconColor: string | undefined = (brandColors as any)[app!];
|
||||
|
||||
if (!iconColor) {
|
||||
if (!app) {
|
||||
// Device plugin
|
||||
iconColor = colors.macOSTitleBarIconBlur;
|
||||
} else {
|
||||
const pluginColors = [
|
||||
colors.seaFoam,
|
||||
colors.teal,
|
||||
colors.lime,
|
||||
colors.lemon,
|
||||
colors.orange,
|
||||
colors.tomato,
|
||||
colors.cherry,
|
||||
colors.pink,
|
||||
colors.grape,
|
||||
];
|
||||
|
||||
iconColor = pluginColors[parseInt(app, 36) % pluginColors.length];
|
||||
}
|
||||
}
|
||||
return iconColor;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
useTrackedCallback,
|
||||
useValue,
|
||||
} from 'flipper-plugin';
|
||||
import {navPluginStateSelector} from '../../chrome/LocationsButton';
|
||||
import {State} from '../../reducers';
|
||||
|
||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||
import type {NavigationPlugin} from '../../../../plugins/navigation/index';
|
||||
@@ -136,3 +136,15 @@ const StyledAutoComplete = styled(AutoComplete)({
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const NAVIGATION_PLUGIN_ID = 'Navigation';
|
||||
|
||||
function navPluginStateSelector(state: State) {
|
||||
const {selectedApp, clients} = state.connections;
|
||||
if (!selectedApp) return undefined;
|
||||
const client = clients.find((client) => client.id === selectedApp);
|
||||
if (!client) return undefined;
|
||||
return client.sandyPluginStates.get(NAVIGATION_PLUGIN_ID)?.instanceApi as
|
||||
| undefined
|
||||
| NavigationPlugin;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,15 @@ import {Glyph, Layout, styled} from '../../ui';
|
||||
import {theme, NUX, Tracked} from 'flipper-plugin';
|
||||
import {useDispatch, useStore} from '../../utils/useStore';
|
||||
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
|
||||
import {ClientPluginDefinition, DevicePluginDefinition} from '../../plugin';
|
||||
import {
|
||||
ClientPluginDefinition,
|
||||
DevicePluginDefinition,
|
||||
PluginDefinition,
|
||||
} from '../../plugin';
|
||||
import {selectPlugin, starPlugin} from '../../reducers/connections';
|
||||
import Client from '../../Client';
|
||||
import {State} from '../../reducers';
|
||||
import BaseDevice from '../../devices/BaseDevice';
|
||||
import {getFavoritePlugins} from '../../chrome/mainsidebar/sidebarUtils';
|
||||
import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib';
|
||||
import {useMemoize} from '../../utils/useMemoize';
|
||||
import MetroDevice from '../../devices/MetroDevice';
|
||||
@@ -649,3 +652,28 @@ function iconStyle(disabled: boolean) {
|
||||
height: 24,
|
||||
};
|
||||
}
|
||||
|
||||
function getFavoritePlugins(
|
||||
device: BaseDevice,
|
||||
client: Client,
|
||||
allPlugins: PluginDefinition[],
|
||||
starredPlugins: undefined | string[],
|
||||
returnFavoredPlugins: boolean, // if false, unfavoried plugins are returned
|
||||
): PluginDefinition[] {
|
||||
if (device.isArchived) {
|
||||
if (!returnFavoredPlugins) {
|
||||
return [];
|
||||
}
|
||||
// for archived plugins, all stored plugins are enabled
|
||||
return allPlugins.filter(
|
||||
(plugin) => client.plugins.indexOf(plugin.id) !== -1,
|
||||
);
|
||||
}
|
||||
if (!starredPlugins || !starredPlugins.length) {
|
||||
return returnFavoredPlugins ? [] : allPlugins;
|
||||
}
|
||||
return allPlugins.filter((plugin) => {
|
||||
const idx = starredPlugins.indexOf(plugin.id);
|
||||
return idx === -1 ? !returnFavoredPlugins : returnFavoredPlugins;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,21 +7,17 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React, {useContext, useState, useRef, useCallback, useMemo} from 'react';
|
||||
import React, {useState, useCallback, useMemo} from 'react';
|
||||
import electron, {MenuItemConstructorOptions} from 'electron';
|
||||
import styled from '@emotion/styled';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {keyframes} from '@emotion/css';
|
||||
import {Button as AntdButton, Dropdown, Menu} from 'antd';
|
||||
|
||||
import {colors} from './colors';
|
||||
import Glyph, {IconSize} from './Glyph';
|
||||
import {ButtonGroupContext} from './ButtonGroup';
|
||||
import {useStore} from '../../utils/useStore';
|
||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||
import type {ButtonProps} from 'antd/lib/button';
|
||||
import {DownOutlined, CheckOutlined} from '@ant-design/icons';
|
||||
import {theme, Tracked} from 'flipper-plugin';
|
||||
import {theme} from 'flipper-plugin';
|
||||
|
||||
type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
|
||||
|
||||
@@ -266,120 +262,7 @@ type Props = {
|
||||
* A simple button, used in many parts of the application.
|
||||
*/
|
||||
export default function Button(props: Props) {
|
||||
const isSandy = useIsSandy();
|
||||
return isSandy ? <SandyButton {...props} /> : <ClassicButton {...props} />;
|
||||
}
|
||||
|
||||
function ClassicButton(props: Props) {
|
||||
const windowIsFocused = useStore(
|
||||
(state) => state.application.windowIsFocused,
|
||||
);
|
||||
const inButtonGroup = useContext(ButtonGroupContext);
|
||||
const [active, setActive] = useState(false);
|
||||
const [wasClosed, setWasClosed] = useState(false);
|
||||
|
||||
const _ref = useRef<any>();
|
||||
|
||||
const onMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
setActive(true);
|
||||
setWasClosed(false);
|
||||
props.onMouseDown?.(e);
|
||||
},
|
||||
[props.onMouseDown],
|
||||
);
|
||||
|
||||
const onMouseUp = useCallback(() => {
|
||||
if (props.disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (props.dropdown && !wasClosed) {
|
||||
const menu = electron.remote.Menu.buildFromTemplate(props.dropdown);
|
||||
const position: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
} = {};
|
||||
const {current} = _ref;
|
||||
if (current) {
|
||||
const node = findDOMNode(current);
|
||||
if (node instanceof Element) {
|
||||
const {left, bottom} = node.getBoundingClientRect();
|
||||
position.x = Math.floor(left);
|
||||
position.y = Math.floor(bottom) + 6;
|
||||
}
|
||||
}
|
||||
menu.popup({
|
||||
window: electron.remote.getCurrentWindow(),
|
||||
// @ts-ignore: async is private API in electron
|
||||
async: true,
|
||||
...position,
|
||||
callback: () => {
|
||||
setWasClosed(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
setActive(false);
|
||||
setWasClosed(false);
|
||||
}, [props.disabled, props.dropdown, wasClosed]);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (props.disabled === true) {
|
||||
return;
|
||||
}
|
||||
props.onClick?.(e);
|
||||
if (props.href != null) {
|
||||
electron.shell.openExternal(props.href);
|
||||
}
|
||||
},
|
||||
[props.disabled, props.onClick, props.href],
|
||||
);
|
||||
|
||||
const {icon, children, selected, iconSize, iconVariant, ...restProps} = props;
|
||||
|
||||
let color = colors.macOSTitleBarIcon;
|
||||
if (props.disabled === true) {
|
||||
color = colors.macOSTitleBarIconBlur;
|
||||
} else if (windowIsFocused && selected === true) {
|
||||
color = colors.macOSTitleBarIconSelected;
|
||||
} else if (!windowIsFocused && (selected == null || selected === false)) {
|
||||
color = colors.macOSTitleBarIconBlur;
|
||||
} else if (!windowIsFocused && selected === true) {
|
||||
color = colors.macOSTitleBarIconSelectedBlur;
|
||||
} else if (selected == null && active) {
|
||||
color = colors.macOSTitleBarIconActive;
|
||||
} else if (props.type === 'danger') {
|
||||
color = colors.red;
|
||||
}
|
||||
|
||||
let iconComponent;
|
||||
if (icon != null) {
|
||||
iconComponent = (
|
||||
<Icon
|
||||
name={icon}
|
||||
size={iconSize || (props.compact === true ? 12 : 16)}
|
||||
color={color}
|
||||
variant={iconVariant || 'filled'}
|
||||
hasText={Boolean(children)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tracked>
|
||||
<StyledButton
|
||||
{...restProps}
|
||||
ref={_ref as any}
|
||||
windowIsFocused={windowIsFocused}
|
||||
onClick={onClick}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
inButtonGroup={inButtonGroup}>
|
||||
{iconComponent}
|
||||
{children}
|
||||
</StyledButton>
|
||||
</Tracked>
|
||||
);
|
||||
return <SandyButton {...props} />;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import React, {createContext} from 'react';
|
||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||
import {Space} from 'antd';
|
||||
|
||||
const ButtonGroupContainer = styled.div({
|
||||
@@ -35,14 +34,9 @@ export const ButtonGroupContext = createContext(false);
|
||||
* ```
|
||||
*/
|
||||
export default function ButtonGroup({children}: {children: React.ReactNode}) {
|
||||
const isSandy = useIsSandy(); // according to Ant design guides buttons should only be grouped if they are radios
|
||||
return isSandy ? (
|
||||
return (
|
||||
<ButtonGroupContext.Provider value={true}>
|
||||
<Space>{children}</Space>
|
||||
</ButtonGroupContext.Provider>
|
||||
) : (
|
||||
<ButtonGroupContext.Provider value={true}>
|
||||
<ButtonGroupContainer>{children}</ButtonGroupContainer>
|
||||
</ButtonGroupContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,23 +7,11 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import {colors} from './colors';
|
||||
import {useCallback} from 'react';
|
||||
import {shell} from 'electron';
|
||||
import React from 'react';
|
||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||
import {Typography} from 'antd';
|
||||
|
||||
const StyledLink = styled.span({
|
||||
color: colors.highlight,
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
});
|
||||
StyledLink.displayName = 'Link:StyledLink';
|
||||
|
||||
const AntOriginalLink = Typography.Link;
|
||||
|
||||
export default function Link(props: {
|
||||
@@ -32,7 +20,6 @@ export default function Link(props: {
|
||||
style?: React.CSSProperties;
|
||||
onClick?: ((event: React.MouseEvent<any>) => void) | undefined;
|
||||
}) {
|
||||
const isSandy = useIsSandy();
|
||||
const onClick = useCallback(
|
||||
(e: React.MouseEvent<any>) => {
|
||||
shell.openExternal(props.href);
|
||||
@@ -42,13 +29,7 @@ export default function Link(props: {
|
||||
[props.href],
|
||||
);
|
||||
|
||||
return isSandy ? (
|
||||
<AntOriginalLink {...props} onClick={props.onClick ?? onClick} />
|
||||
) : (
|
||||
<StyledLink onClick={props.onClick ?? onClick} style={props.style}>
|
||||
{props.children || props.href}
|
||||
</StyledLink>
|
||||
);
|
||||
return <AntOriginalLink {...props} onClick={props.onClick ?? onClick} />;
|
||||
}
|
||||
|
||||
// XXX. For consistent usage, we monkey patch AntDesign's Link component,
|
||||
|
||||
@@ -8,39 +8,10 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {colors} from './colors';
|
||||
import FlexRow from './FlexRow';
|
||||
import FlexBox from './FlexBox';
|
||||
import styled from '@emotion/styled';
|
||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
||||
import {theme, Layout} from 'flipper-plugin';
|
||||
|
||||
/**
|
||||
* A toolbar.
|
||||
*/
|
||||
const ToolbarContainer = styled(FlexRow)<{
|
||||
position?: 'bottom' | 'top';
|
||||
compact?: boolean;
|
||||
}>((props) => ({
|
||||
userSelect: 'none',
|
||||
backgroundColor: colors.light02,
|
||||
borderBottom:
|
||||
props.position === 'bottom'
|
||||
? 'none'
|
||||
: `1px solid ${colors.sectionHeaderBorder}`,
|
||||
borderTop:
|
||||
props.position === 'bottom'
|
||||
? `1px solid ${colors.sectionHeaderBorder}`
|
||||
: 'none',
|
||||
flexShrink: 0,
|
||||
height: props.compact ? 28 : 42,
|
||||
lineHeight: '32px',
|
||||
alignItems: 'center',
|
||||
padding: 6,
|
||||
width: '100%',
|
||||
}));
|
||||
ToolbarContainer.displayName = 'ToolbarContainer';
|
||||
|
||||
const SandyToolbarContainer = styled(Layout.Horizontal)({
|
||||
flexWrap: 'wrap',
|
||||
padding: theme.space.small,
|
||||
@@ -55,21 +26,15 @@ Spacer.displayName = 'Spacer';
|
||||
export default function Toolbar({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
position?: 'bottom' | 'top';
|
||||
compact?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}) {
|
||||
const isSandy = useIsSandy();
|
||||
return isSandy ? (
|
||||
return (
|
||||
<SandyToolbarContainer style={style} gap={theme.space.small} center>
|
||||
{children}
|
||||
</SandyToolbarContainer>
|
||||
) : (
|
||||
<ToolbarContainer style={style} {...rest}>
|
||||
{children}
|
||||
</ToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {store} from '../store';
|
||||
|
||||
export default function isSandyEnabled() {
|
||||
return !store.getState().settingsState.disableSandy;
|
||||
}
|
||||
@@ -8,12 +8,12 @@
|
||||
*/
|
||||
|
||||
import {useStore} from '../../../app/src/utils/useStore';
|
||||
import isSandyEnabled from './isSandyEnabled';
|
||||
|
||||
/**
|
||||
* This hook returns whether dark mode is currently being used.
|
||||
* Generally should be avoided in favor of using the above theme object,
|
||||
* which will provide colors that reflect the theme
|
||||
*/
|
||||
export function useIsDarkMode(): boolean {
|
||||
return useStore((state) => isSandyEnabled() && state.settingsState.darkMode);
|
||||
return useStore((state) => state.settingsState.darkMode);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user