import {memo, useMemo, useRef} from 'react'; import { AutoSizer, CellMeasurer, List as ReactVirtualizedList, ListRowProps, } from 'react-virtualized'; import styled from '@emotion/styled'; import Placeholder from 'sentry/components/placeholder'; import {t} from 'sentry/locale'; import type {Crumb} from 'sentry/types/breadcrumbs'; import BreadcrumbRow from 'sentry/views/replays/detail/breadcrumbs/breadcrumbRow'; import useScrollToCurrentItem from 'sentry/views/replays/detail/breadcrumbs/useScrollToCurrentItem'; import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight'; import NoRowRenderer from 'sentry/views/replays/detail/noRowRenderer'; import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList'; import useVirtualizedInspector from '../useVirtualizedInspector'; type Props = { breadcrumbs: undefined | Crumb[]; startTimestampMs: number; }; // Ensure this object is created once as it is an input to // `useVirtualizedList`'s memoization const cellMeasurer = { fixedWidth: true, minHeight: 53, }; function Breadcrumbs({breadcrumbs, startTimestampMs}: Props) { const listRef = useRef(null); // Keep a reference of object paths that are expanded (via ) // by log row, so they they can be restored as the Console pane is scrolling. // Due to virtualization, components can be unmounted as the user scrolls, so // state needs to be remembered. // // Note that this is intentionally not in state because we do not want to // re-render when items are expanded/collapsed, though it may work in state as well. const expandPathsRef = useRef(new Map>()); const deps = useMemo(() => [breadcrumbs], [breadcrumbs]); const {cache, updateList} = useVirtualizedList({ cellMeasurer, ref: listRef, deps, }); const {handleDimensionChange} = useVirtualizedInspector({ cache, listRef, expandPathsRef, }); useScrollToCurrentItem({ breadcrumbs, ref: listRef, startTimestampMs, }); const renderRow = ({index, key, style, parent}: ListRowProps) => { const item = (breadcrumbs || [])[index]; return ( ); }; return ( {breadcrumbs ? ( {({height, width}) => ( ( {}}> {t('No breadcrumbs recorded')} )} overscanRowCount={5} ref={listRef} rowCount={breadcrumbs.length} rowHeight={cache.rowHeight} rowRenderer={renderRow} width={width} /> )} ) : ( )} ); } const BreadcrumbContainer = styled('div')` position: relative; height: 100%; overflow: hidden; border: 1px solid ${p => p.theme.border}; border-radius: ${p => p.theme.borderRadius}; .beforeHoverTime + .afterHoverTime:before { background-color: ${p => p.theme.surface200}; border-top: 1px solid ${p => p.theme.purple200}; content: ''; left: 0; position: absolute; top: 0; width: 999999999%; } .beforeHoverTime:last-child:before { background-color: ${p => p.theme.surface200}; border-bottom: 1px solid ${p => p.theme.purple200}; content: ''; right: 0; position: absolute; bottom: 0; width: 999999999%; } .beforeCurrentTime + .afterCurrentTime:before { background-color: ${p => p.theme.purple100}; border-top: 1px solid ${p => p.theme.purple300}; content: ''; left: 0; position: absolute; top: 0; width: 999999999%; } .beforeCurrentTime:last-child:before { background-color: ${p => p.theme.purple100}; border-bottom: 1px solid ${p => p.theme.purple300}; content: ''; right: 0; position: absolute; bottom: 0; width: 999999999%; } `; export default memo(Breadcrumbs);