import {useCallback, useRef} from 'react';
import styled from '@emotion/styled';
import {
Panel as BasePanel,
PanelHeader as BasePanelHeader,
} from 'sentry/components/panels';
import Placeholder from 'sentry/components/placeholder';
import {useReplayContext} from 'sentry/components/replays/replayContext';
import {relativeTimeInMs} from 'sentry/components/replays/utils';
import {t} from 'sentry/locale';
import space from 'sentry/styles/space';
import {Crumb} from 'sentry/types/breadcrumbs';
import {getPrevBreadcrumb} from 'sentry/utils/replays/getBreadcrumb';
import {useCurrentItemScroller} from 'sentry/utils/replays/hooks/useCurrentItemScroller';
import FluidPanel from 'sentry/views/replays/detail/layout/fluidPanel';
import BreadcrumbItem from './breadcrumbItem';
function CrumbPlaceholder({number}: {number: number}) {
return (
{[...Array(number)].map((_, i) => (
))}
);
}
type Props = {};
function Breadcrumbs({}: Props) {
const {
clearAllHighlights,
currentHoverTime,
currentTime,
highlight,
removeHighlight,
replay,
setCurrentHoverTime,
setCurrentTime,
} = useReplayContext();
const event = replay?.getEvent();
const allCrumbs = replay?.getRawCrumbs();
const crumbListContainerRef = useRef(null);
useCurrentItemScroller(crumbListContainerRef);
const startTimestamp = event?.startTimestamp || 0;
const isLoaded = Boolean(event);
const crumbs =
allCrumbs?.filter(crumb => !['console'].includes(crumb.category || '')) || [];
const currentUserAction = getPrevBreadcrumb({
crumbs,
targetTimestampMs: startTimestamp * 1000 + currentTime,
allowExact: true,
});
const closestUserAction =
currentHoverTime !== undefined
? getPrevBreadcrumb({
crumbs,
targetTimestampMs: startTimestamp * 1000 + (currentHoverTime ?? 0),
allowExact: true,
})
: undefined;
const handleMouseEnter = useCallback(
(item: Crumb) => {
if (startTimestamp) {
setCurrentHoverTime(relativeTimeInMs(item.timestamp ?? '', startTimestamp));
}
if (item.data && 'nodeId' in item.data) {
// XXX: Kind of hacky, but mouseLeave does not fire if you move from a
// crumb to a tooltip
clearAllHighlights();
highlight({nodeId: item.data.nodeId, annotation: item.data.label});
}
},
[setCurrentHoverTime, startTimestamp, highlight, clearAllHighlights]
);
const handleMouseLeave = useCallback(
(item: Crumb) => {
setCurrentHoverTime(undefined);
if (item.data && 'nodeId' in item.data) {
removeHighlight({nodeId: item.data.nodeId});
}
},
[setCurrentHoverTime, removeHighlight]
);
const handleClick = useCallback(
(crumb: Crumb) => {
crumb.timestamp !== undefined
? setCurrentTime(relativeTimeInMs(crumb.timestamp, startTimestamp))
: null;
},
[setCurrentTime, startTimestamp]
);
const content = isLoaded ? (
{crumbs.map(crumb => (
))}
) : (
);
return (
{t('Breadcrumbs')}}
>
{content}
);
}
const BreadcrumbContainer = styled('div')`
padding: ${space(0.5)};
`;
const Panel = styled(BasePanel)`
width: 100%;
height: 100%;
overflow: hidden;
margin-bottom: 0;
`;
const PanelHeader = styled(BasePanelHeader)`
background-color: ${p => p.theme.background};
border-bottom: 1px solid ${p => p.theme.innerBorder};
font-size: ${p => p.theme.fontSizeSmall};
color: ${p => p.theme.gray500};
text-transform: capitalize;
padding: ${space(1)} ${space(1.5)} ${space(1)};
font-weight: 600;
`;
const PlaceholderMargin = styled(Placeholder)`
margin-bottom: ${space(1)};
width: auto;
border-radius: ${p => p.theme.borderRadius};
`;
export default Breadcrumbs;