Top level selection moved to reducer
Summary: Intention of this change is to simplify and standardise the way top level selection is done. Top level selection is now part of the state, and thus setting it is done in the reducer instead. Reviewed By: antonk52 Differential Revision: D47844742 fbshipit-source-id: 8e5aad8975f0046b85098b988cca40228316c249
This commit is contained in:
committed by
Facebook GitHub Bot
parent
63ed9d7324
commit
b1b056485c
@@ -11,6 +11,13 @@ import {v1 as uuidv1} from 'uuid';
|
|||||||
import {getRenderHostInstance} from 'flipper-frontend-core';
|
import {getRenderHostInstance} from 'flipper-frontend-core';
|
||||||
import {Actions} from './';
|
import {Actions} from './';
|
||||||
|
|
||||||
|
export type ToplevelNavigationItem =
|
||||||
|
| 'appinspect'
|
||||||
|
| 'flipperlogs'
|
||||||
|
| 'notification'
|
||||||
|
| 'connectivity'
|
||||||
|
| undefined;
|
||||||
|
|
||||||
export type LauncherMsg = {
|
export type LauncherMsg = {
|
||||||
message: string;
|
message: string;
|
||||||
severity: 'warning' | 'error';
|
severity: 'warning' | 'error';
|
||||||
@@ -37,6 +44,7 @@ export type ShareType = {
|
|||||||
} & SubShareType;
|
} & SubShareType;
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
|
topLevelSelection: ToplevelNavigationItem;
|
||||||
hasLeftSidebar: boolean;
|
hasLeftSidebar: boolean;
|
||||||
leftSidebarVisible: boolean;
|
leftSidebarVisible: boolean;
|
||||||
rightSidebarVisible: boolean;
|
rightSidebarVisible: boolean;
|
||||||
@@ -59,6 +67,10 @@ export type Action =
|
|||||||
type: BooleanActionType;
|
type: BooleanActionType;
|
||||||
payload?: boolean;
|
payload?: boolean;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'topLevelSelection';
|
||||||
|
payload: ToplevelNavigationItem;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'windowIsFocused';
|
type: 'windowIsFocused';
|
||||||
payload: {isFocused: boolean; time: number};
|
payload: {isFocused: boolean; time: number};
|
||||||
@@ -80,6 +92,7 @@ export type Action =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initialState: () => State = () => ({
|
export const initialState: () => State = () => ({
|
||||||
|
topLevelSelection: 'appinspect',
|
||||||
hasLeftSidebar: true,
|
hasLeftSidebar: true,
|
||||||
leftSidebarVisible: true,
|
leftSidebarVisible: true,
|
||||||
rightSidebarVisible: true,
|
rightSidebarVisible: true,
|
||||||
@@ -132,6 +145,19 @@ export default function reducer(
|
|||||||
[action.type]: newValue,
|
[action.type]: newValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else if (action.type === 'topLevelSelection') {
|
||||||
|
const topLevelSelection = action.payload;
|
||||||
|
|
||||||
|
const hasLeftSidebar =
|
||||||
|
topLevelSelection === 'appinspect' ||
|
||||||
|
topLevelSelection === 'notification';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
leftSidebarVisible: hasLeftSidebar,
|
||||||
|
hasLeftSidebar,
|
||||||
|
topLevelSelection,
|
||||||
|
};
|
||||||
} else if (action.type === 'windowIsFocused') {
|
} else if (action.type === 'windowIsFocused') {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -174,6 +200,13 @@ export const toggleAction = (
|
|||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setTopLevelSelection = (
|
||||||
|
payload: ToplevelNavigationItem,
|
||||||
|
): Action => ({
|
||||||
|
type: 'topLevelSelection',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
export const toggleLeftSidebarVisible = (payload?: boolean): Action => ({
|
export const toggleLeftSidebarVisible = (payload?: boolean): Action => ({
|
||||||
type: 'leftSidebarVisible',
|
type: 'leftSidebarVisible',
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@@ -161,13 +161,12 @@ export type Action =
|
|||||||
}
|
}
|
||||||
| RegisterPluginAction;
|
| RegisterPluginAction;
|
||||||
|
|
||||||
const DEFAULT_PLUGIN = 'DeviceLogs';
|
|
||||||
const DEFAULT_DEVICE_BLACKLIST: DeviceOS[] = ['MacOS', 'Metro', 'Windows'];
|
const DEFAULT_DEVICE_BLACKLIST: DeviceOS[] = ['MacOS', 'Metro', 'Windows'];
|
||||||
const INITAL_STATE: State = {
|
const INITAL_STATE: State = {
|
||||||
devices: [],
|
devices: [],
|
||||||
selectedDevice: null,
|
selectedDevice: null,
|
||||||
selectedAppId: null,
|
selectedAppId: null,
|
||||||
selectedPlugin: DEFAULT_PLUGIN,
|
selectedPlugin: null,
|
||||||
pluginMenuEntries: [],
|
pluginMenuEntries: [],
|
||||||
userPreferredDevice: null,
|
userPreferredDevice: null,
|
||||||
userPreferredPlugin: null,
|
userPreferredPlugin: null,
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ import {
|
|||||||
WarningOutlined,
|
WarningOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
|
setTopLevelSelection,
|
||||||
toggleLeftSidebarVisible,
|
toggleLeftSidebarVisible,
|
||||||
toggleRightSidebarVisible,
|
toggleRightSidebarVisible,
|
||||||
|
ToplevelNavigationItem,
|
||||||
} from '../reducers/application';
|
} from '../reducers/application';
|
||||||
import {ToplevelNavItem, ToplevelProps} from './SandyApp';
|
|
||||||
import PluginManager from '../chrome/plugin-manager/PluginManager';
|
import PluginManager from '../chrome/plugin-manager/PluginManager';
|
||||||
import {showEmulatorLauncher} from './appinspect/LaunchEmulator';
|
import {showEmulatorLauncher} from './appinspect/LaunchEmulator';
|
||||||
import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen';
|
import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen';
|
||||||
@@ -57,11 +58,11 @@ import {
|
|||||||
import UpdateIndicator from '../chrome/UpdateIndicator';
|
import UpdateIndicator from '../chrome/UpdateIndicator';
|
||||||
import {css} from '@emotion/css';
|
import {css} from '@emotion/css';
|
||||||
import constants from '../fb-stubs/constants';
|
import constants from '../fb-stubs/constants';
|
||||||
import {setStaticView} from '../reducers/connections';
|
import {selectPlugin, setStaticView} from '../reducers/connections';
|
||||||
import {StyleGuide} from './StyleGuide';
|
import {StyleGuide} from './StyleGuide';
|
||||||
import {openDeeplinkDialog} from '../deeplink';
|
import {openDeeplinkDialog} from '../deeplink';
|
||||||
import SettingsSheet from '../chrome/SettingsSheet';
|
import SettingsSheet from '../chrome/SettingsSheet';
|
||||||
import WelcomeScreen from './WelcomeScreen';
|
import WelcomeScreen, {WelcomeScreenStaticView} from './WelcomeScreen';
|
||||||
import {AppSelector} from './appinspect/AppSelector';
|
import {AppSelector} from './appinspect/AppSelector';
|
||||||
import {
|
import {
|
||||||
NavbarScreenRecordButton,
|
NavbarScreenRecordButton,
|
||||||
@@ -69,11 +70,21 @@ import {
|
|||||||
} from '../chrome/ScreenCaptureButtons';
|
} from '../chrome/ScreenCaptureButtons';
|
||||||
import {StatusMessage} from './appinspect/AppInspect';
|
import {StatusMessage} from './appinspect/AppInspect';
|
||||||
import {TroubleshootingGuide} from './appinspect/fb-stubs/TroubleshootingGuide';
|
import {TroubleshootingGuide} from './appinspect/fb-stubs/TroubleshootingGuide';
|
||||||
|
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
||||||
|
import {ConnectivityHub} from '../chrome/ConnectivityHub';
|
||||||
|
|
||||||
export const Navbar = withTrackingScope(function Navbar({
|
export const Navbar = withTrackingScope(function Navbar() {
|
||||||
toplevelSelection,
|
const {topLevelSelection, selectedDevice, selectedAppId, selectedPlugin} =
|
||||||
setToplevelSelection,
|
useStore((state) => {
|
||||||
}: ToplevelProps) {
|
return {
|
||||||
|
topLevelSelection: state.application.topLevelSelection,
|
||||||
|
selectedDevice: state.connections.selectedDevice,
|
||||||
|
selectedAppId: state.connections.selectedAppId,
|
||||||
|
selectedPlugin: state.connections.selectedPlugin,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
return (
|
return (
|
||||||
<Layout.Horizontal
|
<Layout.Horizontal
|
||||||
borderBottom
|
borderBottom
|
||||||
@@ -90,9 +101,20 @@ export const Navbar = withTrackingScope(function Navbar({
|
|||||||
<NavbarButton
|
<NavbarButton
|
||||||
icon={MobileOutlined}
|
icon={MobileOutlined}
|
||||||
label="App Inspect"
|
label="App Inspect"
|
||||||
toggled={toplevelSelection === 'appinspect'}
|
toggled={topLevelSelection === 'appinspect'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setToplevelSelection('appinspect');
|
dispatch(setTopLevelSelection('appinspect'));
|
||||||
|
if (selectedPlugin) {
|
||||||
|
dispatch(
|
||||||
|
selectPlugin({
|
||||||
|
selectedPlugin,
|
||||||
|
selectedAppId,
|
||||||
|
selectedDevice: selectedDevice,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dispatch(setStaticView(WelcomeScreenStaticView));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AppSelector />
|
<AppSelector />
|
||||||
@@ -110,11 +132,8 @@ export const Navbar = withTrackingScope(function Navbar({
|
|||||||
<Layout.Horizontal style={{gap: 6, alignItems: 'center'}}>
|
<Layout.Horizontal style={{gap: 6, alignItems: 'center'}}>
|
||||||
<NoConnectivityWarning />
|
<NoConnectivityWarning />
|
||||||
|
|
||||||
<NotificationButton
|
<NotificationButton topLevelSelection={topLevelSelection} />
|
||||||
toplevelSelection={toplevelSelection}
|
<TroubleshootMenu />
|
||||||
setToplevelSelection={setToplevelSelection}
|
|
||||||
/>
|
|
||||||
<TroubleshootMenu setToplevelSelection={setToplevelSelection} />
|
|
||||||
<ExtrasMenu />
|
<ExtrasMenu />
|
||||||
<RightSidebarToggleButton />
|
<RightSidebarToggleButton />
|
||||||
|
|
||||||
@@ -224,23 +243,25 @@ function ExportEverythingEverywhereAllAtOnceStatusModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function NotificationButton({
|
function NotificationButton({
|
||||||
toplevelSelection,
|
topLevelSelection,
|
||||||
setToplevelSelection,
|
}: {
|
||||||
}: ToplevelProps) {
|
topLevelSelection: ToplevelNavigationItem;
|
||||||
|
}) {
|
||||||
const notifications = useStore((state) => state.notifications);
|
const notifications = useStore((state) => state.notifications);
|
||||||
const activeNotifications = useMemoize(filterNotifications, [
|
const activeNotifications = useMemoize(filterNotifications, [
|
||||||
notifications.activeNotifications,
|
notifications.activeNotifications,
|
||||||
notifications.blocklistedPlugins,
|
notifications.blocklistedPlugins,
|
||||||
notifications.blocklistedCategories,
|
notifications.blocklistedCategories,
|
||||||
]);
|
]);
|
||||||
|
const dispatch = useDispatch();
|
||||||
return (
|
return (
|
||||||
<NavbarButton
|
<NavbarButton
|
||||||
icon={BellOutlined}
|
icon={BellOutlined}
|
||||||
label="Alerts"
|
label="Alerts"
|
||||||
zIndex={AlertsZIndex}
|
zIndex={AlertsZIndex}
|
||||||
toggled={toplevelSelection === 'notification'}
|
toggled={topLevelSelection === 'notification'}
|
||||||
count={activeNotifications.length}
|
count={activeNotifications.length}
|
||||||
onClick={() => setToplevelSelection('notification')}
|
onClick={() => dispatch(setTopLevelSelection('notification'))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -444,11 +465,7 @@ const submenu = css`
|
|||||||
const AlertsZIndex = 101;
|
const AlertsZIndex = 101;
|
||||||
const TroubleShootZIndex = 100;
|
const TroubleShootZIndex = 100;
|
||||||
|
|
||||||
function TroubleshootMenu({
|
function TroubleshootMenu() {
|
||||||
setToplevelSelection,
|
|
||||||
}: {
|
|
||||||
setToplevelSelection: (x: ToplevelNavItem) => void;
|
|
||||||
}) {
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const [status, setStatus] = useState<
|
const [status, setStatus] = useState<
|
||||||
ExportEverythingEverywhereAllAtOnceStatus | undefined
|
ExportEverythingEverywhereAllAtOnceStatus | undefined
|
||||||
@@ -498,7 +515,10 @@ function TroubleshootMenu({
|
|||||||
{getRenderHostInstance().GK('flipper_connection_troubleshoot') && (
|
{getRenderHostInstance().GK('flipper_connection_troubleshoot') && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="connectivity"
|
key="connectivity"
|
||||||
onClick={() => setToplevelSelection('connectivity')}>
|
onClick={() => {
|
||||||
|
store.dispatch(setTopLevelSelection('connectivity'));
|
||||||
|
store.dispatch(setStaticView(ConnectivityHub));
|
||||||
|
}}>
|
||||||
Troubleshoot Connectivity
|
Troubleshoot Connectivity
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
@@ -511,7 +531,10 @@ function TroubleshootMenu({
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="flipperlogs"
|
key="flipperlogs"
|
||||||
onClick={() => setToplevelSelection('flipperlogs')}>
|
onClick={() => {
|
||||||
|
store.dispatch(setTopLevelSelection('flipperlogs'));
|
||||||
|
store.dispatch(setStaticView(FlipperDevTools));
|
||||||
|
}}>
|
||||||
<Layout.Horizontal center gap="small">
|
<Layout.Horizontal center gap="small">
|
||||||
Flipper Logs <Badge count={flipperErrorLogCount} />
|
Flipper Logs <Badge count={flipperErrorLogCount} />
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useState, useCallback} from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import {
|
import {
|
||||||
TrackingScope,
|
TrackingScope,
|
||||||
useLogger,
|
useLogger,
|
||||||
@@ -21,13 +21,7 @@ import {theme} from 'flipper-plugin';
|
|||||||
import {Logger} from 'flipper-common';
|
import {Logger} from 'flipper-common';
|
||||||
|
|
||||||
import {Navbar} from './Navbar';
|
import {Navbar} from './Navbar';
|
||||||
import {useStore, useDispatch} from '../utils/useStore';
|
import {useStore} from '../utils/useStore';
|
||||||
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
|
||||||
import {setStaticView} from '../reducers/connections';
|
|
||||||
import {
|
|
||||||
toggleHasLeftSidebar,
|
|
||||||
toggleLeftSidebarVisible,
|
|
||||||
} from '../reducers/application';
|
|
||||||
import {AppInspect} from './appinspect/AppInspect';
|
import {AppInspect} from './appinspect/AppInspect';
|
||||||
import PluginContainer from '../PluginContainer';
|
import PluginContainer from '../PluginContainer';
|
||||||
import {ContentContainer} from './ContentContainer';
|
import {ContentContainer} from './ContentContainer';
|
||||||
@@ -47,63 +41,16 @@ import {isFBEmployee} from '../utils/fbEmployee';
|
|||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
import isProduction from '../utils/isProduction';
|
import isProduction from '../utils/isProduction';
|
||||||
import {getRenderHostInstance} from 'flipper-frontend-core';
|
import {getRenderHostInstance} from 'flipper-frontend-core';
|
||||||
import {ConnectivityHub} from '../chrome/ConnectivityHub';
|
|
||||||
|
|
||||||
export type ToplevelNavItem =
|
|
||||||
| 'appinspect'
|
|
||||||
| 'flipperlogs'
|
|
||||||
| 'notification'
|
|
||||||
| 'connectivity'
|
|
||||||
| undefined;
|
|
||||||
export type ToplevelProps = {
|
|
||||||
toplevelSelection: ToplevelNavItem;
|
|
||||||
setToplevelSelection: (_newSelection: ToplevelNavItem) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SandyApp() {
|
export function SandyApp() {
|
||||||
const logger = useLogger();
|
const logger = useLogger();
|
||||||
const dispatch = useDispatch();
|
|
||||||
const leftSidebarVisible = useStore(
|
const leftSidebarVisible = useStore(
|
||||||
(state) => state.application.leftSidebarVisible,
|
(state) => state.application.leftSidebarVisible,
|
||||||
);
|
);
|
||||||
const staticView = useStore((state) => state.connections.staticView);
|
const topLevelSelection = useStore(
|
||||||
|
(state) => state.application.topLevelSelection,
|
||||||
/**
|
|
||||||
* Top level navigation uses two pieces of state, selection stored here, and selection that is based on what is stored in the reducer (which might be influenced by redux action dispatches to different means).
|
|
||||||
* The logic here is to sync both, but without modifying the navigation related reducers to not break classic Flipper.
|
|
||||||
* It is possible to simplify this in the future.
|
|
||||||
*/
|
|
||||||
const [toplevelSelection, setStoredToplevelSelection] =
|
|
||||||
useState<ToplevelNavItem>('appinspect');
|
|
||||||
|
|
||||||
// Handle toplevel nav clicks from LeftRail
|
|
||||||
const setToplevelSelection = useCallback(
|
|
||||||
(newSelection: ToplevelNavItem) => {
|
|
||||||
// toggle sidebar visibility if needed
|
|
||||||
const hasLeftSidebar =
|
|
||||||
newSelection === 'appinspect' || newSelection === 'notification';
|
|
||||||
|
|
||||||
dispatch(toggleHasLeftSidebar(hasLeftSidebar));
|
|
||||||
if (hasLeftSidebar) {
|
|
||||||
if (newSelection === toplevelSelection) {
|
|
||||||
dispatch(toggleLeftSidebarVisible());
|
|
||||||
} else {
|
|
||||||
dispatch(toggleLeftSidebarVisible(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (newSelection) {
|
|
||||||
case 'flipperlogs':
|
|
||||||
dispatch(setStaticView(FlipperDevTools));
|
|
||||||
break;
|
|
||||||
case 'connectivity':
|
|
||||||
dispatch(setStaticView(ConnectivityHub));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
setStoredToplevelSelection(newSelection);
|
|
||||||
},
|
|
||||||
[dispatch, toplevelSelection],
|
|
||||||
);
|
);
|
||||||
|
const staticView = useStore((state) => state.connections.staticView);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `Flipper (${getVersionString()}${
|
document.title = `Flipper (${getVersionString()}${
|
||||||
@@ -162,10 +109,10 @@ export function SandyApp() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const leftMenuContent = !leftSidebarVisible ? null : toplevelSelection ===
|
const leftMenuContent = !leftSidebarVisible ? null : topLevelSelection ===
|
||||||
'appinspect' ? (
|
'appinspect' ? (
|
||||||
<AppInspect />
|
<AppInspect />
|
||||||
) : toplevelSelection === 'notification' ? (
|
) : topLevelSelection === 'notification' ? (
|
||||||
<Notification />
|
<Notification />
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@@ -173,10 +120,7 @@ export function SandyApp() {
|
|||||||
<RootElement>
|
<RootElement>
|
||||||
<Layout.Bottom>
|
<Layout.Bottom>
|
||||||
<Layout.Top gap={16}>
|
<Layout.Top gap={16}>
|
||||||
<Navbar
|
<Navbar />
|
||||||
toplevelSelection={toplevelSelection}
|
|
||||||
setToplevelSelection={setToplevelSelection}
|
|
||||||
/>
|
|
||||||
<Layout.Left
|
<Layout.Left
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: theme.space.large,
|
paddingLeft: theme.space.large,
|
||||||
@@ -185,7 +129,7 @@ export function SandyApp() {
|
|||||||
<Layout.Horizontal>
|
<Layout.Horizontal>
|
||||||
<_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>
|
</TrackingScope>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user