/** * Copyright (c) Meta Platforms, Inc. and 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 * as React from 'react'; import {copy} from 'clipboard-js'; import {toNormalUrl} from 'jsc-safe-url'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; import Skeleton from './Skeleton'; import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck'; import type {Source as InspectedElementSource} from 'react-devtools-shared/src/shared/types'; import styles from './InspectedElementSourcePanel.css'; type Props = { source: InspectedElementSource, symbolicatedSourcePromise: Promise, }; function InspectedElementSourcePanel({ source, symbolicatedSourcePromise, }: Props): React.Node { return (
source
}>
}> ); } function CopySourceButton({source, symbolicatedSourcePromise}: Props) { const symbolicatedSource = React.use(symbolicatedSourcePromise); if (symbolicatedSource == null) { const {sourceURL, line, column} = source; const handleCopy = withPermissionsCheck( {permissions: ['clipboardWrite']}, () => copy(`${sourceURL}:${line}:${column}`), ); return ( ); } const {sourceURL, line, column} = symbolicatedSource; const handleCopy = withPermissionsCheck( {permissions: ['clipboardWrite']}, () => copy(`${sourceURL}:${line}:${column}`), ); return ( ); } function FormattedSourceString({source, symbolicatedSourcePromise}: Props) { const symbolicatedSource = React.use(symbolicatedSourcePromise); if (symbolicatedSource == null) { const {sourceURL, line} = source; return (
{formatSourceForDisplay(sourceURL, line)}
); } const {sourceURL, line} = symbolicatedSource; return (
{formatSourceForDisplay(sourceURL, line)}
); } // This function is based on describeComponentFrame() in packages/shared/ReactComponentStackFrame function formatSourceForDisplay(sourceURL: string, line: number) { // Metro can return JSC-safe URLs, which have `//&` as a delimiter // https://www.npmjs.com/package/jsc-safe-url const sanitizedSourceURL = sourceURL.includes('//&') ? toNormalUrl(sourceURL) : sourceURL; // Note: this RegExp doesn't work well with URLs from Metro, // which provides bundle URL with query parameters prefixed with /& const BEFORE_SLASH_RE = /^(.*)[\\\/]/; let nameOnly = sanitizedSourceURL.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 = sanitizedSourceURL.match(BEFORE_SLASH_RE); if (match) { const pathBeforeSlash = match[1]; if (pathBeforeSlash) { const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, ''); nameOnly = folderName + '/' + nameOnly; } } } return `${nameOnly}:${line}`; } export default InspectedElementSourcePanel;