Render sidebar
Summary: Restore sidebar functionality for Sandy plugins Also needed to fix some circular dependency issues as fallout. Reviewed By: cekkaewnumchai Differential Revision: D24362215 fbshipit-source-id: 0a09ac35ba981322ae0793edc3aa79ffddf2ce73
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ba5f067320
commit
a2fac737f6
@@ -45,7 +45,7 @@ import {activateMenuItems} from './MenuBar';
|
||||
import {Message} from './reducers/pluginMessageQueue';
|
||||
import {Idler} from './utils/Idler';
|
||||
import {processMessageQueue} from './utils/messageQueue';
|
||||
import {ToggleButton, SmallText} from './ui';
|
||||
import {ToggleButton, SmallText, Layout} from './ui';
|
||||
import {SandyPluginRenderer} from 'flipper-plugin';
|
||||
import {isDevicePluginDefinition} from './utils/pluginUtils';
|
||||
import ArchivedDevice from './devices/ArchivedDevice';
|
||||
@@ -67,7 +67,6 @@ const Waiting = styled(FlexColumn)({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
background: colors.light02,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
@@ -95,6 +94,7 @@ const ProgressBarBar = styled.div<{progress: number}>(({progress}) => ({
|
||||
|
||||
type OwnProps = {
|
||||
logger: Logger;
|
||||
isSandy?: boolean;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
@@ -362,6 +362,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
||||
isArchivedDevice,
|
||||
selectedApp,
|
||||
settingsState,
|
||||
isSandy,
|
||||
} = this.props;
|
||||
if (!activePlugin || !target || !pluginKey) {
|
||||
console.warn(`No selected plugin. Rendering empty!`);
|
||||
@@ -429,7 +430,17 @@ class PluginContainer extends PureComponent<Props, State> {
|
||||
};
|
||||
pluginElement = React.createElement(activePlugin, props);
|
||||
}
|
||||
return (
|
||||
return isSandy ? (
|
||||
<Layout.Right>
|
||||
<ErrorBoundary
|
||||
heading={`Plugin "${
|
||||
activePlugin.title || 'Unknown'
|
||||
}" encountered an error during render`}>
|
||||
{pluginElement}
|
||||
</ErrorBoundary>
|
||||
<SidebarContainer id="detailsSidebar" />
|
||||
</Layout.Right>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Container key="plugin">
|
||||
<ErrorBoundary
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {useEffect, useMemo, useContext} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {ReactReduxContext} from 'react-redux';
|
||||
import Sidebar from '../ui/components/Sidebar';
|
||||
import {connect} from 'react-redux';
|
||||
import {toggleRightSidebarAvailable} from '../reducers/application';
|
||||
import {State as Store} from '../reducers';
|
||||
import {useDispatch, useStore} from '../utils/useStore';
|
||||
import {useIsSandy} from '../sandy-chrome/SandyContext';
|
||||
import {ContentContainer} from '../sandy-chrome/ContentContainer';
|
||||
|
||||
type OwnProps = {
|
||||
children: any;
|
||||
@@ -20,57 +22,47 @@ type OwnProps = {
|
||||
minWidth?: number;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
rightSidebarVisible: boolean;
|
||||
rightSidebarAvailable: boolean;
|
||||
};
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
export default function DetailSidebar({children, width, minWidth}: OwnProps) {
|
||||
const reduxContext = useContext(ReactReduxContext);
|
||||
const domNode = useMemo(() => document.getElementById('detailsSidebar'), []);
|
||||
|
||||
type DispatchFromProps = {
|
||||
toggleRightSidebarAvailable: (visible?: boolean) => any;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
class DetailSidebar extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.updateSidebarAvailablility();
|
||||
if (!reduxContext || !domNode) {
|
||||
// For unit tests, make sure to render elements inline
|
||||
return <div id="detailsSidebar">{children}</div>;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateSidebarAvailablility();
|
||||
}
|
||||
const isSandy = useIsSandy();
|
||||
const dispatch = useDispatch();
|
||||
const {rightSidebarAvailable, rightSidebarVisible} = useStore((state) => {
|
||||
const {rightSidebarAvailable, rightSidebarVisible} = state.application;
|
||||
return {rightSidebarAvailable, rightSidebarVisible};
|
||||
});
|
||||
|
||||
updateSidebarAvailablility() {
|
||||
const available = Boolean(this.props.children);
|
||||
if (available !== this.props.rightSidebarAvailable) {
|
||||
this.props.toggleRightSidebarAvailable(available);
|
||||
}
|
||||
}
|
||||
useEffect(
|
||||
function updateSidebarAvailablility() {
|
||||
const available = Boolean(children);
|
||||
if (available !== rightSidebarAvailable) {
|
||||
dispatch(toggleRightSidebarAvailable(available));
|
||||
}
|
||||
},
|
||||
[children, rightSidebarAvailable, dispatch],
|
||||
);
|
||||
|
||||
render() {
|
||||
const domNode = document.getElementById('detailsSidebar');
|
||||
return (
|
||||
this.props.children &&
|
||||
this.props.rightSidebarVisible &&
|
||||
return (
|
||||
(children &&
|
||||
rightSidebarVisible &&
|
||||
domNode &&
|
||||
ReactDOM.createPortal(
|
||||
<Sidebar
|
||||
minWidth={this.props.minWidth}
|
||||
width={this.props.width || 300}
|
||||
position="right">
|
||||
{this.props.children}
|
||||
minWidth={minWidth}
|
||||
width={width || 300}
|
||||
position="right"
|
||||
gutter={isSandy}>
|
||||
{isSandy ? <ContentContainer>{children}</ContentContainer> : children}
|
||||
</Sidebar>,
|
||||
domNode,
|
||||
)
|
||||
);
|
||||
}
|
||||
)) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({application: {rightSidebarVisible, rightSidebarAvailable}}) => ({
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
}),
|
||||
{
|
||||
toggleRightSidebarAvailable,
|
||||
},
|
||||
)(DetailSidebar);
|
||||
|
||||
@@ -19,7 +19,6 @@ import {StaticView, setStaticView} from '../../reducers/connections';
|
||||
import {setActiveSheet} from '../../reducers/application';
|
||||
import UserAccount from '../UserAccount';
|
||||
import SupportRequestFormV2 from '../../fb-stubs/SupportRequestFormV2';
|
||||
import WatchTools from '../../fb-stubs/WatchTools';
|
||||
import {
|
||||
isStaticViewActive,
|
||||
PluginIcon,
|
||||
@@ -32,6 +31,7 @@ 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 = {};
|
||||
|
||||
|
||||
19
desktop/app/src/sandy-chrome/ContentContainer.tsx
Normal file
19
desktop/app/src/sandy-chrome/ContentContainer.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 {Layout, styled} from '../ui';
|
||||
import {theme} from './theme';
|
||||
|
||||
export const ContentContainer = styled(Layout.Container)({
|
||||
overflow: 'hidden',
|
||||
background: theme.backgroundDefault,
|
||||
border: `1px solid ${theme.dividerColor}`,
|
||||
borderRadius: theme.containerBorderRadius,
|
||||
boxShadow: `0px 0px 5px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.05)`,
|
||||
});
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import React, {cloneElement, useState, useCallback, useMemo} from 'react';
|
||||
import {styled, Layout} from '../ui';
|
||||
import {Button, Divider, Badge, Tooltip, Avatar, Popover} from 'antd';
|
||||
import {
|
||||
MobileFilled,
|
||||
@@ -23,7 +22,10 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import {SidebarLeft, SidebarRight} from './SandyIcons';
|
||||
import {useDispatch, useStore} from '../utils/useStore';
|
||||
import {toggleLeftSidebarVisible} from '../reducers/application';
|
||||
import {
|
||||
toggleLeftSidebarVisible,
|
||||
toggleRightSidebarVisible,
|
||||
} from '../reducers/application';
|
||||
import {theme} from './theme';
|
||||
import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen';
|
||||
import SettingsSheet from '../chrome/SettingsSheet';
|
||||
@@ -34,6 +36,8 @@ import {ToplevelProps} from './SandyApp';
|
||||
import {useValue} from 'flipper-plugin';
|
||||
import {logout} from '../reducers/user';
|
||||
import config from '../fb-stubs/config';
|
||||
import Layout from '../ui/components/Layout';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({
|
||||
width: kind === 'small' ? 32 : 36,
|
||||
@@ -52,11 +56,13 @@ function LeftRailButton({
|
||||
count,
|
||||
title,
|
||||
onClick,
|
||||
disabled,
|
||||
}: {
|
||||
icon?: React.ReactElement;
|
||||
small?: boolean;
|
||||
toggled?: boolean;
|
||||
selected?: boolean; // TODO: make sure only one element can be selected
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
count?: number | true;
|
||||
title: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
@@ -78,6 +84,7 @@ function LeftRailButton({
|
||||
type={selected ? 'primary' : 'ghost'}
|
||||
icon={iconElement}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
style={{
|
||||
color: toggled ? theme.primaryColor : undefined,
|
||||
background: toggled ? theme.backgroundWash : undefined,
|
||||
@@ -99,9 +106,9 @@ export function LeftRail({
|
||||
setToplevelSelection,
|
||||
}: ToplevelProps) {
|
||||
return (
|
||||
<Layout.Container borderRight padv={12} padh={6} width={48}>
|
||||
<Layout.Container borderRight padv={12} width={48}>
|
||||
<Layout.Bottom>
|
||||
<Layout.Vertical center gap={10}>
|
||||
<Layout.Vertical center gap={10} padh={6}>
|
||||
<LeftRailButton
|
||||
icon={<MobileFilled />}
|
||||
title="App Inspect"
|
||||
@@ -118,7 +125,7 @@ export function LeftRail({
|
||||
setToplevelSelection={setToplevelSelection}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
<Layout.Vertical center gap={10}>
|
||||
<Layout.Vertical center gap={10} padh={6}>
|
||||
<SetupDoctorButton />
|
||||
<WelcomeScreenButton />
|
||||
<ShowSettingsButton />
|
||||
@@ -127,11 +134,7 @@ export function LeftRail({
|
||||
small
|
||||
title="Feedback / Bug Reporter"
|
||||
/>
|
||||
<LeftRailButton
|
||||
icon={<SidebarRight />}
|
||||
small
|
||||
title="Right Sidebar Toggle"
|
||||
/>
|
||||
<RightSidebarToggleButton />
|
||||
<LeftSidebarToggleButton />
|
||||
{config.showLogin && <LoginButton />}
|
||||
</Layout.Vertical>
|
||||
@@ -141,12 +144,11 @@ export function LeftRail({
|
||||
}
|
||||
|
||||
function LeftSidebarToggleButton() {
|
||||
const dispatch = useDispatch();
|
||||
const mainMenuVisible = useStore(
|
||||
(state) => state.application.leftSidebarVisible,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<LeftRailButton
|
||||
icon={<SidebarLeft />}
|
||||
@@ -160,6 +162,29 @@ function LeftSidebarToggleButton() {
|
||||
);
|
||||
}
|
||||
|
||||
function RightSidebarToggleButton() {
|
||||
const dispatch = useDispatch();
|
||||
const rightSidebarAvailable = useStore(
|
||||
(state) => state.application.rightSidebarAvailable,
|
||||
);
|
||||
const rightSidebarVisible = useStore(
|
||||
(state) => state.application.rightSidebarVisible,
|
||||
);
|
||||
|
||||
return (
|
||||
<LeftRailButton
|
||||
icon={<SidebarRight />}
|
||||
small
|
||||
title="Right Sidebar Toggle"
|
||||
toggled={rightSidebarVisible}
|
||||
disabled={!rightSidebarAvailable}
|
||||
onClick={() => {
|
||||
dispatch(toggleRightSidebarVisible());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DebugLogsButton({
|
||||
toplevelSelection,
|
||||
setToplevelSelection,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {setStaticView} from '../reducers/connections';
|
||||
import {toggleLeftSidebarVisible} from '../reducers/application';
|
||||
import {AppInspect} from './appinspect/AppInspect';
|
||||
import PluginContainer from '../PluginContainer';
|
||||
import {ContentContainer} from './ContentContainer';
|
||||
|
||||
export type ToplevelNavItem = 'appinspect' | 'flipperlogs' | undefined;
|
||||
export type ToplevelProps = {
|
||||
@@ -95,25 +96,15 @@ export function SandyApp({logger}: {logger: Logger}) {
|
||||
</Sidebar>
|
||||
</Layout.Horizontal>
|
||||
<MainContainer>
|
||||
<ContentContainer>
|
||||
{staticView ? (
|
||||
React.createElement(staticView, {
|
||||
{staticView ? (
|
||||
<ContentContainer>
|
||||
{React.createElement(staticView, {
|
||||
logger: logger,
|
||||
})
|
||||
) : (
|
||||
<PluginContainer logger={logger} />
|
||||
)}
|
||||
</ContentContainer>
|
||||
<Sidebar
|
||||
width={300}
|
||||
minWidth={220}
|
||||
maxWidth={800}
|
||||
gutter
|
||||
position="right">
|
||||
<ContentContainer style={{marginRight: theme.space.large}}>
|
||||
<RightMenu />
|
||||
})}
|
||||
</ContentContainer>
|
||||
</Sidebar>
|
||||
) : (
|
||||
<PluginContainer logger={logger} isSandy />
|
||||
)}
|
||||
</MainContainer>
|
||||
</Layout.Left>
|
||||
</Layout.Top>
|
||||
@@ -121,19 +112,7 @@ export function SandyApp({logger}: {logger: Logger}) {
|
||||
);
|
||||
}
|
||||
|
||||
const MainContainer = styled(Layout.Right)({
|
||||
const MainContainer = styled(Layout.Container)({
|
||||
background: theme.backgroundWash,
|
||||
padding: `${theme.space.large}px ${theme.space.large}px ${theme.space.large}px 0`,
|
||||
});
|
||||
|
||||
export const ContentContainer = styled(Layout.Container)({
|
||||
background: theme.backgroundDefault,
|
||||
border: `1px solid ${theme.dividerColor}`,
|
||||
borderRadius: theme.containerBorderRadius,
|
||||
boxShadow: `0px 0px 5px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.05)`,
|
||||
marginTop: theme.space.large,
|
||||
marginBottom: theme.space.large,
|
||||
});
|
||||
|
||||
function RightMenu() {
|
||||
return <div>RightMenu</div>;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ const Container = styled.div<ContainerProps>(
|
||||
height,
|
||||
...rest
|
||||
}) => ({
|
||||
boxSizing: 'border-box',
|
||||
minWidth: `0`, // ensures the Container can shrink smaller than it's largest
|
||||
width,
|
||||
height,
|
||||
@@ -64,6 +65,7 @@ const Container = styled.div<ContainerProps>(
|
||||
);
|
||||
|
||||
const ScrollParent = styled.div({
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
@@ -141,11 +143,7 @@ const Layout = {
|
||||
let [child1, child2] = props.children;
|
||||
if (props.scrollable) child2 = <ScrollContainer>{child2}</ScrollContainer>;
|
||||
return (
|
||||
<SandySplitContainer
|
||||
{...props}
|
||||
flexDirection="column"
|
||||
flex1={0}
|
||||
flex2={1}>
|
||||
<SandySplitContainer {...props} flexDirection="column" grow={2}>
|
||||
{child1}
|
||||
{child2}
|
||||
</SandySplitContainer>
|
||||
@@ -157,11 +155,7 @@ const Layout = {
|
||||
let [child1, child2] = props.children;
|
||||
if (props.scrollable) child1 = <ScrollContainer>{child1}</ScrollContainer>;
|
||||
return (
|
||||
<SandySplitContainer
|
||||
{...props}
|
||||
flexDirection="column"
|
||||
flex1={1}
|
||||
flex2={0}>
|
||||
<SandySplitContainer {...props} flexDirection="column" grow={1}>
|
||||
{child1}
|
||||
{child2}
|
||||
</SandySplitContainer>
|
||||
@@ -173,7 +167,7 @@ const Layout = {
|
||||
let [child1, child2] = props.children;
|
||||
if (props.scrollable) child2 = <ScrollContainer>{child2}</ScrollContainer>;
|
||||
return (
|
||||
<SandySplitContainer {...props} flexDirection="row" flex1={0} flex2={1}>
|
||||
<SandySplitContainer {...props} flexDirection="row" grow={2}>
|
||||
{child1}
|
||||
{child2}
|
||||
</SandySplitContainer>
|
||||
@@ -185,7 +179,7 @@ const Layout = {
|
||||
let [child1, child2] = props.children;
|
||||
if (props.scrollable) child1 = <ScrollContainer>{child1}</ScrollContainer>;
|
||||
return (
|
||||
<SandySplitContainer {...props} flexDirection="row" flex1={1} flex2={0}>
|
||||
<SandySplitContainer {...props} flexDirection="row" grow={1}>
|
||||
{child1}
|
||||
{child2}
|
||||
</SandySplitContainer>
|
||||
@@ -202,23 +196,25 @@ Object.keys(Layout).forEach((key) => {
|
||||
});
|
||||
|
||||
const SandySplitContainer = styled.div<{
|
||||
flex1: number;
|
||||
flex2: number;
|
||||
grow: 1 | 2;
|
||||
center?: boolean;
|
||||
flexDirection: CSSProperties['flexDirection'];
|
||||
}>((props) => ({
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
flexDirection: props.flexDirection,
|
||||
alignItems: props.center ? 'center' : 'stretch',
|
||||
overflow: 'hidden',
|
||||
'> :first-child': {
|
||||
flexGrow: props.flex1,
|
||||
flexShrink: props.flex1,
|
||||
flex: props.grow === 1 ? growStyle : fixedStyle,
|
||||
},
|
||||
'> :last-child': {
|
||||
flexGrow: props.flex2,
|
||||
flexShrink: props.flex2,
|
||||
flex: props.grow === 2 ? growStyle : fixedStyle,
|
||||
},
|
||||
}));
|
||||
|
||||
const fixedStyle = `0 0 auto`;
|
||||
const growStyle = `1 0 0`;
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -7,20 +7,12 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as Flipper from 'flipper';
|
||||
// eslint-disable-next-line
|
||||
import {act} from '@testing-library/react';
|
||||
|
||||
{
|
||||
// These mocks are needed because seammammals still uses Flipper in its UI implementation,
|
||||
// so we need to mock some things
|
||||
|
||||
// @ts-ignore
|
||||
jest.spyOn(Flipper.DetailSidebar, 'type').mockImplementation((props) => {
|
||||
return <div className="DetailsSidebar">{props.children}</div>;
|
||||
});
|
||||
|
||||
const origRequestIdleCallback = window.requestIdleCallback;
|
||||
const origCancelIdleCallback = window.cancelIdleCallback;
|
||||
// @ts-ignore
|
||||
|
||||
Reference in New Issue
Block a user