main
js 122 lines 3.41 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import * as React from 'react';
11 import {copy} from 'clipboard-js';
12
13 import Button from '../Button';
14 import ButtonIcon from '../ButtonIcon';
15 import Skeleton from './Skeleton';
16 import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
17
18 import useOpenResource from '../useOpenResource';
19
20 import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
21 import type {ReactFunctionLocation} from 'shared/ReactTypes';
22 import styles from './InspectedElementSourcePanel.css';
23
24 import formatLocationForDisplay from './formatLocationForDisplay';
25
26 type Props = {
27 source: ReactFunctionLocation,
28 symbolicatedSourcePromise: Promise<SourceMappedLocation | null>,
29 };
30
31 function InspectedElementSourcePanel({
32 source,
33 symbolicatedSourcePromise,
34 }: Props): React.Node {
35 return (
36 <div>
37 <div className={styles.SourceHeaderRow}>
38 <div className={styles.SourceHeader}>source</div>
39
40 <React.Suspense
41 fallback={
42 <Button disabled={true} title="Loading source maps...">
43 <ButtonIcon type="copy" />
44 </Button>
45 }>
46 <CopySourceButton
47 source={source}
48 symbolicatedSourcePromise={symbolicatedSourcePromise}
49 />
50 </React.Suspense>
51 </div>
52
53 <React.Suspense
54 fallback={
55 <div className={styles.SourceOneLiner}>
56 <Skeleton height={16} width="40%" />
57 </div>
58 }>
59 <FormattedSourceString
60 source={source}
61 symbolicatedSourcePromise={symbolicatedSourcePromise}
62 />
63 </React.Suspense>
64 </div>
65 );
66 }
67
68 function CopySourceButton({source, symbolicatedSourcePromise}: Props) {
69 const symbolicatedSource = React.use(symbolicatedSourcePromise);
70 if (symbolicatedSource == null) {
71 const [, sourceURL, line, column] = source;
72 const handleCopy = withPermissionsCheck(
73 {permissions: ['clipboardWrite']},
74 () => copy(`${sourceURL}:${line}:${column}`),
75 );
76
77 return (
78 <Button onClick={handleCopy} title="Copy to clipboard">
79 <ButtonIcon type="copy" />
80 </Button>
81 );
82 }
83
84 const [, sourceURL, line, column] = symbolicatedSource.location;
85 const handleCopy = withPermissionsCheck(
86 {permissions: ['clipboardWrite']},
87 () => copy(`${sourceURL}:${line}:${column}`),
88 );
89
90 return (
91 <Button onClick={handleCopy} title="Copy to clipboard">
92 <ButtonIcon type="copy" />
93 </Button>
94 );
95 }
96
97 function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
98 const symbolicatedSource = React.use(symbolicatedSourcePromise);
99
100 const [linkIsEnabled, viewSource] = useOpenResource(
101 source,
102 symbolicatedSource == null ? null : symbolicatedSource.location,
103 );
104
105 const [, sourceURL, line, column] =
106 symbolicatedSource == null ? source : symbolicatedSource.location;
107
108 return (
109 <div
110 className={styles.SourceOneLiner}
111 data-testname="InspectedElementView-FormattedSourceString">
112 <span
113 className={linkIsEnabled ? styles.Link : null}
114 title={sourceURL + ':' + line}
115 onClick={viewSource}>
116 {formatLocationForDisplay(sourceURL, line, column)}
117 </span>
118 </div>
119 );
120 }
121
122 export default InspectedElementSourcePanel;