Add import/export left rail menu

Summary: See D32311662 for details

Reviewed By: mweststrate

Differential Revision: D32316987

fbshipit-source-id: c2d173f981cce1b148f463d981977e23258ffc02
This commit is contained in:
Andrey Goncharov
2021-11-12 07:12:18 -08:00
committed by Facebook GitHub Bot
parent 32f722264f
commit 2591d1629e
4 changed files with 124 additions and 22 deletions

View File

@@ -376,7 +376,7 @@ function getTemplate(
{ {
label: 'Trigger deeplink...', label: 'Trigger deeplink...',
click() { click() {
openDeeplinkDialog(store, logger); openDeeplinkDialog(store);
}, },
}, },
{ {

View File

@@ -8,7 +8,7 @@
*/ */
import {Group, SUPPORTED_GROUPS} from './reducers/supportForm'; import {Group, SUPPORTED_GROUPS} from './reducers/supportForm';
import {Logger} from 'flipper-common'; import {getLogger, Logger} from 'flipper-common';
import {Store} from './reducers/index'; import {Store} from './reducers/index';
import {importDataToStore} from './utils/exportData'; import {importDataToStore} from './utils/exportData';
import {selectPlugin, getAllClients} from './reducers/connections'; import {selectPlugin, getAllClients} from './reducers/connections';
@@ -150,13 +150,13 @@ export const uriComponents = (url: string): Array<string> => {
return []; return [];
}; };
export function openDeeplinkDialog(store: Store, logger: Logger) { export function openDeeplinkDialog(store: Store) {
Dialog.prompt({ Dialog.prompt({
title: 'Open deeplink', title: 'Open deeplink',
message: 'Enter a deeplink:', message: 'Enter a deeplink:',
defaultValue: 'flipper://', defaultValue: 'flipper://',
onConfirm: async (deeplink) => { onConfirm: async (deeplink) => {
await handleDeeplink(store, logger, deeplink); await handleDeeplink(store, getLogger(), deeplink);
return deeplink; return deeplink;
}, },
}); });

View File

@@ -8,7 +8,7 @@
*/ */
import React, {cloneElement, useState, useCallback, useMemo} from 'react'; import React, {cloneElement, useState, useCallback, useMemo} from 'react';
import {Button, Divider, Badge, Tooltip, Avatar, Popover} from 'antd'; import {Button, Divider, Badge, Tooltip, Avatar, Popover, Menu} from 'antd';
import { import {
MobileFilled, MobileFilled,
AppstoreOutlined, AppstoreOutlined,
@@ -20,6 +20,7 @@ import {
QuestionCircleOutlined, QuestionCircleOutlined,
MedicineBoxOutlined, MedicineBoxOutlined,
RocketOutlined, RocketOutlined,
SwapOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import {SidebarLeft, SidebarRight} from './SandyIcons'; import {SidebarLeft, SidebarRight} from './SandyIcons';
import {useDispatch, useStore} from '../utils/useStore'; import {useDispatch, useStore} from '../utils/useStore';
@@ -27,7 +28,13 @@ import {
toggleLeftSidebarVisible, toggleLeftSidebarVisible,
toggleRightSidebarVisible, toggleRightSidebarVisible,
} from '../reducers/application'; } from '../reducers/application';
import {theme, Layout, withTrackingScope, Dialog} from 'flipper-plugin'; import {
theme,
Layout,
withTrackingScope,
Dialog,
useTrackedCallback,
} from 'flipper-plugin';
import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen'; import SetupDoctorScreen, {checkHasNewProblem} from './SetupDoctorScreen';
import SettingsSheet from '../chrome/SettingsSheet'; import SettingsSheet from '../chrome/SettingsSheet';
import WelcomeScreen from './WelcomeScreen'; import WelcomeScreen from './WelcomeScreen';
@@ -50,6 +57,17 @@ import FpsGraph from '../chrome/FpsGraph';
import UpdateIndicator from '../chrome/UpdateIndicator'; import UpdateIndicator from '../chrome/UpdateIndicator';
import PluginManager from '../chrome/plugin-manager/PluginManager'; import PluginManager from '../chrome/plugin-manager/PluginManager';
import {showLoginDialog} from '../chrome/fb-stubs/SignInSheet'; import {showLoginDialog} from '../chrome/fb-stubs/SignInSheet';
import SubMenu from 'antd/lib/menu/SubMenu';
import constants from '../fb-stubs/constants';
import {
canFileExport,
canOpenDialog,
showOpenDialog,
startFileExport,
startLinkExport,
} from '../utils/exportData';
import {openDeeplinkDialog} from '../deeplink';
import {css} from '@emotion/css';
const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({ const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({
width: kind === 'small' ? 32 : 36, width: kind === 'small' ? 32 : 36,
@@ -76,7 +94,7 @@ export function LeftRailButton({
selected?: boolean; selected?: boolean;
disabled?: boolean; disabled?: boolean;
count?: number | true; count?: number | true;
title: string; title?: string;
onClick?: React.MouseEventHandler<HTMLElement>; onClick?: React.MouseEventHandler<HTMLElement>;
}) { }) {
let iconElement = let iconElement =
@@ -89,22 +107,31 @@ export function LeftRailButton({
<Badge count={count}>{iconElement}</Badge> <Badge count={count}>{iconElement}</Badge>
); );
} }
return (
<Tooltip title={title} placement="right"> let res = (
<LeftRailButtonElem <LeftRailButtonElem
title={title} title={title}
kind={small ? 'small' : undefined} kind={small ? 'small' : undefined}
type={selected ? 'primary' : 'ghost'} type={selected ? 'primary' : 'ghost'}
icon={iconElement} icon={iconElement}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
style={{ style={{
color: toggled ? theme.primaryColor : undefined, color: toggled ? theme.primaryColor : undefined,
background: toggled ? theme.backgroundWash : undefined, background: toggled ? theme.backgroundWash : undefined,
}} }}
/> />
</Tooltip>
); );
if (title) {
res = (
<Tooltip title={title} placement="right">
{res}
</Tooltip>
);
}
return res;
} }
const LeftRailDivider = styled(Divider)({ const LeftRailDivider = styled(Divider)({
@@ -163,6 +190,7 @@ export const LeftRail = withTrackingScope(function LeftRail({
<SupportFormButton /> <SupportFormButton />
<RightSidebarToggleButton /> <RightSidebarToggleButton />
<LeftSidebarToggleButton /> <LeftSidebarToggleButton />
<ImportExportButton />
{config.showLogin && <LoginButton />} {config.showLogin && <LoginButton />}
</Layout.Container> </Layout.Container>
</Layout.Bottom> </Layout.Bottom>
@@ -170,6 +198,72 @@ export const LeftRail = withTrackingScope(function LeftRail({
); );
}); });
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 ImportExportButton() {
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],
);
return (
<Menu mode="vertical" className={menu}>
<SubMenu
popupOffset={[10, 0]}
key="importexport"
title={<LeftRailButton icon={<SwapOutlined />} small />}
className={submenu}>
{canFileExport() ? (
<Menu.Item key="exportFile" onClick={startFileExportTracked}>
Export file
</Menu.Item>
) : null}
{constants.ENABLE_SHAREABLE_LINK ? (
<Menu.Item key="exportShareableLink" onClick={startLinkExportTracked}>
Export shareable link
</Menu.Item>
) : null}
{canOpenDialog() ? (
<Menu.Item key="importFlipperFile" onClick={startImportTracked}>
Import Flipper file
</Menu.Item>
) : null}
<Menu.Item
key="triggerDeeplink"
onClick={() => openDeeplinkDialog(store)}>
Trigger deeplink
</Menu.Item>
</SubMenu>
</Menu>
);
}
function LeftSidebarToggleButton() { function LeftSidebarToggleButton() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const mainMenuVisible = useStore( const mainMenuVisible = useStore(

View File

@@ -599,6 +599,10 @@ export const importFileToStore = (file: string, store: Store) => {
}); });
}; };
export function canOpenDialog() {
return !!getRenderHostInstance().showOpenDialog;
}
export function showOpenDialog(store: Store) { export function showOpenDialog(store: Store) {
return getRenderHostInstance() return getRenderHostInstance()
.showOpenDialog?.({ .showOpenDialog?.({
@@ -613,6 +617,10 @@ export function showOpenDialog(store: Store) {
}); });
} }
export function canFileExport() {
return !!getRenderHostInstance().showSaveDialog;
}
export async function startFileExport(dispatch: Store['dispatch']) { export async function startFileExport(dispatch: Store['dispatch']) {
const file = await getRenderHostInstance().showSaveDialog?.({ const file = await getRenderHostInstance().showSaveDialog?.({
title: 'FlipperExport', title: 'FlipperExport',