8
*/
9
10
import {copy} from 'clipboard-js';
11
-import React, {useCallback, useContext} from 'react';
11
+import React, {Fragment, useCallback, useContext} from 'react';
12
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
13
-import {BridgeContext, StoreContext} from '../context';
13
+import {BridgeContext, ContextMenuContext, StoreContext} from '../context';
14
+import ContextMenu from '../../ContextMenu/ContextMenu';
15
+import ContextMenuItem from '../../ContextMenu/ContextMenuItem';
16
import Button from '../Button';
17
import ButtonIcon from '../ButtonIcon';
18
+import Icon from '../Icon';
19
import HooksTree from './HooksTree';
20
import {ModalDialogContext} from '../ModalDialog';
21
import HocBadges from './HocBadges';
36
37
import styles from './SelectedElement.css';
38
36
-import type {GetInspectedElementPath} from './InspectedElementContext';
39
+import type {
40
+ CopyInspectedElementPath,
41
+ GetInspectedElementPath,
42
+ StoreAsGlobal,
43
+} from './InspectedElementContext';
44
import type {Element, InspectedElement} from './types';
45
import type {ElementType} from 'react-devtools-shared/src/types';
46
56
const store = useContext(StoreContext);
57
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
58
52
- const {getInspectedElementPath, getInspectedElement} = useContext(
53
- InspectedElementContext,
54
- );
59
+ const {
60
+ copyInspectedElementPath,
61
+ getInspectedElementPath,
62
+ getInspectedElement,
63
+ storeAsGlobal,
64
+ viewInspectedElementPath,
65
+ } = useContext(InspectedElementContext);
66
67
const element =
68
inspectedElementID !== null
250
key={
251
inspectedElementID /* Force reset when seleted Element changes */
252
}
253
+ copyInspectedElementPath={copyInspectedElementPath}
254
element={element}
255
getInspectedElementPath={getInspectedElementPath}
256
inspectedElement={inspectedElement}
257
+ storeAsGlobal={storeAsGlobal}
258
+ viewInspectedElementPath={viewInspectedElementPath}
259
/>
260
)}
261
</div>
262
);
263
}
264
265
+export type CopyPath = (path: Array<string | number>) => void;
266
export type InspectPath = (path: Array<string | number>) => void;
267
268
type InspectedElementViewProps = {|
269
+ copyInspectedElementPath: CopyInspectedElementPath,
270
element: Element,
271
getInspectedElementPath: GetInspectedElementPath,
272
inspectedElement: InspectedElement,
273
+ storeAsGlobal: StoreAsGlobal,
274
|};
275
276
const IS_SUSPENDED = 'Suspended';
277
278
function InspectedElementView({
279
+ copyInspectedElementPath,
280
element,
281
getInspectedElementPath,
282
inspectedElement,
283
+ storeAsGlobal,
284
+ viewInspectedElementPath,
285
}: InspectedElementViewProps) {
286
const {id, type} = element;
287
const {
301
const bridge = useContext(BridgeContext);
302
const store = useContext(StoreContext);
303
304
+ const {
305
+ isEnabledForInspectedElement,
306
+ viewAttributeSourceFunction,
307
+ } = useContext(ContextMenuContext);
308
+
309
const inspectContextPath = useCallback(
310
(path: Array<string | number>) => {
311
getInspectedElementPath(id, ['context', ...path]);
377
}
378
379
return (
355
- <div className={styles.InspectedElement}>
356
- <HocBadges element={element} />
357
- <InspectedElementTree
358
- label="props"
359
- data={props}
360
- inspectPath={inspectPropsPath}
361
- overrideValueFn={overridePropsFn}
362
- showWhenEmpty={true}
363
- canAddEntries={typeof overridePropsFn === 'function'}
364
- />
365
- {type === ElementTypeSuspense ? (
380
+ <Fragment>
381
+ <div className={styles.InspectedElement}>
382
+ <HocBadges element={element} />
383
<InspectedElementTree
367
- label="suspense"
368
- data={{
369
- [IS_SUSPENDED]: state !== null,
370
- }}
371
- overrideValueFn={overrideSuspenseFn}
384
+ label="props"
385
+ data={props}
386
+ inspectPath={inspectPropsPath}
387
+ overrideValueFn={overridePropsFn}
388
+ pathRoot="props"
389
+ showWhenEmpty={true}
390
+ canAddEntries={typeof overridePropsFn === 'function'}
391
/>
373
- ) : (
392
+ {type === ElementTypeSuspense ? (
393
+ <InspectedElementTree
394
+ label="suspense"
395
+ data={{
396
+ [IS_SUSPENDED]: state !== null,
397
+ }}
398
+ overrideValueFn={overrideSuspenseFn}
399
+ />
400
+ ) : (
401
+ <InspectedElementTree
402
+ label="state"
403
+ data={state}
404
+ inspectPath={inspectStatePath}
405
+ overrideValueFn={overrideStateFn}
406
+ pathRoot="state"
407
+ />
408
+ )}
409
+ <HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
410
<InspectedElementTree
375
- label="state"
376
- data={state}
377
- inspectPath={inspectStatePath}
378
- overrideValueFn={overrideStateFn}
411
+ label={hasLegacyContext ? 'legacy context' : 'context'}
412
+ data={context}
413
+ inspectPath={inspectContextPath}
414
+ overrideValueFn={overrideContextFn}
415
+ pathRoot="context"
416
/>
380
- )}
381
- <HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
382
- <InspectedElementTree
383
- label={hasLegacyContext ? 'legacy context' : 'context'}
384
- data={context}
385
- inspectPath={inspectContextPath}
386
- overrideValueFn={overrideContextFn}
387
- />
388
-
389
- <NativeStyleEditor />
390
-
391
- {ownerID === null &&
392
- owners !== null &&
393
- owners.length > 0 && (
394
- <div className={styles.Owners}>
395
- <div className={styles.OwnersHeader}>rendered by</div>
396
- {owners.map(owner => (
397
- <OwnerView
398
- key={owner.id}
399
- displayName={owner.displayName || 'Anonymous'}
400
- hocDisplayNames={owner.hocDisplayNames}
401
- id={owner.id}
402
- isInStore={store.containsElement(owner.id)}
403
- type={owner.type}
404
- />
405
- ))}
406
- </div>
417
+
418
+ <NativeStyleEditor />
419
+
420
+ {ownerID === null &&
421
+ owners !== null &&
422
+ owners.length > 0 && (
423
+ <div className={styles.Owners}>
424
+ <div className={styles.OwnersHeader}>rendered by</div>
425
+ {owners.map(owner => (
426
+ <OwnerView
427
+ key={owner.id}
428
+ displayName={owner.displayName || 'Anonymous'}
429
+ hocDisplayNames={owner.hocDisplayNames}
430
+ id={owner.id}
431
+ isInStore={store.containsElement(owner.id)}
432
+ type={owner.type}
433
+ />
434
+ ))}
435
+ </div>
436
+ )}
437
+
438
+ {source !== null && (
439
+ <Source fileName={source.fileName} lineNumber={source.lineNumber} />
440
)}
441
+ </div>
442
409
- {source !== null && (
410
- <Source fileName={source.fileName} lineNumber={source.lineNumber} />
443
+ {isEnabledForInspectedElement && (
444
+ <ContextMenu id="SelectedElement">
445
+ {data => (
446
+ <Fragment>
447
+ <ContextMenuItem
448
+ onClick={() => copyInspectedElementPath(id, data.path)}
449
+ title="Copy value to clipboard">
450
+ <Icon className={styles.ContextMenuIcon} type="copy" /> Copy
451
+ value to clipboard
452
+ </ContextMenuItem>
453
+ <ContextMenuItem
454
+ onClick={() => storeAsGlobal(id, data.path)}
455
+ title="Store as global variable">
456
+ <Icon
457
+ className={styles.ContextMenuIcon}
458
+ type="store-as-global-variable"
459
+ />{' '}
460
+ Store as global variable
461
+ </ContextMenuItem>
462
+ {viewAttributeSourceFunction !== null &&
463
+ data.type === 'function' && (
464
+ <ContextMenuItem
465
+ onClick={() => viewAttributeSourceFunction(id, data.path)}
466
+ title="Go to definition">
467
+ <Icon className={styles.ContextMenuIcon} type="code" /> Go
468
+ to definition
469
+ </ContextMenuItem>
470
+ )}
471
+ </Fragment>
472
+ )}
473
+ </ContextMenu>
474
)}
412
- </div>
475
+ </Fragment>
476
);
477
}
478