import {css} from '@emotion/react'; import styled from '@emotion/styled'; import * as Timeline from 'sentry/components/replays/breadcrumbs/timeline'; import {getCrumbsByColumn} from 'sentry/components/replays/utils'; import Tooltip from 'sentry/components/tooltip'; import space from 'sentry/styles/space'; import {Crumb} from 'sentry/types/breadcrumbs'; import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab'; import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers'; import type {Color} from 'sentry/utils/theme'; import theme from 'sentry/utils/theme'; import BreadcrumbItem from 'sentry/views/replays/detail/breadcrumbs/breadcrumbItem'; const EVENT_STICK_MARKER_WIDTH = 4; type Props = { crumbs: Crumb[]; durationMs: number; startTimestampMs: number; width: number; className?: string; }; function ReplayTimelineEvents({ className, crumbs, durationMs, startTimestampMs, width, }: Props) { const totalColumns = Math.floor(width / EVENT_STICK_MARKER_WIDTH); const eventsByCol = getCrumbsByColumn( startTimestampMs, durationMs, crumbs, totalColumns ); return ( {Array.from(eventsByCol.entries()).map(([column, breadcrumbs]) => ( ))} ); } const EventColumn = styled(Timeline.Col)<{column: number}>` grid-column: ${p => Math.floor(p.column)}; place-items: stretch; display: grid; align-items: center; position: relative; &:hover { z-index: ${p => p.theme.zIndex.initial}; } `; function Event({ crumbs, startTimestampMs, }: { crumbs: Crumb[]; startTimestampMs: number; className?: string; }) { const {setActiveTab} = useActiveReplayTab(); const {handleMouseEnter, handleMouseLeave, handleClick} = useCrumbHandlers(startTimestampMs); const title = crumbs.map(crumb => ( )); const overlayStyle = css` /* We make sure to override existing styles */ padding: ${space(0.5)} !important; max-width: 291px !important; width: 291px; @media screen and (max-width: ${theme.breakpoints.small}) { max-width: 220px !important; } `; // If we have more than 3 events we want to make sure of showing all the different colors that we have const colors = [...new Set(crumbs.map(crumb => crumb.color))]; // We just need to stack up to 3 times const totalStackNumber = Math.min(crumbs.length, 3); // If there is only 1 event use the tab navigation handler on the node const nodeClickHandler = () => { if (crumbs.length === 1) { const crumb = crumbs[0]; switch (crumb.type) { case 'navigation': case 'debug': setActiveTab('network'); break; case 'ui': setActiveTab('dom'); break; case 'error': default: setActiveTab('console'); break; } } }; return ( {crumbs.slice(0, totalStackNumber).map((crumb, index) => ( ))} ); } const getNodeDimensions = ({ stack, }: { stack: { index: number; totalStackNumber: number; }; }) => { const {totalStackNumber, index} = stack; const multiplier = totalStackNumber - index; const size = (multiplier + 1) * 4; return ` width: ${size}px; height: ${size}px; `; }; const IconNodeTooltip = styled(Tooltip)` display: grid; justify-items: center; align-items: center; `; const IconPosition = styled('div')` position: absolute; transform: translate(-50%); margin-left: ${EVENT_STICK_MARKER_WIDTH / 2}px; `; const IconNode = styled('div')<{ color: Color; stack: { index: number; totalStackNumber: number; }; }>` grid-column: 1; grid-row: 1; ${getNodeDimensions} border-radius: 50%; color: ${p => p.theme.white}; background: ${p => p.theme[p.color] ?? p.color}; box-shadow: ${p => p.theme.dropShadowLightest}; user-select: none; `; export default ReplayTimelineEvents;