diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 68c1fa70ba401..6cabe7d78f7fa 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -201,6 +201,7 @@ export default class Agent extends EventEmitter<{| bridge.addListener('updateComponentFilters', this.updateComponentFilters); bridge.addListener('viewAttributeSource', this.viewAttributeSource); bridge.addListener('viewElementSource', this.viewElementSource); + bridge.addListener('launchEditor', this.launchEditor); // Temporarily support older standalone front-ends sending commands to newer embedded backends. // We do this because React Native embeds the React DevTools backend, @@ -655,6 +656,27 @@ export default class Agent extends EventEmitter<{| } }; + launchEditor = ({launchEditorEndpoint, fileName, lineNumber}) => { + fetch( + `/${launchEditorEndpoint}?fileName=${fileName}&lineNumber=${lineNumber}`, + ) + .then(res => { + if (res.ok) { + console.log(`open ${fileName} in editor success`); + } else { + console.warn(`open ${fileName} in editor failed`); + console.warn( + 'Please make sure the open editor server middleware(e.g. react-dev-utils) installed correctly!', + ); + } + }) + .catch(_ => { + console.error( + 'make sure the open editor server middleware(e.g. react-dev-utils) installed correctly!', + ); + }); + }; + viewElementSource = ({id, rendererID}: ElementAndRendererID) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index dad25e93258c1..af6113ac2cd2f 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -119,6 +119,12 @@ type UpdateConsolePatchSettingsParams = {| showInlineWarningsAndErrors: boolean, |}; +type LauchEditorParams = {| + launchEditorEndpoint: string, + fileName: string, + lineNumber: string, +|}; + export type BackendEvents = {| extensionBackendInitialized: [], inspectedElement: [InspectedElementPayload], @@ -173,6 +179,7 @@ type FrontendEvents = {| updateConsolePatchSettings: [UpdateConsolePatchSettingsParams], viewAttributeSource: [ViewAttributeSourceParams], viewElementSource: [ElementAndRendererID], + launchEditor: [LauchEditorParams], // React Native style editor plug-in. NativeStyleEditor_measure: [ElementAndRendererID], diff --git a/packages/react-devtools-shared/src/devtools/views/ButtonIcon.js b/packages/react-devtools-shared/src/devtools/views/ButtonIcon.js index 0c7fe00b42aac..f5d990696269e 100644 --- a/packages/react-devtools-shared/src/devtools/views/ButtonIcon.js +++ b/packages/react-devtools-shared/src/devtools/views/ButtonIcon.js @@ -36,6 +36,7 @@ export type IconType = | 'undo' | 'up' | 'view-dom' + | 'launch-editor' | 'view-source'; type Props = {| @@ -124,6 +125,9 @@ export default function ButtonIcon({className = '', type}: Props) { case 'view-source': pathData = PATH_VIEW_SOURCE; break; + case 'launch-editor': + pathData = PATH_LAUNCH_EDITOR; + break; default: console.warn(`Unsupported type "${type}" specified for ButtonIcon`); break; @@ -244,3 +248,9 @@ const PATH_VIEW_DOM = ` const PATH_VIEW_SOURCE = ` M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z `; + +const PATH_LAUNCH_EDITOR = `M896 435.2a25.6 25.6 0 0 1-25.6-25.6v-256H640a25.6 25.6 0 1 1 0-51.2h256a25.6 25.6 0 0 1 25.6 + 25.6v281.6a25.6 25.6 0 0 1-25.6 25.6zM844.8 921.6H128a25.6 25.6 0 0 1-25.6-25.6V179.2a25.6 25.6 0 0 1 25.6-25.6h358.4a25.6 + 25.6 0 1 1 0 51.2H153.6v665.6h665.6V537.6a25.6 25.6 0 1 1 51.2 0V896a25.6 25.6 0 0 1-25.6 25.6zM533.952 515.648a25.6 + 25.6 0 0 1-18.112-43.712l362.048-362.048a25.6 25.6 0 0 1 36.224 36.224L552.064 508.16a25.472 25.472 0 0 1-18.112 7.488z +`; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index 4d8662d80dae5..3191325925fac 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -12,6 +12,7 @@ import * as React from 'react'; import {Fragment, useCallback, useContext} from 'react'; import {TreeDispatcherContext} from './TreeContext'; import {BridgeContext, ContextMenuContext, StoreContext} from '../context'; +import {SettingsContext} from '../Settings/SettingsContext'; import ContextMenu from '../../ContextMenu/ContextMenu'; import ContextMenuItem from '../../ContextMenu/ContextMenuItem'; import Button from '../Button'; @@ -241,11 +242,26 @@ type SourceProps = {| |}; function Source({fileName, lineNumber}: SourceProps) { + const bridge = useContext(BridgeContext); + const {launchEditorEndpoint} = useContext(SettingsContext); + const handleCopy = () => copy(`${fileName}:${lineNumber}`); + const handleLaunchEditor = () => { + bridge.send('launchEditor', { + launchEditorEndpoint, + fileName, + lineNumber, + }); + }; return (