mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
303 lines
8.7 KiB
JavaScript
303 lines
8.7 KiB
JavaScript
/**
|
|
* 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 {CommitDataFrontend} from './types';
|
|
|
|
import * as React from 'react';
|
|
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
import {FixedSizeList} from 'react-window';
|
|
import SnapshotCommitListItem from './SnapshotCommitListItem';
|
|
import {minBarWidth} from './constants';
|
|
import {formatDuration, formatTime} from './utils';
|
|
import Tooltip from './Tooltip';
|
|
|
|
import styles from './SnapshotCommitList.css';
|
|
|
|
export type ItemData = {
|
|
commitTimes: Array<number>,
|
|
filteredCommitIndices: Array<number>,
|
|
maxDuration: number,
|
|
selectedCommitIndex: number | null,
|
|
selectedFilteredCommitIndex: number | null,
|
|
selectCommitIndex: (index: number) => void,
|
|
setHoveredCommitIndex: (index: number) => void,
|
|
startCommitDrag: (newDragState: DragState) => void,
|
|
totalDurations: Array<number>,
|
|
};
|
|
|
|
type Props = {
|
|
commitData: CommitDataFrontend,
|
|
commitTimes: Array<number>,
|
|
filteredCommitIndices: Array<number>,
|
|
selectedCommitIndex: number | null,
|
|
selectedFilteredCommitIndex: number | null,
|
|
selectCommitIndex: (index: number) => void,
|
|
totalDurations: Array<number>,
|
|
};
|
|
|
|
export default function SnapshotCommitList({
|
|
commitData,
|
|
commitTimes,
|
|
filteredCommitIndices,
|
|
selectedCommitIndex,
|
|
selectedFilteredCommitIndex,
|
|
selectCommitIndex,
|
|
totalDurations,
|
|
}: Props): React.Node {
|
|
return (
|
|
<AutoSizer>
|
|
{({height, width}) => (
|
|
<List
|
|
commitData={commitData}
|
|
commitTimes={commitTimes}
|
|
height={height}
|
|
filteredCommitIndices={filteredCommitIndices}
|
|
selectedCommitIndex={selectedCommitIndex}
|
|
selectedFilteredCommitIndex={selectedFilteredCommitIndex}
|
|
selectCommitIndex={selectCommitIndex}
|
|
totalDurations={totalDurations}
|
|
width={width}
|
|
/>
|
|
)}
|
|
</AutoSizer>
|
|
);
|
|
}
|
|
|
|
type ListProps = {
|
|
commitData: CommitDataFrontend,
|
|
commitTimes: Array<number>,
|
|
height: number,
|
|
filteredCommitIndices: Array<number>,
|
|
selectedCommitIndex: number | null,
|
|
selectedFilteredCommitIndex: number | null,
|
|
selectCommitIndex: (index: number) => void,
|
|
totalDurations: Array<number>,
|
|
width: number,
|
|
};
|
|
|
|
type DragState = {
|
|
commitIndex: number,
|
|
left: number,
|
|
sizeIncrement: number,
|
|
};
|
|
|
|
function List({
|
|
commitData,
|
|
selectedCommitIndex,
|
|
commitTimes,
|
|
height,
|
|
filteredCommitIndices,
|
|
selectedFilteredCommitIndex,
|
|
selectCommitIndex,
|
|
totalDurations,
|
|
width,
|
|
}: ListProps) {
|
|
// $FlowFixMe[incompatible-use]
|
|
const listRef = useRef<FixedSizeList<ItemData> | null>(null);
|
|
const divRef = useRef<HTMLDivElement | null>(null);
|
|
const prevCommitIndexRef = useRef<number | null>(null);
|
|
|
|
// Make sure a newly selected snapshot is fully visible within the list.
|
|
useEffect(() => {
|
|
if (selectedFilteredCommitIndex !== prevCommitIndexRef.current) {
|
|
prevCommitIndexRef.current = selectedFilteredCommitIndex;
|
|
if (selectedFilteredCommitIndex !== null && listRef.current !== null) {
|
|
listRef.current.scrollToItem(selectedFilteredCommitIndex);
|
|
}
|
|
}
|
|
}, [listRef, selectedFilteredCommitIndex]);
|
|
|
|
const itemSize = useMemo(
|
|
() => Math.max(minBarWidth, width / filteredCommitIndices.length),
|
|
[filteredCommitIndices, width],
|
|
);
|
|
const maxDuration = useMemo(
|
|
() => totalDurations.reduce((max, duration) => Math.max(max, duration), 0),
|
|
[totalDurations],
|
|
);
|
|
|
|
const maxCommitIndex = filteredCommitIndices.length - 1;
|
|
|
|
const [dragState, setDragState] = useState<DragState | null>(null);
|
|
|
|
const handleDragCommit = ({buttons, pageX}: any) => {
|
|
if (buttons === 0) {
|
|
setDragState(null);
|
|
return;
|
|
}
|
|
|
|
if (dragState !== null) {
|
|
const {commitIndex, left, sizeIncrement} = dragState;
|
|
|
|
let newCommitIndex = commitIndex;
|
|
let newCommitLeft = left;
|
|
|
|
if (pageX < newCommitLeft) {
|
|
while (pageX < newCommitLeft) {
|
|
newCommitLeft -= sizeIncrement;
|
|
newCommitIndex -= 1;
|
|
}
|
|
} else {
|
|
let newCommitRectRight = newCommitLeft + sizeIncrement;
|
|
while (pageX > newCommitRectRight) {
|
|
newCommitRectRight += sizeIncrement;
|
|
newCommitIndex += 1;
|
|
}
|
|
}
|
|
|
|
if (newCommitIndex < 0) {
|
|
newCommitIndex = 0;
|
|
} else if (newCommitIndex > maxCommitIndex) {
|
|
newCommitIndex = maxCommitIndex;
|
|
}
|
|
|
|
selectCommitIndex(newCommitIndex);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (dragState === null) {
|
|
return;
|
|
}
|
|
|
|
const element = divRef.current;
|
|
if (element !== null) {
|
|
const ownerDocument = element.ownerDocument;
|
|
ownerDocument.addEventListener('mousemove', handleDragCommit);
|
|
return () => {
|
|
ownerDocument.removeEventListener('mousemove', handleDragCommit);
|
|
};
|
|
}
|
|
}, [dragState]);
|
|
|
|
const [hoveredCommitIndex, setHoveredCommitIndex] = useState<number | null>(
|
|
null,
|
|
);
|
|
|
|
// Pass required contextual data down to the ListItem renderer.
|
|
const itemData = useMemo<ItemData>(
|
|
() => ({
|
|
commitTimes,
|
|
filteredCommitIndices,
|
|
maxDuration,
|
|
selectedCommitIndex,
|
|
selectedFilteredCommitIndex,
|
|
selectCommitIndex,
|
|
setHoveredCommitIndex,
|
|
startCommitDrag: setDragState,
|
|
totalDurations,
|
|
}),
|
|
[
|
|
commitTimes,
|
|
filteredCommitIndices,
|
|
maxDuration,
|
|
selectedCommitIndex,
|
|
selectedFilteredCommitIndex,
|
|
selectCommitIndex,
|
|
setHoveredCommitIndex,
|
|
totalDurations,
|
|
],
|
|
);
|
|
|
|
let tooltipLabel = null;
|
|
if (hoveredCommitIndex !== null) {
|
|
const {
|
|
duration,
|
|
effectDuration,
|
|
passiveEffectDuration,
|
|
priorityLevel,
|
|
timestamp,
|
|
} = commitData[hoveredCommitIndex];
|
|
|
|
// Only some React versions include commit durations.
|
|
// Show a richer tooltip only for builds that have that info.
|
|
if (
|
|
effectDuration !== null ||
|
|
passiveEffectDuration !== null ||
|
|
priorityLevel !== null
|
|
) {
|
|
tooltipLabel = (
|
|
<ul className={styles.TooltipList}>
|
|
{priorityLevel !== null && (
|
|
<li className={styles.TooltipListItem}>
|
|
<label className={styles.TooltipLabel}>Priority</label>
|
|
<span className={styles.TooltipValue}>{priorityLevel}</span>
|
|
</li>
|
|
)}
|
|
<li className={styles.TooltipListItem}>
|
|
<label className={styles.TooltipLabel}>Committed at</label>
|
|
<span className={styles.TooltipValue}>
|
|
{formatTime(timestamp)}s
|
|
</span>
|
|
</li>
|
|
<li className={styles.TooltipListItem}>
|
|
<div className={styles.DurationsWrapper}>
|
|
<label className={styles.TooltipLabel}>Durations</label>
|
|
<ul className={styles.DurationsList}>
|
|
<li className={styles.DurationsListItem}>
|
|
<label className={styles.DurationsLabel}>Render</label>
|
|
<span className={styles.DurationsValue}>
|
|
{formatDuration(duration)}ms
|
|
</span>
|
|
</li>
|
|
{effectDuration !== null && (
|
|
<li className={styles.DurationsListItem}>
|
|
<label className={styles.DurationsLabel}>
|
|
Layout effects
|
|
</label>
|
|
<span className={styles.DurationsValue}>
|
|
{formatDuration(effectDuration)}ms
|
|
</span>
|
|
</li>
|
|
)}
|
|
{passiveEffectDuration !== null && (
|
|
<li className={styles.DurationsListItem}>
|
|
<label className={styles.DurationsLabel}>
|
|
Passive effects
|
|
</label>
|
|
<span className={styles.DurationsValue}>
|
|
{formatDuration(passiveEffectDuration)}ms
|
|
</span>
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
);
|
|
} else {
|
|
tooltipLabel = `${formatDuration(duration)}ms at ${formatTime(
|
|
timestamp,
|
|
)}s`;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Tooltip className={styles.Tooltip} label={tooltipLabel}>
|
|
<div
|
|
ref={divRef}
|
|
style={{height, width}}
|
|
onMouseLeave={() => setHoveredCommitIndex(null)}>
|
|
<FixedSizeList
|
|
className={styles.List}
|
|
layout="horizontal"
|
|
height={height}
|
|
itemCount={filteredCommitIndices.length}
|
|
itemData={itemData}
|
|
itemSize={itemSize}
|
|
ref={(listRef: any) /* Flow bug? */}
|
|
width={width}>
|
|
{SnapshotCommitListItem}
|
|
</FixedSizeList>
|
|
</div>
|
|
</Tooltip>
|
|
);
|
|
}
|