Scheduling Profiler: Redesign with DevTools styling (#19707)
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
This commit is contained in:
parent
bcc0aa4633
commit
38a512acad
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
const {readFileSync} = require('fs');
|
||||
const {resolve} = require('path');
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
const {readFileSync} = require('fs');
|
||||
const {resolve} = require('path');
|
||||
|
||||
function getGitCommit() {
|
||||
try {
|
||||
return execSync('git show -s --format=%h')
|
||||
.toString()
|
||||
.trim();
|
||||
} catch (error) {
|
||||
// Mozilla runs this command from a git archive.
|
||||
// In that context, there is no Git revision.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getVersionString() {
|
||||
const packageVersion = JSON.parse(
|
||||
readFileSync(resolve(__dirname, './package.json')),
|
||||
).version;
|
||||
|
||||
const commit = getGitCommit();
|
||||
|
||||
return `${packageVersion}-${commit}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVersionString,
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "react-devtools-scheduling-profiler",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production cross-env TARGET=remote webpack --config webpack.config.js",
|
||||
|
@ -18,6 +18,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
|
||||
"@reach/menu-button": "^0.11.2",
|
||||
"@reach/tooltip": "^0.11.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"css-loader": "^4.2.1",
|
||||
"file-loader": "^6.0.0",
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
.DevTools {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.TabContent {
|
||||
flex: 1 1 100%;
|
||||
overflow: auto;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.DevTools, .DevTools * {
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: var(--font-smoothing);
|
||||
}
|
|
@ -7,22 +7,30 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactProfilerData} from './types';
|
||||
// Reach styles need to come before any component styles.
|
||||
// This makes overriding the styles simpler.
|
||||
import '@reach/menu-button/styles.css';
|
||||
import '@reach/tooltip/styles.css';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
|
||||
import ImportPage from './ImportPage';
|
||||
import CanvasPage from './CanvasPage';
|
||||
import {ModalDialogContextController} from 'react-devtools-shared/src/devtools/views/ModalDialog';
|
||||
import {SchedulingProfiler} from './SchedulingProfiler';
|
||||
import {useBrowserTheme} from './hooks';
|
||||
|
||||
import styles from './App.css';
|
||||
import 'react-devtools-shared/src/devtools/views/root.css';
|
||||
|
||||
export default function App() {
|
||||
const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
|
||||
null,
|
||||
);
|
||||
useBrowserTheme();
|
||||
|
||||
if (profilerData) {
|
||||
return <CanvasPage profilerData={profilerData} />;
|
||||
} else {
|
||||
return <ImportPage onDataImported={setProfilerData} />;
|
||||
}
|
||||
return (
|
||||
<ModalDialogContextController>
|
||||
<div className={styles.DevTools}>
|
||||
<div className={styles.TabContent}>
|
||||
<SchedulingProfiler />
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialogContextController>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -52,18 +52,15 @@ import {
|
|||
import {COLORS} from './content-views/constants';
|
||||
|
||||
import EventTooltip from './EventTooltip';
|
||||
import {ContextMenu, ContextMenuItem, useContextMenu} from './context';
|
||||
import ContextMenu from './context/ContextMenu';
|
||||
import ContextMenuItem from './context/ContextMenuItem';
|
||||
import useContextMenu from './context/useContextMenu';
|
||||
import {getBatchRange} from './utils/getBatchRange';
|
||||
|
||||
import styles from './CanvasPage.css';
|
||||
|
||||
const CONTEXT_MENU_ID = 'canvas';
|
||||
|
||||
type ContextMenuContextData = {|
|
||||
data: ReactProfilerData,
|
||||
hoveredEvent: ReactHoverContextInfo | null,
|
||||
|};
|
||||
|
||||
type Props = {|
|
||||
profilerData: ReactProfilerData,
|
||||
|};
|
||||
|
@ -284,7 +281,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
|
||||
useCanvasInteraction(canvasRef, interactor);
|
||||
|
||||
useContextMenu<ContextMenuContextData>({
|
||||
useContextMenu({
|
||||
data: {
|
||||
data,
|
||||
hoveredEvent,
|
||||
|
@ -357,7 +354,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
}
|
||||
});
|
||||
}
|
||||
}, [hoveredEvent]);
|
||||
}, [
|
||||
hoveredEvent,
|
||||
data, // Attach onHover callbacks when views are re-created on data change
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const {current: userTimingMarksView} = userTimingMarksViewRef;
|
||||
|
@ -396,7 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
<Fragment>
|
||||
<canvas ref={canvasRef} height={height} width={width} />
|
||||
<ContextMenu id={CONTEXT_MENU_ID}>
|
||||
{(contextData: ContextMenuContextData) => {
|
||||
{contextData => {
|
||||
if (contextData.hoveredEvent == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
|
||||
*/
|
||||
.Input {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
}
|
||||
|
||||
.ErrorMessage {
|
||||
margin: 0.5rem 0;
|
||||
color: var(--color-dim);
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--font-size-monospace-normal);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 type {TimelineEvent} from '@elg/speedscope';
|
||||
import type {ReactProfilerData} from './types';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCallback, useContext, useRef} from 'react';
|
||||
|
||||
import Button from 'react-devtools-shared/src/devtools/views/Button';
|
||||
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
|
||||
import {ModalDialogContext} from 'react-devtools-shared/src/devtools/views/ModalDialog';
|
||||
|
||||
import preprocessData from './utils/preprocessData';
|
||||
import {readInputData} from './utils/readInputData';
|
||||
|
||||
import styles from './ImportButton.css';
|
||||
|
||||
type Props = {|
|
||||
onDataImported: (profilerData: ReactProfilerData) => void,
|
||||
|};
|
||||
|
||||
export default function ImportButton({onDataImported}: Props) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
|
||||
|
||||
const handleFiles = useCallback(async () => {
|
||||
const input = inputRef.current;
|
||||
if (input === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.files.length > 0) {
|
||||
try {
|
||||
const readFile = await readInputData(input.files[0]);
|
||||
const events: TimelineEvent[] = JSON.parse(readFile);
|
||||
if (events.length > 0) {
|
||||
onDataImported(preprocessData(events));
|
||||
}
|
||||
} catch (error) {
|
||||
modalDialogDispatch({
|
||||
type: 'SHOW',
|
||||
title: 'Import failed',
|
||||
content: (
|
||||
<>
|
||||
<div>The profiling data you selected cannot be imported.</div>
|
||||
{error !== null && (
|
||||
<div className={styles.ErrorMessage}>{error.message}</div>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reset input element to allow the same file to be re-imported
|
||||
input.value = '';
|
||||
}, [onDataImported, modalDialogDispatch]);
|
||||
|
||||
const uploadData = useCallback(() => {
|
||||
if (inputRef.current !== null) {
|
||||
inputRef.current.click();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.Input}
|
||||
type="file"
|
||||
onChange={handleFiles}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<Button onClick={uploadData} title="Load profile...">
|
||||
<ButtonIcon type="import" />
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
.App {
|
||||
min-height: 100vh;
|
||||
background-color: #282c34;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.Card {
|
||||
display: flex;
|
||||
flex: 0 1 1000px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Card {
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.Card:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.Column {
|
||||
margin: 2rem;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
.Column:last-of-type {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.Link {
|
||||
color: #2683E2;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.Link:hover {
|
||||
color: #1572D1;
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 0 0.5em;
|
||||
border: 1px solid #d7dfe4;
|
||||
margin: 0 0.2em;
|
||||
background-color: #f6f6f6;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.Screenshot {
|
||||
width: 35rem;
|
||||
max-width: 100%;
|
||||
min-width: 25rem;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #d7dfe4;
|
||||
border-radius: 0.4em;
|
||||
box-shadow: 0 2px 4px #ddd;
|
||||
}
|
||||
|
||||
.Header {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.LegendKey {
|
||||
margin: 0;
|
||||
margin-bottom: 1.25rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.LegendItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.Buttons {
|
||||
float: left; /* Float the buttons side by side */
|
||||
}
|
||||
|
||||
.ImportButton,
|
||||
.ViewSourceButton {
|
||||
width: 10rem;
|
||||
font-size: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ImportButton {
|
||||
background-color: #2683E2;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ImportButton:hover {
|
||||
background-color: #1572D1;
|
||||
}
|
||||
|
||||
.ImportButtonFile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ViewSourceButton {
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ViewSourceButton span {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.ViewSourceButton span:after {
|
||||
content: '\00bb';
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
right: -20px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.ViewSourceButton:hover {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ViewSourceButton:hover span {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.ViewSourceButton:hover span:after {
|
||||
opacity: 1;
|
||||
right: 0;
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 type {TimelineEvent} from '@elg/speedscope';
|
||||
import type {ReactProfilerData} from './types';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCallback, useRef} from 'react';
|
||||
|
||||
import profilerBrowser from './assets/profilerBrowser.png';
|
||||
import style from './ImportPage.css';
|
||||
|
||||
import preprocessData from './utils/preprocessData';
|
||||
import {readInputData} from './utils/readInputData';
|
||||
|
||||
type Props = {|
|
||||
onDataImported: (profilerData: ReactProfilerData) => void,
|
||||
|};
|
||||
|
||||
export default function ImportPage({onDataImported}: Props) {
|
||||
const processTimeline = useCallback(
|
||||
(events: TimelineEvent[]) => {
|
||||
if (events.length > 0) {
|
||||
onDataImported(preprocessData(events));
|
||||
}
|
||||
},
|
||||
[onDataImported],
|
||||
);
|
||||
|
||||
const handleProfilerInput = useCallback(
|
||||
async (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
const readFile = await readInputData(event.target.files[0]);
|
||||
processTimeline(JSON.parse(readFile));
|
||||
},
|
||||
[processTimeline],
|
||||
);
|
||||
|
||||
const upload = useRef(null);
|
||||
|
||||
return (
|
||||
<div className={style.App}>
|
||||
<div className={style.Card}>
|
||||
<div className={style.Row}>
|
||||
<div className={style.Column}>
|
||||
<img
|
||||
src={profilerBrowser}
|
||||
className={style.Screenshot}
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
<div className={style.Column}>
|
||||
<h2 className={style.Header}>React Concurrent Mode Profiler</h2>
|
||||
<p>
|
||||
Import a captured{' '}
|
||||
<a
|
||||
className={style.Link}
|
||||
href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance">
|
||||
performance profile
|
||||
</a>{' '}
|
||||
from Chrome Devtools. To zoom, scroll while holding down{' '}
|
||||
<kbd>Ctrl</kbd> or <kbd>Shift</kbd>
|
||||
</p>
|
||||
<ul className={style.LegendKey}>
|
||||
<li className={style.LegendItem}>
|
||||
<svg height="20" width="20">
|
||||
<circle cx="10" cy="10" r="5" fill="#ff718e" />
|
||||
</svg>
|
||||
State Update Scheduled
|
||||
</li>
|
||||
<li className={style.LegendItem}>
|
||||
<svg height="20" width="20">
|
||||
<circle cx="10" cy="10" r="5" fill="#9fc3f3" />
|
||||
</svg>
|
||||
State Update Scheduled
|
||||
</li>
|
||||
<li className={style.LegendItem}>
|
||||
<svg height="20" width="20">
|
||||
<circle cx="10" cy="10" r="5" fill="#a6e59f" />
|
||||
</svg>
|
||||
Suspended
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className={style.Buttons}>
|
||||
<label htmlFor="upload">
|
||||
<button
|
||||
className={style.ImportButton}
|
||||
onClick={() => upload.current && upload.current.click()}>
|
||||
Import
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
ref={upload}
|
||||
className={style.ImportButtonFile}
|
||||
onChange={handleProfilerInput}
|
||||
accept="application/json"
|
||||
/>
|
||||
</label>
|
||||
<a href="https://github.com/facebook/react/tree/master/packages/react-devtools-scheduling-profiler">
|
||||
<button className={style.ViewSourceButton}>
|
||||
<span>Source </span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
.SchedulingProfiler {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-sans-normal);
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.SchedulingProfiler, .SchedulingProfiler * {
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: var(--font-smoothing);
|
||||
}
|
||||
|
||||
.Content {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Paragraph {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-flow: wrap;
|
||||
}
|
||||
|
||||
.EmptyStateContainer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Header {
|
||||
font-size: var(--font-size-sans-large);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.Toolbar {
|
||||
height: 2.25rem;
|
||||
padding: 0 0.25rem;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.VRule {
|
||||
height: 20px;
|
||||
width: 1px;
|
||||
border-left: 1px solid var(--color-border);
|
||||
padding-left: 0.25rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.Spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Link {
|
||||
color: var(--color-button);
|
||||
}
|
||||
|
||||
.ScreenshotWrapper {
|
||||
max-width: 30rem;
|
||||
padding: 0 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.Screenshot {
|
||||
width: 100%;
|
||||
border-radius: 0.4em;
|
||||
border: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
.AppName {
|
||||
font-size: var(--font-size-sans-large);
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 350px) {
|
||||
.AppName {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 600px) {
|
||||
.ScreenshotWrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 type {ReactProfilerData} from './types';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
|
||||
import ImportButton from './ImportButton';
|
||||
import {ModalDialog} from 'react-devtools-shared/src/devtools/views/ModalDialog';
|
||||
import ReactLogo from 'react-devtools-shared/src/devtools/views/ReactLogo';
|
||||
|
||||
import CanvasPage from './CanvasPage';
|
||||
|
||||
import profilerBrowser from './assets/profilerBrowser.png';
|
||||
import styles from './SchedulingProfiler.css';
|
||||
|
||||
export function SchedulingProfiler(_: {||}) {
|
||||
const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const view = profilerData ? (
|
||||
<CanvasPage profilerData={profilerData} />
|
||||
) : (
|
||||
<Welcome onDataImported={setProfilerData} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.SchedulingProfiler}>
|
||||
<div className={styles.Toolbar}>
|
||||
<ReactLogo />
|
||||
<span className={styles.AppName}>Concurrent Mode Profiler</span>
|
||||
<div className={styles.VRule} />
|
||||
<ImportButton onDataImported={setProfilerData} />
|
||||
<div className={styles.Spacer} />
|
||||
</div>
|
||||
<div className={styles.Content}>
|
||||
{view}
|
||||
<ModalDialog />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type WelcomeProps = {|
|
||||
onDataImported: (profilerData: ReactProfilerData) => void,
|
||||
|};
|
||||
|
||||
const Welcome = ({onDataImported}: WelcomeProps) => (
|
||||
<div className={styles.EmptyStateContainer}>
|
||||
<div className={styles.ScreenshotWrapper}>
|
||||
<img
|
||||
src={profilerBrowser}
|
||||
className={styles.Screenshot}
|
||||
alt="Profiler screenshot"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.Header}>Welcome!</div>
|
||||
<div className={styles.Row}>
|
||||
Click the import button
|
||||
<ImportButton onDataImported={onDataImported} /> to import a Chrome
|
||||
performance profile.
|
||||
</div>
|
||||
</div>
|
||||
);
|
10
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js
vendored
Normal file
10
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerFeatureFlags.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export const enableDarkMode = false;
|
|
@ -6,6 +6,10 @@
|
|||
*
|
||||
* @flow
|
||||
*/
|
||||
// App constants
|
||||
|
||||
export {
|
||||
COMFORTABLE_LINE_HEIGHT,
|
||||
COMPACT_LINE_HEIGHT,
|
||||
} from 'react-devtools-shared/src/constants.js';
|
||||
|
||||
export const REACT_TOTAL_NUM_LANES = 31;
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {
|
||||
unstable_createMutableSource as createMutableSource,
|
||||
unstable_useMutableSource as useMutableSource,
|
||||
useLayoutEffect,
|
||||
} from 'react';
|
||||
|
||||
import {updateThemeVariables} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
|
||||
import {enableDarkMode} from './SchedulingProfilerFeatureFlags';
|
||||
|
||||
export type BrowserTheme = 'dark' | 'light';
|
||||
|
||||
const DARK_MODE_QUERY = '(prefers-color-scheme: dark)';
|
||||
|
||||
const getSnapshot = window =>
|
||||
window.matchMedia(DARK_MODE_QUERY).matches ? 'dark' : 'light';
|
||||
|
||||
const darkModeMutableSource = createMutableSource(
|
||||
window,
|
||||
() => window.matchMedia(DARK_MODE_QUERY).matches,
|
||||
);
|
||||
|
||||
const subscribe = (window, callback) => {
|
||||
const mediaQueryList = window.matchMedia(DARK_MODE_QUERY);
|
||||
mediaQueryList.addEventListener('change', callback);
|
||||
return () => {
|
||||
mediaQueryList.removeEventListener('change', callback);
|
||||
};
|
||||
};
|
||||
|
||||
export function useBrowserTheme(): void {
|
||||
const theme = useMutableSource(darkModeMutableSource, getSnapshot, subscribe);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const documentElements = [((document.documentElement: any): HTMLElement)];
|
||||
if (enableDarkMode) {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
updateThemeVariables('light', documentElements);
|
||||
break;
|
||||
case 'dark':
|
||||
updateThemeVariables('dark', documentElements);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unsupported theme value "${theme}"`);
|
||||
}
|
||||
} else {
|
||||
updateThemeVariables('light', documentElements);
|
||||
}
|
||||
}, [theme]);
|
||||
}
|
|
@ -1,13 +1,21 @@
|
|||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-sans-normal);
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.Container {
|
||||
height: 100%;
|
||||
}
|
|
@ -15,9 +15,10 @@ import {unstable_createRoot as createRoot} from 'react-dom';
|
|||
import nullthrows from 'nullthrows';
|
||||
import App from './App';
|
||||
|
||||
import './index.css';
|
||||
import styles from './index.css';
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = styles.Container;
|
||||
container.id = 'root';
|
||||
|
||||
const body = nullthrows(document.body, 'Expect document.body to exist');
|
||||
|
|
|
@ -4,6 +4,7 @@ const {resolve} = require('path');
|
|||
const {DefinePlugin} = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const {getVersionString} = require('./buildUtils');
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
|
@ -21,6 +22,8 @@ const shouldUseDevServer = TARGET === 'local';
|
|||
|
||||
const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
|
||||
|
||||
const DEVTOOLS_VERSION = getVersionString();
|
||||
|
||||
const imageInlineSizeLimit = 10000;
|
||||
|
||||
const config = {
|
||||
|
@ -41,6 +44,7 @@ const config = {
|
|||
__DEV__,
|
||||
__PROFILE__: false,
|
||||
__EXPERIMENTAL__: true,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'React Concurrent Mode Profiler',
|
||||
|
|
|
@ -248,7 +248,7 @@ function updateDisplayDensity(
|
|||
root.style.fontSize = fontSize;
|
||||
}
|
||||
|
||||
function updateThemeVariables(
|
||||
export function updateThemeVariables(
|
||||
theme: Theme,
|
||||
documentElements: DocumentElements,
|
||||
): void {
|
||||
|
|
115
yarn.lock
115
yarn.lock
|
@ -1833,6 +1833,14 @@
|
|||
schema-utils "^2.6.5"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@reach/auto-id@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.11.2.tgz#c66a905c5401d1ac3da8d26165b8d27d6e778fa6"
|
||||
integrity sha512-YZ21b0Kb88wJ0t7QjSznWOYskARQMnmXY9Y2XZ5RyYcZ2krT4s3+ghghpfaPs6BKcrZDonZCrU65OFDJPa1jAw==
|
||||
dependencies:
|
||||
"@reach/utils" "0.11.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/auto-id@0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
|
||||
|
@ -1843,6 +1851,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
|
||||
integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg==
|
||||
|
||||
"@reach/descendants@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.11.2.tgz#49ea1b5eb91aba8ae6dce57f6575c38aff1f9756"
|
||||
integrity sha512-63Wdx32/RyjGRJc4UZKK7F1sIrb6jeGkDwvQH0hv0lRAhEjsiSQ1t2JTYDml3testFP48J0B2xS7JzNeY0zoQw==
|
||||
dependencies:
|
||||
"@reach/utils" "0.11.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/menu-button@^0.1.17":
|
||||
version "0.1.18"
|
||||
resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.1.18.tgz#cb9e3bf1c2a2bdb5d618697b87ad353dfbca123e"
|
||||
|
@ -1855,11 +1871,47 @@
|
|||
"@reach/window-size" "^0.1.4"
|
||||
warning "^4.0.2"
|
||||
|
||||
"@reach/menu-button@^0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.11.2.tgz#7c1a955431b6b5c25e77eeaceac347bfc728bee9"
|
||||
integrity sha512-8fU8Gp7wBBhMBFSGxuF62HSdjgaib+NXEM+MhOIYcAacDnP8cxkpvag/Nm/7cMdV/O5JRuuZBralG854AxaO2g==
|
||||
dependencies:
|
||||
"@reach/auto-id" "0.11.2"
|
||||
"@reach/descendants" "0.11.2"
|
||||
"@reach/popover" "0.11.2"
|
||||
"@reach/utils" "0.11.2"
|
||||
prop-types "^15.7.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/observe-rect@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
|
||||
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==
|
||||
|
||||
"@reach/observe-rect@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
|
||||
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
|
||||
|
||||
"@reach/popover@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/popover/-/popover-0.11.2.tgz#d7d14d8d74b0a376e31dd9a3223d16802360e0d3"
|
||||
integrity sha512-fB5lScg7Sfu3k0c+bjX7QbnFOJ1nsCWYGvrH1M9qlQolWM6gSJcD+ANnEHIb/wF85zuxzgdxZY5bjTj0RZc6Lg==
|
||||
dependencies:
|
||||
"@reach/portal" "0.11.2"
|
||||
"@reach/rect" "0.11.2"
|
||||
"@reach/utils" "0.11.2"
|
||||
tabbable "^4.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/portal@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.11.2.tgz#19a671be9ff010a345892b81e710cb6e4d9f9762"
|
||||
integrity sha512-/53A/rY5oX2Y7D5TpvsP+V5cSd+4MPY6f21mAmVn4DCVwpkCFOlJ059ZL7ixS85M0Jz48YQnnvBJUqwkxqUG/g==
|
||||
dependencies:
|
||||
"@reach/utils" "0.11.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/portal@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa"
|
||||
|
@ -1867,6 +1919,16 @@
|
|||
dependencies:
|
||||
"@reach/component-component" "^0.1.3"
|
||||
|
||||
"@reach/rect@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.11.2.tgz#bc92f97f87e81d1307054fd41c40874d3fbf5491"
|
||||
integrity sha512-eoUWayAADi1ITtrc+8jN9NsBTUkfpORkOs5bQb4RnR6UA/3zlxo5VPuxWgWAG0BCohZlqsxg7NpvckNAyaiAAQ==
|
||||
dependencies:
|
||||
"@reach/observe-rect" "1.2.0"
|
||||
"@reach/utils" "0.11.2"
|
||||
prop-types "^15.7.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/rect@0.2.1", "@reach/rect@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
|
||||
|
@ -1875,6 +1937,19 @@
|
|||
"@reach/component-component" "^0.1.3"
|
||||
"@reach/observe-rect" "^1.0.3"
|
||||
|
||||
"@reach/tooltip@^0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.11.2.tgz#db5c11fa72995801baac157db46f5bb6cacd494b"
|
||||
integrity sha512-aTi3aLkRZMHrNiHt84vnyTWNj84rPdGYkxAfaFpxgkaKpos3XmaOPiR+n/3YzQUoJXISuw8mZezcrDtsSpr3aA==
|
||||
dependencies:
|
||||
"@reach/auto-id" "0.11.2"
|
||||
"@reach/portal" "0.11.2"
|
||||
"@reach/rect" "0.11.2"
|
||||
"@reach/utils" "0.11.2"
|
||||
"@reach/visually-hidden" "0.11.1"
|
||||
prop-types "^15.7.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/tooltip@^0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.2.tgz#a861ce38269b586597ab40417323b33d3d6dc927"
|
||||
|
@ -1887,11 +1962,27 @@
|
|||
"@reach/visually-hidden" "^0.1.4"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@reach/utils@0.11.2":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.11.2.tgz#be1f03650db56fd67a16d3fc70e5262cdb139cec"
|
||||
integrity sha512-fBTolYj+rKTROXmf0zHO0rCWSvw7J0ALmYj5QxW4DmITMOH5uyRuWDWOfqohIGFbOtF/sum50WTB3tvx76d+Aw==
|
||||
dependencies:
|
||||
"@types/warning" "^3.0.0"
|
||||
tslib "^2.0.0"
|
||||
warning "^4.0.3"
|
||||
|
||||
"@reach/utils@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f"
|
||||
integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ==
|
||||
|
||||
"@reach/visually-hidden@0.11.1":
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.11.1.tgz#3d7a5742acf18372f35b9e216680edd8073f35e3"
|
||||
integrity sha512-TZZNSttor2jG6ZPGI35s/8G0FNSz49QrJIhAZbnUqHyPf3+jtNqAC0dOWW8Nuk+mApDDDVYd2KZ9P2vnzllNnQ==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/visually-hidden@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
|
||||
|
@ -2067,6 +2158,11 @@
|
|||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/warning@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
|
||||
integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "1.4.2"
|
||||
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:
|
|||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
|
||||
|
||||
tabbable@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261"
|
||||
integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
|
@ -13060,7 +13161,7 @@ truncate-utf8-bytes@^1.0.0:
|
|||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
tslib@^1.10.0, tslib@^1.9.0:
|
||||
tslib@^1.10.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||
|
@ -13070,6 +13171,16 @@ tslib@^1.8.1:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
||||
tslib@^1.9.0:
|
||||
version "1.11.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.2.tgz#9c79d83272c9a7aaf166f73915c9667ecdde3cc9"
|
||||
integrity sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
|
||||
integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
|
||||
|
||||
tsutils@^3.17.1:
|
||||
version "3.17.1"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
||||
|
@ -13565,7 +13676,7 @@ walker@^1.0.7, walker@~1.0.5:
|
|||
dependencies:
|
||||
makeerror "1.0.x"
|
||||
|
||||
warning@^4.0.2:
|
||||
warning@^4.0.2, warning@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||
|
|
Loading…
Reference in New Issue