/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import {copy} from 'clipboard-js'; import * as React from 'react'; import {Fragment, useCallback, useContext} from 'react'; import {TreeDispatcherContext} from './TreeContext'; import {BridgeContext, ContextMenuContext, StoreContext} from '../context'; import ContextMenu from '../../ContextMenu/ContextMenu'; import ContextMenuItem from '../../ContextMenu/ContextMenuItem'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; import Icon from '../Icon'; import HocBadges from './HocBadges'; import InspectedElementContextTree from './InspectedElementContextTree'; import InspectedElementErrorsAndWarningsTree from './InspectedElementErrorsAndWarningsTree'; import InspectedElementHooksTree from './InspectedElementHooksTree'; import InspectedElementPropsTree from './InspectedElementPropsTree'; import InspectedElementStateTree from './InspectedElementStateTree'; import InspectedElementSuspenseToggle from './InspectedElementSuspenseToggle'; import NativeStyleEditor from './NativeStyleEditor'; import Badge from './Badge'; import {useHighlightNativeElement} from '../hooks'; import { copyInspectedElementPath as copyInspectedElementPathAPI, storeAsGlobal as storeAsGlobalAPI, } from 'react-devtools-shared/src/backendAPI'; import styles from './InspectedElementView.css'; import type {ContextMenuContextType} from '../context'; import type {Element, InspectedElement, SerializedElement} from './types'; import type {ElementType} from 'react-devtools-shared/src/types'; export type CopyPath = (path: Array) => void; export type InspectPath = (path: Array) => void; type Props = {| element: Element, inspectedElement: InspectedElement, |}; export default function InspectedElementView({ element, inspectedElement, }: Props) { const {id} = element; const { owners, rendererPackageName, rendererVersion, rootType, source, } = inspectedElement; const bridge = useContext(BridgeContext); const store = useContext(StoreContext); const { isEnabledForInspectedElement: isContextMenuEnabledForInspectedElement, viewAttributeSourceFunction, } = useContext(ContextMenuContext); const rendererLabel = rendererPackageName !== null && rendererVersion !== null ? `${rendererPackageName}@${rendererVersion}` : null; const showOwnersList = owners !== null && owners.length > 0; const showRenderedBy = showOwnersList || rendererLabel !== null || rootType !== null; return (
{showRenderedBy && (
rendered by
{showOwnersList && ((owners: any): Array).map(owner => ( ))} {rootType !== null && (
{rootType}
)} {rendererLabel !== null && (
{rendererLabel}
)}
)} {source !== null && ( )}
{isContextMenuEnabledForInspectedElement && ( {({path, type: pathType}) => { const copyInspectedElementPath = () => { const rendererID = store.getRendererIDForElement(id); if (rendererID !== null) { copyInspectedElementPathAPI({ bridge, id, path, rendererID, }); } }; const storeAsGlobal = () => { const rendererID = store.getRendererIDForElement(id); if (rendererID !== null) { storeAsGlobalAPI({ bridge, id, path, rendererID, }); } }; return ( Copy value to clipboard {' '} Store as global variable {viewAttributeSourceFunction !== null && pathType === 'function' && ( viewAttributeSourceFunction(id, path)} title="Go to definition"> Go to definition )} ); }} )}
); } // This function is based on describeComponentFrame() in packages/shared/ReactComponentStackFrame function formatSourceForDisplay(fileName: string, lineNumber: string) { const BEFORE_SLASH_RE = /^(.*)[\\\/]/; let nameOnly = fileName.replace(BEFORE_SLASH_RE, ''); // In DEV, include code for a common special case: // prefer "folder/index.js" instead of just "index.js". if (/^index\./.test(nameOnly)) { const match = fileName.match(BEFORE_SLASH_RE); if (match) { const pathBeforeSlash = match[1]; if (pathBeforeSlash) { const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, ''); nameOnly = folderName + '/' + nameOnly; } } } return `${nameOnly}:${lineNumber}`; } type SourceProps = {| fileName: string, lineNumber: string, |}; function Source({fileName, lineNumber}: SourceProps) { const handleCopy = () => copy(`${fileName}:${lineNumber}`); return (
source
{formatSourceForDisplay(fileName, lineNumber)}
); } type OwnerViewProps = {| displayName: string, hocDisplayNames: Array | null, id: number, isInStore: boolean, type: ElementType, |}; function OwnerView({ displayName, hocDisplayNames, id, isInStore, type, }: OwnerViewProps) { const dispatch = useContext(TreeDispatcherContext); const { highlightNativeElement, clearHighlightNativeElement, } = useHighlightNativeElement(); const handleClick = useCallback( () => dispatch({ type: 'SELECT_ELEMENT_BY_ID', payload: id, }), [dispatch, id], ); const onMouseEnter = () => highlightNativeElement(id); const onMouseLeave = clearHighlightNativeElement; return ( ); }