/** * 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, { cloneElement, useState, useCallback, useMemo, useEffect, } from 'react'; import {Button, Divider, Badge, Tooltip, Avatar, Popover} from 'antd'; import { MobileFilled, AppstoreOutlined, BellOutlined, FileExclamationOutlined, LoginOutlined, BugOutlined, SettingOutlined, QuestionCircleOutlined, MedicineBoxOutlined, RocketOutlined, } from '@ant-design/icons'; import {SidebarLeft, SidebarRight} from './SandyIcons'; import {useDispatch, useStore} from '../utils/useStore'; import { ACTIVE_SHEET_PLUGINS, ACTIVE_SHEET_SIGN_IN, setActiveSheet, toggleLeftSidebarVisible, toggleRightSidebarVisible, } from '../reducers/application'; import {theme, Layout, withTrackingScope} from 'flipper-plugin'; import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen'; import SettingsSheet from '../chrome/SettingsSheet'; import WelcomeScreen from './WelcomeScreen'; import {errorCounterAtom} from '../chrome/ConsoleLogs'; import {ToplevelProps} from './SandyApp'; import {useValue} from 'flipper-plugin'; import {logout} from '../reducers/user'; import config from '../fb-stubs/config'; import styled from '@emotion/styled'; import {showEmulatorLauncher} from './appinspect/LaunchEmulator'; import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2'; import {setStaticView, StaticView} from '../reducers/connections'; import {getInstance} from '../fb-stubs/Logger'; import {getUser} from '../fb-stubs/user'; import {SandyRatingButton} from '../chrome/RatingButton'; import {filterNotifications} from './notification/notificationUtils'; import {useMemoize} from 'flipper-plugin'; import isProduction from '../utils/isProduction'; import NetworkGraph from '../chrome/NetworkGraph'; import FpsGraph from '../chrome/FpsGraph'; import UpdateIndicator from '../chrome/UpdateIndicator'; import {UserNotSignedInError, UserUnauthorizedError} from '../utils/errors'; const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({ width: kind === 'small' ? 32 : 36, height: kind === 'small' ? 32 : 36, padding: '5px 0', border: 'none', boxShadow: 'none', })); LeftRailButtonElem.displayName = 'LeftRailButtonElem'; export function LeftRailButton({ icon, small, selected, toggled, count, title, onClick, disabled, }: { icon?: React.ReactElement; small?: boolean; toggled?: boolean; selected?: boolean; disabled?: boolean; count?: number | true; title: string; onClick?: React.MouseEventHandler; }) { let iconElement = icon && cloneElement(icon, {style: {fontSize: small ? 16 : 24}}); if (count !== undefined) { iconElement = count === true ? ( {iconElement} ) : ( {iconElement} ); } return ( ); } const LeftRailDivider = styled(Divider)({ margin: `10px 0`, width: 32, minWidth: 32, }); LeftRailDivider.displayName = 'LeftRailDividier'; export const LeftRail = withTrackingScope(function LeftRail({ toplevelSelection, setToplevelSelection, }: ToplevelProps) { const dispatch = useDispatch(); return ( } title="App Inspect" selected={toplevelSelection === 'appinspect'} onClick={() => { setToplevelSelection('appinspect'); }} /> } title="Plugin Manager" onClick={() => { dispatch(setActiveSheet(ACTIVE_SHEET_PLUGINS)); }} /> {!isProduction() && (
)} {config.showLogin && }
); }); function LeftSidebarToggleButton() { const dispatch = useDispatch(); const mainMenuVisible = useStore( (state) => state.application.leftSidebarVisible, ); return ( } small title="Left Sidebar Toggle" toggled={mainMenuVisible} onClick={() => { dispatch(toggleLeftSidebarVisible()); }} /> ); } function RightSidebarToggleButton() { const dispatch = useDispatch(); const rightSidebarAvailable = useStore( (state) => state.application.rightSidebarAvailable, ); const rightSidebarVisible = useStore( (state) => state.application.rightSidebarVisible, ); return ( } small title="Right Sidebar Toggle" toggled={rightSidebarVisible} disabled={!rightSidebarAvailable} onClick={() => { dispatch(toggleRightSidebarVisible()); }} /> ); } function NotificationButton({ toplevelSelection, setToplevelSelection, }: ToplevelProps) { const notifications = useStore((state) => state.notifications); const activeNotifications = useMemoize(filterNotifications, [ notifications.activeNotifications, notifications.blocklistedPlugins, notifications.blocklistedCategories, ]); return ( } title="Notifications" selected={toplevelSelection === 'notification'} count={activeNotifications.length} onClick={() => setToplevelSelection('notification')} /> ); } function DebugLogsButton({ toplevelSelection, setToplevelSelection, }: ToplevelProps) { const errorCount = useValue(errorCounterAtom); return ( } title="Flipper Logs" selected={toplevelSelection === 'flipperlogs'} count={errorCount} onClick={() => { setToplevelSelection('flipperlogs'); }} /> ); } function LaunchEmulatorButton() { const store = useStore(); return ( } title="Start Emulator / Simulator" onClick={() => { showEmulatorLauncher(store); }} small /> ); } function SetupDoctorButton() { const [visible, setVisible] = useState(false); const result = useStore( (state) => state.healthchecks.healthcheckReport.result, ); const hasNewProblem = useMemo(() => checkHasNewProblem(result), [result]); const onClose = useCallback(() => setVisible(false), []); return ( <> } small title="Setup Doctor" count={hasNewProblem ? true : undefined} onClick={() => setVisible(true)} /> ); } function ShowSettingsButton() { const [showSettings, setShowSettings] = useState(false); const onClose = useCallback(() => setShowSettings(false), []); return ( <> } small title="Settings" onClick={() => setShowSettings(true)} selected={showSettings} /> {showSettings && ( )} ); } function SupportFormButton() { const dispatch = useDispatch(); const staticView = useStore((state) => state.connections.staticView); return config.isFBBuild ? ( } small title="Feedback / Bug Reporter" selected={isStaticViewActive(staticView, SupportRequestFormV2)} onClick={() => { getInstance().track('usage', 'support-form-source', { source: 'sidebar', group: undefined, }); dispatch(setStaticView(SupportRequestFormV2)); }} /> ) : null; } function WelcomeScreenButton() { const settings = useStore((state) => state.settingsState); const {showWelcomeAtStartup} = settings; const dispatch = useDispatch(); const [visible, setVisible] = useState(showWelcomeAtStartup); return ( <> } small title="Help / Start Screen" onClick={() => setVisible(true)} /> setVisible(false)} showAtStartup={showWelcomeAtStartup} onCheck={(value) => dispatch({ type: 'UPDATE_SETTINGS', payload: {...settings, showWelcomeAtStartup: value}, }) } /> ); } function LoginButton() { const dispatch = useDispatch(); const user = useStore((state) => state.user); const login = (user?.id ?? null) !== null; const profileUrl = user?.profile_picture?.uri; const showLogin = useCallback(() => { dispatch(setActiveSheet(ACTIVE_SHEET_SIGN_IN)); }, [dispatch]); const [showLogout, setShowLogout] = useState(false); const onHandleVisibleChange = useCallback( (visible) => setShowLogout(visible), [], ); useEffect(() => { if (config.showLogin) { getUser().catch((error) => { if ( error instanceof UserUnauthorizedError || error instanceof UserNotSignedInError ) { showLogin(); } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return login ? ( { onHandleVisibleChange(false); dispatch(logout()); }}> Log Out } trigger="click" placement="right" visible={showLogout} overlayStyle={{padding: 0}} onVisibleChange={onHandleVisibleChange}> ) : ( <> } title="Log In" onClick={showLogin} /> ); } function isStaticViewActive( current: StaticView, selected: StaticView, ): boolean { return Boolean(current && selected && current === selected); }