consoleMessage.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {ComponentProps, Fragment, useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import DateTime from 'sentry/components/dateTime';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import {useReplayContext} from 'sentry/components/replays/replayContext';
  6. import {relativeTimeInMs, showPlayerTime} from 'sentry/components/replays/utils';
  7. import Tooltip from 'sentry/components/tooltip';
  8. import {IconClose, IconWarning} from 'sentry/icons';
  9. import space from 'sentry/styles/space';
  10. import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter';
  11. import ViewIssueLink from 'sentry/views/replays/detail/console/viewIssueLink';
  12. const ICONS = {
  13. error: <IconClose isCircled size="xs" />,
  14. warning: <IconWarning size="xs" />,
  15. };
  16. interface Props extends ComponentProps<typeof MessageFormatter> {
  17. hasOccurred: boolean;
  18. isActive: boolean;
  19. isCurrent: boolean;
  20. isLast: boolean;
  21. isOcurring: boolean;
  22. startTimestampMs: number;
  23. }
  24. function ConsoleMessage({
  25. breadcrumb,
  26. isActive = false,
  27. isOcurring = false,
  28. hasOccurred,
  29. isLast,
  30. isCurrent,
  31. startTimestampMs = 0,
  32. }: Props) {
  33. const {setCurrentTime, setCurrentHoverTime} = useReplayContext();
  34. const diff = relativeTimeInMs(breadcrumb.timestamp || '', startTimestampMs);
  35. const handleOnClick = useCallback(() => setCurrentTime(diff), [setCurrentTime, diff]);
  36. const handleOnMouseOver = useCallback(
  37. () => setCurrentHoverTime(diff),
  38. [setCurrentHoverTime, diff]
  39. );
  40. const handleOnMouseOut = useCallback(
  41. () => setCurrentHoverTime(undefined),
  42. [setCurrentHoverTime]
  43. );
  44. const timeHandlers = {
  45. isActive,
  46. isCurrent,
  47. isOcurring,
  48. hasOccurred,
  49. };
  50. return (
  51. <Fragment>
  52. <Icon
  53. isLast={isLast}
  54. level={breadcrumb.level}
  55. onMouseOver={handleOnMouseOver}
  56. onMouseOut={handleOnMouseOut}
  57. {...timeHandlers}
  58. >
  59. {ICONS[breadcrumb.level]}
  60. </Icon>
  61. <Message
  62. isLast={isLast}
  63. level={breadcrumb.level}
  64. onMouseOver={handleOnMouseOver}
  65. onMouseOut={handleOnMouseOut}
  66. aria-current={isCurrent}
  67. {...timeHandlers}
  68. >
  69. <ErrorBoundary mini>
  70. <MessageFormatter breadcrumb={breadcrumb} />
  71. </ErrorBoundary>
  72. <ViewIssueLink breadcrumb={breadcrumb} />
  73. </Message>
  74. <ConsoleTimestamp isLast={isLast} level={breadcrumb.level} {...timeHandlers}>
  75. <Tooltip title={<DateTime date={breadcrumb.timestamp} seconds />}>
  76. <ConsoleTimestampButton
  77. onClick={handleOnClick}
  78. onMouseOver={handleOnMouseOver}
  79. onMouseOut={handleOnMouseOut}
  80. >
  81. {showPlayerTime(breadcrumb.timestamp || '', startTimestampMs)}
  82. </ConsoleTimestampButton>
  83. </Tooltip>
  84. </ConsoleTimestamp>
  85. </Fragment>
  86. );
  87. }
  88. const Common = styled('div')<{
  89. isActive: boolean;
  90. isCurrent: boolean;
  91. isLast: boolean;
  92. level: string;
  93. hasOccurred?: boolean;
  94. isOcurring?: boolean;
  95. }>`
  96. background-color: ${p =>
  97. ['warning', 'error'].includes(p.level)
  98. ? p.theme.alert[p.level].backgroundLight
  99. : 'inherit'};
  100. color: ${({hasOccurred = true, ...p}) => {
  101. if (!hasOccurred) {
  102. return p.theme.gray300;
  103. }
  104. if (['warning', 'error'].includes(p.level)) {
  105. return p.theme.alert[p.level].iconHoverColor;
  106. }
  107. return 'inherit';
  108. }};
  109. transition: color 0.5s ease;
  110. border-bottom: ${p => {
  111. if (p.isCurrent) {
  112. return `1px solid ${p.theme.purple300}`;
  113. }
  114. if (p.isActive && !p.isOcurring) {
  115. return `1px solid ${p.theme.purple200}`;
  116. }
  117. if (p.isLast) {
  118. return 'none';
  119. }
  120. return `1px solid ${p.theme.innerBorder}`;
  121. }};
  122. `;
  123. const ConsoleTimestamp = styled(Common)`
  124. padding: ${space(0.25)} ${space(1)};
  125. `;
  126. const ConsoleTimestampButton = styled('button')`
  127. background: none;
  128. border: none;
  129. `;
  130. const Icon = styled(Common)<{isOcurring?: boolean}>`
  131. padding: ${space(0.5)} ${space(1)};
  132. position: relative;
  133. &:after {
  134. content: '';
  135. position: absolute;
  136. top: 0;
  137. left: 0;
  138. z-index: 1;
  139. height: 100%;
  140. width: ${space(0.5)};
  141. background-color: ${p => (p.isOcurring ? p.theme.focus : 'transparent')};
  142. }
  143. `;
  144. const Message = styled(Common)`
  145. padding: ${space(0.25)} 0;
  146. white-space: pre-wrap;
  147. word-break: break-word;
  148. display: flex;
  149. justify-content: space-between;
  150. `;
  151. export default ConsoleMessage;