diff --git a/desktop/app/src/Client.tsx b/desktop/app/src/Client.tsx index 24f5bc0c5..f1b5d50cc 100644 --- a/desktop/app/src/Client.tsx +++ b/desktop/app/src/Client.tsx @@ -26,11 +26,7 @@ import {processMessagesLater} from './utils/messageQueue'; import {emitBytesReceived} from './dispatcher/tracking'; import {debounce} from 'lodash'; import {batch} from 'react-redux'; -import { - createState, - _SandyPluginInstance, - _getFlipperLibImplementation, -} from 'flipper-plugin'; +import {createState, _SandyPluginInstance, getFlipperLib} from 'flipper-plugin'; import {freeze} from 'immer'; import GK from './fb-stubs/GK'; import {message} from 'antd'; @@ -224,7 +220,7 @@ export default class Client extends EventEmitter { this.sandyPluginStates.set( plugin.id, new _SandyPluginInstance( - _getFlipperLibImplementation(), + getFlipperLib(), plugin, this, getPluginKey(this.id, {serial: this.query.device_id}, plugin.id), @@ -260,7 +256,7 @@ export default class Client extends EventEmitter { this.sandyPluginStates.set( plugin.id, new _SandyPluginInstance( - _getFlipperLibImplementation(), + getFlipperLib(), plugin, this, getPluginKey(this.id, {serial: this.query.device_id}, plugin.id), diff --git a/desktop/app/src/MenuBar.tsx b/desktop/app/src/MenuBar.tsx index 5bdb7a82c..dfbbad388 100644 --- a/desktop/app/src/MenuBar.tsx +++ b/desktop/app/src/MenuBar.tsx @@ -29,6 +29,7 @@ import { NormalizedMenuEntry, _buildInMenuEntries, _wrapInteractionHandler, + getFlipperLib, } from 'flipper-plugin'; import {StyleGuide} from './sandy-chrome/StyleGuide'; import {showEmulatorLauncher} from './sandy-chrome/appinspect/LaunchEmulator'; @@ -373,19 +374,21 @@ function getTemplate( { label: 'Getting started', click: function () { - shell.openExternal('https://fbflipper.com/docs/getting-started/index'); + getFlipperLib().openLink( + 'https://fbflipper.com/docs/getting-started/index', + ); }, }, { label: 'Create plugins', click: function () { - shell.openExternal('https://fbflipper.com/docs/tutorial/intro'); + getFlipperLib().openLink('https://fbflipper.com/docs/tutorial/intro'); }, }, { label: 'Report problems', click: function () { - shell.openExternal(constants.FEEDBACK_GROUP_LINK); + getFlipperLib().openLink(constants.FEEDBACK_GROUP_LINK); }, }, { diff --git a/desktop/app/src/NotificationsHub.tsx b/desktop/app/src/NotificationsHub.tsx index f0b490933..b3c0c76ab 100644 --- a/desktop/app/src/NotificationsHub.tsx +++ b/desktop/app/src/NotificationsHub.tsx @@ -24,7 +24,6 @@ import { import {PluginDefinition, DevicePluginMap, ClientPluginMap} from './plugin'; import {connect} from 'react-redux'; import React, {Component, Fragment} from 'react'; -import {clipboard} from 'electron'; import { PluginNotification, updatePluginBlocklist, @@ -35,6 +34,7 @@ import {State as StoreState} from './reducers/index'; import textContent from './utils/textContent'; import createPaste from './fb-stubs/createPaste'; import {getPluginTitle} from './utils/pluginUtils'; +import {getFlipperLib} from 'flipper-plugin'; type OwnProps = { onClear: () => void; @@ -459,7 +459,7 @@ class NotificationItem extends Component< createPaste(this.getContent()); }; - copy = () => clipboard.writeText(this.getContent()); + copy = () => getFlipperLib().writeTextToClipboard(this.getContent()); getContent = (): string => [ diff --git a/desktop/app/src/chrome/DoctorSheet.tsx b/desktop/app/src/chrome/DoctorSheet.tsx index 98a6e7632..df3ee6834 100644 --- a/desktop/app/src/chrome/DoctorSheet.tsx +++ b/desktop/app/src/chrome/DoctorSheet.tsx @@ -37,7 +37,7 @@ import runHealthchecks, { HealthcheckSettings, HealthcheckEventsHandler, } from '../utils/runHealthchecks'; -import {shell} from 'electron'; +import {getFlipperLib} from 'flipper-plugin'; import {reportUsage} from '../utils/metrics'; type StateFromProps = { @@ -295,7 +295,7 @@ class DoctorSheet extends Component { } openHelpUrl(helpUrl?: string): void { - helpUrl && shell.openExternal(helpUrl); + helpUrl && getFlipperLib().openLink(helpUrl); } async runHealthchecks(): Promise { diff --git a/desktop/app/src/chrome/ShareSheetExportUrl.tsx b/desktop/app/src/chrome/ShareSheetExportUrl.tsx index 3d38c6553..2934f7150 100644 --- a/desktop/app/src/chrome/ShareSheetExportUrl.tsx +++ b/desktop/app/src/chrome/ShareSheetExportUrl.tsx @@ -27,7 +27,6 @@ import { EXPORT_FLIPPER_TRACE_EVENT, displayFetchMetadataErrors, } from '../utils/exportData'; -import {clipboard} from 'electron'; import ShareSheetErrorList from './ShareSheetErrorList'; import {reportPlatformFailures} from '../utils/metrics'; import CancellableExportStatus from './CancellableExportStatus'; @@ -36,6 +35,7 @@ import ShareSheetPendingDialog from './ShareSheetPendingDialog'; import {getInstance as getLogger} from '../fb-stubs/Logger'; import {resetSupportFormV2State} from '../reducers/supportForm'; import {MiddlewareAPI} from '../reducers/index'; +import {getFlipperLib} from 'flipper-plugin'; export const SHARE_FLIPPER_TRACE_EVENT = 'share-flipper-link'; @@ -148,7 +148,7 @@ export default class ShareSheetExportUrl extends Component { if (flipperUrl) { this.store.dispatch(setExportURL(flipperUrl)); if (this.state.runInBackground) { - clipboard.writeText(String(flipperUrl)); + getFlipperLib().writeTextToClipboard(String(flipperUrl)); new Notification('Shareable Flipper Export created', { body: 'URL copied to clipboard', requireInteraction: true, diff --git a/desktop/app/src/devices/BaseDevice.tsx b/desktop/app/src/devices/BaseDevice.tsx index aa3399479..f348fb8de 100644 --- a/desktop/app/src/devices/BaseDevice.tsx +++ b/desktop/app/src/devices/BaseDevice.tsx @@ -16,7 +16,7 @@ import { DeviceLogListener, Idler, createState, - _getFlipperLibImplementation, + getFlipperLib, } from 'flipper-plugin'; import {PluginDefinition, DevicePluginMap} from '../plugin'; import {DeviceSpec, OS as PluginOS, PluginDetails} from 'flipper-plugin-lib'; @@ -245,7 +245,7 @@ export default class BaseDevice { this.sandyPluginStates.set( plugin.id, new _SandyDevicePluginInstance( - _getFlipperLibImplementation(), + getFlipperLib(), plugin, this, // break circular dep, one of those days again... diff --git a/desktop/app/src/init.tsx b/desktop/app/src/init.tsx index 78ae6c7d4..4c41728ff 100644 --- a/desktop/app/src/init.tsx +++ b/desktop/app/src/init.tsx @@ -40,13 +40,13 @@ import { _LoggerContext, Layout, theme, + getFlipperLib, } from 'flipper-plugin'; import isProduction from './utils/isProduction'; import {Button, Input, Result, Typography} from 'antd'; import constants from './fb-stubs/constants'; import styled from '@emotion/styled'; import {CopyOutlined} from '@ant-design/icons'; -import {clipboard} from 'electron/common'; import {getVersionString} from './utils/versionString'; import {PersistGate} from 'redux-persist/integration/react'; import {ipcRenderer} from 'electron'; @@ -107,7 +107,7 @@ class AppFrame extends React.Component< key="copy_error" icon={} onClick={() => { - clipboard.writeText(this.getError()); + getFlipperLib().writeTextToClipboard(this.getError()); }}> Copy error , diff --git a/desktop/app/src/sandy-chrome/WelcomeScreen.tsx b/desktop/app/src/sandy-chrome/WelcomeScreen.tsx index 74909e452..cc02c825e 100644 --- a/desktop/app/src/sandy-chrome/WelcomeScreen.tsx +++ b/desktop/app/src/sandy-chrome/WelcomeScreen.tsx @@ -23,7 +23,7 @@ const {Text, Title} = Typography; import constants from '../fb-stubs/constants'; import isProduction from '../utils/isProduction'; import {getAppVersion} from '../utils/info'; -import {shell} from 'electron'; +import {getFlipperLib} from 'flipper-plugin'; const RowContainer = styled(FlexRow)({ alignItems: 'flex-start', @@ -89,7 +89,7 @@ function WelcomeFooter({ ); } -const openExternal = (url: string) => () => shell && shell.openExternal(url); +const openExternal = (url: string) => () => getFlipperLib().openLink(url); export default function WelcomeScreen({ visible, diff --git a/desktop/app/src/ui/components/Button.tsx b/desktop/app/src/ui/components/Button.tsx index b2487822e..f6bd6f2cb 100644 --- a/desktop/app/src/ui/components/Button.tsx +++ b/desktop/app/src/ui/components/Button.tsx @@ -8,14 +8,14 @@ */ import React, {useState, useCallback, useMemo} from 'react'; -import electron, {MenuItemConstructorOptions} from 'electron'; +import {MenuItemConstructorOptions} from 'electron'; import styled from '@emotion/styled'; import {Button as AntdButton, Dropdown, Menu} from 'antd'; import Glyph, {IconSize} from './Glyph'; import type {ButtonProps} from 'antd/lib/button'; import {DownOutlined, CheckOutlined} from '@ant-design/icons'; -import {theme} from 'flipper-plugin'; +import {theme, getFlipperLib} from 'flipper-plugin'; type ButtonType = 'primary' | 'success' | 'warning' | 'danger'; @@ -124,7 +124,7 @@ function SandyButton({ } onClick?.(e); if (href != null) { - electron.shell.openExternal(href); // TODO: decouple from Electron + getFlipperLib().openLink(href); } }, [disabled, onClick, href], diff --git a/desktop/app/src/ui/components/Link.tsx b/desktop/app/src/ui/components/Link.tsx index 47e392a9e..4b8110f3a 100644 --- a/desktop/app/src/ui/components/Link.tsx +++ b/desktop/app/src/ui/components/Link.tsx @@ -7,7 +7,7 @@ * @format */ -import {shell} from 'electron'; +import {getFlipperLib} from 'flipper-plugin'; import {Typography} from 'antd'; const AntOriginalLink = Typography.Link; @@ -15,7 +15,7 @@ const AntOriginalLink = Typography.Link; // used by patch for Typography.Link in AntD // @ts-ignore global.flipperOpenLink = function openLinkExternal(url: string) { - shell.openExternal(url); + getFlipperLib().openLink(url); }; export default AntOriginalLink; diff --git a/desktop/app/src/ui/components/Markdown.tsx b/desktop/app/src/ui/components/Markdown.tsx index 6a59f3620..7efdf500d 100644 --- a/desktop/app/src/ui/components/Markdown.tsx +++ b/desktop/app/src/ui/components/Markdown.tsx @@ -11,7 +11,7 @@ import React, {CSSProperties, ReactNode} from 'react'; import styled from '@emotion/styled'; import ReactMarkdown from 'react-markdown'; import {colors} from './colors'; -import {shell} from 'electron'; +import {getFlipperLib} from 'flipper-plugin'; const Container = styled.div({ padding: 10, @@ -73,7 +73,9 @@ const Link = styled.span({ }); function LinkReference(props: {href: string; children: Array}) { return ( - shell.openExternal(props.href)}>{props.children} + getFlipperLib().openLink(props.href)}> + {props.children} + ); } diff --git a/desktop/app/src/utils/flipperLibImplementation.tsx b/desktop/app/src/utils/flipperLibImplementation.tsx index ccf2c154a..00d547c4a 100644 --- a/desktop/app/src/utils/flipperLibImplementation.tsx +++ b/desktop/app/src/utils/flipperLibImplementation.tsx @@ -13,7 +13,7 @@ import type {Store} from '../reducers'; import createPaste from '../fb-stubs/createPaste'; import GK from '../fb-stubs/GK'; import type BaseDevice from '../devices/BaseDevice'; -import {clipboard} from 'electron'; +import {clipboard, shell} from 'electron'; import constants from '../fb-stubs/constants'; import {addNotification} from '../reducers/notifications'; import {deconstructPluginKey} from './clientUtils'; @@ -50,6 +50,9 @@ export function initializeFlipperLibImplementation( writeTextToClipboard(text: string) { clipboard.writeText(text); }, + openLink(url: string) { + shell.openExternal(url); + }, showNotification(pluginId, notification) { const parts = deconstructPluginKey(pluginId); store.dispatch( diff --git a/desktop/app/src/utils/vscodeUtils.tsx b/desktop/app/src/utils/vscodeUtils.tsx index 0a5230c80..5f9893b20 100644 --- a/desktop/app/src/utils/vscodeUtils.tsx +++ b/desktop/app/src/utils/vscodeUtils.tsx @@ -7,13 +7,15 @@ * @format */ +import {getFlipperLib} from 'flipper-plugin'; import {getPreferredEditorUriScheme} from '../fb-stubs/user'; -import {shell} from 'electron'; let preferredEditorUriScheme: string | undefined = undefined; export function callVSCode(plugin: string, command: string, params?: string) { - getVSCodeUrl(plugin, command, params).then((url) => shell.openExternal(url)); + getVSCodeUrl(plugin, command, params).then((url) => + getFlipperLib().openLink(url), + ); } export async function getVSCodeUrl( diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index 73edc900f..b93d87c69 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -52,6 +52,7 @@ test('Correct top level API exposed', () => { "createDataSource", "createState", "createTablePlugin", + "getFlipperLib", "produce", "renderReactRoot", "sleep", diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index ea21f2adc..8dd008651 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -37,7 +37,7 @@ export {createState, useValue, Atom} from './state/atom'; export {batch} from './state/batch'; export { FlipperLib, - getFlipperLibImplementation as _getFlipperLibImplementation, + getFlipperLib, setFlipperLibImplementation as _setFlipperLibImplementation, } from './plugin/FlipperLib'; export { diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx index 5ae8ab320..de9f93a2c 100644 --- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx +++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx @@ -30,19 +30,20 @@ export interface FlipperLib { deeplink: unknown, ): void; writeTextToClipboard(text: string): void; + openLink(url: string): void; showNotification(pluginKey: string, notification: Notification): void; DetailsSidebarImplementation?( props: DetailSidebarProps, ): React.ReactElement | null; } -let flipperLibInstance: FlipperLib | undefined; +export let flipperLibInstance: FlipperLib | undefined; export function tryGetFlipperLibImplementation(): FlipperLib | undefined { return flipperLibInstance; } -export function getFlipperLibImplementation(): FlipperLib { +export function getFlipperLib(): FlipperLib { if (!flipperLibInstance) { throw new Error('Flipper lib not instantiated'); } diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index ab28d76de..858695c10 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -374,6 +374,7 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { }, selectPlugin: jest.fn(), writeTextToClipboard: jest.fn(), + openLink: jest.fn(), showNotification: jest.fn(), }; } diff --git a/desktop/flipper-plugin/src/ui/elements-inspector/elements.tsx b/desktop/flipper-plugin/src/ui/elements-inspector/elements.tsx index 839ccd872..b284ff340 100644 --- a/desktop/flipper-plugin/src/ui/elements-inspector/elements.tsx +++ b/desktop/flipper-plugin/src/ui/elements-inspector/elements.tsx @@ -14,7 +14,7 @@ import styled from '@emotion/styled'; import React, {MouseEvent, KeyboardEvent} from 'react'; import {theme} from '../theme'; import {Layout} from '../Layout'; -import {_getFlipperLibImplementation} from 'flipper-plugin'; +import {getFlipperLib} from 'flipper-plugin'; import {DownOutlined, RightOutlined} from '@ant-design/icons'; const {Text} = Typography; @@ -221,7 +221,7 @@ class ElementsRow extends PureComponent { { label: 'Copy', click: () => { - _getFlipperLibImplementation()?.writeTextToClipboard( + getFlipperLib()?.writeTextToClipboard( props.onCopyExpandedTree(props.element, 0), ); }, @@ -229,7 +229,7 @@ class ElementsRow extends PureComponent { { label: 'Copy expanded child elements', click: () => - _getFlipperLibImplementation()?.writeTextToClipboard( + getFlipperLib()?.writeTextToClipboard( props.onCopyExpandedTree(props.element, 255), ), }, @@ -253,7 +253,7 @@ class ElementsRow extends PureComponent { return { label: `Copy ${o.name}`, click: () => { - _getFlipperLibImplementation()?.writeTextToClipboard(o.value); + getFlipperLib()?.writeTextToClipboard(o.value); }, }; }), @@ -555,9 +555,7 @@ export class Elements extends PureComponent { ) { e.stopPropagation(); e.preventDefault(); - _getFlipperLibImplementation()?.writeTextToClipboard( - selectedElement.name, - ); + getFlipperLib()?.writeTextToClipboard(selectedElement.name); return; } diff --git a/desktop/plugins/public/hermesdebuggerrn/Banner.tsx b/desktop/plugins/public/hermesdebuggerrn/Banner.tsx index ce4d3918e..fc01d36da 100644 --- a/desktop/plugins/public/hermesdebuggerrn/Banner.tsx +++ b/desktop/plugins/public/hermesdebuggerrn/Banner.tsx @@ -8,8 +8,8 @@ */ import React from 'react'; -import {shell} from 'electron'; import {styled, colors, FlexRow, Text, GK} from 'flipper'; +import {Typography} from 'antd'; const BannerContainer = styled(FlexRow)({ height: '30px', @@ -34,14 +34,6 @@ const BannerLink = styled(CustomLink)({ }, }); -const StyledLink = styled.span({ - '&:hover': { - cursor: 'pointer', - }, -}); - -StyledLink.displayName = 'CustomLink:StyledLink'; - function CustomLink(props: { href: string; className?: string; @@ -49,12 +41,12 @@ function CustomLink(props: { style?: React.CSSProperties; }) { return ( - shell.openExternal(props.href)} + href={props.href} style={props.style}> {props.children || props.href} - + ); } diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 7cffd1ed1..679e77b2e 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -935,6 +935,13 @@ See `View > Flipper Style Guide` inside the Flipper application for more details ## Utilities +### getFlipperLib + +A set of globally available utilities like opening links, interacting with the clipboard, etc. +Example: `getFlipperLib().writeTextToClipboard("hello from Flipper"); + +The full set of utilities can be found [here](https://github.com/facebook/flipper/blob/master/desktop/flipper-plugin/src/plugin/FlipperLib.tsx#L20) + ### createTablePlugin Utility to create a plugin that consists of a master table and details JSON view with minimal effort. See the [Showing a table](../tutorial/js-table.mdx) tutorial for an example.