8
8
*/
9
9
10
10
import * as React from 'react' ;
11
- import { useCallback , useContext } from 'react' ;
11
+ import { useCallback , useContext , useSyncExternalStore } from 'react' ;
12
12
import { TreeDispatcherContext , TreeStateContext } from './TreeContext' ;
13
13
import { BridgeContext , StoreContext , OptionsContext } from '../context' ;
14
14
import Button from '../Button' ;
@@ -20,6 +20,8 @@ import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
20
20
import CannotSuspendWarningMessage from './CannotSuspendWarningMessage' ;
21
21
import InspectedElementView from './InspectedElementView' ;
22
22
import { InspectedElementContext } from './InspectedElementContext' ;
23
+ import { getOpenInEditorURL } from '../../../utils' ;
24
+ import { LOCAL_STORAGE_OPEN_IN_EDITOR_URL } from '../../../constants' ;
23
25
24
26
import styles from './InspectedElement.css' ;
25
27
@@ -123,6 +125,21 @@ export default function InspectedElementWrapper(_: Props) {
123
125
inspectedElement != null &&
124
126
inspectedElement . canToggleSuspense ;
125
127
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
+
126
143
const toggleErrored = useCallback ( ( ) => {
127
144
if ( inspectedElement == null || targetErrorBoundaryID == null ) {
128
145
return ;
@@ -198,6 +215,18 @@ export default function InspectedElementWrapper(_: Props) {
198
215
}
199
216
} , [ bridge , dispatch , element , isSuspended , modalDialogDispatch , store ] ) ;
200
217
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
+
201
230
if ( element === null ) {
202
231
return (
203
232
< div className = { styles . InspectedElement } >
@@ -223,7 +252,14 @@ export default function InspectedElementWrapper(_: Props) {
223
252
{ element . displayName }
224
253
</ div >
225
254
</ 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
+ ) }
227
263
{ canToggleError && (
228
264
< Toggle
229
265
className = { styles . IconButton }
0 commit comments