Skip to content

Commit 38a512a

Browse files
taneliangBrian Vaughn
and
Brian Vaughn
authored
Scheduling Profiler: Redesign with DevTools styling (facebook#19707)
Co-authored-by: Brian Vaughn <[email protected]>
1 parent bcc0aa4 commit 38a512a

20 files changed

+577
-311
lines changed

packages/react-devtools-extensions/utils.js

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its 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+
18
const {execSync} = require('child_process');
29
const {readFileSync} = require('fs');
310
const {resolve} = require('path');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its 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+
8+
const {execSync} = require('child_process');
9+
const {readFileSync} = require('fs');
10+
const {resolve} = require('path');
11+
12+
function getGitCommit() {
13+
try {
14+
return execSync('git show -s --format=%h')
15+
.toString()
16+
.trim();
17+
} catch (error) {
18+
// Mozilla runs this command from a git archive.
19+
// In that context, there is no Git revision.
20+
return null;
21+
}
22+
}
23+
24+
function getVersionString() {
25+
const packageVersion = JSON.parse(
26+
readFileSync(resolve(__dirname, './package.json')),
27+
).version;
28+
29+
const commit = getGitCommit();
30+
31+
return `${packageVersion}-${commit}`;
32+
}
33+
34+
module.exports = {
35+
getVersionString,
36+
};

packages/react-devtools-scheduling-profiler/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "react-devtools-scheduling-profiler",
4-
"version": "0.0.1",
4+
"version": "0.0.0",
55
"license": "MIT",
66
"scripts": {
77
"build": "cross-env NODE_ENV=production cross-env TARGET=remote webpack --config webpack.config.js",
@@ -18,6 +18,8 @@
1818
},
1919
"devDependencies": {
2020
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
21+
"@reach/menu-button": "^0.11.2",
22+
"@reach/tooltip": "^0.11.2",
2123
"babel-loader": "^8.1.0",
2224
"css-loader": "^4.2.1",
2325
"file-loader": "^6.0.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.DevTools {
2+
width: 100%;
3+
height: 100%;
4+
display: flex;
5+
flex-direction: column;
6+
background-color: var(--color-background);
7+
color: var(--color-text);
8+
}
9+
10+
.TabContent {
11+
flex: 1 1 100%;
12+
overflow: auto;
13+
-webkit-app-region: no-drag;
14+
}
15+
16+
.DevTools, .DevTools * {
17+
box-sizing: border-box;
18+
-webkit-font-smoothing: var(--font-smoothing);
19+
}

packages/react-devtools-scheduling-profiler/src/App.js

+20-12
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,30 @@
77
* @flow
88
*/
99

10-
import type {ReactProfilerData} from './types';
10+
// Reach styles need to come before any component styles.
11+
// This makes overriding the styles simpler.
12+
import '@reach/menu-button/styles.css';
13+
import '@reach/tooltip/styles.css';
1114

1215
import * as React from 'react';
13-
import {useState} from 'react';
1416

15-
import ImportPage from './ImportPage';
16-
import CanvasPage from './CanvasPage';
17+
import {ModalDialogContextController} from 'react-devtools-shared/src/devtools/views/ModalDialog';
18+
import {SchedulingProfiler} from './SchedulingProfiler';
19+
import {useBrowserTheme} from './hooks';
20+
21+
import styles from './App.css';
22+
import 'react-devtools-shared/src/devtools/views/root.css';
1723

1824
export default function App() {
19-
const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
20-
null,
21-
);
25+
useBrowserTheme();
2226

23-
if (profilerData) {
24-
return <CanvasPage profilerData={profilerData} />;
25-
} else {
26-
return <ImportPage onDataImported={setProfilerData} />;
27-
}
27+
return (
28+
<ModalDialogContextController>
29+
<div className={styles.DevTools}>
30+
<div className={styles.TabContent}>
31+
<SchedulingProfiler />
32+
</div>
33+
</div>
34+
</ModalDialogContextController>
35+
);
2836
}

packages/react-devtools-scheduling-profiler/src/CanvasPage.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,15 @@ import {
5252
import {COLORS} from './content-views/constants';
5353

5454
import EventTooltip from './EventTooltip';
55-
import {ContextMenu, ContextMenuItem, useContextMenu} from './context';
55+
import ContextMenu from './context/ContextMenu';
56+
import ContextMenuItem from './context/ContextMenuItem';
57+
import useContextMenu from './context/useContextMenu';
5658
import {getBatchRange} from './utils/getBatchRange';
5759

5860
import styles from './CanvasPage.css';
5961

6062
const CONTEXT_MENU_ID = 'canvas';
6163

62-
type ContextMenuContextData = {|
63-
data: ReactProfilerData,
64-
hoveredEvent: ReactHoverContextInfo | null,
65-
|};
66-
6764
type Props = {|
6865
profilerData: ReactProfilerData,
6966
|};
@@ -284,7 +281,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
284281

285282
useCanvasInteraction(canvasRef, interactor);
286283

287-
useContextMenu<ContextMenuContextData>({
284+
useContextMenu({
288285
data: {
289286
data,
290287
hoveredEvent,
@@ -357,7 +354,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
357354
}
358355
});
359356
}
360-
}, [hoveredEvent]);
357+
}, [
358+
hoveredEvent,
359+
data, // Attach onHover callbacks when views are re-created on data change
360+
]);
361361

362362
useLayoutEffect(() => {
363363
const {current: userTimingMarksView} = userTimingMarksViewRef;
@@ -396,7 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
396396
<Fragment>
397397
<canvas ref={canvasRef} height={height} width={width} />
398398
<ContextMenu id={CONTEXT_MENU_ID}>
399-
{(contextData: ContextMenuContextData) => {
399+
{contextData => {
400400
if (contextData.hoveredEvent == null) {
401401
return null;
402402
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
3+
*/
4+
.Input {
5+
position: absolute !important;
6+
height: 1px;
7+
width: 1px;
8+
overflow: hidden;
9+
clip: rect(1px, 1px, 1px, 1px);
10+
}
11+
12+
.ErrorMessage {
13+
margin: 0.5rem 0;
14+
color: var(--color-dim);
15+
font-family: var(--font-family-monospace);
16+
font-size: var(--font-size-monospace-normal);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its 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 type {TimelineEvent} from '@elg/speedscope';
11+
import type {ReactProfilerData} from './types';
12+
13+
import * as React from 'react';
14+
import {useCallback, useContext, useRef} from 'react';
15+
16+
import Button from 'react-devtools-shared/src/devtools/views/Button';
17+
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
18+
import {ModalDialogContext} from 'react-devtools-shared/src/devtools/views/ModalDialog';
19+
20+
import preprocessData from './utils/preprocessData';
21+
import {readInputData} from './utils/readInputData';
22+
23+
import styles from './ImportButton.css';
24+
25+
type Props = {|
26+
onDataImported: (profilerData: ReactProfilerData) => void,
27+
|};
28+
29+
export default function ImportButton({onDataImported}: Props) {
30+
const inputRef = useRef<HTMLInputElement | null>(null);
31+
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
32+
33+
const handleFiles = useCallback(async () => {
34+
const input = inputRef.current;
35+
if (input === null) {
36+
return;
37+
}
38+
39+
if (input.files.length > 0) {
40+
try {
41+
const readFile = await readInputData(input.files[0]);
42+
const events: TimelineEvent[] = JSON.parse(readFile);
43+
if (events.length > 0) {
44+
onDataImported(preprocessData(events));
45+
}
46+
} catch (error) {
47+
modalDialogDispatch({
48+
type: 'SHOW',
49+
title: 'Import failed',
50+
content: (
51+
<>
52+
<div>The profiling data you selected cannot be imported.</div>
53+
{error !== null && (
54+
<div className={styles.ErrorMessage}>{error.message}</div>
55+
)}
56+
</>
57+
),
58+
});
59+
}
60+
}
61+
62+
// Reset input element to allow the same file to be re-imported
63+
input.value = '';
64+
}, [onDataImported, modalDialogDispatch]);
65+
66+
const uploadData = useCallback(() => {
67+
if (inputRef.current !== null) {
68+
inputRef.current.click();
69+
}
70+
}, []);
71+
72+
return (
73+
<>
74+
<input
75+
ref={inputRef}
76+
className={styles.Input}
77+
type="file"
78+
onChange={handleFiles}
79+
tabIndex={-1}
80+
/>
81+
<Button onClick={uploadData} title="Load profile...">
82+
<ButtonIcon type="import" />
83+
</Button>
84+
</>
85+
);
86+
}

0 commit comments

Comments
 (0)