88 */
99
1010import * as React from 'react' ;
11- import { useCallback , useContext } from 'react' ;
11+ import { useCallback , useContext , useSyncExternalStore } from 'react' ;
1212import { TreeDispatcherContext , TreeStateContext } from './TreeContext' ;
1313import { BridgeContext , StoreContext , OptionsContext } from '../context' ;
1414import Button from '../Button' ;
@@ -20,6 +20,8 @@ import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
2020import CannotSuspendWarningMessage from './CannotSuspendWarningMessage' ;
2121import InspectedElementView from './InspectedElementView' ;
2222import { InspectedElementContext } from './InspectedElementContext' ;
23+ import { getOpenInEditorURL } from '../../../utils' ;
24+ import { LOCAL_STORAGE_OPEN_IN_EDITOR_URL } from '../../../constants' ;
2325
2426import styles from './InspectedElement.css' ;
2527
@@ -123,6 +125,21 @@ export default function InspectedElementWrapper(_: Props) {
123125 inspectedElement != null &&
124126 inspectedElement . canToggleSuspense ;
125127
128+ const editorURL = useSyncExternalStore (
129+ function subscribe ( callback ) {
130+ window . addEventListener ( LOCAL_STORAGE_OPEN_IN_EDITOR_URL , callback ) ;
131+ return function unsubscribe ( ) {
132+ window . removeEventListener ( LOCAL_STORAGE_OPEN_IN_EDITOR_URL , callback ) ;
133+ } ;
134+ } ,
135+ function getState ( ) {
136+ return getOpenInEditorURL ( ) ;
137+ } ,
138+ ) ;
139+
140+ const canOpenInEditor =
141+ editorURL && inspectedElement != null && inspectedElement . source != null ;
142+
126143 const toggleErrored = useCallback ( ( ) => {
127144 if ( inspectedElement == null || targetErrorBoundaryID == null ) {
128145 return ;
@@ -198,6 +215,18 @@ export default function InspectedElementWrapper(_: Props) {
198215 }
199216 } , [ bridge , dispatch , element , isSuspended , modalDialogDispatch , store ] ) ;
200217
218+ const onOpenInEditor = useCallback ( ( ) => {
219+ const source = inspectedElement ?. source ;
220+ if ( source == null || editorURL == null ) {
221+ return ;
222+ }
223+
224+ const url = new URL ( editorURL ) ;
225+ url . href = url . href . replace ( '{path}' , source . fileName ) ;
226+ url . href = url . href . replace ( '{line}' , String ( source . lineNumber ) ) ;
227+ window . open ( url ) ;
228+ } , [ inspectedElement , editorURL ] ) ;
229+
201230 if ( element === null ) {
202231 return (
203232 < div className = { styles . InspectedElement } >
@@ -223,7 +252,14 @@ export default function InspectedElementWrapper(_: Props) {
223252 { element . displayName }
224253 </ div >
225254 </ div >
226-
255+ { canOpenInEditor && (
256+ < Button
257+ className = { styles . IconButton }
258+ onClick = { onOpenInEditor }
259+ title = "Open in editor" >
260+ < ButtonIcon type = "editor" />
261+ </ Button >
262+ ) }
227263 { canToggleError && (
228264 < Toggle
229265 className = { styles . IconButton }
0 commit comments