[DevTools] Resign Timeline Profiler Sidebar (#24816)

This PR:
* Redesigned the sidebar to resemble the flamegraph profiler sidebar and added title and timestamp to the sidebar
* Added ability to copy the component stack (for places where you're unable to link to source)

https://user-images.githubusercontent.com/2735514/176564897-5301d6d4-429a-4ea3-86cd-74427cff4ce6.mov
This commit is contained in:
Luna Ruan 2022-06-29 20:54:06 -04:00 committed by GitHub
parent 1974d08c93
commit 4e1fcfa771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 84 deletions

View File

@ -5,6 +5,7 @@
padding: 0;
border-radius: 0.25rem;
flex: 0 0 auto;
cursor: pointer;
}
.ButtonContent {
display: inline-flex;

View File

@ -20,18 +20,15 @@
}
.ListItem {
margin: 0;
flex: 1 1;
margin: 0 0 0.5rem;
}
.Label {
display: flex;
justify-content: space-between;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
[data-source="true"]:hover .Label > .Button {
background-color: var(--color-background-hover);
flex: 1 1;
}
.Value {
@ -39,32 +36,31 @@
font-size: var(--font-size-monospace-normal);
}
.NothingSelected {
display: flex;
.Row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100%;
border-top: 1px solid var(--color-border);
}
.UnclickableSource,
.ClickableSource {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
font-family: var(--font-family-sans);
font-size: var(--font-size-sans-normal);
}
.UnclickableSource {
color: var(--color-dim);
}
.Button {
display: flex;
flex: 1;
max-width: 95%;
overflow: hidden;
text-overflow: ellipsis;
.ClickableSource {
color: var(--color-text);
}
[data-source="true"] .Button {
cursor: pointer;
}
.Button > span {
display: block;
text-align: left;
}
.Source {
.ClickableSource:focus,
.ClickableSource:hover {
background-color: var(--color-background-hover);
}

View File

@ -15,18 +15,25 @@ import ButtonIcon from '../ButtonIcon';
import ViewSourceContext from '../Components/ViewSourceContext';
import {useContext} from 'react';
import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
import {
formatTimestamp,
getSchedulingEventLabel,
} from 'react-devtools-timeline/src/utils/formatting';
import {stackToComponentSources} from 'react-devtools-shared/src/devtools/utils';
import {copy} from 'clipboard-js';
import styles from './SidebarEventInfo.css';
export type Props = {||};
function SchedulingEventInfo({eventInfo}: {eventInfo: SchedulingEvent}) {
const {viewUrlSourceFunction} = useContext(ViewSourceContext);
type SchedulingEventProps = {|
eventInfo: SchedulingEvent,
|};
const componentStack = eventInfo.componentStack
? stackToComponentSources(eventInfo.componentStack)
: null;
function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
const {viewUrlSourceFunction} = useContext(ViewSourceContext);
const {componentName, timestamp} = eventInfo;
const componentStack = eventInfo.componentStack || null;
const viewSource = source => {
if (viewUrlSourceFunction != null && source != null) {
@ -35,45 +42,60 @@ function SchedulingEventInfo({eventInfo}: {eventInfo: SchedulingEvent}) {
};
return (
<div className={styles.Content} tabIndex={0}>
{componentStack ? (
<ol className={styles.List}>
{componentStack.map(([displayName, source], index) => {
const hasSource = source != null;
return (
<li
key={index}
className={styles.ListItem}
data-source={hasSource}>
<label className={styles.Label}>
<Button
className={styles.Button}
onClick={() => viewSource(source)}>
{displayName}
</Button>
{hasSource && (
<ButtonIcon className={styles.Source} type="view-source" />
)}
</label>
</li>
);
})}
</ol>
) : null}
</div>
<>
<div className={styles.Toolbar}>
{componentName} {getSchedulingEventLabel(eventInfo)}
</div>
<div className={styles.Content} tabIndex={0}>
<ul className={styles.List}>
<li className={styles.ListItem}>
<label className={styles.Label}>Timestamp</label>:{' '}
<span className={styles.Value}>{formatTimestamp(timestamp)}</span>
</li>
{componentStack && (
<li className={styles.ListItem}>
<div className={styles.Row}>
<label className={styles.Label}>Rendered by</label>
<Button
onClick={() => copy(componentStack)}
title="Copy component stack to clipboard">
<ButtonIcon type="copy" />
</Button>
</div>
<ul className={styles.List}>
{stackToComponentSources(componentStack).map(
([displayName, source], index) => {
return (
<li key={index}>
<Button
className={
source
? styles.ClickableSource
: styles.UnclickableSource
}
disabled={!source}
onClick={() => viewSource(source)}>
{displayName}
</Button>
</li>
);
},
)}
</ul>
</li>
)}
</ul>
</div>
</>
);
}
export default function SidebarEventInfo(_: Props) {
const {selectedEvent} = useContext(TimelineContext);
// (TODO) Refactor in next PR so this supports multiple types of events
return selectedEvent ? (
<>
<div className={styles.Toolbar}>Event Component Tree</div>
{selectedEvent.schedulingEvent ? (
<SchedulingEventInfo eventInfo={selectedEvent.schedulingEvent} />
) : null}
</>
) : null;
if (selectedEvent && selectedEvent.schedulingEvent) {
return <SchedulingEventInfo eventInfo={selectedEvent.schedulingEvent} />;
}
return null;
}

View File

@ -24,7 +24,12 @@ import type {
} from './types';
import * as React from 'react';
import {formatDuration, formatTimestamp, trimString} from './utils/formatting';
import {
formatDuration,
formatTimestamp,
trimString,
getSchedulingEventLabel,
} from './utils/formatting';
import {getBatchRange} from './utils/getBatchRange';
import useSmartTooltip from './utils/useSmartTooltip';
import styles from './EventTooltip.css';
@ -40,19 +45,6 @@ type Props = {|
width: number,
|};
function getSchedulingEventLabel(event: SchedulingEvent): string | null {
switch (event.type) {
case 'schedule-render':
return 'render scheduled';
case 'schedule-state-update':
return 'state update scheduled';
case 'schedule-force-update':
return 'force update scheduled';
default:
return null;
}
}
function getReactMeasureLabel(type): string | null {
switch (type) {
case 'commit':

View File

@ -7,6 +7,8 @@
* @flow
*/
import type {SchedulingEvent} from '../types';
import prettyMilliseconds from 'pretty-ms';
export function formatTimestamp(ms: number) {
@ -28,3 +30,16 @@ export function trimString(string: string, length: number): string {
}
return string;
}
export function getSchedulingEventLabel(event: SchedulingEvent): string | null {
switch (event.type) {
case 'schedule-render':
return 'render scheduled';
case 'schedule-state-update':
return 'state update scheduled';
case 'schedule-force-update':
return 'force update scheduled';
default:
return null;
}
}