Skip to content

Commit

Permalink
Scheduling Profiler: Redesign with DevTools styling (#19707)
Browse files Browse the repository at this point in the history
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
  • Loading branch information
taneliang and Brian Vaughn authored Sep 3, 2020
1 parent bcc0aa4 commit 38a512a
Show file tree
Hide file tree
Showing 20 changed files with 577 additions and 311 deletions.
7 changes: 7 additions & 0 deletions packages/react-devtools-extensions/utils.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
36 changes: 36 additions & 0 deletions packages/react-devtools-scheduling-profiler/buildUtils.js
Original file line number Diff line number Diff line change
@@ -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,
};
4 changes: 3 additions & 1 deletion packages/react-devtools-scheduling-profiler/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions packages/react-devtools-scheduling-profiler/src/App.css
Original file line number Diff line number Diff line change
@@ -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);
}
32 changes: 20 additions & 12 deletions packages/react-devtools-scheduling-profiler/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
}
18 changes: 9 additions & 9 deletions packages/react-devtools-scheduling-profiler/src/CanvasPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
|};
Expand Down Expand Up @@ -284,7 +281,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {

useCanvasInteraction(canvasRef, interactor);

useContextMenu<ContextMenuContextData>({
useContextMenu({
data: {
data,
hoveredEvent,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
17 changes: 17 additions & 0 deletions packages/react-devtools-scheduling-profiler/src/ImportButton.css
Original file line number Diff line number Diff line change
@@ -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);
}
86 changes: 86 additions & 0 deletions packages/react-devtools-scheduling-profiler/src/ImportButton.js
Original file line number Diff line number Diff line change
@@ -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>
</>
);
}
Loading

0 comments on commit 38a512a

Please sign in to comment.