mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[DevTools] Add open in editor for fb (#22649)
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
This commit is contained in:
@@ -30,6 +30,7 @@ const __DEV__ = NODE_ENV === 'development';
|
||||
|
||||
const DEVTOOLS_VERSION = getVersionString();
|
||||
|
||||
const EDITOR_URL = process.env.EDITOR_URL || null;
|
||||
const LOGGING_URL = process.env.LOGGING_URL || null;
|
||||
|
||||
const featureFlagTarget =
|
||||
@@ -83,6 +84,7 @@ module.exports = {
|
||||
__TEST__: NODE_ENV === 'test',
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-core"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
|
||||
'process.env.LOGGING_URL': `"${LOGGING_URL}"`,
|
||||
'process.env.NODE_ENV': `"${NODE_ENV}"`,
|
||||
|
||||
@@ -32,6 +32,7 @@ const __DEV__ = NODE_ENV === 'development';
|
||||
|
||||
const DEVTOOLS_VERSION = getVersionString(process.env.DEVTOOLS_VERSION);
|
||||
|
||||
const EDITOR_URL = process.env.EDITOR_URL || null;
|
||||
const LOGGING_URL = process.env.LOGGING_URL || null;
|
||||
|
||||
const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
|
||||
@@ -92,6 +93,7 @@ module.exports = {
|
||||
__TEST__: NODE_ENV === 'test',
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
|
||||
'process.env.LOGGING_URL': `"${LOGGING_URL}"`,
|
||||
'process.env.NODE_ENV': `"${NODE_ENV}"`,
|
||||
|
||||
@@ -20,6 +20,8 @@ if (!NODE_ENV) {
|
||||
|
||||
const __DEV__ = NODE_ENV === 'development';
|
||||
|
||||
const EDITOR_URL = process.env.EDITOR_URL || null;
|
||||
|
||||
const DEVTOOLS_VERSION = getVersionString();
|
||||
|
||||
const babelOptions = {
|
||||
@@ -76,6 +78,7 @@ module.exports = {
|
||||
__TEST__: NODE_ENV === 'test',
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-inline"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
|
||||
'process.env.NODE_ENV': `"${NODE_ENV}"`,
|
||||
'process.env.DARK_MODE_DIMMED_WARNING_COLOR': `"${DARK_MODE_DIMMED_WARNING_COLOR}"`,
|
||||
|
||||
@@ -32,6 +32,9 @@ export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
|
||||
export const SESSION_STORAGE_LAST_SELECTION_KEY =
|
||||
'React::DevTools::lastSelection';
|
||||
|
||||
export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL =
|
||||
'React::DevTools::openInEditorUrl';
|
||||
|
||||
export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
|
||||
'React::DevTools::parseHookNames';
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export type IconType =
|
||||
| 'copy'
|
||||
| 'delete'
|
||||
| 'down'
|
||||
| 'editor'
|
||||
| 'expanded'
|
||||
| 'export'
|
||||
| 'filter'
|
||||
@@ -72,6 +73,9 @@ export default function ButtonIcon({className = '', type}: Props) {
|
||||
case 'down':
|
||||
pathData = PATH_DOWN;
|
||||
break;
|
||||
case 'editor':
|
||||
pathData = PATH_EDITOR;
|
||||
break;
|
||||
case 'expanded':
|
||||
pathData = PATH_EXPANDED;
|
||||
break;
|
||||
@@ -268,3 +272,7 @@ 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_EDITOR = `
|
||||
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
|
||||
`;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCallback, useContext} from 'react';
|
||||
import {useCallback, useContext, useSyncExternalStore} from 'react';
|
||||
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
|
||||
import {BridgeContext, StoreContext, OptionsContext} from '../context';
|
||||
import Button from '../Button';
|
||||
@@ -20,6 +20,8 @@ import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
|
||||
import CannotSuspendWarningMessage from './CannotSuspendWarningMessage';
|
||||
import InspectedElementView from './InspectedElementView';
|
||||
import {InspectedElementContext} from './InspectedElementContext';
|
||||
import {getOpenInEditorURL} from '../../../utils';
|
||||
import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
|
||||
|
||||
import styles from './InspectedElement.css';
|
||||
|
||||
@@ -123,6 +125,21 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
inspectedElement != null &&
|
||||
inspectedElement.canToggleSuspense;
|
||||
|
||||
const editorURL = useSyncExternalStore(
|
||||
function subscribe(callback) {
|
||||
window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
|
||||
return function unsubscribe() {
|
||||
window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
|
||||
};
|
||||
},
|
||||
function getState() {
|
||||
return getOpenInEditorURL();
|
||||
},
|
||||
);
|
||||
|
||||
const canOpenInEditor =
|
||||
editorURL && inspectedElement != null && inspectedElement.source != null;
|
||||
|
||||
const toggleErrored = useCallback(() => {
|
||||
if (inspectedElement == null || targetErrorBoundaryID == null) {
|
||||
return;
|
||||
@@ -198,6 +215,18 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
}
|
||||
}, [bridge, dispatch, element, isSuspended, modalDialogDispatch, store]);
|
||||
|
||||
const onOpenInEditor = useCallback(() => {
|
||||
const source = inspectedElement?.source;
|
||||
if (source == null || editorURL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(editorURL);
|
||||
url.href = url.href.replace('{path}', source.fileName);
|
||||
url.href = url.href.replace('{line}', String(source.lineNumber));
|
||||
window.open(url);
|
||||
}, [inspectedElement, editorURL]);
|
||||
|
||||
if (element === null) {
|
||||
return (
|
||||
<div className={styles.InspectedElement}>
|
||||
@@ -223,7 +252,14 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
{element.displayName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canOpenInEditor && (
|
||||
<Button
|
||||
className={styles.IconButton}
|
||||
onClick={onOpenInEditor}
|
||||
title="Open in editor">
|
||||
<ButtonIcon type="editor" />
|
||||
</Button>
|
||||
)}
|
||||
{canToggleError && (
|
||||
<Toggle
|
||||
className={styles.IconButton}
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {useSubscription} from '../hooks';
|
||||
import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
|
||||
import {useLocalStorage, useSubscription} from '../hooks';
|
||||
import {StoreContext} from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
ElementTypeProfiler,
|
||||
ElementTypeSuspense,
|
||||
} from 'react-devtools-shared/src/types';
|
||||
import {getDefaultOpenInEditorURL} from 'react-devtools-shared/src/utils';
|
||||
|
||||
import styles from './SettingsShared.css';
|
||||
|
||||
@@ -81,6 +83,11 @@ export default function ComponentsSettings(_: {||}) {
|
||||
[setParseHookNames],
|
||||
);
|
||||
|
||||
const [openInEditorURL, setOpenInEditorURL] = useLocalStorage<string>(
|
||||
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
|
||||
getDefaultOpenInEditorURL(),
|
||||
);
|
||||
|
||||
const [componentFilters, setComponentFilters] = useState<
|
||||
Array<ComponentFilter>,
|
||||
>(() => [...store.componentFilters]);
|
||||
@@ -271,6 +278,19 @@ export default function ComponentsSettings(_: {||}) {
|
||||
<span className={styles.Warning}>(may be slow)</span>
|
||||
</label>
|
||||
|
||||
<label className={styles.OpenInURLSetting}>
|
||||
Open in Editor URL:{' '}
|
||||
<input
|
||||
className={styles.Input}
|
||||
type="text"
|
||||
placeholder={process.env.EDITOR_URL ?? 'vscode://file/{path}:{line}'}
|
||||
value={openInEditorURL}
|
||||
onChange={event => {
|
||||
setOpenInEditorURL(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className={styles.Header}>Hide components where...</div>
|
||||
|
||||
<table className={styles.Table}>
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.OpenInURLSetting {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.OptionGroup {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
@@ -30,6 +34,10 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.Spacer {
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
.Select {
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,9 @@ export function useLocalStorage<T>(
|
||||
value instanceof Function ? (value: any)(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
localStorageSetItem(key, JSON.stringify(valueToStore));
|
||||
|
||||
// Notify listeners that this setting has changed.
|
||||
window.dispatchEvent(new Event(key));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
17
packages/react-devtools-shared/src/utils.js
vendored
17
packages/react-devtools-shared/src/utils.js
vendored
@@ -34,6 +34,7 @@ import {
|
||||
import {ElementTypeRoot} from 'react-devtools-shared/src/types';
|
||||
import {
|
||||
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
|
||||
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
|
||||
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
|
||||
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
|
||||
LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
|
||||
@@ -386,6 +387,22 @@ export function setShowInlineWarningsAndErrors(value: boolean): void {
|
||||
);
|
||||
}
|
||||
|
||||
export function getDefaultOpenInEditorURL(): string {
|
||||
return typeof process.env.EDITOR_URL === 'string'
|
||||
? process.env.EDITOR_URL
|
||||
: '';
|
||||
}
|
||||
|
||||
export function getOpenInEditorURL(): string {
|
||||
try {
|
||||
const raw = localStorageGetItem(LOCAL_STORAGE_OPEN_IN_EDITOR_URL);
|
||||
if (raw != null) {
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
} catch (error) {}
|
||||
return getDefaultOpenInEditorURL();
|
||||
}
|
||||
|
||||
export function separateDisplayNameAndHOCs(
|
||||
displayName: string | null,
|
||||
type: ElementType,
|
||||
|
||||
@@ -24,6 +24,8 @@ if (!TARGET) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const EDITOR_URL = process.env.EDITOR_URL || null;
|
||||
|
||||
const builtModulesDir = resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
@@ -69,6 +71,7 @@ const config = {
|
||||
__PROFILE__: false,
|
||||
__TEST__: NODE_ENV === 'test',
|
||||
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-shell"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.DARK_MODE_DIMMED_WARNING_COLOR': `"${DARK_MODE_DIMMED_WARNING_COLOR}"`,
|
||||
|
||||
Reference in New Issue
Block a user