Skip to content

Commit 255221c

Browse files
ezzakBrian Vaughn
and
Brian Vaughn
authored
[DevTools] Add open in editor for fb (#22649)
Co-authored-by: Brian Vaughn <[email protected]>
1 parent 00ced1e commit 255221c

File tree

11 files changed

+108
-3
lines changed

11 files changed

+108
-3
lines changed

packages/react-devtools-core/webpack.standalone.js

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const __DEV__ = NODE_ENV === 'development';
3030

3131
const DEVTOOLS_VERSION = getVersionString();
3232

33+
const EDITOR_URL = process.env.EDITOR_URL || null;
3334
const LOGGING_URL = process.env.LOGGING_URL || null;
3435

3536
const featureFlagTarget =
@@ -83,6 +84,7 @@ module.exports = {
8384
__TEST__: NODE_ENV === 'test',
8485
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-core"`,
8586
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
87+
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
8688
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
8789
'process.env.LOGGING_URL': `"${LOGGING_URL}"`,
8890
'process.env.NODE_ENV': `"${NODE_ENV}"`,

packages/react-devtools-extensions/webpack.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const __DEV__ = NODE_ENV === 'development';
3232

3333
const DEVTOOLS_VERSION = getVersionString(process.env.DEVTOOLS_VERSION);
3434

35+
const EDITOR_URL = process.env.EDITOR_URL || null;
3536
const LOGGING_URL = process.env.LOGGING_URL || null;
3637

3738
const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
@@ -92,6 +93,7 @@ module.exports = {
9293
__TEST__: NODE_ENV === 'test',
9394
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`,
9495
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
96+
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
9597
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
9698
'process.env.LOGGING_URL': `"${LOGGING_URL}"`,
9799
'process.env.NODE_ENV': `"${NODE_ENV}"`,

packages/react-devtools-inline/webpack.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ if (!NODE_ENV) {
2020

2121
const __DEV__ = NODE_ENV === 'development';
2222

23+
const EDITOR_URL = process.env.EDITOR_URL || null;
24+
2325
const DEVTOOLS_VERSION = getVersionString();
2426

2527
const babelOptions = {
@@ -76,6 +78,7 @@ module.exports = {
7678
__TEST__: NODE_ENV === 'test',
7779
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-inline"`,
7880
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
81+
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
7982
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
8083
'process.env.NODE_ENV': `"${NODE_ENV}"`,
8184
'process.env.DARK_MODE_DIMMED_WARNING_COLOR': `"${DARK_MODE_DIMMED_WARNING_COLOR}"`,

packages/react-devtools-shared/src/constants.js

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
3232
export const SESSION_STORAGE_LAST_SELECTION_KEY =
3333
'React::DevTools::lastSelection';
3434

35+
export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL =
36+
'React::DevTools::openInEditorUrl';
37+
3538
export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
3639
'React::DevTools::parseHookNames';
3740

packages/react-devtools-shared/src/devtools/views/ButtonIcon.js

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type IconType =
1919
| 'copy'
2020
| 'delete'
2121
| 'down'
22+
| 'editor'
2223
| 'expanded'
2324
| 'export'
2425
| 'filter'
@@ -72,6 +73,9 @@ export default function ButtonIcon({className = '', type}: Props) {
7273
case 'down':
7374
pathData = PATH_DOWN;
7475
break;
76+
case 'editor':
77+
pathData = PATH_EDITOR;
78+
break;
7579
case 'expanded':
7680
pathData = PATH_EXPANDED;
7781
break;
@@ -268,3 +272,7 @@ const PATH_VIEW_DOM = `
268272
const PATH_VIEW_SOURCE = `
269273
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
270274
`;
275+
276+
const PATH_EDITOR = `
277+
M7 5h10v2h2V3c0-1.1-.9-1.99-2-1.99L7 1c-1.1 0-2 .9-2 2v4h2V5zm8.41 11.59L20 12l-4.59-4.59L14 8.83 17.17 12 14 15.17l1.41 1.42zM10 15.17L6.83 12 10 8.83 8.59 7.41 4 12l4.59 4.59L10 15.17zM17 19H7v-2H5v4c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-4h-2v2z
278+
`;

packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js

+38-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import * as React from 'react';
11-
import {useCallback, useContext} from 'react';
11+
import {useCallback, useContext, useSyncExternalStore} from 'react';
1212
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
1313
import {BridgeContext, StoreContext, OptionsContext} from '../context';
1414
import Button from '../Button';
@@ -20,6 +20,8 @@ import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
2020
import CannotSuspendWarningMessage from './CannotSuspendWarningMessage';
2121
import InspectedElementView from './InspectedElementView';
2222
import {InspectedElementContext} from './InspectedElementContext';
23+
import {getOpenInEditorURL} from '../../../utils';
24+
import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
2325

2426
import 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}

packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
useRef,
1717
useState,
1818
} from 'react';
19-
import {useSubscription} from '../hooks';
19+
import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
20+
import {useLocalStorage, useSubscription} from '../hooks';
2021
import {StoreContext} from '../context';
2122
import Button from '../Button';
2223
import ButtonIcon from '../ButtonIcon';
@@ -37,6 +38,7 @@ import {
3738
ElementTypeProfiler,
3839
ElementTypeSuspense,
3940
} from 'react-devtools-shared/src/types';
41+
import {getDefaultOpenInEditorURL} from 'react-devtools-shared/src/utils';
4042

4143
import styles from './SettingsShared.css';
4244

@@ -81,6 +83,11 @@ export default function ComponentsSettings(_: {||}) {
8183
[setParseHookNames],
8284
);
8385

86+
const [openInEditorURL, setOpenInEditorURL] = useLocalStorage<string>(
87+
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
88+
getDefaultOpenInEditorURL(),
89+
);
90+
8491
const [componentFilters, setComponentFilters] = useState<
8592
Array<ComponentFilter>,
8693
>(() => [...store.componentFilters]);
@@ -271,6 +278,19 @@ export default function ComponentsSettings(_: {||}) {
271278
<span className={styles.Warning}>(may be slow)</span>
272279
</label>
273280

281+
<label className={styles.OpenInURLSetting}>
282+
Open in Editor URL:{' '}
283+
<input
284+
className={styles.Input}
285+
type="text"
286+
placeholder={process.env.EDITOR_URL ?? 'vscode://file/{path}:{line}'}
287+
value={openInEditorURL}
288+
onChange={event => {
289+
setOpenInEditorURL(event.target.value);
290+
}}
291+
/>
292+
</label>
293+
274294
<div className={styles.Header}>Hide components where...</div>
275295

276296
<table className={styles.Table}>

packages/react-devtools-shared/src/devtools/views/Settings/SettingsShared.css

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
margin-bottom: 0;
1515
}
1616

17+
.OpenInURLSetting {
18+
margin: 0.5rem 0;
19+
}
20+
1721
.OptionGroup {
1822
display: inline-flex;
1923
flex-direction: row;
@@ -30,6 +34,10 @@
3034
margin-right: 0.5rem;
3135
}
3236

37+
.Spacer {
38+
height: 0.5rem;
39+
}
40+
3341
.Select {
3442
}
3543

packages/react-devtools-shared/src/devtools/views/hooks.js

+3
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ export function useLocalStorage<T>(
170170
value instanceof Function ? (value: any)(storedValue) : value;
171171
setStoredValue(valueToStore);
172172
localStorageSetItem(key, JSON.stringify(valueToStore));
173+
174+
// Notify listeners that this setting has changed.
175+
window.dispatchEvent(new Event(key));
173176
} catch (error) {
174177
console.log(error);
175178
}

packages/react-devtools-shared/src/utils.js

+17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
import {ElementTypeRoot} from 'react-devtools-shared/src/types';
3535
import {
3636
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
37+
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
3738
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
3839
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
3940
LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
@@ -386,6 +387,22 @@ export function setShowInlineWarningsAndErrors(value: boolean): void {
386387
);
387388
}
388389

390+
export function getDefaultOpenInEditorURL(): string {
391+
return typeof process.env.EDITOR_URL === 'string'
392+
? process.env.EDITOR_URL
393+
: '';
394+
}
395+
396+
export function getOpenInEditorURL(): string {
397+
try {
398+
const raw = localStorageGetItem(LOCAL_STORAGE_OPEN_IN_EDITOR_URL);
399+
if (raw != null) {
400+
return JSON.parse(raw);
401+
}
402+
} catch (error) {}
403+
return getDefaultOpenInEditorURL();
404+
}
405+
389406
export function separateDisplayNameAndHOCs(
390407
displayName: string | null,
391408
type: ElementType,

packages/react-devtools-shell/webpack.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ if (!TARGET) {
2424
process.exit(1);
2525
}
2626

27+
const EDITOR_URL = process.env.EDITOR_URL || null;
28+
2729
const builtModulesDir = resolve(
2830
__dirname,
2931
'..',
@@ -69,6 +71,7 @@ const config = {
6971
__PROFILE__: false,
7072
__TEST__: NODE_ENV === 'test',
7173
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
74+
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
7275
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-shell"`,
7376
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
7477
'process.env.DARK_MODE_DIMMED_WARNING_COLOR': `"${DARK_MODE_DIMMED_WARNING_COLOR}"`,

0 commit comments

Comments
 (0)