Scheduling Profiler: Redesign with DevTools styling (#19707)
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
E-Liang Tan committed
Sep 4, 2020 at 00:08 UTC
38a512acade57578738e2c5ebbb0709cd56a9bbd
20 files changed
+577
-311
packages/react-devtools-extensions/utils.js
+7
@@ -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
+
8
const {execSync} = require('child_process');
9
const {readFileSync} = require('fs');
10
const {resolve} = require('path');
packages/react-devtools-scheduling-profiler/buildUtils.js
new
+36
@@ -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
@@ -1,7 +1,7 @@
1
{
2
"private": true,
3
"name": "react-devtools-scheduling-profiler",
4
- "version": "0.0.1",
4
+ "version": "0.0.0",
5
"license": "MIT",
6
"scripts": {
7
"build": "cross-env NODE_ENV=production cross-env TARGET=remote webpack --config webpack.config.js",
@@ -18,6 +18,8 @@
18
},
19
"devDependencies": {
20
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
21
+ "@reach/menu-button": "^0.11.2",
22
+ "@reach/tooltip": "^0.11.2",
23
"babel-loader": "^8.1.0",
24
"css-loader": "^4.2.1",
25
"file-loader": "^6.0.0",
packages/react-devtools-scheduling-profiler/src/App.css
new
+19
@@ -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
@@ -7,22 +7,30 @@
7
* @flow
8
*/
9
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';
14
15
import * as React from 'react';
13
-import {useState} from 'react';
16
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';
23
24
export default function App() {
19
- const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
20
- null,
21
- );
25
+ useBrowserTheme();
26
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
+ );
36
}
packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+9
-9
@@ -52,18 +52,15 @@ import {
52
import {COLORS} from './content-views/constants';
53
54
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';
58
import {getBatchRange} from './utils/getBatchRange';
59
60
import styles from './CanvasPage.css';
61
62
const CONTEXT_MENU_ID = 'canvas';
63
62
-type ContextMenuContextData = {|
63
- data: ReactProfilerData,
64
- hoveredEvent: ReactHoverContextInfo | null,
65
-|};
66
-
64
type Props = {|
65
profilerData: ReactProfilerData,
66
|};
@@ -284,7 +281,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
281
282
useCanvasInteraction(canvasRef, interactor);
283
287
- useContextMenu<ContextMenuContextData>({
284
+ useContextMenu({
285
data: {
286
data,
287
hoveredEvent,
@@ -357,7 +354,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
354
}
355
});
356
}
360
- }, [hoveredEvent]);
357
+ }, [
358
+ hoveredEvent,
359
+ data, // Attach onHover callbacks when views are re-created on data change
360
+ ]);
361
362
useLayoutEffect(() => {
363
const {current: userTimingMarksView} = userTimingMarksViewRef;
@@ -396,7 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
396
<Fragment>
397
<canvas ref={canvasRef} height={height} width={width} />
398
<ContextMenu id={CONTEXT_MENU_ID}>
399
- {(contextData: ContextMenuContextData) => {
399
+ {contextData => {
400
if (contextData.hoveredEvent == null) {
401
return null;
402
}
packages/react-devtools-scheduling-profiler/src/ImportButton.css
new
+17
@@ -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
+}
packages/react-devtools-scheduling-profiler/src/ImportButton.js
new
+86
@@ -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
+}
packages/react-devtools-scheduling-profiler/src/ImportPage.css
deleted
-163
@@ -1,163 +0,0 @@
1
-.App {
2
- min-height: 100vh;
3
- background-color: #282c34;
4
- display: flex;
5
- align-items: center;
6
- justify-content: center;
7
- font-size: 1.25rem;
8
- padding: 2rem;
9
-}
10
-
11
-.Card {
12
- display: flex;
13
- flex: 0 1 1000px;
14
- flex-wrap: wrap;
15
- align-items: center;
16
- justify-content: center;
17
-}
18
-
19
-.Card {
20
- background-color: white;
21
- box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
22
- border-radius: 5px;
23
- transition: 0.3s;
24
-}
25
-
26
-.Card:hover {
27
- box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
28
-}
29
-
30
-.Row {
31
- display: flex;
32
- flex-direction: row;
33
- flex-wrap: wrap;
34
-}
35
-
36
-.Column {
37
- margin: 2rem;
38
- margin-right: 0;
39
- display: flex;
40
- flex-direction: column;
41
- flex-basis: 100%;
42
- flex: 1;
43
-}
44
-.Column:last-of-type {
45
- margin-right: 2rem;
46
-}
47
-
48
-.Link {
49
- color: #2683E2;
50
- transition: 0.2s;
51
-}
52
-.Link:hover {
53
- color: #1572D1;
54
-}
55
-
56
-kbd {
57
- display: inline-block;
58
- padding: 0 0.5em;
59
- border: 1px solid #d7dfe4;
60
- margin: 0 0.2em;
61
- background-color: #f6f6f6;
62
- border-radius: 0.2em;
63
-}
64
-
65
-.Screenshot {
66
- width: 35rem;
67
- max-width: 100%;
68
- min-width: 25rem;
69
- align-self: center;
70
- justify-content: center;
71
- border: 1px solid #d7dfe4;
72
- border-radius: 0.4em;
73
- box-shadow: 0 2px 4px #ddd;
74
-}
75
-
76
-.Header {
77
- margin-top: 0px;
78
-}
79
-
80
-.LegendKey {
81
- margin: 0;
82
- margin-bottom: 1.25rem;
83
- list-style: none;
84
- padding: 0;
85
-}
86
-
87
-.LegendItem {
88
- display: flex;
89
- align-items: center;
90
-}
91
-
92
-@keyframes spin {
93
- from {
94
- transform: rotate(0deg);
95
- }
96
- to {
97
- transform: rotate(360deg);
98
- }
99
-}
100
-
101
-.Buttons {
102
- float: left; /* Float the buttons side by side */
103
-}
104
-
105
-.ImportButton,
106
-.ViewSourceButton {
107
- width: 10rem;
108
- font-size: 1.5rem;
109
- padding: 1.5rem;
110
- text-align: center;
111
- transition: all 0.3s;
112
- cursor: pointer;
113
-}
114
-
115
-.ImportButton {
116
- background-color: #2683E2;
117
- border: none;
118
- color: #ffffff;
119
-}
120
-
121
-.ImportButton:hover {
122
- background-color: #1572D1;
123
-}
124
-
125
-.ImportButtonFile {
126
- display: none;
127
-}
128
-
129
-.ViewSourceButton {
130
- background-color: transparent;
131
- color: black;
132
- border: none;
133
-}
134
-
135
-.ViewSourceButton span {
136
- cursor: pointer;
137
- display: inline-block;
138
- position: relative;
139
- transition: 0.3s;
140
-}
141
-
142
-.ViewSourceButton span:after {
143
- content: '\00bb';
144
- position: absolute;
145
- opacity: 0;
146
- top: 0;
147
- right: -20px;
148
- transition: 0.3s;
149
-}
150
-
151
-.ViewSourceButton:hover {
152
- background-color: white;
153
- color: black;
154
-}
155
-
156
-.ViewSourceButton:hover span {
157
- padding-right: 25px;
158
-}
159
-
160
-.ViewSourceButton:hover span:after {
161
- opacity: 1;
162
- right: 0;
163
-}
packages/react-devtools-scheduling-profiler/src/ImportPage.js
deleted
-116
@@ -1,116 +0,0 @@
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, useRef} from 'react';
15
-
16
-import profilerBrowser from './assets/profilerBrowser.png';
17
-import style from './ImportPage.css';
18
-
19
-import preprocessData from './utils/preprocessData';
20
-import {readInputData} from './utils/readInputData';
21
-
22
-type Props = {|
23
- onDataImported: (profilerData: ReactProfilerData) => void,
24
-|};
25
-
26
-export default function ImportPage({onDataImported}: Props) {
27
- const processTimeline = useCallback(
28
- (events: TimelineEvent[]) => {
29
- if (events.length > 0) {
30
- onDataImported(preprocessData(events));
31
- }
32
- },
33
- [onDataImported],
34
- );
35
-
36
- const handleProfilerInput = useCallback(
37
- async (event: SyntheticInputEvent<HTMLInputElement>) => {
38
- const readFile = await readInputData(event.target.files[0]);
39
- processTimeline(JSON.parse(readFile));
40
- },
41
- [processTimeline],
42
- );
43
-
44
- const upload = useRef(null);
45
-
46
- return (
47
- <div className={style.App}>
48
- <div className={style.Card}>
49
- <div className={style.Row}>
50
- <div className={style.Column}>
51
- <img
52
- src={profilerBrowser}
53
- className={style.Screenshot}
54
- alt="logo"
55
- />
56
- </div>
57
- <div className={style.Column}>
58
- <h2 className={style.Header}>React Concurrent Mode Profiler</h2>
59
- <p>
60
- Import a captured{' '}
61
- <a
62
- className={style.Link}
63
- href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance">
64
- performance profile
65
- </a>{' '}
66
- from Chrome Devtools. To zoom, scroll while holding down{' '}
67
- <kbd>Ctrl</kbd> or <kbd>Shift</kbd>
68
- </p>
69
- <ul className={style.LegendKey}>
70
- <li className={style.LegendItem}>
71
- <svg height="20" width="20">
72
- <circle cx="10" cy="10" r="5" fill="#ff718e" />
73
- </svg>
74
- State Update Scheduled
75
- </li>
76
- <li className={style.LegendItem}>
77
- <svg height="20" width="20">
78
- <circle cx="10" cy="10" r="5" fill="#9fc3f3" />
79
- </svg>
80
- State Update Scheduled
81
- </li>
82
- <li className={style.LegendItem}>
83
- <svg height="20" width="20">
84
- <circle cx="10" cy="10" r="5" fill="#a6e59f" />
85
- </svg>
86
- Suspended
87
- </li>
88
- </ul>
89
-
90
- <div className={style.Buttons}>
91
- <label htmlFor="upload">
92
- <button
93
- className={style.ImportButton}
94
- onClick={() => upload.current && upload.current.click()}>
95
- Import
96
- </button>
97
- <input
98
- type="file"
99
- ref={upload}
100
- className={style.ImportButtonFile}
101
- onChange={handleProfilerInput}
102
- accept="application/json"
103
- />
104
- </label>
105
- <a href="https://github.com/facebook/react/tree/master/packages/react-devtools-scheduling-profiler">
106
- <button className={style.ViewSourceButton}>
107
- <span>Source </span>
108
- </button>
109
- </a>
110
- </div>
111
- </div>
112
- </div>
113
- </div>
114
- </div>
115
- );
116
-}
packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css
new
+101
@@ -0,0 +1,101 @@
1
+.SchedulingProfiler {
2
+ width: 100%;
3
+ height: 100%;
4
+ position: relative;
5
+ display: flex;
6
+ flex-direction: column;
7
+ font-family: var(--font-family-sans);
8
+ font-size: var(--font-size-sans-normal);
9
+ background-color: var(--color-background);
10
+ color: var(--color-text);
11
+}
12
+
13
+.SchedulingProfiler, .SchedulingProfiler * {
14
+ box-sizing: border-box;
15
+ -webkit-font-smoothing: var(--font-smoothing);
16
+}
17
+
18
+.Content {
19
+ position: relative;
20
+ flex: 1 1 auto;
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ justify-content: center;
25
+}
26
+
27
+.Paragraph {
28
+ text-align: center;
29
+}
30
+
31
+.Row {
32
+ display: flex;
33
+ flex-direction: row;
34
+ align-items: center;
35
+ justify-content: center;
36
+ flex-flow: wrap;
37
+}
38
+
39
+.EmptyStateContainer {
40
+ text-align: center;
41
+}
42
+
43
+.Header {
44
+ font-size: var(--font-size-sans-large);
45
+ margin-bottom: 0.5rem;
46
+}
47
+
48
+.Toolbar {
49
+ height: 2.25rem;
50
+ padding: 0 0.25rem;
51
+ flex: 0 0 auto;
52
+ display: flex;
53
+ align-items: center;
54
+ border-bottom: 1px solid var(--color-border);
55
+}
56
+
57
+.VRule {
58
+ height: 20px;
59
+ width: 1px;
60
+ border-left: 1px solid var(--color-border);
61
+ padding-left: 0.25rem;
62
+ margin-left: 0.25rem;
63
+}
64
+
65
+.Spacer {
66
+ flex: 1;
67
+}
68
+
69
+.Link {
70
+ color: var(--color-button);
71
+}
72
+
73
+.ScreenshotWrapper {
74
+ max-width: 30rem;
75
+ padding: 0 1rem;
76
+ margin-bottom: 2rem;
77
+}
78
+
79
+.Screenshot {
80
+ width: 100%;
81
+ border-radius: 0.4em;
82
+ border: 2px solid var(--color-border);
83
+}
84
+
85
+.AppName {
86
+ font-size: var(--font-size-sans-large);
87
+ margin-right: 0.5rem;
88
+ user-select: none;
89
+}
90
+
91
+@media screen and (max-width: 350px) {
92
+ .AppName {
93
+ display: none;
94
+ }
95
+}
96
+
97
+@media screen and (max-height: 600px) {
98
+ .ScreenshotWrapper {
99
+ display: none;
100
+ }
101
+}
\ No newline at end of file
packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
new
+72
@@ -0,0 +1,72 @@
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 {ReactProfilerData} from './types';
11
+
12
+import * as React from 'react';
13
+import {useState} from 'react';
14
+
15
+import ImportButton from './ImportButton';
16
+import {ModalDialog} from 'react-devtools-shared/src/devtools/views/ModalDialog';
17
+import ReactLogo from 'react-devtools-shared/src/devtools/views/ReactLogo';
18
+
19
+import CanvasPage from './CanvasPage';
20
+
21
+import profilerBrowser from './assets/profilerBrowser.png';
22
+import styles from './SchedulingProfiler.css';
23
+
24
+export function SchedulingProfiler(_: {||}) {
25
+ const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
26
+ null,
27
+ );
28
+
29
+ const view = profilerData ? (
30
+ <CanvasPage profilerData={profilerData} />
31
+ ) : (
32
+ <Welcome onDataImported={setProfilerData} />
33
+ );
34
+
35
+ return (
36
+ <div className={styles.SchedulingProfiler}>
37
+ <div className={styles.Toolbar}>
38
+ <ReactLogo />
39
+ <span className={styles.AppName}>Concurrent Mode Profiler</span>
40
+ <div className={styles.VRule} />
41
+ <ImportButton onDataImported={setProfilerData} />
42
+ <div className={styles.Spacer} />
43
+ </div>
44
+ <div className={styles.Content}>
45
+ {view}
46
+ <ModalDialog />
47
+ </div>
48
+ </div>
49
+ );
50
+}
51
+
52
+type WelcomeProps = {|
53
+ onDataImported: (profilerData: ReactProfilerData) => void,
54
+|};
55
+
56
+const Welcome = ({onDataImported}: WelcomeProps) => (
57
+ <div className={styles.EmptyStateContainer}>
58
+ <div className={styles.ScreenshotWrapper}>
59
+ <img
60
+ src={profilerBrowser}
61
+ className={styles.Screenshot}
62
+ alt="Profiler screenshot"
63
+ />
64
+ </div>
65
+ <div className={styles.Header}>Welcome!</div>
66
+ <div className={styles.Row}>
67
+ Click the import button
68
+ <ImportButton onDataImported={onDataImported} /> to import a Chrome
69
+ performance profile.
70
+ </div>
71
+ </div>
72
+);
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js
new
+10
@@ -0,0 +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
+ * @flow
8
+ */
9
+
10
+export const enableDarkMode = false;
packages/react-devtools-scheduling-profiler/src/constants.js
+5
-1
@@ -6,6 +6,10 @@
6
*
7
* @flow
8
*/
9
-// App constants
9
+
10
+export {
11
+ COMFORTABLE_LINE_HEIGHT,
12
+ COMPACT_LINE_HEIGHT,
13
+} from 'react-devtools-shared/src/constants.js';
14
15
export const REACT_TOTAL_NUM_LANES = 31;
packages/react-devtools-scheduling-profiler/src/hooks.js
new
+59
@@ -0,0 +1,59 @@
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 {
11
+ unstable_createMutableSource as createMutableSource,
12
+ unstable_useMutableSource as useMutableSource,
13
+ useLayoutEffect,
14
+} from 'react';
15
+
16
+import {updateThemeVariables} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
17
+import {enableDarkMode} from './SchedulingProfilerFeatureFlags';
18
+
19
+export type BrowserTheme = 'dark' | 'light';
20
+
21
+const DARK_MODE_QUERY = '(prefers-color-scheme: dark)';
22
+
23
+const getSnapshot = window =>
24
+ window.matchMedia(DARK_MODE_QUERY).matches ? 'dark' : 'light';
25
+
26
+const darkModeMutableSource = createMutableSource(
27
+ window,
28
+ () => window.matchMedia(DARK_MODE_QUERY).matches,
29
+);
30
+
31
+const subscribe = (window, callback) => {
32
+ const mediaQueryList = window.matchMedia(DARK_MODE_QUERY);
33
+ mediaQueryList.addEventListener('change', callback);
34
+ return () => {
35
+ mediaQueryList.removeEventListener('change', callback);
36
+ };
37
+};
38
+
39
+export function useBrowserTheme(): void {
40
+ const theme = useMutableSource(darkModeMutableSource, getSnapshot, subscribe);
41
+
42
+ useLayoutEffect(() => {
43
+ const documentElements = [((document.documentElement: any): HTMLElement)];
44
+ if (enableDarkMode) {
45
+ switch (theme) {
46
+ case 'light':
47
+ updateThemeVariables('light', documentElements);
48
+ break;
49
+ case 'dark':
50
+ updateThemeVariables('dark', documentElements);
51
+ break;
52
+ default:
53
+ throw Error(`Unsupported theme value "${theme}"`);
54
+ }
55
+ } else {
56
+ updateThemeVariables('light', documentElements);
57
+ }
58
+ }, [theme]);
59
+}
packages/react-devtools-scheduling-profiler/src/index.css
+13
-5
@@ -1,13 +1,21 @@
1
+html {
2
+ height: 100%;
3
+}
4
+
5
body {
6
+ height: 100%;
7
margin: 0;
3
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
- sans-serif;
6
- -webkit-font-smoothing: antialiased;
7
- -moz-osx-font-smoothing: grayscale;
8
+ font-family: var(--font-family-sans);
9
+ font-size: var(--font-size-sans-normal);
10
+ background-color: var(--color-background);
11
+ color: var(--color-text);
12
}
13
14
code {
15
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16
monospace;
17
}
18
+
19
+.Container {
20
+ height: 100%;
21
+}
\ No newline at end of file
packages/react-devtools-scheduling-profiler/src/index.js
+2
-1
@@ -15,9 +15,10 @@ import {unstable_createRoot as createRoot} from 'react-dom';
15
import nullthrows from 'nullthrows';
16
import App from './App';
17
18
-import './index.css';
18
+import styles from './index.css';
19
20
const container = document.createElement('div');
21
+container.className = styles.Container;
22
container.id = 'root';
23
24
const body = nullthrows(document.body, 'Expect document.body to exist');
packages/react-devtools-scheduling-profiler/webpack.config.js
+4
@@ -4,6 +4,7 @@ const {resolve} = require('path');
4
const {DefinePlugin} = require('webpack');
5
const HtmlWebpackPlugin = require('html-webpack-plugin');
6
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
7
+const {getVersionString} = require('./buildUtils');
8
9
const NODE_ENV = process.env.NODE_ENV;
10
if (!NODE_ENV) {
@@ -21,6 +22,8 @@ const shouldUseDevServer = TARGET === 'local';
22
23
const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
24
25
+const DEVTOOLS_VERSION = getVersionString();
26
+
27
const imageInlineSizeLimit = 10000;
28
29
const config = {
@@ -41,6 +44,7 @@ const config = {
44
__DEV__,
45
__PROFILE__: false,
46
__EXPERIMENTAL__: true,
47
+ 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
48
}),
49
new HtmlWebpackPlugin({
50
title: 'React Concurrent Mode Profiler',
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+1
-1
@@ -248,7 +248,7 @@ function updateDisplayDensity(
248
root.style.fontSize = fontSize;
249
}
250
251
-function updateThemeVariables(
251
+export function updateThemeVariables(
252
theme: Theme,
253
documentElements: DocumentElements,
254
): void {
yarn.lock
+113
-2
@@ -1833,6 +1833,14 @@
1833
schema-utils "^2.6.5"
1834
source-map "^0.7.3"
1835
1836
+"@reach/auto-id@0.11.2":
1837
+ version "0.11.2"
1838
+ resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.11.2.tgz#c66a905c5401d1ac3da8d26165b8d27d6e778fa6"
1839
+ integrity sha512-YZ21b0Kb88wJ0t7QjSznWOYskARQMnmXY9Y2XZ5RyYcZ2krT4s3+ghghpfaPs6BKcrZDonZCrU65OFDJPa1jAw==
1840
+ dependencies:
1841
+ "@reach/utils" "0.11.2"
1842
+ tslib "^2.0.0"
1843
+
1844
"@reach/auto-id@0.2.0":
1845
version "0.2.0"
1846
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
@@ -1843,6 +1851,14 @@
1851
resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
1852
integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg==
1853
1854
+"@reach/descendants@0.11.2":
1855
+ version "0.11.2"
1856
+ resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.11.2.tgz#49ea1b5eb91aba8ae6dce57f6575c38aff1f9756"
1857
+ integrity sha512-63Wdx32/RyjGRJc4UZKK7F1sIrb6jeGkDwvQH0hv0lRAhEjsiSQ1t2JTYDml3testFP48J0B2xS7JzNeY0zoQw==
1858
+ dependencies:
1859
+ "@reach/utils" "0.11.2"
1860
+ tslib "^2.0.0"
1861
+
1862
"@reach/menu-button@^0.1.17":
1863
version "0.1.18"
1864
resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.1.18.tgz#cb9e3bf1c2a2bdb5d618697b87ad353dfbca123e"
@@ -1855,11 +1871,47 @@
1871
"@reach/window-size" "^0.1.4"
1872
warning "^4.0.2"
1873
1874
+"@reach/menu-button@^0.11.2":
1875
+ version "0.11.2"
1876
+ resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.11.2.tgz#7c1a955431b6b5c25e77eeaceac347bfc728bee9"
1877
+ integrity sha512-8fU8Gp7wBBhMBFSGxuF62HSdjgaib+NXEM+MhOIYcAacDnP8cxkpvag/Nm/7cMdV/O5JRuuZBralG854AxaO2g==
1878
+ dependencies:
1879
+ "@reach/auto-id" "0.11.2"
1880
+ "@reach/descendants" "0.11.2"
1881
+ "@reach/popover" "0.11.2"
1882
+ "@reach/utils" "0.11.2"
1883
+ prop-types "^15.7.2"
1884
+ tslib "^2.0.0"
1885
+
1886
+"@reach/observe-rect@1.2.0":
1887
+ version "1.2.0"
1888
+ resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
1889
+ integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==
1890
+
1891
"@reach/observe-rect@^1.0.3":
1892
version "1.0.3"
1893
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
1894
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
1895
1896
+"@reach/popover@0.11.2":
1897
+ version "0.11.2"
1898
+ resolved "https://registry.yarnpkg.com/@reach/popover/-/popover-0.11.2.tgz#d7d14d8d74b0a376e31dd9a3223d16802360e0d3"
1899
+ integrity sha512-fB5lScg7Sfu3k0c+bjX7QbnFOJ1nsCWYGvrH1M9qlQolWM6gSJcD+ANnEHIb/wF85zuxzgdxZY5bjTj0RZc6Lg==
1900
+ dependencies:
1901
+ "@reach/portal" "0.11.2"
1902
+ "@reach/rect" "0.11.2"
1903
+ "@reach/utils" "0.11.2"
1904
+ tabbable "^4.0.0"
1905
+ tslib "^2.0.0"
1906
+
1907
+"@reach/portal@0.11.2":
1908
+ version "0.11.2"
1909
+ resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.11.2.tgz#19a671be9ff010a345892b81e710cb6e4d9f9762"
1910
+ integrity sha512-/53A/rY5oX2Y7D5TpvsP+V5cSd+4MPY6f21mAmVn4DCVwpkCFOlJ059ZL7ixS85M0Jz48YQnnvBJUqwkxqUG/g==
1911
+ dependencies:
1912
+ "@reach/utils" "0.11.2"
1913
+ tslib "^2.0.0"
1914
+
1915
"@reach/portal@^0.2.1":
1916
version "0.2.1"
1917
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa"
@@ -1867,6 +1919,16 @@
1919
dependencies:
1920
"@reach/component-component" "^0.1.3"
1921
1922
+"@reach/rect@0.11.2":
1923
+ version "0.11.2"
1924
+ resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.11.2.tgz#bc92f97f87e81d1307054fd41c40874d3fbf5491"
1925
+ integrity sha512-eoUWayAADi1ITtrc+8jN9NsBTUkfpORkOs5bQb4RnR6UA/3zlxo5VPuxWgWAG0BCohZlqsxg7NpvckNAyaiAAQ==
1926
+ dependencies:
1927
+ "@reach/observe-rect" "1.2.0"
1928
+ "@reach/utils" "0.11.2"
1929
+ prop-types "^15.7.2"
1930
+ tslib "^2.0.0"
1931
+
1932
"@reach/rect@0.2.1", "@reach/rect@^0.2.1":
1933
version "0.2.1"
1934
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
@@ -1875,6 +1937,19 @@
1937
"@reach/component-component" "^0.1.3"
1938
"@reach/observe-rect" "^1.0.3"
1939
1940
+"@reach/tooltip@^0.11.2":
1941
+ version "0.11.2"
1942
+ resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.11.2.tgz#db5c11fa72995801baac157db46f5bb6cacd494b"
1943
+ integrity sha512-aTi3aLkRZMHrNiHt84vnyTWNj84rPdGYkxAfaFpxgkaKpos3XmaOPiR+n/3YzQUoJXISuw8mZezcrDtsSpr3aA==
1944
+ dependencies:
1945
+ "@reach/auto-id" "0.11.2"
1946
+ "@reach/portal" "0.11.2"
1947
+ "@reach/rect" "0.11.2"
1948
+ "@reach/utils" "0.11.2"
1949
+ "@reach/visually-hidden" "0.11.1"
1950
+ prop-types "^15.7.2"
1951
+ tslib "^2.0.0"
1952
+
1953
"@reach/tooltip@^0.2.2":
1954
version "0.2.2"
1955
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.2.tgz#a861ce38269b586597ab40417323b33d3d6dc927"
@@ -1887,11 +1962,27 @@
1962
"@reach/visually-hidden" "^0.1.4"
1963
prop-types "^15.7.2"
1964
1965
+"@reach/utils@0.11.2":
1966
+ version "0.11.2"
1967
+ resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.11.2.tgz#be1f03650db56fd67a16d3fc70e5262cdb139cec"
1968
+ integrity sha512-fBTolYj+rKTROXmf0zHO0rCWSvw7J0ALmYj5QxW4DmITMOH5uyRuWDWOfqohIGFbOtF/sum50WTB3tvx76d+Aw==
1969
+ dependencies:
1970
+ "@types/warning" "^3.0.0"
1971
+ tslib "^2.0.0"
1972
+ warning "^4.0.3"
1973
+
1974
"@reach/utils@^0.2.3":
1975
version "0.2.3"
1976
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f"
1977
integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ==
1978
1979
+"@reach/visually-hidden@0.11.1":
1980
+ version "0.11.1"
1981
+ resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.11.1.tgz#3d7a5742acf18372f35b9e216680edd8073f35e3"
1982
+ integrity sha512-TZZNSttor2jG6ZPGI35s/8G0FNSz49QrJIhAZbnUqHyPf3+jtNqAC0dOWW8Nuk+mApDDDVYd2KZ9P2vnzllNnQ==
1983
+ dependencies:
1984
+ tslib "^2.0.0"
1985
+
1986
"@reach/visually-hidden@^0.1.4":
1987
version "0.1.4"
1988
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
@@ -2067,6 +2158,11 @@
2158
dependencies:
2159
source-map "^0.6.1"
2160
2161
+"@types/warning@^3.0.0":
2162
+ version "3.0.0"
2163
+ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
2164
+ integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
2165
+
2166
"@types/webpack-sources@*":
2167
version "1.4.2"
2168
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.2.tgz#5d3d4dea04008a779a90135ff96fb5c0c9e6292c"
@@ -12695,6 +12791,11 @@ symbol-tree@^3.2.2, symbol-tree@^3.2.4:
12791
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
12792
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
12793
12794
+tabbable@^4.0.0:
12795
+ version "4.0.0"
12796
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261"
12797
+ integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==
12798
+
12799
table@^5.2.3:
12800
version "5.4.6"
12801
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -13060,7 +13161,7 @@ truncate-utf8-bytes@^1.0.0:
13161
dependencies:
13162
utf8-byte-length "^1.0.1"
13163
13063
-tslib@^1.10.0, tslib@^1.9.0:
13164
+tslib@^1.10.0:
13165
version "1.13.0"
13166
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
13167
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
@@ -13070,6 +13171,16 @@ tslib@^1.8.1:
13171
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
13172
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
13173
13174
+tslib@^1.9.0:
13175
+ version "1.11.2"
13176
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.2.tgz#9c79d83272c9a7aaf166f73915c9667ecdde3cc9"
13177
+ integrity sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==
13178
+
13179
+tslib@^2.0.0:
13180
+ version "2.0.1"
13181
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
13182
+ integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
13183
+
13184
tsutils@^3.17.1:
13185
version "3.17.1"
13186
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
@@ -13565,7 +13676,7 @@ walker@^1.0.7, walker@~1.0.5:
13676
dependencies:
13677
makeerror "1.0.x"
13678
13568
-warning@^4.0.2:
13679
+warning@^4.0.2, warning@^4.0.3:
13680
version "4.0.3"
13681
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
13682
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==