splitCrumbs.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import first from 'lodash/first';
  4. import last from 'lodash/last';
  5. import {Hovercard} from 'sentry/components/hovercard';
  6. import TextOverflow from 'sentry/components/textOverflow';
  7. import Tooltip from 'sentry/components/tooltip';
  8. import {tn} from 'sentry/locale';
  9. import space from 'sentry/styles/space';
  10. import {BreadcrumbTypeNavigation, Crumb} from 'sentry/types/breadcrumbs';
  11. import BreadcrumbItem from 'sentry/views/replays/detail/breadcrumbs/breadcrumbItem';
  12. type MaybeOnClickHandler = null | ((crumb: Crumb) => void);
  13. function splitCrumbs({
  14. crumbs,
  15. onClick,
  16. startTimestampMs,
  17. }: {
  18. crumbs: BreadcrumbTypeNavigation[];
  19. onClick: MaybeOnClickHandler;
  20. startTimestampMs: number;
  21. }) {
  22. const firstUrl = first(crumbs)?.data?.to;
  23. const summarizedCrumbs = crumbs.slice(1, -1) as Crumb[];
  24. const lastUrl = last(crumbs)?.data?.to;
  25. if (crumbs.length === 0) {
  26. // This one shouldn't overflow, but by including the component css stays
  27. // consistent with the other Segment types
  28. return [
  29. <Span key="summary">
  30. <TextOverflow>{tn('%s Page', '%s Pages', 0)}</TextOverflow>
  31. </Span>,
  32. ];
  33. }
  34. if (crumbs.length > 3) {
  35. return [
  36. <SingleLinkSegment
  37. key="first"
  38. path={firstUrl}
  39. onClick={onClick ? () => onClick(first(crumbs) as Crumb) : null}
  40. />,
  41. <SummarySegment
  42. key="summary"
  43. crumbs={summarizedCrumbs}
  44. startTimestampMs={startTimestampMs}
  45. handleOnClick={onClick}
  46. />,
  47. <SingleLinkSegment
  48. key="last"
  49. path={lastUrl}
  50. onClick={onClick ? () => onClick(last(crumbs) as Crumb) : null}
  51. />,
  52. ];
  53. }
  54. return crumbs.map((crumb, i) => (
  55. <SingleLinkSegment
  56. key={i}
  57. path={firstUrl}
  58. onClick={onClick ? () => onClick(crumb as Crumb) : null}
  59. />
  60. ));
  61. }
  62. function SingleLinkSegment({
  63. onClick,
  64. path,
  65. }: {
  66. onClick: null | (() => void);
  67. path: undefined | string;
  68. }) {
  69. if (!path) {
  70. return null;
  71. }
  72. const content = (
  73. <Tooltip title={path}>
  74. <TextOverflow ellipsisDirection="left">{path}</TextOverflow>
  75. </Tooltip>
  76. );
  77. if (onClick) {
  78. return (
  79. <Link href="#" onClick={onClick}>
  80. {content}
  81. </Link>
  82. );
  83. }
  84. return <Span>{content}</Span>;
  85. }
  86. function SummarySegment({
  87. crumbs,
  88. handleOnClick,
  89. startTimestampMs,
  90. }: {
  91. crumbs: Crumb[];
  92. handleOnClick: MaybeOnClickHandler;
  93. startTimestampMs: number;
  94. }) {
  95. const summaryItems = crumbs.map(crumb => (
  96. <BreadcrumbItem
  97. key={crumb.id}
  98. crumb={crumb}
  99. startTimestampMs={startTimestampMs}
  100. isHovered={false}
  101. isSelected={false}
  102. onClick={handleOnClick}
  103. />
  104. ));
  105. return (
  106. <Span>
  107. <HalfPaddingHovercard body={summaryItems} position="right">
  108. <TextOverflow>{tn('%s Page', '%s Pages', summaryItems.length)}</TextOverflow>
  109. </HalfPaddingHovercard>
  110. </Span>
  111. );
  112. }
  113. const Span = styled('span')`
  114. color: ${p => p.theme.subText};
  115. font-size: ${p => p.theme.fontSizeSmall};
  116. line-height: 0;
  117. `;
  118. const Link = styled('a')`
  119. color: ${p => p.theme.subText};
  120. font-size: ${p => p.theme.fontSizeSmall};
  121. line-height: 0;
  122. text-decoration: underline;
  123. `;
  124. const HalfPaddingHovercard = styled(
  125. ({children, bodyClassName, ...props}: React.ComponentProps<typeof Hovercard>) => (
  126. <Hovercard bodyClassName={bodyClassName || '' + ' half-padding'} {...props}>
  127. {children}
  128. </Hovercard>
  129. )
  130. )`
  131. .half-padding {
  132. padding: ${space(0.5)};
  133. }
  134. `;
  135. export default splitCrumbs;