timeAndScrubberGrid.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import ReplayTimeline from 'sentry/components/replays/breadcrumbs/replayTimeline';
  6. import {PlayerScrubber} from 'sentry/components/replays/player/scrubber';
  7. import useScrubberMouseTracking from 'sentry/components/replays/player/useScrubberMouseTracking';
  8. import {useReplayContext} from 'sentry/components/replays/replayContext';
  9. import {formatTime} from 'sentry/components/replays/utils';
  10. import {IconAdd, IconSubtract} from 'sentry/icons';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. type TimeAndScrubberGridProps = {
  14. isCompact?: boolean;
  15. showZoom?: boolean;
  16. };
  17. function TimelineSizeBar() {
  18. const {replay, timelineScale, setTimelineScale} = useReplayContext();
  19. const durationMs = replay?.getDurationMs();
  20. const maxScale = durationMs ? Math.ceil(durationMs / 60000) : 10;
  21. return (
  22. <ButtonBar>
  23. <Button
  24. size="xs"
  25. title={t('Zoom out')}
  26. icon={<IconSubtract />}
  27. borderless
  28. onClick={() => setTimelineScale(Math.max(timelineScale - 1, 1))}
  29. aria-label={t('Zoom out')}
  30. disabled={timelineScale === 1}
  31. />
  32. <span style={{padding: `0 ${space(0.5)}`}}>
  33. {timelineScale}
  34. {t('x')}
  35. </span>
  36. <Button
  37. size="xs"
  38. title={t('Zoom in')}
  39. icon={<IconAdd />}
  40. borderless
  41. onClick={() => setTimelineScale(Math.min(timelineScale + 1, maxScale))}
  42. aria-label={t('Zoom in')}
  43. disabled={timelineScale === maxScale}
  44. />
  45. </ButtonBar>
  46. );
  47. }
  48. function TimeAndScrubberGrid({
  49. isCompact = false,
  50. showZoom = false,
  51. }: TimeAndScrubberGridProps) {
  52. const {currentTime, replay} = useReplayContext();
  53. const durationMs = replay?.getDurationMs();
  54. const elem = useRef<HTMLDivElement>(null);
  55. const mouseTrackingProps = useScrubberMouseTracking({elem});
  56. return (
  57. <Grid id="replay-timeline-player" isCompact={isCompact}>
  58. <Time style={{gridArea: 'currentTime'}}>{formatTime(currentTime)}</Time>
  59. <div style={{gridArea: 'timeline'}}>
  60. <ReplayTimeline />
  61. </div>
  62. <div style={{gridArea: 'timelineSize', fontVariantNumeric: 'tabular-nums'}}>
  63. {showZoom ? <TimelineSizeBar /> : null}
  64. </div>
  65. <StyledScrubber style={{gridArea: 'scrubber'}} ref={elem} {...mouseTrackingProps}>
  66. <PlayerScrubber showZoomIndicators={showZoom} />
  67. </StyledScrubber>
  68. <Time style={{gridArea: 'duration'}}>
  69. {durationMs ? formatTime(durationMs) : '--:--'}
  70. </Time>
  71. </Grid>
  72. );
  73. }
  74. const Grid = styled('div')<{isCompact: boolean}>`
  75. width: 100%;
  76. display: grid;
  77. grid-template-areas:
  78. '. timeline timelineSize'
  79. 'currentTime scrubber duration';
  80. grid-column-gap: ${space(1)};
  81. grid-template-columns: max-content auto max-content;
  82. align-items: center;
  83. ${p =>
  84. p.isCompact
  85. ? `
  86. order: -1;
  87. min-width: 100%;
  88. margin-top: -8px;
  89. `
  90. : ''}
  91. `;
  92. const StyledScrubber = styled('div')`
  93. height: 32px;
  94. display: flex;
  95. align-items: center;
  96. `;
  97. const Time = styled('span')`
  98. font-variant-numeric: tabular-nums;
  99. padding: 0 ${space(1.5)};
  100. `;
  101. export default TimeAndScrubberGrid;