togglableAddress.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import styled from '@emotion/styled';
  2. import {STACKTRACE_PREVIEW_TOOLTIP_DELAY} from 'sentry/components/stacktracePreview';
  3. import Tooltip from 'sentry/components/tooltip';
  4. import {IconFilter} from 'sentry/icons';
  5. import {t} from 'sentry/locale';
  6. import space from 'sentry/styles/space';
  7. import {Theme} from 'sentry/utils/theme';
  8. import {formatAddress, parseAddress} from '../utils';
  9. type Props = {
  10. address: string;
  11. isAbsolute: boolean;
  12. isFoundByStackScanning: boolean;
  13. isInlineFrame: boolean;
  14. startingAddress: string | null;
  15. className?: string;
  16. /**
  17. * Is the stack trace being previewed in a hovercard?
  18. */
  19. isHoverPreviewed?: boolean;
  20. onToggle?: (event: React.MouseEvent<SVGElement>) => void;
  21. relativeAddressMaxlength?: number;
  22. };
  23. function TogglableAddress({
  24. startingAddress,
  25. address,
  26. relativeAddressMaxlength,
  27. isInlineFrame,
  28. isFoundByStackScanning,
  29. isAbsolute,
  30. onToggle,
  31. isHoverPreviewed,
  32. className,
  33. }: Props) {
  34. const convertAbsoluteAddressToRelative = () => {
  35. if (!startingAddress) {
  36. return '';
  37. }
  38. const relativeAddress = formatAddress(
  39. parseAddress(address) - parseAddress(startingAddress),
  40. relativeAddressMaxlength
  41. );
  42. return `+${relativeAddress}`;
  43. };
  44. const getAddressTooltip = () => {
  45. if (isInlineFrame && isFoundByStackScanning) {
  46. return t('Inline frame, found by stack scanning');
  47. }
  48. if (isInlineFrame) {
  49. return t('Inline frame');
  50. }
  51. if (isFoundByStackScanning) {
  52. return t('Found by stack scanning');
  53. }
  54. return undefined;
  55. };
  56. const relativeAddress = convertAbsoluteAddressToRelative();
  57. const canBeConverted = !!relativeAddress;
  58. const formattedAddress = !relativeAddress || isAbsolute ? address : relativeAddress;
  59. const tooltipTitle = getAddressTooltip();
  60. const tooltipDelay = isHoverPreviewed ? STACKTRACE_PREVIEW_TOOLTIP_DELAY : undefined;
  61. return (
  62. <Wrapper className={className}>
  63. {onToggle && canBeConverted && (
  64. <AddressIconTooltip
  65. title={isAbsolute ? t('Switch to relative') : t('Switch to absolute')}
  66. containerDisplayMode="inline-flex"
  67. delay={tooltipDelay}
  68. >
  69. <AddressToggleIcon onClick={onToggle} size="xs" color="purple300" />
  70. </AddressIconTooltip>
  71. )}
  72. <Tooltip
  73. title={tooltipTitle}
  74. disabled={!(isFoundByStackScanning || isInlineFrame)}
  75. delay={tooltipDelay}
  76. >
  77. <Address
  78. isFoundByStackScanning={isFoundByStackScanning}
  79. isInlineFrame={isInlineFrame}
  80. canBeConverted={canBeConverted}
  81. >
  82. {formattedAddress}
  83. </Address>
  84. </Tooltip>
  85. </Wrapper>
  86. );
  87. }
  88. const AddressIconTooltip = styled(Tooltip)`
  89. align-items: center;
  90. margin-right: ${space(0.75)};
  91. `;
  92. const AddressToggleIcon = styled(IconFilter)`
  93. cursor: pointer;
  94. visibility: hidden;
  95. display: none;
  96. @media (min-width: ${p => p.theme.breakpoints.small}) {
  97. display: block;
  98. }
  99. `;
  100. const getAddresstextBorderBottom = (
  101. p: Pick<Partial<Props>, 'isFoundByStackScanning' | 'isInlineFrame'> & {theme: Theme}
  102. ) => {
  103. if (p.isFoundByStackScanning) {
  104. return `1px dashed ${p.theme.red300}`;
  105. }
  106. if (p.isInlineFrame) {
  107. return `1px dashed ${p.theme.blue300}`;
  108. }
  109. return 'none';
  110. };
  111. const Address = styled('span')<Partial<Props> & {canBeConverted: boolean}>`
  112. border-bottom: ${getAddresstextBorderBottom};
  113. white-space: nowrap;
  114. @media (min-width: ${p => p.theme.breakpoints.small}) {
  115. padding-left: ${p => (p.canBeConverted ? null : '18px')};
  116. }
  117. `;
  118. const Wrapper = styled('span')`
  119. font-family: ${p => p.theme.text.familyMono};
  120. font-size: ${p => p.theme.fontSizeExtraSmall};
  121. color: ${p => p.theme.textColor};
  122. letter-spacing: -0.25px;
  123. width: 100%;
  124. flex-grow: 0;
  125. flex-shrink: 0;
  126. display: inline-flex;
  127. align-items: center;
  128. padding: 0 ${space(0.5)} 0 0;
  129. order: 1;
  130. @media (min-width: ${props => props.theme.breakpoints.small}) {
  131. padding: 0 ${space(0.5)};
  132. order: 0;
  133. }
  134. `;
  135. export default TogglableAddress;
  136. export {AddressToggleIcon};