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,
|
FlipperDevicePlugin,
|
||||||
} from './plugin';
|
} from './plugin';
|
||||||
import BaseDevice, {OS} from './devices/BaseDevice';
|
import BaseDevice, {OS} from './devices/BaseDevice';
|
||||||
import {LegacyApp} from './chrome/LegacyApp';
|
|
||||||
import {Logger} from './fb-interfaces/Logger';
|
import {Logger} from './fb-interfaces/Logger';
|
||||||
import {Store} from './reducers/index';
|
import {Store} from './reducers/index';
|
||||||
import {setPluginState} from './reducers/pluginStates';
|
import {setPluginState} from './reducers/pluginStates';
|
||||||
@@ -124,7 +123,6 @@ export interface FlipperClientConnection<D, M> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Client extends EventEmitter {
|
export default class Client extends EventEmitter {
|
||||||
app: LegacyApp | undefined;
|
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
query: ClientQuery;
|
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 {createState, useValue} from 'flipper-plugin';
|
||||||
import {useLocalStorage} from '../utils/useLocalStorage';
|
import {useLocalStorage} from '../utils/useLocalStorage';
|
||||||
import {theme} from 'flipper-plugin';
|
import {theme} from 'flipper-plugin';
|
||||||
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
|
||||||
import {useIsDarkMode} from '../utils/useIsDarkMode';
|
import {useIsDarkMode} from '../utils/useIsDarkMode';
|
||||||
|
|
||||||
const MAX_LOG_ITEMS = 1000;
|
const MAX_LOG_ITEMS = 1000;
|
||||||
@@ -65,7 +64,6 @@ const allLogLevels: Methods[] = [
|
|||||||
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
|
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
|
||||||
|
|
||||||
export function ConsoleLogs() {
|
export function ConsoleLogs() {
|
||||||
const isSandy = useIsSandy();
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
const logs = useValue(logsAtom);
|
const logs = useValue(logsAtom);
|
||||||
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
|
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
|
||||||
@@ -90,7 +88,7 @@ export function ConsoleLogs() {
|
|||||||
);
|
);
|
||||||
}, [logLevels, setLogLevels]);
|
}, [logLevels, setLogLevels]);
|
||||||
|
|
||||||
const styles = useMemo(() => buildTheme(isSandy), [isSandy]);
|
const styles = useMemo(buildTheme, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Top>
|
<Layout.Top>
|
||||||
@@ -106,7 +104,7 @@ export function ConsoleLogs() {
|
|||||||
<Console
|
<Console
|
||||||
logs={logs}
|
logs={logs}
|
||||||
filter={logLevels}
|
filter={logLevels}
|
||||||
variant={isDarkMode || !isSandy ? 'dark' : 'light'}
|
variant={isDarkMode ? 'dark' : 'light'}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
/>
|
/>
|
||||||
</Layout.ScrollContainer>
|
</Layout.ScrollContainer>
|
||||||
@@ -114,15 +112,7 @@ export function ConsoleLogs() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTheme(isSandy: boolean): Styles {
|
function buildTheme(): Styles {
|
||||||
if (!isSandy) {
|
|
||||||
const bg = '#333';
|
|
||||||
return {
|
|
||||||
BASE_BACKGROUND_COLOR: bg,
|
|
||||||
BASE_COLOR: 'white',
|
|
||||||
LOG_BACKGROUND: bg,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
// See: https://github.com/samdenty/console-feed/blob/master/src/definitions/Styles.d.ts
|
// See: https://github.com/samdenty/console-feed/blob/master/src/definitions/Styles.d.ts
|
||||||
BASE_BACKGROUND_COLOR: 'transparent',
|
BASE_BACKGROUND_COLOR: 'transparent',
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import ReactDOM from 'react-dom';
|
|||||||
import Sidebar from '../ui/components/Sidebar';
|
import Sidebar from '../ui/components/Sidebar';
|
||||||
import {toggleRightSidebarAvailable} from '../reducers/application';
|
import {toggleRightSidebarAvailable} from '../reducers/application';
|
||||||
import {useDispatch, useStore} from '../utils/useStore';
|
import {useDispatch, useStore} from '../utils/useStore';
|
||||||
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
|
||||||
import {ContentContainer} from '../sandy-chrome/ContentContainer';
|
import {ContentContainer} from '../sandy-chrome/ContentContainer';
|
||||||
import {Layout} from '../ui';
|
import {Layout} from '../ui';
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ export default function DetailSidebar({children, width, minWidth}: OwnProps) {
|
|||||||
return <div>{children}</div>;
|
return <div>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSandy = useIsSandy();
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {rightSidebarAvailable, rightSidebarVisible} = useStore((state) => {
|
const {rightSidebarAvailable, rightSidebarVisible} = useStore((state) => {
|
||||||
const {rightSidebarAvailable, rightSidebarVisible} = state.application;
|
const {rightSidebarAvailable, rightSidebarVisible} = state.application;
|
||||||
@@ -72,16 +70,10 @@ export default function DetailSidebar({children, width, minWidth}: OwnProps) {
|
|||||||
minWidth={minWidth}
|
minWidth={minWidth}
|
||||||
width={width || 300}
|
width={width || 300}
|
||||||
position="right"
|
position="right"
|
||||||
gutter={isSandy}>
|
gutter>
|
||||||
{isSandy ? (
|
<ContentContainer>
|
||||||
<ContentContainer>
|
<Layout.ScrollContainer vertical>{children}</Layout.ScrollContainer>
|
||||||
<Layout.ScrollContainer vertical>
|
</ContentContainer>
|
||||||
{children}
|
|
||||||
</Layout.ScrollContainer>
|
|
||||||
</ContentContainer>
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</Sidebar>,
|
</Sidebar>,
|
||||||
domNode,
|
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;
|
return hasProblems(result) && !result.isAcknowledged;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = {
|
type State = {
|
||||||
acknowledgeCheckboxVisible: boolean;
|
acknowledgeCheckboxVisible: boolean;
|
||||||
acknowledgeOnClose?: boolean;
|
acknowledgeOnClose?: boolean;
|
||||||
selectedCheckKey?: string;
|
selectedCheckKey?: string;
|
||||||
|
|||||||
@@ -7,16 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {Button} from '../ui';
|
||||||
FlexColumn,
|
|
||||||
Button,
|
|
||||||
styled,
|
|
||||||
Text,
|
|
||||||
FlexRow,
|
|
||||||
Spacer,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
} from '../ui';
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
@@ -26,31 +17,8 @@ import {Settings} from '../reducers/settings';
|
|||||||
import {Collapse, Form, Input as AntInput} from 'antd';
|
import {Collapse, Form, Input as AntInput} from 'antd';
|
||||||
import {Html5Outlined} from '@ant-design/icons';
|
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 = {
|
type OwnProps = {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
useSandy?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateFromProps = {
|
type StateFromProps = {
|
||||||
@@ -96,7 +64,7 @@ class JSEmulatorLauncherSheet extends Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {url, height, width} = this.state;
|
const {url, height, width} = this.state;
|
||||||
return this.props.useSandy ? (
|
return (
|
||||||
<Form labelCol={{span: 4}}>
|
<Form labelCol={{span: 4}}>
|
||||||
<Form.Item label="Url">
|
<Form.Item label="Url">
|
||||||
<AntInput value={url} onChange={this.onUrlChange} />
|
<AntInput value={url} onChange={this.onUrlChange} />
|
||||||
@@ -113,27 +81,6 @@ class JSEmulatorLauncherSheet extends Component<Props, State> {
|
|||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</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 />}
|
extra={<Html5Outlined />}
|
||||||
header="Launch JS Web App"
|
header="Launch JS Web App"
|
||||||
key="launch-js-web-app">
|
key="launch-js-web-app">
|
||||||
<Launcher onHide={onClose} useSandy />
|
<Launcher onHide={onClose} />
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</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 React, {useCallback, useEffect, useState} from 'react';
|
||||||
import {Button, ButtonGroup, colors} from '../ui';
|
|
||||||
import MetroDevice, {MetroReportableEvent} from '../devices/MetroDevice';
|
import MetroDevice, {MetroReportableEvent} from '../devices/MetroDevice';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import {useStore} from '../utils/useStore';
|
import {useStore} from '../utils/useStore';
|
||||||
import {Button as AntButton} from 'antd';
|
import {Button as AntButton} from 'antd';
|
||||||
import {MenuOutlined, ReloadOutlined} from '@ant-design/icons';
|
import {MenuOutlined, ReloadOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
type LogEntry = {};
|
type LogEntry = {};
|
||||||
|
|
||||||
export type PersistedState = {
|
export default function MetroButton() {
|
||||||
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}) {
|
|
||||||
const device = useStore((state) =>
|
const device = useStore((state) =>
|
||||||
state.connections.devices.find(
|
state.connections.devices.find(
|
||||||
(device) => device.os === 'Metro' && !device.isArchived,
|
(device) => device.os === 'Metro' && !device.isArchived,
|
||||||
@@ -68,7 +29,7 @@ export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
|||||||
[device],
|
[device],
|
||||||
);
|
);
|
||||||
const [progress, setProgress] = useState(1);
|
const [progress, setProgress] = useState(1);
|
||||||
const [hasBuildError, setHasBuildError] = useState(false);
|
const [_hasBuildError, setHasBuildError] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@@ -98,7 +59,7 @@ export default function MetroButton({useSandy}: {useSandy?: boolean}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return useSandy ? (
|
return (
|
||||||
<>
|
<>
|
||||||
<AntButton
|
<AntButton
|
||||||
icon={<ReloadOutlined />}
|
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 GK from '../fb-stubs/GK';
|
||||||
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
||||||
import {FeedbackPrompt} 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 {StarOutlined} from '@ant-design/icons';
|
||||||
import {Popover, Rate} from 'antd';
|
import {Popover, Rate} from 'antd';
|
||||||
import {useStore} from '../utils/useStore';
|
import {useStore} from '../utils/useStore';
|
||||||
@@ -458,7 +456,3 @@ export function SandyRatingButton() {
|
|||||||
</Popover>
|
</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 as AntButton, message} from 'antd';
|
||||||
import {Button, ButtonGroup} from '../ui';
|
|
||||||
import React, {useState, useEffect, useCallback} from 'react';
|
import React, {useState, useEffect, useCallback} from 'react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
@@ -16,7 +15,7 @@ import {capture, CAPTURE_LOCATION, getFileName} from '../utils/screenshot';
|
|||||||
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
|
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
|
||||||
import {useStore} from '../utils/useStore';
|
import {useStore} from '../utils/useStore';
|
||||||
|
|
||||||
export async function openFile(path: string | null) {
|
async function openFile(path: string | null) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return;
|
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 selectedDevice = useStore((state) => state.connections.selectedDevice);
|
||||||
const [isTakingScreenshot, setIsTakingScreenshot] = useState(false);
|
const [isTakingScreenshot, setIsTakingScreenshot] = useState(false);
|
||||||
const [isRecordingAvailable, setIsRecordingAvailable] = useState(false);
|
const [isRecordingAvailable, setIsRecordingAvailable] = useState(false);
|
||||||
@@ -84,7 +83,7 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
|
|||||||
}
|
}
|
||||||
}, [selectedDevice, isRecording]);
|
}, [selectedDevice, isRecording]);
|
||||||
|
|
||||||
return useSandy ? (
|
return (
|
||||||
<>
|
<>
|
||||||
<AntButton
|
<AntButton
|
||||||
icon={<CameraOutlined />}
|
icon={<CameraOutlined />}
|
||||||
@@ -103,24 +102,5 @@ export default function ScreenCaptureButtons({useSandy}: {useSandy?: boolean}) {
|
|||||||
danger={isRecording}
|
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
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui';
|
import {FlexColumn, Button} from '../ui';
|
||||||
import React, {Component, useContext} from 'react';
|
import React, {Component, useContext} from 'react';
|
||||||
import {updateSettings, Action} from '../reducers/settings';
|
import {updateSettings, Action} from '../reducers/settings';
|
||||||
import {
|
import {
|
||||||
@@ -29,22 +29,10 @@ import {reportUsage} from '../utils/metrics';
|
|||||||
import {Modal, message} from 'antd';
|
import {Modal, message} from 'antd';
|
||||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
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 = {
|
type OwnProps = {
|
||||||
useSandy?: boolean;
|
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
platform: NodeJS.Platform;
|
platform: NodeJS.Platform;
|
||||||
|
noModal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateFromProps = {
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
enableAndroid,
|
enableAndroid,
|
||||||
@@ -140,12 +111,9 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
enablePrefetching,
|
enablePrefetching,
|
||||||
idbPath,
|
idbPath,
|
||||||
reactNative,
|
reactNative,
|
||||||
disableSandy,
|
|
||||||
darkMode,
|
darkMode,
|
||||||
} = this.state.updatedSettings;
|
} = this.state.updatedSettings;
|
||||||
|
|
||||||
const {useSandy} = this.props;
|
|
||||||
|
|
||||||
const settingsPristine =
|
const settingsPristine =
|
||||||
isEqual(this.props.settings, this.state.updatedSettings) &&
|
isEqual(this.props.settings, this.state.updatedSettings) &&
|
||||||
isEqual(this.props.launcherSettings, this.state.updatedLauncherSettings);
|
isEqual(this.props.launcherSettings, this.state.updatedLauncherSettings);
|
||||||
@@ -265,41 +233,17 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ToggledSection
|
<ToggledSection
|
||||||
label="Disable Sandy UI"
|
label="Enable dark theme (experimental)"
|
||||||
toggled={this.state.updatedSettings.disableSandy}
|
toggled={darkMode}
|
||||||
onChange={(v) => {
|
onChange={(enabled) => {
|
||||||
this.setState({
|
this.setState((prevState) => ({
|
||||||
updatedSettings: {
|
updatedSettings: {
|
||||||
...this.state.updatedSettings,
|
...prevState.updatedSettings,
|
||||||
disableSandy: v,
|
darkMode: enabled,
|
||||||
},
|
},
|
||||||
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}
|
|
||||||
onChange={(enabled) => {
|
|
||||||
this.setState((prevState) => ({
|
|
||||||
updatedSettings: {
|
|
||||||
...prevState.updatedSettings,
|
|
||||||
darkMode: enabled,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ToggledSection
|
<ToggledSection
|
||||||
label="React Native keyboard shortcuts"
|
label="React Native keyboard shortcuts"
|
||||||
toggled={reactNative.shortcuts.enabled}
|
toggled={reactNative.shortcuts.enabled}
|
||||||
@@ -388,9 +332,14 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return useSandy
|
return this.props.noModal ? (
|
||||||
? this.renderSandyContainer(contents, footer)
|
<>
|
||||||
: this.renderNativeContainer(contents, footer);
|
{contents}
|
||||||
|
{footer}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
this.renderSandyContainer(contents, footer)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {FlexColumn, Button, styled, Text, Input, Link, colors} from '../ui';
|
||||||
FlexColumn,
|
|
||||||
Button,
|
|
||||||
styled,
|
|
||||||
Text,
|
|
||||||
FlexRow,
|
|
||||||
Spacer,
|
|
||||||
Input,
|
|
||||||
Link,
|
|
||||||
colors,
|
|
||||||
} from '../ui';
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {writeKeychain, getUser} from '../fb-stubs/user';
|
import {writeKeychain, getUser} from '../fb-stubs/user';
|
||||||
import {login} from '../reducers/user';
|
import {login} from '../reducers/user';
|
||||||
@@ -29,11 +19,6 @@ import {reportPlatformFailures} from '../utils/metrics';
|
|||||||
import {Modal} from 'antd';
|
import {Modal} from 'antd';
|
||||||
import {TrackingScope} from 'flipper-plugin';
|
import {TrackingScope} from 'flipper-plugin';
|
||||||
|
|
||||||
const Container = styled(FlexColumn)({
|
|
||||||
padding: 20,
|
|
||||||
width: 500,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Title = styled(Text)({
|
const Title = styled(Text)({
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -49,7 +34,6 @@ const TokenInput = styled(Input)({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
useSandy?: boolean;
|
|
||||||
onHide: () => any;
|
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() {
|
render() {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
@@ -212,9 +180,7 @@ class SignInSheet extends Component<Props, State> {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.props.useSandy
|
return this.renderSandyContainer(content, footer);
|
||||||
? this.renderSandyContainer(content, footer)
|
|
||||||
: this.renderNativeContainer(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
|
<div
|
||||||
className="css-12n892b"
|
className="css-12n892b"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
className="ant-btn ant-btn-default"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
>
|
>
|
||||||
Close
|
<span>
|
||||||
</div>
|
Close
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-auhar3-TooltipContainer e1abcqbd0"
|
className="css-auhar3-TooltipContainer e1abcqbd0"
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className="css-ia8nd7-StyledButton enfqd41"
|
className="ant-btn ant-btn-primary"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Submit
|
<span>
|
||||||
</div>
|
Submit
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,30 +260,31 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="css-12n892b"
|
className="css-12n892b"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
className="ant-btn ant-btn-default"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
>
|
>
|
||||||
Close
|
<span>
|
||||||
</div>
|
Close
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-auhar3-TooltipContainer e1abcqbd0"
|
className="css-auhar3-TooltipContainer e1abcqbd0"
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
className="ant-btn ant-btn-primary"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Submit
|
<span>
|
||||||
</div>
|
Submit
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,23 +29,24 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<div
|
<button
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
className="ant-btn ant-btn-default"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
>
|
>
|
||||||
Cancel
|
<span>
|
||||||
</div>
|
Cancel
|
||||||
<div
|
</span>
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
</button>
|
||||||
|
<button
|
||||||
|
className="ant-btn ant-btn-primary"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Run In Background
|
<span>
|
||||||
</div>
|
Run In Background
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -79,23 +80,24 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<div
|
<button
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
className="ant-btn ant-btn-default"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
>
|
>
|
||||||
Cancel
|
<span>
|
||||||
</div>
|
Cancel
|
||||||
<div
|
</span>
|
||||||
className="css-xing9h-StyledButton enfqd41"
|
</button>
|
||||||
|
<button
|
||||||
|
className="ant-btn ant-btn-primary"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseDown={[Function]}
|
type="button"
|
||||||
onMouseUp={[Function]}
|
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Run In Background
|
<span>
|
||||||
</div>
|
Run In Background
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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"
|
class="css-9dawc5-View-FlexBox-FlexColumn"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
||||||
@@ -139,8 +139,9 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<span
|
<a
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="ant-typography"
|
||||||
|
href="https://yarnpkg.com/en/package/flipper-plugin-hello"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||||
@@ -148,7 +149,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
size="16"
|
size="16"
|
||||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
/>
|
/>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -156,12 +157,14 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
title=""
|
title=""
|
||||||
width="15%"
|
width="15%"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-iby3qx-StyledButton enfqd41"
|
class="ant-btn ant-btn-primary"
|
||||||
type="primary"
|
type="button"
|
||||||
>
|
>
|
||||||
Install
|
<span>
|
||||||
</div>
|
Install
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -206,8 +209,9 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<span
|
<a
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="ant-typography"
|
||||||
|
href="https://yarnpkg.com/en/package/flipper-plugin-world"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||||
@@ -215,7 +219,7 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
size="16"
|
size="16"
|
||||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
/>
|
/>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -223,19 +227,21 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
title=""
|
title=""
|
||||||
width="15%"
|
width="15%"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-iby3qx-StyledButton enfqd41"
|
class="ant-btn ant-btn-primary"
|
||||||
type="primary"
|
type="button"
|
||||||
>
|
>
|
||||||
Install
|
<span>
|
||||||
</div>
|
Install
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
||||||
@@ -278,14 +284,15 @@ exports[`load PluginInstaller list 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-1rxpsn8-StyledButton enfqd41"
|
class="ant-btn ant-btn-primary"
|
||||||
disabled=""
|
disabled=""
|
||||||
title="Cannot install plugin package by the specified path"
|
type="button"
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Install
|
<span>
|
||||||
</div>
|
Install
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<div
|
<div
|
||||||
class="css-170i4ha-View-FlexBox-FlexRow-ErrorGlyphContainer evd5j490"
|
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"
|
class="css-9dawc5-View-FlexBox-FlexColumn"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
class="css-bl14sl-View-FlexBox-SearchBox e271nro4"
|
||||||
@@ -434,8 +441,9 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<span
|
<a
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="ant-typography"
|
||||||
|
href="https://yarnpkg.com/en/package/flipper-plugin-hello"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||||
@@ -443,7 +451,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
size="16"
|
size="16"
|
||||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
/>
|
/>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -451,11 +459,14 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
title=""
|
title=""
|
||||||
width="15%"
|
width="15%"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-iby3qx-StyledButton enfqd41"
|
class="ant-btn ant-btn-default"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Remove
|
<span>
|
||||||
</div>
|
Remove
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -500,8 +511,9 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
class="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<span
|
<a
|
||||||
class="css-ad6n9d-StyledLink e1mzoj7l0"
|
class="ant-typography"
|
||||||
|
href="https://yarnpkg.com/en/package/flipper-plugin-world"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
class="css-1tfjvvq-ColoredIconCustom e528dze0"
|
||||||
@@ -509,7 +521,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
size="16"
|
size="16"
|
||||||
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
/>
|
/>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -517,19 +529,21 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
title=""
|
title=""
|
||||||
width="15%"
|
width="15%"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-iby3qx-StyledButton enfqd41"
|
class="ant-btn ant-btn-primary"
|
||||||
type="primary"
|
type="button"
|
||||||
>
|
>
|
||||||
Install
|
<span>
|
||||||
</div>
|
Install
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-cjspgc-View-FlexBox-FlexRow-ToolbarContainer e13mj6h82"
|
class="css-1lxv8hi-Container-Horizontal-SandyToolbarContainer e13mj6h81"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
class="css-1spj5hr-View-FlexBox-FlexRow-Container ersmi542"
|
||||||
@@ -572,14 +586,15 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
class="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
class="css-1rxpsn8-StyledButton enfqd41"
|
class="ant-btn ant-btn-primary"
|
||||||
disabled=""
|
disabled=""
|
||||||
title="Cannot install plugin package by the specified path"
|
type="button"
|
||||||
type="primary"
|
|
||||||
>
|
>
|
||||||
Install
|
<span>
|
||||||
</div>
|
Install
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<div
|
<div
|
||||||
class="css-170i4ha-View-FlexBox-FlexRow-ErrorGlyphContainer evd5j490"
|
class="css-170i4ha-View-FlexBox-FlexRow-ErrorGlyphContainer evd5j490"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
InstalledPluginDetails,
|
InstalledPluginDetails,
|
||||||
installPluginFromFile,
|
installPluginFromFile,
|
||||||
} from 'flipper-plugin-lib';
|
} from 'flipper-plugin-lib';
|
||||||
import {Actions, State, Store} from '../reducers/index';
|
import {State, Store} from '../reducers/index';
|
||||||
import {
|
import {
|
||||||
PluginDownloadStatus,
|
PluginDownloadStatus,
|
||||||
pluginDownloadStarted,
|
pluginDownloadStarted,
|
||||||
@@ -26,17 +26,9 @@ import fs from 'fs-extra';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import tmp from 'tmp';
|
import tmp from 'tmp';
|
||||||
import {promisify} from 'util';
|
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 {reportPlatformFailures, reportUsage} from '../utils/metrics';
|
||||||
import {addNotification, removeNotification} from '../reducers/notifications';
|
|
||||||
import reloadFlipper from '../utils/reloadFlipper';
|
|
||||||
import {activatePlugin, pluginInstalled} from '../reducers/pluginManager';
|
import {activatePlugin, pluginInstalled} from '../reducers/pluginManager';
|
||||||
import {Dispatch} from 'redux';
|
|
||||||
import {showErrorNotification} from '../utils/notifications';
|
import {showErrorNotification} from '../utils/notifications';
|
||||||
import isSandyEnabled from '../utils/isSandyEnabled';
|
|
||||||
|
|
||||||
// Adapter which forces node.js implementation for axios instead of browser implementation
|
// Adapter which forces node.js implementation for axios instead of browser implementation
|
||||||
// used by default in Electron. Node.js implementation is better, because it
|
// used by default in Electron. Node.js implementation is better, because it
|
||||||
@@ -144,8 +136,6 @@ async function handlePluginDownload(
|
|||||||
notifyIfFailed: startedByUser,
|
notifyIfFailed: startedByUser,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else if (!isSandyEnabled()) {
|
|
||||||
notifyAboutUpdatedPluginNonSandy(installedPlugin, store.dispatch);
|
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`,
|
`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 ContextMenuProvider from './ui/components/ContextMenuProvider';
|
||||||
import GK from './fb-stubs/GK';
|
import GK from './fb-stubs/GK';
|
||||||
import {init as initLogger} from './fb-stubs/Logger';
|
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 setupPrefetcher from './fb-stubs/Prefetcher';
|
||||||
import {persistStore} from 'redux-persist';
|
import {persistStore} from 'redux-persist';
|
||||||
import {Store} from './reducers/index';
|
import {Store} from './reducers/index';
|
||||||
@@ -42,7 +42,6 @@ import {
|
|||||||
_LoggerContext,
|
_LoggerContext,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import isProduction from './utils/isProduction';
|
import isProduction from './utils/isProduction';
|
||||||
import isSandyEnabled from './utils/isSandyEnabled';
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
||||||
// By default Node.JS has its internal certificate storage and doesn't use
|
// By default Node.JS has its internal certificate storage and doesn't use
|
||||||
@@ -67,7 +66,7 @@ const AppFrame = ({logger}: {logger: Logger}) => (
|
|||||||
<PopoverProvider>
|
<PopoverProvider>
|
||||||
<ContextMenuProvider>
|
<ContextMenuProvider>
|
||||||
<_NuxManagerContext.Provider value={_createNuxManager()}>
|
<_NuxManagerContext.Provider value={_createNuxManager()}>
|
||||||
<App logger={logger} />
|
<SandyApp />
|
||||||
</_NuxManagerContext.Provider>
|
</_NuxManagerContext.Provider>
|
||||||
</ContextMenuProvider>
|
</ContextMenuProvider>
|
||||||
</PopoverProvider>
|
</PopoverProvider>
|
||||||
@@ -124,18 +123,14 @@ function init() {
|
|||||||
store,
|
store,
|
||||||
{name: 'loadTheme', fireImmediately: true, throttleMs: 500},
|
{name: 'loadTheme', fireImmediately: true, throttleMs: 500},
|
||||||
(state) => ({
|
(state) => ({
|
||||||
sandy: isSandyEnabled(),
|
|
||||||
dark: state.settingsState.darkMode,
|
dark: state.settingsState.darkMode,
|
||||||
}),
|
}),
|
||||||
(theme) => {
|
(theme) => {
|
||||||
(document.getElementById(
|
(document.getElementById(
|
||||||
'flipper-theme-import',
|
'flipper-theme-import',
|
||||||
) as HTMLLinkElement).href = `themes/${
|
) as HTMLLinkElement).href = `themes/${
|
||||||
theme.sandy && theme.dark ? 'dark' : 'light'
|
theme.dark ? 'dark' : 'light'
|
||||||
}.css`;
|
}.css`;
|
||||||
document
|
|
||||||
.getElementById('root')
|
|
||||||
?.classList.toggle('flipperlegacy_design', !theme.sandy);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export type Settings = {
|
|||||||
openDevMenu: string;
|
openDevMenu: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
disableSandy: boolean;
|
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
showWelcomeAtStartup: boolean;
|
showWelcomeAtStartup: boolean;
|
||||||
};
|
};
|
||||||
@@ -78,7 +77,6 @@ const initialState: Settings = {
|
|||||||
openDevMenu: 'Alt+Shift+D',
|
openDevMenu: 'Alt+Shift+D',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disableSandy: false,
|
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
showWelcomeAtStartup: true,
|
showWelcomeAtStartup: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ import config from '../fb-stubs/config';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {showEmulatorLauncher} from './appinspect/LaunchEmulator';
|
import {showEmulatorLauncher} from './appinspect/LaunchEmulator';
|
||||||
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
|
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
|
||||||
import {setStaticView} from '../reducers/connections';
|
import {setStaticView, StaticView} from '../reducers/connections';
|
||||||
import {getInstance} from '../fb-stubs/Logger';
|
import {getInstance} from '../fb-stubs/Logger';
|
||||||
import {isStaticViewActive} from '../chrome/mainsidebar/sidebarUtils';
|
|
||||||
import {getUser} from '../fb-stubs/user';
|
import {getUser} from '../fb-stubs/user';
|
||||||
import {SandyRatingButton} from '../chrome/RatingButton';
|
import {SandyRatingButton} from '../chrome/RatingButton';
|
||||||
import {filterNotifications} from './notification/notificationUtils';
|
import {filterNotifications} from './notification/notificationUtils';
|
||||||
@@ -310,7 +309,7 @@ function ShowSettingsButton() {
|
|||||||
selected={showSettings}
|
selected={showSettings}
|
||||||
/>
|
/>
|
||||||
{showSettings && (
|
{showSettings && (
|
||||||
<SettingsSheet platform={process.platform} onHide={onClose} useSandy />
|
<SettingsSheet platform={process.platform} onHide={onClose} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -417,7 +416,14 @@ function LoginButton() {
|
|||||||
title="Log In"
|
title="Log In"
|
||||||
onClick={() => setShowLogin(true)}
|
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 {styled} from '../ui';
|
||||||
import {Layout, Sidebar} from '../ui';
|
import {Layout, Sidebar} from '../ui';
|
||||||
import {theme} from 'flipper-plugin';
|
import {theme} from 'flipper-plugin';
|
||||||
|
import {ipcRenderer} from 'electron';
|
||||||
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
|
|
||||||
import {LeftRail} from './LeftRail';
|
import {LeftRail} from './LeftRail';
|
||||||
import {registerStartupTime} from '../chrome/LegacyApp';
|
|
||||||
import {useStore, useDispatch} from '../utils/useStore';
|
import {useStore, useDispatch} from '../utils/useStore';
|
||||||
import {SandyContext} from './SandyContext';
|
|
||||||
import {ConsoleLogs} from '../chrome/ConsoleLogs';
|
import {ConsoleLogs} from '../chrome/ConsoleLogs';
|
||||||
import {setStaticView} from '../reducers/connections';
|
import {setStaticView} from '../reducers/connections';
|
||||||
import {
|
import {
|
||||||
@@ -30,10 +30,15 @@ import {ContentContainer} from './ContentContainer';
|
|||||||
import {Notification} from './notification/Notification';
|
import {Notification} from './notification/Notification';
|
||||||
import {SheetRenderer} from '../chrome/SheetRenderer';
|
import {SheetRenderer} from '../chrome/SheetRenderer';
|
||||||
import {hasNewChangesToShow} from '../chrome/ChangelogSheet';
|
import {hasNewChangesToShow} from '../chrome/ChangelogSheet';
|
||||||
import {SandyWelcomScreen} from './SandyWelcomeScreen';
|
import {SandyWelcomeScreen} from './SandyWelcomeScreen';
|
||||||
import {getVersionString} from '../utils/versionString';
|
import {getVersionString} from '../utils/versionString';
|
||||||
import config from '../fb-stubs/config';
|
import config from '../fb-stubs/config';
|
||||||
import {WelcomeScreenStaticView} from './WelcomeScreen';
|
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 =
|
export type ToplevelNavItem =
|
||||||
| 'appinspect'
|
| 'appinspect'
|
||||||
@@ -99,6 +104,27 @@ export function SandyApp() {
|
|||||||
// eslint-disable-next-line
|
// 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 ===
|
const leftMenuContent = !leftSidebarVisible ? null : toplevelSelection ===
|
||||||
'appinspect' ? (
|
'appinspect' ? (
|
||||||
<AppInspect />
|
<AppInspect />
|
||||||
@@ -107,53 +133,51 @@ export function SandyApp() {
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SandyContext.Provider value={true}>
|
<Layout.Top>
|
||||||
<Layout.Top>
|
<>
|
||||||
<>
|
<SheetRenderer logger={logger} />
|
||||||
<SheetRenderer logger={logger} />
|
<SandyWelcomeScreen />
|
||||||
<SandyWelcomScreen />
|
</>
|
||||||
</>
|
<Layout.Left>
|
||||||
<Layout.Left>
|
<Layout.Horizontal>
|
||||||
<Layout.Horizontal>
|
<LeftRail
|
||||||
<LeftRail
|
toplevelSelection={toplevelSelection}
|
||||||
toplevelSelection={toplevelSelection}
|
setToplevelSelection={setToplevelSelection}
|
||||||
setToplevelSelection={setToplevelSelection}
|
/>
|
||||||
/>
|
<Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
||||||
<Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
{leftMenuContent && (
|
||||||
{leftMenuContent && (
|
<TrackingScope scope={toplevelSelection!}>
|
||||||
<TrackingScope scope={toplevelSelection!}>
|
{leftMenuContent}
|
||||||
{leftMenuContent}
|
|
||||||
</TrackingScope>
|
|
||||||
)}
|
|
||||||
</Sidebar>
|
|
||||||
</Layout.Horizontal>
|
|
||||||
<MainContainer>
|
|
||||||
{outOfContentsContainer}
|
|
||||||
{staticView ? (
|
|
||||||
<TrackingScope
|
|
||||||
scope={
|
|
||||||
(staticView as any).displayName ??
|
|
||||||
staticView.name ??
|
|
||||||
staticView.constructor?.name ??
|
|
||||||
'unknown static view'
|
|
||||||
}>
|
|
||||||
{staticView === WelcomeScreenStaticView ? (
|
|
||||||
React.createElement(staticView) /* avoid shadow */
|
|
||||||
) : (
|
|
||||||
<ContentContainer>
|
|
||||||
{React.createElement(staticView, {
|
|
||||||
logger: logger,
|
|
||||||
})}
|
|
||||||
</ContentContainer>
|
|
||||||
)}
|
|
||||||
</TrackingScope>
|
</TrackingScope>
|
||||||
) : (
|
|
||||||
<PluginContainer logger={logger} isSandy />
|
|
||||||
)}
|
)}
|
||||||
</MainContainer>
|
</Sidebar>
|
||||||
</Layout.Left>
|
</Layout.Horizontal>
|
||||||
</Layout.Top>
|
<MainContainer>
|
||||||
</SandyContext.Provider>
|
{outOfContentsContainer}
|
||||||
|
{staticView ? (
|
||||||
|
<TrackingScope
|
||||||
|
scope={
|
||||||
|
(staticView as any).displayName ??
|
||||||
|
staticView.name ??
|
||||||
|
staticView.constructor?.name ??
|
||||||
|
'unknown static view'
|
||||||
|
}>
|
||||||
|
{staticView === WelcomeScreenStaticView ? (
|
||||||
|
React.createElement(staticView) /* avoid shadow */
|
||||||
|
) : (
|
||||||
|
<ContentContainer>
|
||||||
|
{React.createElement(staticView, {
|
||||||
|
logger: logger,
|
||||||
|
})}
|
||||||
|
</ContentContainer>
|
||||||
|
)}
|
||||||
|
</TrackingScope>
|
||||||
|
) : (
|
||||||
|
<PluginContainer logger={logger} isSandy />
|
||||||
|
)}
|
||||||
|
</MainContainer>
|
||||||
|
</Layout.Left>
|
||||||
|
</Layout.Top>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,3 +207,23 @@ const MainContainer = styled(Layout.Container)({
|
|||||||
background: theme.backgroundWash,
|
background: theme.backgroundWash,
|
||||||
padding: `${theme.space.large}px ${theme.space.large}px ${theme.space.large}px 0`,
|
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;
|
const {Title, Text, Link} = Typography;
|
||||||
|
|
||||||
export function SandyWelcomScreen() {
|
export function SandyWelcomeScreen() {
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
const [showWelcomeScreen, setShowWelcomeScreen] = useLocalStorage(
|
const [showWelcomeScreen, setShowWelcomeScreen] = useLocalStorage(
|
||||||
'flipper-sandy-show-welcome-screen',
|
'flipper-sandy-show-welcome-screen',
|
||||||
@@ -63,10 +63,6 @@ export function SandyWelcomScreen() {
|
|||||||
to find new or improved features.
|
to find new or improved features.
|
||||||
</Text>
|
</Text>
|
||||||
<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 ? (
|
{constants.IS_PUBLIC_BUILD ? (
|
||||||
<>
|
<>
|
||||||
Feel free let us know your thoughts about the new experience on{' '}
|
Feel free let us know your thoughts about the new experience on{' '}
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export function AppInspect() {
|
|||||||
)}
|
)}
|
||||||
{!isArchived && activeDevice && (
|
{!isArchived && activeDevice && (
|
||||||
<Toolbar gap>
|
<Toolbar gap>
|
||||||
<MetroButton useSandy />
|
<MetroButton />
|
||||||
<ScreenCaptureButtons useSandy />
|
<ScreenCaptureButtons />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
)}
|
)}
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
|
|||||||
@@ -27,10 +27,9 @@ import {
|
|||||||
selectDevice,
|
selectDevice,
|
||||||
} from '../../reducers/connections';
|
} from '../../reducers/connections';
|
||||||
import BaseDevice, {OS} from '../../devices/BaseDevice';
|
import BaseDevice, {OS} from '../../devices/BaseDevice';
|
||||||
import {getColorByApp} from '../../chrome/mainsidebar/sidebarUtils';
|
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import {State} from '../../reducers';
|
import {State} from '../../reducers';
|
||||||
import {brandIcons} from '../../ui/components/colors';
|
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
|
||||||
import {showEmulatorLauncher} from './LaunchEmulator';
|
import {showEmulatorLauncher} from './LaunchEmulator';
|
||||||
|
|
||||||
const {Text, Link, Title} = Typography;
|
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,
|
useTrackedCallback,
|
||||||
useValue,
|
useValue,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {navPluginStateSelector} from '../../chrome/LocationsButton';
|
import {State} from '../../reducers';
|
||||||
|
|
||||||
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
// eslint-disable-next-line flipper/no-relative-imports-across-packages
|
||||||
import type {NavigationPlugin} from '../../../../plugins/navigation/index';
|
import type {NavigationPlugin} from '../../../../plugins/navigation/index';
|
||||||
@@ -136,3 +136,15 @@ const StyledAutoComplete = styled(AutoComplete)({
|
|||||||
flex: 1,
|
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 {theme, NUX, Tracked} from 'flipper-plugin';
|
||||||
import {useDispatch, useStore} from '../../utils/useStore';
|
import {useDispatch, useStore} from '../../utils/useStore';
|
||||||
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
|
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
|
||||||
import {ClientPluginDefinition, DevicePluginDefinition} from '../../plugin';
|
import {
|
||||||
|
ClientPluginDefinition,
|
||||||
|
DevicePluginDefinition,
|
||||||
|
PluginDefinition,
|
||||||
|
} from '../../plugin';
|
||||||
import {selectPlugin, starPlugin} from '../../reducers/connections';
|
import {selectPlugin, starPlugin} from '../../reducers/connections';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import {State} from '../../reducers';
|
import {State} from '../../reducers';
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
import {getFavoritePlugins} from '../../chrome/mainsidebar/sidebarUtils';
|
|
||||||
import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib';
|
import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib';
|
||||||
import {useMemoize} from '../../utils/useMemoize';
|
import {useMemoize} from '../../utils/useMemoize';
|
||||||
import MetroDevice from '../../devices/MetroDevice';
|
import MetroDevice from '../../devices/MetroDevice';
|
||||||
@@ -649,3 +652,28 @@ function iconStyle(disabled: boolean) {
|
|||||||
height: 24,
|
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
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useContext, useState, useRef, useCallback, useMemo} from 'react';
|
import React, {useState, useCallback, useMemo} from 'react';
|
||||||
import electron, {MenuItemConstructorOptions} from 'electron';
|
import electron, {MenuItemConstructorOptions} from 'electron';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {findDOMNode} from 'react-dom';
|
|
||||||
import {keyframes} from '@emotion/css';
|
import {keyframes} from '@emotion/css';
|
||||||
import {Button as AntdButton, Dropdown, Menu} from 'antd';
|
import {Button as AntdButton, Dropdown, Menu} from 'antd';
|
||||||
|
|
||||||
import {colors} from './colors';
|
import {colors} from './colors';
|
||||||
import Glyph, {IconSize} from './Glyph';
|
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 type {ButtonProps} from 'antd/lib/button';
|
||||||
import {DownOutlined, CheckOutlined} from '@ant-design/icons';
|
import {DownOutlined, CheckOutlined} from '@ant-design/icons';
|
||||||
import {theme, Tracked} from 'flipper-plugin';
|
import {theme} from 'flipper-plugin';
|
||||||
|
|
||||||
type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
|
type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
|
||||||
|
|
||||||
@@ -266,120 +262,7 @@ type Props = {
|
|||||||
* A simple button, used in many parts of the application.
|
* A simple button, used in many parts of the application.
|
||||||
*/
|
*/
|
||||||
export default function Button(props: Props) {
|
export default function Button(props: Props) {
|
||||||
const isSandy = useIsSandy();
|
return <SandyButton {...props} />;
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import React, {createContext} from 'react';
|
import React, {createContext} from 'react';
|
||||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
|
||||||
import {Space} from 'antd';
|
import {Space} from 'antd';
|
||||||
|
|
||||||
const ButtonGroupContainer = styled.div({
|
const ButtonGroupContainer = styled.div({
|
||||||
@@ -35,14 +34,9 @@ export const ButtonGroupContext = createContext(false);
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export default function ButtonGroup({children}: {children: React.ReactNode}) {
|
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 (
|
||||||
return isSandy ? (
|
|
||||||
<ButtonGroupContext.Provider value={true}>
|
<ButtonGroupContext.Provider value={true}>
|
||||||
<Space>{children}</Space>
|
<Space>{children}</Space>
|
||||||
</ButtonGroupContext.Provider>
|
</ButtonGroupContext.Provider>
|
||||||
) : (
|
|
||||||
<ButtonGroupContext.Provider value={true}>
|
|
||||||
<ButtonGroupContainer>{children}</ButtonGroupContainer>
|
|
||||||
</ButtonGroupContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,23 +7,11 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import {colors} from './colors';
|
|
||||||
import {useCallback} from 'react';
|
import {useCallback} from 'react';
|
||||||
import {shell} from 'electron';
|
import {shell} from 'electron';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
|
||||||
import {Typography} from 'antd';
|
import {Typography} from 'antd';
|
||||||
|
|
||||||
const StyledLink = styled.span({
|
|
||||||
color: colors.highlight,
|
|
||||||
'&:hover': {
|
|
||||||
cursor: 'pointer',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
StyledLink.displayName = 'Link:StyledLink';
|
|
||||||
|
|
||||||
const AntOriginalLink = Typography.Link;
|
const AntOriginalLink = Typography.Link;
|
||||||
|
|
||||||
export default function Link(props: {
|
export default function Link(props: {
|
||||||
@@ -32,7 +20,6 @@ export default function Link(props: {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
onClick?: ((event: React.MouseEvent<any>) => void) | undefined;
|
onClick?: ((event: React.MouseEvent<any>) => void) | undefined;
|
||||||
}) {
|
}) {
|
||||||
const isSandy = useIsSandy();
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(e: React.MouseEvent<any>) => {
|
(e: React.MouseEvent<any>) => {
|
||||||
shell.openExternal(props.href);
|
shell.openExternal(props.href);
|
||||||
@@ -42,13 +29,7 @@ export default function Link(props: {
|
|||||||
[props.href],
|
[props.href],
|
||||||
);
|
);
|
||||||
|
|
||||||
return isSandy ? (
|
return <AntOriginalLink {...props} onClick={props.onClick ?? onClick} />;
|
||||||
<AntOriginalLink {...props} onClick={props.onClick ?? onClick} />
|
|
||||||
) : (
|
|
||||||
<StyledLink onClick={props.onClick ?? onClick} style={props.style}>
|
|
||||||
{props.children || props.href}
|
|
||||||
</StyledLink>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX. For consistent usage, we monkey patch AntDesign's Link component,
|
// XXX. For consistent usage, we monkey patch AntDesign's Link component,
|
||||||
|
|||||||
@@ -8,39 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {colors} from './colors';
|
|
||||||
import FlexRow from './FlexRow';
|
|
||||||
import FlexBox from './FlexBox';
|
import FlexBox from './FlexBox';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {useIsSandy} from '../../sandy-chrome/SandyContext';
|
|
||||||
import {theme, Layout} from 'flipper-plugin';
|
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)({
|
const SandyToolbarContainer = styled(Layout.Horizontal)({
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
padding: theme.space.small,
|
padding: theme.space.small,
|
||||||
@@ -55,21 +26,15 @@ Spacer.displayName = 'Spacer';
|
|||||||
export default function Toolbar({
|
export default function Toolbar({
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
...rest
|
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
position?: 'bottom' | 'top';
|
position?: 'bottom' | 'top';
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}) {
|
}) {
|
||||||
const isSandy = useIsSandy();
|
return (
|
||||||
return isSandy ? (
|
|
||||||
<SandyToolbarContainer style={style} gap={theme.space.small} center>
|
<SandyToolbarContainer style={style} gap={theme.space.small} center>
|
||||||
{children}
|
{children}
|
||||||
</SandyToolbarContainer>
|
</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 {useStore} from '../../../app/src/utils/useStore';
|
||||||
import isSandyEnabled from './isSandyEnabled';
|
|
||||||
/**
|
/**
|
||||||
* This hook returns whether dark mode is currently being used.
|
* This hook returns whether dark mode is currently being used.
|
||||||
* Generally should be avoided in favor of using the above theme object,
|
* Generally should be avoided in favor of using the above theme object,
|
||||||
* which will provide colors that reflect the theme
|
* which will provide colors that reflect the theme
|
||||||
*/
|
*/
|
||||||
export function useIsDarkMode(): boolean {
|
export function useIsDarkMode(): boolean {
|
||||||
return useStore((state) => isSandyEnabled() && state.settingsState.darkMode);
|
return useStore((state) => state.settingsState.darkMode);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user