-
Notifications
You must be signed in to change notification settings - Fork 47.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add fiber summary tooltip to devtools profiling (#18048)
* Add tooltip component * Separate logic of ProfilerWhatChanged to a component * Add hovered Fiber info tooltip component * Add flame graph chart tooltip * Add commit ranked list tooltip * Fix flow issues * Minor improvement in filter * Fix flickering issue * Resolved issues on useCallbacks and mouse event listeners * Fix lints * Remove unnecessary useCallback
- Loading branch information
1 parent
2512c30
commit 44e5f5e
Showing
13 changed files
with
527 additions
and
176 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
packages/react-devtools-shared/src/devtools/views/Components/ProfilerWhatChanged.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
.Component { | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.Item { | ||
margin-top: 0.25rem; | ||
} | ||
|
||
.Key { | ||
font-family: var(--font-family-monospace); | ||
font-size: var(--font-size-monospace-small); | ||
line-height: 1; | ||
} | ||
|
||
.Key:first-of-type::before { | ||
content: ' ('; | ||
} | ||
|
||
.Key::after { | ||
content: ', '; | ||
} | ||
|
||
.Key:last-of-type::after { | ||
content: ')'; | ||
} | ||
|
||
.Label { | ||
font-weight: bold; | ||
margin-bottom: 0.5rem; | ||
} |
138 changes: 138 additions & 0 deletions
138
packages/react-devtools-shared/src/devtools/views/Components/ProfilerWhatChanged.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/** | ||
* 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 React, {useContext} from 'react'; | ||
import {ProfilerContext} from '../Profiler/ProfilerContext'; | ||
import {StoreContext} from '../context'; | ||
|
||
import styles from './ProfilerWhatChanged.css'; | ||
|
||
type ProfilerWhatChangedProps = {| | ||
fiberID: number, | ||
|}; | ||
|
||
export default function ProfilerWhatChanged({ | ||
fiberID, | ||
}: ProfilerWhatChangedProps) { | ||
const {profilerStore} = useContext(StoreContext); | ||
const {rootID, selectedCommitIndex} = useContext(ProfilerContext); | ||
|
||
// TRICKY | ||
// Handle edge case where no commit is selected because of a min-duration filter update. | ||
// If the commit index is null, suspending for data below would throw an error. | ||
// TODO (ProfilerContext) This check should not be necessary. | ||
if (selectedCommitIndex === null) { | ||
return null; | ||
} | ||
|
||
const {changeDescriptions} = profilerStore.getCommitData( | ||
((rootID: any): number), | ||
selectedCommitIndex, | ||
); | ||
|
||
if (changeDescriptions === null) { | ||
return null; | ||
} | ||
|
||
const changeDescription = changeDescriptions.get(fiberID); | ||
if (changeDescription == null) { | ||
return null; | ||
} | ||
|
||
if (changeDescription.isFirstMount) { | ||
return ( | ||
<div className={styles.Component}> | ||
<label className={styles.Label}>Why did this render?</label> | ||
<div className={styles.Item}> | ||
This is the first time the component rendered. | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
const changes = []; | ||
|
||
if (changeDescription.context === true) { | ||
changes.push( | ||
<div key="context" className={styles.Item}> | ||
• Context changed | ||
</div>, | ||
); | ||
} else if ( | ||
typeof changeDescription.context === 'object' && | ||
changeDescription.context !== null && | ||
changeDescription.context.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="context" className={styles.Item}> | ||
• Context changed: | ||
{changeDescription.context.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if (changeDescription.didHooksChange) { | ||
changes.push( | ||
<div key="hooks" className={styles.Item}> | ||
• Hooks changed | ||
</div>, | ||
); | ||
} | ||
|
||
if ( | ||
changeDescription.props !== null && | ||
changeDescription.props.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="props" className={styles.Item}> | ||
• Props changed: | ||
{changeDescription.props.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if ( | ||
changeDescription.state !== null && | ||
changeDescription.state.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="state" className={styles.Item}> | ||
• State changed: | ||
{changeDescription.state.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if (changes.length === 0) { | ||
changes.push( | ||
<div key="nothing" className={styles.Item}> | ||
The parent component rendered. | ||
</div>, | ||
); | ||
} | ||
|
||
return ( | ||
<div className={styles.Component}> | ||
<label className={styles.Label}>Why did this render?</label> | ||
{changes} | ||
</div> | ||
); | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/react-devtools-shared/src/devtools/views/Components/Tooltip.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
.Tooltip { | ||
position: absolute; | ||
pointer-events: none; | ||
border: none; | ||
border-radius: 0.25rem; | ||
padding: 0.25rem 0.5rem; | ||
font-family: var(--font-family-sans); | ||
font-size: 12px; | ||
background-color: var(--color-tooltip-background); | ||
color: var(--color-tooltip-text); | ||
opacity: 1; | ||
/* Make sure this is above the DevTools, which are above the Overlay */ | ||
z-index: 10000002; | ||
} | ||
|
||
.Tooltip.hidden { | ||
opacity: 0; | ||
} | ||
|
||
|
||
.Container { | ||
width: -moz-max-content; | ||
width: -webkit-max-content; | ||
} |
106 changes: 106 additions & 0 deletions
106
packages/react-devtools-shared/src/devtools/views/Components/Tooltip.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** @flow */ | ||
|
||
import React, {useRef} from 'react'; | ||
|
||
import styles from './Tooltip.css'; | ||
|
||
const initialTooltipState = {height: 0, mouseX: 0, mouseY: 0, width: 0}; | ||
|
||
export default function Tooltip({children, label}: any) { | ||
const containerRef = useRef(null); | ||
const tooltipRef = useRef(null); | ||
|
||
// update the position of the tooltip based on current mouse position | ||
const updateTooltipPosition = (event: SyntheticMouseEvent<*>) => { | ||
const element = tooltipRef.current; | ||
if (element != null) { | ||
// first find the mouse position | ||
const mousePosition = getMousePosition(containerRef.current, event); | ||
// use the mouse position to find the position of tooltip | ||
const {left, top} = getTooltipPosition(element, mousePosition); | ||
// update tooltip position | ||
element.style.left = left; | ||
element.style.top = top; | ||
} | ||
}; | ||
|
||
const onMouseMove = (event: SyntheticMouseEvent<*>) => { | ||
updateTooltipPosition(event); | ||
}; | ||
|
||
const tooltipClassName = label === null ? styles.hidden : ''; | ||
|
||
return ( | ||
<div | ||
className={styles.Container} | ||
onMouseMove={onMouseMove} | ||
ref={containerRef}> | ||
<div ref={tooltipRef} className={`${styles.Tooltip} ${tooltipClassName}`}> | ||
{label} | ||
</div> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
// Method used to find the position of the tooltip based on current mouse position | ||
function getTooltipPosition(element, mousePosition) { | ||
const {height, mouseX, mouseY, width} = mousePosition; | ||
const TOOLTIP_OFFSET_X = 5; | ||
const TOOLTIP_OFFSET_Y = 15; | ||
let top = 0; | ||
let left = 0; | ||
|
||
// Let's check the vertical position. | ||
if (mouseY + TOOLTIP_OFFSET_Y + element.offsetHeight >= height) { | ||
// The tooltip doesn't fit below the mouse cursor (which is our | ||
// default strategy). Therefore we try to position it either above the | ||
// mouse cursor or finally aligned with the window's top edge. | ||
if (mouseY - TOOLTIP_OFFSET_Y - element.offsetHeight > 0) { | ||
// We position the tooltip above the mouse cursor if it fits there. | ||
top = `${mouseY - element.offsetHeight - TOOLTIP_OFFSET_Y}px`; | ||
} else { | ||
// Otherwise we align the tooltip with the window's top edge. | ||
top = '0px'; | ||
} | ||
} else { | ||
top = `${mouseY + TOOLTIP_OFFSET_Y}px`; | ||
} | ||
|
||
// Now let's check the horizontal position. | ||
if (mouseX + TOOLTIP_OFFSET_X + element.offsetWidth >= width) { | ||
// The tooltip doesn't fit at the right of the mouse cursor (which is | ||
// our default strategy). Therefore we try to position it either at the | ||
// left of the mouse cursor or finally aligned with the window's left | ||
// edge. | ||
if (mouseX - TOOLTIP_OFFSET_X - element.offsetWidth > 0) { | ||
// We position the tooltip at the left of the mouse cursor if it fits | ||
// there. | ||
left = `${mouseX - element.offsetWidth - TOOLTIP_OFFSET_X}px`; | ||
} else { | ||
// Otherwise, align the tooltip with the window's left edge. | ||
left = '0px'; | ||
} | ||
} else { | ||
left = `${mouseX + TOOLTIP_OFFSET_X * 2}px`; | ||
} | ||
|
||
return {left, top}; | ||
} | ||
|
||
// method used to find the current mouse position inside the container | ||
function getMousePosition( | ||
relativeContainer, | ||
mouseEvent: SyntheticMouseEvent<*>, | ||
) { | ||
if (relativeContainer !== null) { | ||
const {height, top, width} = relativeContainer.getBoundingClientRect(); | ||
|
||
const mouseX = mouseEvent.clientX; | ||
const mouseY = mouseEvent.clientY - top; | ||
|
||
return {height, mouseX, mouseY, width}; | ||
} else { | ||
return initialTooltipState; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.