/** * Copyright (c) Meta Platforms, Inc. and 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, filteredCommitIndices: Array, maxDuration: number, selectedCommitIndex: number | null, selectedFilteredCommitIndex: number | null, selectCommitIndex: (index: number) => void, setHoveredCommitIndex: (index: number) => void, startCommitDrag: (newDragState: DragState) => void, totalDurations: Array, }; type Props = { commitData: $ReadOnlyArray, commitTimes: Array, filteredCommitIndices: Array, selectedCommitIndex: number | null, selectedFilteredCommitIndex: number | null, selectCommitIndex: (index: number) => void, totalDurations: Array, }; export default function SnapshotCommitList({ commitData, commitTimes, filteredCommitIndices, selectedCommitIndex, selectedFilteredCommitIndex, selectCommitIndex, totalDurations, }: Props): React.Node { return ( {({height, width}) => ( )} ); } type ListProps = { commitData: $ReadOnlyArray, commitTimes: Array, height: number, filteredCommitIndices: Array, selectedCommitIndex: number | null, selectedFilteredCommitIndex: number | null, selectCommitIndex: (index: number) => void, totalDurations: Array, 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 | null>(null); const divRef = useRef(null); const prevCommitIndexRef = useRef(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(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( null, ); // Pass required contextual data down to the ListItem renderer. const itemData = useMemo( () => ({ 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 = (
    {priorityLevel !== null && (
  • {priorityLevel}
  • )}
  • {formatTime(timestamp)}s
    • {formatDuration(duration)}ms
    • {effectDuration !== null && (
    • {formatDuration(effectDuration)}ms
    • )} {passiveEffectDuration !== null && (
    • {formatDuration(passiveEffectDuration)}ms
    • )}
); } else { tooltipLabel = `${formatDuration(duration)}ms at ${formatTime( timestamp, )}s`; } } return (
setHoveredCommitIndex(null)}> {SnapshotCommitListItem}
); }