@samitouri / QOS-React-2 / commits / 38a512acad

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==