remove LeftRail
Reviewed By: elboman Differential Revision: D47441161 fbshipit-source-id: f0c792beb64fc2474bf6e72b4e4a69d40b699c1e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a76faafa4b
commit
a5631c8d9f
@@ -1,359 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and 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,
|
|
||||||
ReactElement,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
|
||||||
FlexColumn,
|
|
||||||
FlexRow,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
styled,
|
|
||||||
Input,
|
|
||||||
Link,
|
|
||||||
} from '../ui';
|
|
||||||
import {LeftRailButton} from '../sandy-chrome/LeftRail';
|
|
||||||
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
|
||||||
import {FeedbackPrompt} from '../fb-stubs/UserFeedback';
|
|
||||||
import {StarOutlined} from '@ant-design/icons';
|
|
||||||
import {Popover, Rate} from 'antd';
|
|
||||||
import {useStore} from '../utils/useStore';
|
|
||||||
import {currentUser} from '../fb-stubs/user';
|
|
||||||
import {theme, useValue} from 'flipper-plugin';
|
|
||||||
import {reportPlatformFailures} from 'flipper-common';
|
|
||||||
import {getRenderHostInstance} from 'flipper-frontend-core';
|
|
||||||
|
|
||||||
type NextAction = 'select-rating' | 'leave-comment' | 'finished';
|
|
||||||
|
|
||||||
class PredefinedComment extends Component<{
|
|
||||||
comment: string;
|
|
||||||
selected: boolean;
|
|
||||||
onClick: (_: unknown) => unknown;
|
|
||||||
}> {
|
|
||||||
static Container = styled.div<{selected: boolean}>((props) => {
|
|
||||||
return {
|
|
||||||
border: '1px solid #f2f3f5',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: 24,
|
|
||||||
backgroundColor: props.selected ? '#ecf3ff' : '#f2f3f5',
|
|
||||||
marginBottom: 4,
|
|
||||||
marginRight: 4,
|
|
||||||
padding: '4px 8px',
|
|
||||||
color: props.selected ? 'rgb(56, 88, 152)' : undefined,
|
|
||||||
borderColor: props.selected ? '#3578e5' : undefined,
|
|
||||||
':hover': {
|
|
||||||
borderColor: '#3578e5',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<PredefinedComment.Container
|
|
||||||
onClick={this.props.onClick}
|
|
||||||
selected={this.props.selected}>
|
|
||||||
{this.props.comment}
|
|
||||||
</PredefinedComment.Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Row = styled(FlexRow)({
|
|
||||||
marginTop: 5,
|
|
||||||
marginBottom: 5,
|
|
||||||
justifyContent: 'center',
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#9a9a9a',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
});
|
|
||||||
|
|
||||||
const DismissRow = styled(Row)({
|
|
||||||
marginBottom: 0,
|
|
||||||
marginTop: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const DismissButton = styled.span({
|
|
||||||
'&:hover': {
|
|
||||||
textDecoration: 'underline',
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Spacer = styled(FlexColumn)({
|
|
||||||
flexGrow: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
function dismissRow(dismiss: () => void) {
|
|
||||||
return (
|
|
||||||
<DismissRow key="dismiss">
|
|
||||||
<Spacer />
|
|
||||||
<DismissButton onClick={dismiss}>Dismiss</DismissButton>
|
|
||||||
<Spacer />
|
|
||||||
</DismissRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeedbackComponentState = {
|
|
||||||
rating: number | null;
|
|
||||||
hoveredRating: number;
|
|
||||||
allowUserInfoSharing: boolean;
|
|
||||||
nextAction: NextAction;
|
|
||||||
predefinedComments: {[key: string]: boolean};
|
|
||||||
comment: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FeedbackComponent extends Component<
|
|
||||||
{
|
|
||||||
submitRating: (rating: number) => void;
|
|
||||||
submitComment: (
|
|
||||||
rating: number,
|
|
||||||
comment: string,
|
|
||||||
selectedPredefinedComments: Array<string>,
|
|
||||||
allowUserInfoSharing: boolean,
|
|
||||||
) => void;
|
|
||||||
close: () => void;
|
|
||||||
dismiss: () => void;
|
|
||||||
promptData: FeedbackPrompt;
|
|
||||||
},
|
|
||||||
FeedbackComponentState
|
|
||||||
> {
|
|
||||||
state: FeedbackComponentState = {
|
|
||||||
rating: null,
|
|
||||||
hoveredRating: 0,
|
|
||||||
allowUserInfoSharing: true,
|
|
||||||
nextAction: 'select-rating' as NextAction,
|
|
||||||
predefinedComments: this.props.promptData.predefinedComments.reduce(
|
|
||||||
(acc, cv) => ({...acc, [cv]: false}),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
comment: '',
|
|
||||||
};
|
|
||||||
onSubmitRating(newRating: number) {
|
|
||||||
const nextAction = newRating <= 2 ? 'leave-comment' : 'finished';
|
|
||||||
this.setState({rating: newRating, nextAction: nextAction});
|
|
||||||
this.props.submitRating(newRating);
|
|
||||||
if (nextAction === 'finished') {
|
|
||||||
setTimeout(this.props.close, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onCommentSubmitted(comment: string) {
|
|
||||||
this.setState({nextAction: 'finished'});
|
|
||||||
const selectedPredefinedComments: Array<string> = Object.entries(
|
|
||||||
this.state.predefinedComments,
|
|
||||||
)
|
|
||||||
.map((x) => ({comment: x[0], enabled: x[1]}))
|
|
||||||
.filter((x) => x.enabled)
|
|
||||||
.map((x) => x.comment);
|
|
||||||
const currentRating = this.state.rating;
|
|
||||||
if (currentRating) {
|
|
||||||
this.props.submitComment(
|
|
||||||
currentRating,
|
|
||||||
comment,
|
|
||||||
selectedPredefinedComments,
|
|
||||||
this.state.allowUserInfoSharing,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error('Illegal state: Submitting comment with no rating set.');
|
|
||||||
}
|
|
||||||
setTimeout(this.props.close, 1000);
|
|
||||||
}
|
|
||||||
onAllowUserSharingChanged(allowed: boolean) {
|
|
||||||
this.setState({allowUserInfoSharing: allowed});
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
let body: Array<ReactElement>;
|
|
||||||
switch (this.state.nextAction) {
|
|
||||||
case 'select-rating':
|
|
||||||
body = [
|
|
||||||
<Row key="bodyText">{this.props.promptData.bodyText}</Row>,
|
|
||||||
<Row key="stars" style={{margin: 'auto'}}>
|
|
||||||
<Rate onChange={(newRating) => this.onSubmitRating(newRating)} />
|
|
||||||
</Row>,
|
|
||||||
dismissRow(this.props.dismiss),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case 'leave-comment':
|
|
||||||
const predefinedComments = Object.entries(
|
|
||||||
this.state.predefinedComments,
|
|
||||||
).map((c: [string, unknown], idx: number) => (
|
|
||||||
<PredefinedComment
|
|
||||||
key={idx}
|
|
||||||
comment={c[0]}
|
|
||||||
selected={Boolean(c[1])}
|
|
||||||
onClick={() =>
|
|
||||||
this.setState({
|
|
||||||
predefinedComments: {
|
|
||||||
...this.state.predefinedComments,
|
|
||||||
[c[0]]: !c[1],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
body = [
|
|
||||||
<Row key="predefinedComments">{predefinedComments}</Row>,
|
|
||||||
<Row key="inputRow">
|
|
||||||
<Input
|
|
||||||
style={{height: 30, width: '100%'}}
|
|
||||||
placeholder={this.props.promptData.commentPlaceholder}
|
|
||||||
value={this.state.comment}
|
|
||||||
onChange={(e) => this.setState({comment: e.target.value})}
|
|
||||||
onKeyDown={(e) =>
|
|
||||||
e.key == 'Enter' && this.onCommentSubmitted(this.state.comment)
|
|
||||||
}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</Row>,
|
|
||||||
<Row key="contactCheckbox">
|
|
||||||
<Checkbox
|
|
||||||
checked={this.state.allowUserInfoSharing}
|
|
||||||
onChange={this.onAllowUserSharingChanged.bind(this)}
|
|
||||||
/>
|
|
||||||
{'Tool owner can contact me '}
|
|
||||||
</Row>,
|
|
||||||
<Row key="submit">
|
|
||||||
<Button onClick={() => this.onCommentSubmitted(this.state.comment)}>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Row>,
|
|
||||||
dismissRow(this.props.dismiss),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case 'finished':
|
|
||||||
body = [
|
|
||||||
<Row key="thanks">
|
|
||||||
Thanks for the feedback! You can now help
|
|
||||||
<Link href="https://www.internalfb.com/intern/papercuts/?application=flipper">
|
|
||||||
prioritize bugs and features for Flipper in Papercuts
|
|
||||||
</Link>
|
|
||||||
</Row>,
|
|
||||||
dismissRow(this.props.dismiss),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
console.error('Illegal state: nextAction: ' + this.state.nextAction);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<FlexColumn
|
|
||||||
style={{
|
|
||||||
width: 400,
|
|
||||||
paddingLeft: 20,
|
|
||||||
paddingRight: 20,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
}}>
|
|
||||||
<Row key="heading" style={{color: theme.primaryColor, fontSize: 20}}>
|
|
||||||
{this.state.nextAction === 'finished'
|
|
||||||
? this.props.promptData.postSubmitHeading
|
|
||||||
: this.props.promptData.preSubmitHeading}
|
|
||||||
</Row>
|
|
||||||
{body}
|
|
||||||
</FlexColumn>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SandyRatingButton() {
|
|
||||||
const [promptData, setPromptData] =
|
|
||||||
useState<UserFeedback.FeedbackPrompt | null>(null);
|
|
||||||
const [isShown, setIsShown] = useState(false);
|
|
||||||
const [hasTriggered, setHasTriggered] = useState(false);
|
|
||||||
const sessionId = useStore((store) => store.application.sessionId);
|
|
||||||
const loggedIn = useValue(currentUser());
|
|
||||||
|
|
||||||
const triggerPopover = useCallback(() => {
|
|
||||||
if (!hasTriggered) {
|
|
||||||
setIsShown(true);
|
|
||||||
setHasTriggered(true);
|
|
||||||
}
|
|
||||||
}, [hasTriggered]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
getRenderHostInstance().GK('flipper_enable_star_ratiings') &&
|
|
||||||
!hasTriggered &&
|
|
||||||
loggedIn
|
|
||||||
) {
|
|
||||||
reportPlatformFailures(
|
|
||||||
UserFeedback.getPrompt().then((prompt) => {
|
|
||||||
setPromptData(prompt);
|
|
||||||
setTimeout(triggerPopover, 30000);
|
|
||||||
}),
|
|
||||||
'RatingButton:getPrompt',
|
|
||||||
).catch((e) => {
|
|
||||||
console.warn('Failed to load ratings prompt:', e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [triggerPopover, hasTriggered, loggedIn]);
|
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
const willBeShown = !isShown;
|
|
||||||
setIsShown(willBeShown);
|
|
||||||
setHasTriggered(true);
|
|
||||||
if (!willBeShown) {
|
|
||||||
UserFeedback.dismiss(sessionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitRating = (rating: number) => {
|
|
||||||
UserFeedback.submitRating(rating, sessionId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitComment = (
|
|
||||||
rating: number,
|
|
||||||
comment: string,
|
|
||||||
selectedPredefinedComments: Array<string>,
|
|
||||||
allowUserInfoSharing: boolean,
|
|
||||||
) => {
|
|
||||||
UserFeedback.submitComment(
|
|
||||||
rating,
|
|
||||||
comment,
|
|
||||||
selectedPredefinedComments,
|
|
||||||
allowUserInfoSharing,
|
|
||||||
sessionId,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!promptData) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!promptData.shouldPopup || (hasTriggered && !isShown)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
visible={isShown}
|
|
||||||
content={
|
|
||||||
<FeedbackComponent
|
|
||||||
submitRating={submitRating}
|
|
||||||
submitComment={submitComment}
|
|
||||||
close={() => {
|
|
||||||
setIsShown(false);
|
|
||||||
}}
|
|
||||||
dismiss={onClick}
|
|
||||||
promptData={promptData}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
placement="right"
|
|
||||||
trigger="click">
|
|
||||||
<LeftRailButton
|
|
||||||
icon={<StarOutlined />}
|
|
||||||
title="Rate Flipper"
|
|
||||||
onClick={onClick}
|
|
||||||
small
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and 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} from 'react';
|
|
||||||
import {Button, Divider, Badge, Tooltip, Menu} from 'antd';
|
|
||||||
import {SettingOutlined} from '@ant-design/icons';
|
|
||||||
import {useStore} from '../utils/useStore';
|
|
||||||
import {
|
|
||||||
theme,
|
|
||||||
withTrackingScope,
|
|
||||||
useTrackedCallback,
|
|
||||||
NUX,
|
|
||||||
} from 'flipper-plugin';
|
|
||||||
import SettingsSheet from '../chrome/SettingsSheet';
|
|
||||||
import WelcomeScreen from './WelcomeScreen';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import {setStaticView} from '../reducers/connections';
|
|
||||||
import constants from '../fb-stubs/constants';
|
|
||||||
import {
|
|
||||||
canFileExport,
|
|
||||||
canOpenDialog,
|
|
||||||
showOpenDialog,
|
|
||||||
startFileExport,
|
|
||||||
startLinkExport,
|
|
||||||
} from '../utils/exportData';
|
|
||||||
import {openDeeplinkDialog} from '../deeplink';
|
|
||||||
import {css} from '@emotion/css';
|
|
||||||
import {getRenderHostInstance} from 'flipper-frontend-core';
|
|
||||||
import {StyleGuide} from './StyleGuide';
|
|
||||||
|
|
||||||
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<HTMLElement>;
|
|
||||||
}) {
|
|
||||||
const iconElement =
|
|
||||||
icon && cloneElement(icon, {style: {fontSize: small ? 16 : 24}});
|
|
||||||
|
|
||||||
let res = (
|
|
||||||
<LeftRailButtonElem
|
|
||||||
title={title}
|
|
||||||
kind={small ? 'small' : undefined}
|
|
||||||
type={selected ? 'primary' : 'ghost'}
|
|
||||||
icon={iconElement}
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={disabled}
|
|
||||||
style={{
|
|
||||||
color: toggled ? theme.primaryColor : undefined,
|
|
||||||
background: toggled ? theme.backgroundWash : undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (count !== undefined) {
|
|
||||||
res =
|
|
||||||
count === true ? (
|
|
||||||
<Badge dot offset={[-8, 8]} {...{onClick}}>
|
|
||||||
{res}
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge count={count} offset={[-6, 5]} {...{onClick}}>
|
|
||||||
{res}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
res = (
|
|
||||||
<Tooltip title={title} placement="right">
|
|
||||||
{res}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LeftRailDivider = styled(Divider)({
|
|
||||||
margin: `10px 0`,
|
|
||||||
width: 32,
|
|
||||||
minWidth: 32,
|
|
||||||
});
|
|
||||||
LeftRailDivider.displayName = 'LeftRailDividier';
|
|
||||||
|
|
||||||
export const LeftRail = withTrackingScope(function LeftRail() {
|
|
||||||
return <ExtrasMenu />;
|
|
||||||
});
|
|
||||||
|
|
||||||
const menu = css`
|
|
||||||
border: none;
|
|
||||||
`;
|
|
||||||
const submenu = css`
|
|
||||||
.ant-menu-submenu-title {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px !important;
|
|
||||||
line-height: 32px !important;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.ant-menu-submenu-arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function ExtrasMenu() {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const startFileExportTracked = useTrackedCallback(
|
|
||||||
'File export',
|
|
||||||
() => startFileExport(store.dispatch),
|
|
||||||
[store.dispatch],
|
|
||||||
);
|
|
||||||
const startLinkExportTracked = useTrackedCallback(
|
|
||||||
'Link export',
|
|
||||||
() => startLinkExport(store.dispatch),
|
|
||||||
[store.dispatch],
|
|
||||||
);
|
|
||||||
const startImportTracked = useTrackedCallback(
|
|
||||||
'File import',
|
|
||||||
() => showOpenDialog(store),
|
|
||||||
[store],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
|
||||||
const onSettingsClose = useCallback(() => setShowSettings(false), []);
|
|
||||||
|
|
||||||
const settings = useStore((state) => state.settingsState);
|
|
||||||
const {showWelcomeAtStartup} = settings;
|
|
||||||
const [welcomeVisible, setWelcomeVisible] = useState(showWelcomeAtStartup);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NUX
|
|
||||||
title="Find import, export, deeplink, feedback, settings, and help (welcome) here"
|
|
||||||
placement="right">
|
|
||||||
<Menu
|
|
||||||
mode="vertical"
|
|
||||||
className={menu}
|
|
||||||
selectable={false}
|
|
||||||
style={{backgroundColor: theme.backgroundDefault}}>
|
|
||||||
<Menu.SubMenu
|
|
||||||
popupOffset={[10, 0]}
|
|
||||||
key="extras"
|
|
||||||
title={<LeftRailButton icon={<SettingOutlined />} small />}
|
|
||||||
className={submenu}>
|
|
||||||
{canOpenDialog() ? (
|
|
||||||
<Menu.Item key="importFlipperFile" onClick={startImportTracked}>
|
|
||||||
Import Flipper file
|
|
||||||
</Menu.Item>
|
|
||||||
) : null}
|
|
||||||
{canFileExport() ? (
|
|
||||||
<Menu.Item key="exportFile" onClick={startFileExportTracked}>
|
|
||||||
Export Flipper file
|
|
||||||
</Menu.Item>
|
|
||||||
) : null}
|
|
||||||
{constants.ENABLE_SHAREABLE_LINK ? (
|
|
||||||
<Menu.Item
|
|
||||||
key="exportShareableLink"
|
|
||||||
onClick={startLinkExportTracked}>
|
|
||||||
Export shareable link
|
|
||||||
</Menu.Item>
|
|
||||||
) : null}
|
|
||||||
<Menu.Divider />
|
|
||||||
<Menu.SubMenu title="Plugin developers">
|
|
||||||
<Menu.Item
|
|
||||||
key="styleguide"
|
|
||||||
onClick={() => {
|
|
||||||
store.dispatch(setStaticView(StyleGuide));
|
|
||||||
}}>
|
|
||||||
Flipper Style Guide
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item
|
|
||||||
key="triggerDeeplink"
|
|
||||||
onClick={() => openDeeplinkDialog(store)}>
|
|
||||||
Trigger deeplink
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.SubMenu>
|
|
||||||
<Menu.Divider />
|
|
||||||
<Menu.Item key="settings" onClick={() => setShowSettings(true)}>
|
|
||||||
Settings
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="help" onClick={() => setWelcomeVisible(true)}>
|
|
||||||
Help
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.SubMenu>
|
|
||||||
</Menu>
|
|
||||||
</NUX>
|
|
||||||
{showSettings && (
|
|
||||||
<SettingsSheet
|
|
||||||
platform={
|
|
||||||
getRenderHostInstance().serverConfig.environmentInfo.os.platform
|
|
||||||
}
|
|
||||||
onHide={onSettingsClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<WelcomeScreen
|
|
||||||
visible={welcomeVisible}
|
|
||||||
onClose={() => setWelcomeVisible(false)}
|
|
||||||
showAtStartup={showWelcomeAtStartup}
|
|
||||||
onCheck={(value) =>
|
|
||||||
store.dispatch({
|
|
||||||
type: 'UPDATE_SETTINGS',
|
|
||||||
payload: {...settings, showWelcomeAtStartup: value},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -30,21 +30,20 @@ import {useDispatch, useStore} from '../utils/useStore';
|
|||||||
import config from '../fb-stubs/config';
|
import config from '../fb-stubs/config';
|
||||||
import {isConnected, currentUser, logoutUser} from '../fb-stubs/user';
|
import {isConnected, currentUser, logoutUser} from '../fb-stubs/user';
|
||||||
import {showLoginDialog} from '../chrome/fb-stubs/SignInSheet';
|
import {showLoginDialog} from '../chrome/fb-stubs/SignInSheet';
|
||||||
import {Avatar, Badge, Button, Modal, Popover, Tooltip} from 'antd';
|
import {Avatar, Badge, Button, Menu, Modal, Popover, Tooltip} from 'antd';
|
||||||
import {
|
import {
|
||||||
ApiOutlined,
|
ApiOutlined,
|
||||||
AppstoreAddOutlined,
|
AppstoreAddOutlined,
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
BugOutlined,
|
BugOutlined,
|
||||||
CameraOutlined,
|
CameraOutlined,
|
||||||
EllipsisOutlined,
|
|
||||||
FileExclamationOutlined,
|
FileExclamationOutlined,
|
||||||
LayoutOutlined,
|
LayoutOutlined,
|
||||||
LoginOutlined,
|
LoginOutlined,
|
||||||
MedicineBoxOutlined,
|
MedicineBoxOutlined,
|
||||||
MobileOutlined,
|
MobileOutlined,
|
||||||
QuestionCircleOutlined,
|
|
||||||
RocketOutlined,
|
RocketOutlined,
|
||||||
|
SettingOutlined,
|
||||||
VideoCameraOutlined,
|
VideoCameraOutlined,
|
||||||
WarningOutlined,
|
WarningOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -62,10 +61,22 @@ import NetworkGraph from '../chrome/NetworkGraph';
|
|||||||
import {errorCounterAtom} from '../chrome/ConsoleLogs';
|
import {errorCounterAtom} from '../chrome/ConsoleLogs';
|
||||||
import {filterNotifications} from './notification/notificationUtils';
|
import {filterNotifications} from './notification/notificationUtils';
|
||||||
import {
|
import {
|
||||||
|
canFileExport,
|
||||||
|
canOpenDialog,
|
||||||
exportEverythingEverywhereAllAtOnce,
|
exportEverythingEverywhereAllAtOnce,
|
||||||
ExportEverythingEverywhereAllAtOnceStatus,
|
ExportEverythingEverywhereAllAtOnceStatus,
|
||||||
|
showOpenDialog,
|
||||||
|
startFileExport,
|
||||||
|
startLinkExport,
|
||||||
} from '../utils/exportData';
|
} from '../utils/exportData';
|
||||||
|
import UpdateIndicator from '../chrome/UpdateIndicator';
|
||||||
import {css} from '@emotion/css';
|
import {css} from '@emotion/css';
|
||||||
|
import constants from '../fb-stubs/constants';
|
||||||
|
import {setStaticView} from '../reducers/connections';
|
||||||
|
import {StyleGuide} from './StyleGuide';
|
||||||
|
import {openDeeplinkDialog} from '../deeplink';
|
||||||
|
import SettingsSheet from '../chrome/SettingsSheet';
|
||||||
|
import WelcomeScreen from './WelcomeScreen';
|
||||||
|
|
||||||
export const Navbar = withTrackingScope(function Navbar({
|
export const Navbar = withTrackingScope(function Navbar({
|
||||||
toplevelSelection,
|
toplevelSelection,
|
||||||
@@ -126,12 +137,12 @@ export const Navbar = withTrackingScope(function Navbar({
|
|||||||
toplevelSelection={toplevelSelection}
|
toplevelSelection={toplevelSelection}
|
||||||
setToplevelSelection={setToplevelSelection}
|
setToplevelSelection={setToplevelSelection}
|
||||||
/>
|
/>
|
||||||
|
<ExtrasMenu />
|
||||||
<SetupDoctorButton />
|
<SetupDoctorButton />
|
||||||
<ExportEverythingEverywhereAllAtOnceButton />
|
<ExportEverythingEverywhereAllAtOnceButton />
|
||||||
<NavbarButton label="Help" icon={QuestionCircleOutlined} />
|
|
||||||
<NavbarButton label="More" icon={EllipsisOutlined} />
|
|
||||||
<RightSidebarToggleButton />
|
<RightSidebarToggleButton />
|
||||||
{config.showLogin && <LoginConnectivityButton />}
|
{config.showLogin && <LoginConnectivityButton />}
|
||||||
|
<UpdateIndicator />
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
);
|
);
|
||||||
@@ -607,3 +618,131 @@ export function LeftRailButton({
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const menu = css`
|
||||||
|
border: none;
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
hieght: 56px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const submenu = css`
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
width: 61px !important;
|
||||||
|
height: 56px !important;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.ant-menu-submenu-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ExtrasMenu() {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const startFileExportTracked = useTrackedCallback(
|
||||||
|
'File export',
|
||||||
|
() => startFileExport(store.dispatch),
|
||||||
|
[store.dispatch],
|
||||||
|
);
|
||||||
|
const startLinkExportTracked = useTrackedCallback(
|
||||||
|
'Link export',
|
||||||
|
() => startLinkExport(store.dispatch),
|
||||||
|
[store.dispatch],
|
||||||
|
);
|
||||||
|
const startImportTracked = useTrackedCallback(
|
||||||
|
'File import',
|
||||||
|
() => showOpenDialog(store),
|
||||||
|
[store],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
|
const onSettingsClose = useCallback(() => setShowSettings(false), []);
|
||||||
|
|
||||||
|
const settings = useStore((state) => state.settingsState);
|
||||||
|
const {showWelcomeAtStartup} = settings;
|
||||||
|
const [welcomeVisible, setWelcomeVisible] = useState(showWelcomeAtStartup);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NUX
|
||||||
|
title="Find import, export, deeplink, feedback, settings, and help (welcome) here"
|
||||||
|
placement="right">
|
||||||
|
<Menu
|
||||||
|
mode="vertical"
|
||||||
|
className={menu}
|
||||||
|
selectable={false}
|
||||||
|
style={{backgroundColor: theme.backgroundDefault}}>
|
||||||
|
<Menu.SubMenu
|
||||||
|
popupOffset={[-50, 50]}
|
||||||
|
key="extras"
|
||||||
|
title={<NavbarButton icon={SettingOutlined} label="More" />}
|
||||||
|
className={submenu}>
|
||||||
|
{canOpenDialog() ? (
|
||||||
|
<Menu.Item key="importFlipperFile" onClick={startImportTracked}>
|
||||||
|
Import Flipper file
|
||||||
|
</Menu.Item>
|
||||||
|
) : null}
|
||||||
|
{canFileExport() ? (
|
||||||
|
<Menu.Item key="exportFile" onClick={startFileExportTracked}>
|
||||||
|
Export Flipper file
|
||||||
|
</Menu.Item>
|
||||||
|
) : null}
|
||||||
|
{constants.ENABLE_SHAREABLE_LINK ? (
|
||||||
|
<Menu.Item
|
||||||
|
key="exportShareableLink"
|
||||||
|
onClick={startLinkExportTracked}>
|
||||||
|
Export shareable link
|
||||||
|
</Menu.Item>
|
||||||
|
) : null}
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.SubMenu title="Plugin developers">
|
||||||
|
<Menu.Item
|
||||||
|
key="styleguide"
|
||||||
|
onClick={() => {
|
||||||
|
store.dispatch(setStaticView(StyleGuide));
|
||||||
|
}}>
|
||||||
|
Flipper Style Guide
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
key="triggerDeeplink"
|
||||||
|
onClick={() => openDeeplinkDialog(store)}>
|
||||||
|
Trigger deeplink
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item key="settings" onClick={() => setShowSettings(true)}>
|
||||||
|
Settings
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="help" onClick={() => setWelcomeVisible(true)}>
|
||||||
|
Help
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
</NUX>
|
||||||
|
{showSettings && (
|
||||||
|
<SettingsSheet
|
||||||
|
platform={
|
||||||
|
getRenderHostInstance().serverConfig.environmentInfo.os.platform
|
||||||
|
}
|
||||||
|
onHide={onSettingsClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<WelcomeScreen
|
||||||
|
visible={welcomeVisible}
|
||||||
|
onClose={() => setWelcomeVisible(false)}
|
||||||
|
showAtStartup={showWelcomeAtStartup}
|
||||||
|
onCheck={(value) =>
|
||||||
|
store.dispatch({
|
||||||
|
type: 'UPDATE_SETTINGS',
|
||||||
|
payload: {...settings, showWelcomeAtStartup: value},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {theme} from 'flipper-plugin';
|
|||||||
import {Logger} from 'flipper-common';
|
import {Logger} from 'flipper-common';
|
||||||
|
|
||||||
import {Navbar} from './Navbar';
|
import {Navbar} from './Navbar';
|
||||||
import {LeftRail} from './LeftRail';
|
|
||||||
import {useStore, useDispatch} from '../utils/useStore';
|
import {useStore, useDispatch} from '../utils/useStore';
|
||||||
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
||||||
import {setStaticView} from '../reducers/connections';
|
import {setStaticView} from '../reducers/connections';
|
||||||
@@ -173,9 +172,12 @@ export function SandyApp() {
|
|||||||
toplevelSelection={toplevelSelection}
|
toplevelSelection={toplevelSelection}
|
||||||
setToplevelSelection={setToplevelSelection}
|
setToplevelSelection={setToplevelSelection}
|
||||||
/>
|
/>
|
||||||
<Layout.Left>
|
<Layout.Left
|
||||||
|
style={{
|
||||||
|
paddingLeft: theme.space.large,
|
||||||
|
paddingRight: theme.space.large,
|
||||||
|
}}>
|
||||||
<Layout.Horizontal>
|
<Layout.Horizontal>
|
||||||
<LeftRail />
|
|
||||||
<_Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
<_Sidebar width={250} minWidth={220} maxWidth={800} gutter>
|
||||||
{leftMenuContent && (
|
{leftMenuContent && (
|
||||||
<TrackingScope scope={toplevelSelection!}>
|
<TrackingScope scope={toplevelSelection!}>
|
||||||
|
|||||||
Reference in New Issue
Block a user