Summary: This diff adds `types` fields on the compiler config for every project. This way we can make sure that for example node types and packages are not available in flipper-ui-core. Without an explicit types field, all types would be shared between all packages, and implicitly included into the compilation of everything. For the same reason `types/index.d.ts` has been removed, we want to be intentional on which types are being used in which package. This diff does most of the work, the next diff will fine tune the globals, and do some further cleanup. As an alternative solution I first tried a `nohoist: **/node_modules/types/**` and make sure every package list explicitly the types used in package json, which works but is much more error prone, as for example two different react types versions in two packages will cause the most unreadable compiler error due to the types not being shared and not literally the same. Reviewed By: lawrencelomax Differential Revision: D33124441 fbshipit-source-id: c2b9d768f845ac28005d8331ef5fa1066c7e4cd7
397 lines
12 KiB
TypeScript
397 lines
12 KiB
TypeScript
/**
|
|
* 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, {Component, useContext} from 'react';
|
|
import {Radio} from 'antd';
|
|
import {updateSettings, Action} from '../reducers/settings';
|
|
import {
|
|
Action as LauncherAction,
|
|
updateLauncherSettings,
|
|
} from '../reducers/launcherSettings';
|
|
import {connect} from 'react-redux';
|
|
import {State as Store} from '../reducers';
|
|
import {flush} from '../utils/persistor';
|
|
import ToggledSection from './settings/ToggledSection';
|
|
import {FilePathConfigField, ConfigText} from './settings/configFields';
|
|
import KeyboardShortcutInput from './settings/KeyboardShortcutInput';
|
|
import {isEqual, isMatch, isEmpty} from 'lodash';
|
|
import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
|
|
import {
|
|
LauncherSettings,
|
|
Platform,
|
|
reportUsage,
|
|
Settings,
|
|
} from 'flipper-common';
|
|
import {Modal, message, Button} from 'antd';
|
|
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
|
import {getRenderHostInstance} from '../RenderHost';
|
|
import {loadTheme} from '../utils/loadTheme';
|
|
|
|
type OwnProps = {
|
|
onHide: () => void;
|
|
platform: Platform;
|
|
noModal?: boolean; // used for testing
|
|
};
|
|
|
|
type StateFromProps = {
|
|
settings: Settings;
|
|
launcherSettings: LauncherSettings;
|
|
};
|
|
|
|
type DispatchFromProps = {
|
|
updateSettings: (settings: Settings) => Action;
|
|
updateLauncherSettings: (settings: LauncherSettings) => LauncherAction;
|
|
};
|
|
|
|
type State = {
|
|
updatedSettings: Settings;
|
|
updatedLauncherSettings: LauncherSettings;
|
|
forcedRestartSettings: Partial<Settings>;
|
|
forcedRestartLauncherSettings: Partial<LauncherSettings>;
|
|
};
|
|
|
|
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
|
class SettingsSheet extends Component<Props, State> {
|
|
state: State = {
|
|
updatedSettings: {...this.props.settings},
|
|
updatedLauncherSettings: {...this.props.launcherSettings},
|
|
forcedRestartSettings: {},
|
|
forcedRestartLauncherSettings: {},
|
|
};
|
|
|
|
componentDidMount() {
|
|
reportUsage('settings:opened');
|
|
}
|
|
|
|
applyChanges = async () => {
|
|
this.props.updateSettings(this.state.updatedSettings);
|
|
this.props.updateLauncherSettings(this.state.updatedLauncherSettings);
|
|
this.props.onHide();
|
|
return flush().then(() => {
|
|
getRenderHostInstance().restartFlipper(true);
|
|
});
|
|
};
|
|
|
|
applyChangesWithoutRestart = async () => {
|
|
this.props.updateSettings(this.state.updatedSettings);
|
|
this.props.updateLauncherSettings(this.state.updatedLauncherSettings);
|
|
await flush();
|
|
this.props.onHide();
|
|
};
|
|
|
|
renderSandyContainer(
|
|
contents: React.ReactElement,
|
|
footer: React.ReactElement,
|
|
) {
|
|
return (
|
|
<Modal
|
|
visible
|
|
onCancel={this.props.onHide}
|
|
width={570}
|
|
title="Settings"
|
|
footer={footer}
|
|
bodyStyle={{
|
|
overflow: 'scroll',
|
|
maxHeight: 'calc(100vh - 250px)',
|
|
}}>
|
|
{contents}
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
enableAndroid,
|
|
androidHome,
|
|
enableIOS,
|
|
enablePhysicalIOS,
|
|
enablePrefetching,
|
|
idbPath,
|
|
reactNative,
|
|
darkMode,
|
|
suppressPluginErrors,
|
|
} = this.state.updatedSettings;
|
|
|
|
const settingsPristine =
|
|
isEqual(this.props.settings, this.state.updatedSettings) &&
|
|
isEqual(this.props.launcherSettings, this.state.updatedLauncherSettings);
|
|
|
|
const forcedRestart =
|
|
(!isEmpty(this.state.forcedRestartSettings) &&
|
|
!isMatch(this.props.settings, this.state.forcedRestartSettings)) ||
|
|
(!isEmpty(this.state.forcedRestartLauncherSettings) &&
|
|
!isMatch(
|
|
this.props.launcherSettings,
|
|
this.state.forcedRestartLauncherSettings,
|
|
));
|
|
|
|
const contents = (
|
|
<Layout.Container gap>
|
|
<ToggledSection
|
|
label="Android Developer"
|
|
toggled={enableAndroid}
|
|
onChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {
|
|
...this.state.updatedSettings,
|
|
enableAndroid: v,
|
|
},
|
|
});
|
|
}}>
|
|
<FilePathConfigField
|
|
label="Android SDK location"
|
|
resetValue={
|
|
getRenderHostInstance().serverConfig.settings.androidHome
|
|
}
|
|
defaultValue={androidHome}
|
|
onChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {
|
|
...this.state.updatedSettings,
|
|
androidHome: v,
|
|
},
|
|
});
|
|
}}
|
|
/>
|
|
</ToggledSection>
|
|
<ToggledSection
|
|
label="iOS Developer"
|
|
toggled={enableIOS && this.props.platform === 'darwin'}
|
|
onChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {...this.state.updatedSettings, enableIOS: v},
|
|
});
|
|
}}>
|
|
{' '}
|
|
{this.props.platform === 'darwin' && (
|
|
<ConfigText
|
|
content={'Use "xcode-select" to switch between Xcode versions'}
|
|
/>
|
|
)}
|
|
{this.props.platform !== 'darwin' && (
|
|
<ConfigText
|
|
content={
|
|
'iOS development has limited functionality on non-MacOS devices'
|
|
}
|
|
/>
|
|
)}
|
|
<ToggledSection
|
|
label="Enable physical iOS devices"
|
|
toggled={enablePhysicalIOS}
|
|
frozen={false}
|
|
onChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {
|
|
...this.state.updatedSettings,
|
|
enablePhysicalIOS: v,
|
|
},
|
|
});
|
|
}}>
|
|
<FilePathConfigField
|
|
label="IDB binary location"
|
|
defaultValue={idbPath}
|
|
isRegularFile
|
|
onChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {...this.state.updatedSettings, idbPath: v},
|
|
});
|
|
}}
|
|
/>
|
|
</ToggledSection>
|
|
</ToggledSection>
|
|
<LauncherSettingsPanel
|
|
isPrefetchingEnabled={enablePrefetching}
|
|
onEnablePrefetchingChange={(v) => {
|
|
this.setState({
|
|
updatedSettings: {
|
|
...this.state.updatedSettings,
|
|
enablePrefetching: v,
|
|
},
|
|
});
|
|
}}
|
|
isLocalPinIgnored={this.state.updatedLauncherSettings.ignoreLocalPin}
|
|
onIgnoreLocalPinChange={(v) => {
|
|
this.setState({
|
|
updatedLauncherSettings: {
|
|
...this.state.updatedLauncherSettings,
|
|
ignoreLocalPin: v,
|
|
},
|
|
});
|
|
}}
|
|
releaseChannel={this.state.updatedLauncherSettings.releaseChannel}
|
|
onReleaseChannelChange={(v) => {
|
|
this.setState({
|
|
updatedLauncherSettings: {
|
|
...this.state.updatedLauncherSettings,
|
|
releaseChannel: v,
|
|
},
|
|
forcedRestartLauncherSettings: {
|
|
...this.state.forcedRestartLauncherSettings,
|
|
releaseChannel: v,
|
|
},
|
|
});
|
|
}}
|
|
/>
|
|
<ToggledSection
|
|
label="Suppress error notifications send from client plugins"
|
|
toggled={suppressPluginErrors}
|
|
onChange={(enabled) => {
|
|
this.setState((prevState) => ({
|
|
updatedSettings: {
|
|
...prevState.updatedSettings,
|
|
suppressPluginErrors: enabled,
|
|
},
|
|
}));
|
|
}}
|
|
/>
|
|
<Layout.Container style={{paddingLeft: 15, paddingBottom: 10}}>
|
|
Theme Selection
|
|
<Radio.Group
|
|
value={darkMode}
|
|
onChange={(event) => {
|
|
this.setState((prevState) => ({
|
|
updatedSettings: {
|
|
...prevState.updatedSettings,
|
|
darkMode: event.target.value,
|
|
},
|
|
}));
|
|
loadTheme(event.target.value);
|
|
}}>
|
|
<Radio.Button value="dark">Dark</Radio.Button>
|
|
<Radio.Button value="light">Light</Radio.Button>
|
|
<Radio.Button value="system">Use System Setting</Radio.Button>
|
|
</Radio.Group>
|
|
</Layout.Container>
|
|
<ToggledSection
|
|
label="React Native keyboard shortcuts"
|
|
toggled={reactNative.shortcuts.enabled}
|
|
onChange={(enabled) => {
|
|
this.setState((prevState) => ({
|
|
updatedSettings: {
|
|
...prevState.updatedSettings,
|
|
reactNative: {
|
|
...prevState.updatedSettings.reactNative,
|
|
shortcuts: {
|
|
...prevState.updatedSettings.reactNative.shortcuts,
|
|
enabled,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
}}>
|
|
<KeyboardShortcutInput
|
|
label="Reload application"
|
|
value={reactNative.shortcuts.reload}
|
|
onChange={(reload) => {
|
|
this.setState((prevState) => ({
|
|
updatedSettings: {
|
|
...prevState.updatedSettings,
|
|
reactNative: {
|
|
...prevState.updatedSettings.reactNative,
|
|
shortcuts: {
|
|
...prevState.updatedSettings.reactNative.shortcuts,
|
|
reload,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
}}
|
|
/>
|
|
<KeyboardShortcutInput
|
|
label="Open developer menu"
|
|
value={reactNative.shortcuts.openDevMenu}
|
|
onChange={(openDevMenu) => {
|
|
this.setState((prevState) => ({
|
|
updatedSettings: {
|
|
...prevState.updatedSettings,
|
|
reactNative: {
|
|
...prevState.updatedSettings.reactNative,
|
|
shortcuts: {
|
|
...prevState.updatedSettings.reactNative.shortcuts,
|
|
openDevMenu,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
}}
|
|
/>
|
|
</ToggledSection>
|
|
<Layout.Right center>
|
|
<span>Reset all new user tooltips</span>
|
|
<ResetTooltips />
|
|
</Layout.Right>
|
|
<Layout.Right center>
|
|
<span>Reset all local storage based state</span>
|
|
<ResetLocalState />
|
|
</Layout.Right>
|
|
</Layout.Container>
|
|
);
|
|
|
|
const footer = (
|
|
<>
|
|
<Button onClick={this.props.onHide}>Cancel</Button>
|
|
<Button
|
|
disabled={settingsPristine || forcedRestart}
|
|
onClick={this.applyChangesWithoutRestart}>
|
|
Apply
|
|
</Button>
|
|
<Button
|
|
disabled={settingsPristine}
|
|
type="primary"
|
|
onClick={this.applyChanges}>
|
|
Apply and Restart
|
|
</Button>
|
|
</>
|
|
);
|
|
|
|
return this.props.noModal ? (
|
|
<>
|
|
{contents}
|
|
{footer}
|
|
</>
|
|
) : (
|
|
this.renderSandyContainer(contents, footer)
|
|
);
|
|
}
|
|
}
|
|
|
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
|
({settingsState, launcherSettingsState}) => ({
|
|
settings: settingsState,
|
|
launcherSettings: launcherSettingsState,
|
|
}),
|
|
{updateSettings, updateLauncherSettings},
|
|
)(withTrackingScope(SettingsSheet));
|
|
|
|
function ResetTooltips() {
|
|
const nuxManager = useContext(_NuxManagerContext);
|
|
|
|
return (
|
|
<Button
|
|
onClick={() => {
|
|
nuxManager.resetHints();
|
|
}}>
|
|
Reset hints
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
function ResetLocalState() {
|
|
return (
|
|
<Button
|
|
danger
|
|
onClick={() => {
|
|
window.localStorage.clear();
|
|
message.success('Local storage state cleared');
|
|
}}>
|
|
Reset all state
|
|
</Button>
|
|
);
|
|
}
|