15
import ButtonIcon from '../ButtonIcon';
16
import Icon from '../Icon';
17
import {ModalDialogContext} from '../ModalDialog';
18
-import ViewElementSourceContext from './ViewElementSourceContext';
18
import Toggle from '../Toggle';
19
import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
20
import CannotSuspendWarningMessage from './CannotSuspendWarningMessage';
22
import {InspectedElementContext} from './InspectedElementContext';
23
import {getOpenInEditorURL} from '../../../utils';
24
import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
25
+import FetchFileWithCachingContext from './FetchFileWithCachingContext';
26
+import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
27
+import OpenInEditorButton from './OpenInEditorButton';
28
+import InspectedElementViewSourceButton from './InspectedElementViewSourceButton';
29
+import Skeleton from './Skeleton';
30
31
import styles from './InspectedElement.css';
32
29
-import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
33
+import type {Source} from 'react-devtools-shared/src/shared/types';
34
35
export type Props = {};
36
39
export default function InspectedElementWrapper(_: Props): React.Node {
40
const {inspectedElementID} = useContext(TreeStateContext);
41
const dispatch = useContext(TreeDispatcherContext);
38
- const {canViewElementSourceFunction, viewElementSourceFunction} = useContext(
39
- ViewElementSourceContext,
40
- );
42
const bridge = useContext(BridgeContext);
43
const store = useContext(StoreContext);
44
const {
52
const {hookNames, inspectedElement, parseHookNames, toggleParseHookNames} =
53
useContext(InspectedElementContext);
54
55
+ const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
56
+
57
+ const symbolicatedSourcePromise: null | Promise<Source | null> =
58
+ React.useMemo(() => {
59
+ if (inspectedElement == null) return null;
60
+ if (fetchFileWithCaching == null) return Promise.resolve(null);
61
+
62
+ const {source} = inspectedElement;
63
+ if (source == null) return Promise.resolve(null);
64
+
65
+ const {sourceURL, line, column} = source;
66
+ return symbolicateSourceWithCache(
67
+ fetchFileWithCaching,
68
+ sourceURL,
69
+ line,
70
+ column,
71
+ );
72
+ }, [inspectedElement]);
73
+
74
const element =
75
inspectedElementID !== null
76
? store.getElementByID(inspectedElementID)
104
}
105
}, [bridge, inspectedElementID, store]);
106
87
- const viewSource = useCallback(() => {
88
- if (viewElementSourceFunction != null && inspectedElement !== null) {
89
- viewElementSourceFunction(
90
- inspectedElement.id,
91
- ((inspectedElement: any): InspectedElement),
92
- );
93
- }
94
- }, [inspectedElement, viewElementSourceFunction]);
95
-
96
- // In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source.
97
- // To detect this case, we defer to an injected helper function (if present).
98
- const canViewSource =
99
- inspectedElement !== null &&
100
- inspectedElement.canViewSource &&
101
- viewElementSourceFunction !== null &&
102
- (canViewElementSourceFunction === null ||
103
- canViewElementSourceFunction(inspectedElement));
104
-
107
const isErrored = inspectedElement != null && inspectedElement.isErrored;
108
const targetErrorBoundaryID =
109
inspectedElement != null ? inspectedElement.targetErrorBoundaryID : null;
136
},
137
);
138
137
- const canOpenInEditor =
138
- editorURL && inspectedElement != null && inspectedElement.source != null;
139
-
139
const toggleErrored = useCallback(() => {
140
if (inspectedElement == null || targetErrorBoundaryID == null) {
141
return;
211
}
212
}, [bridge, dispatch, element, isSuspended, modalDialogDispatch, store]);
213
215
- const onOpenInEditor = useCallback(() => {
216
- const source = inspectedElement?.source;
217
- if (source == null || editorURL == null) {
218
- return;
219
- }
220
-
221
- const url = new URL(editorURL);
222
- url.href = url.href
223
- .replace('{path}', source.sourceURL)
224
- .replace('{line}', String(source.line))
225
- .replace('%7Bpath%7D', source.sourceURL)
226
- .replace('%7Bline%7D', String(source.line));
227
- window.open(url);
228
- }, [inspectedElement, editorURL]);
229
-
214
if (element === null) {
215
return (
216
<div className={styles.InspectedElement}>
258
{element.displayName}
259
</div>
260
</div>
277
- {canOpenInEditor && (
278
- <Button onClick={onOpenInEditor} title="Open in editor">
279
- <ButtonIcon type="editor" />
280
- </Button>
281
- )}
261
+
262
+ {!!editorURL &&
263
+ inspectedElement != null &&
264
+ inspectedElement.source != null &&
265
+ symbolicatedSourcePromise != null && (
266
+ <React.Suspense fallback={<Skeleton height={16} width={24} />}>
267
+ <OpenInEditorButton
268
+ editorURL={editorURL}
269
+ source={inspectedElement.source}
270
+ symbolicatedSourcePromise={symbolicatedSourcePromise}
271
+ />
272
+ </React.Suspense>
273
+ )}
274
+
275
{canToggleError && (
276
<Toggle
277
isChecked={isErrored}
310
<ButtonIcon type="log-data" />
311
</Button>
312
)}
313
+
314
{!hideViewSourceAction && (
321
- <Button
322
- disabled={!canViewSource}
323
- onClick={viewSource}
324
- title="View source for this element">
325
- <ButtonIcon type="view-source" />
326
- </Button>
315
+ <InspectedElementViewSourceButton
316
+ canViewSource={inspectedElement?.canViewSource}
317
+ source={inspectedElement?.source}
318
+ symbolicatedSourcePromise={symbolicatedSourcePromise}
319
+ />
320
)}
321
</div>
322
324
<div className={styles.Loading}>Loading...</div>
325
)}
326
334
- {inspectedElement !== null && (
327
+ {inspectedElement !== null && symbolicatedSourcePromise != null && (
328
<InspectedElementView
329
key={
330
inspectedElementID /* Force reset when selected Element changes */
334
inspectedElement={inspectedElement}
335
parseHookNames={parseHookNames}
336
toggleParseHookNames={toggleParseHookNames}
337
+ symbolicatedSourcePromise={symbolicatedSourcePromise}
338
/>
339
)}
340
</div>