Browse Source

feat(replays): Replay layout zoom in and out on timeline (#58852)

Zoom in or out on the replay timeline. The zooming ranges between 1x and
10x the original timeline size, with each zoom changing the size by 50%
of the original timeline size. The default timeline size is 3x the
original timeline size

Relates to https://github.com/getsentry/team-replay/issues/199
Catherine Lee 1 year ago
parent
commit
618a47ba5c

+ 4 - 7
static/app/components/replays/breadcrumbs/replayTimeline.tsx

@@ -21,9 +21,9 @@ import toPercent from 'sentry/utils/number/toPercent';
 import {useDimensions} from 'sentry/utils/useDimensions';
 import {useDimensions} from 'sentry/utils/useDimensions';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
 
 
-type Props = {};
+type Props = {size: number};
 
 
-function ReplayTimeline({}: Props) {
+function ReplayTimeline({size}: Props) {
   const {replay, currentTime} = useReplayContext();
   const {replay, currentTime} = useReplayContext();
 
 
   const panelRef = useRef<HTMLDivElement>(null);
   const panelRef = useRef<HTMLDivElement>(null);
@@ -45,11 +45,8 @@ function ReplayTimeline({}: Props) {
   const chapterFrames = replay.getChapterFrames();
   const chapterFrames = replay.getChapterFrames();
   const networkFrames = replay.getNetworkFrames();
   const networkFrames = replay.getNetworkFrames();
 
 
-  // timeline size is 300% larger
-  const timelineWidthPercentage = 300;
-
   // start of the timeline is in the middle
   // start of the timeline is in the middle
-  const initialTranslatePercentage = 50 / timelineWidthPercentage;
+  const initialTranslatePercentage = 50 / size;
 
 
   const translatePercentage = toPercent(
   const translatePercentage = toPercent(
     initialTranslatePercentage -
     initialTranslatePercentage -
@@ -60,7 +57,7 @@ function ReplayTimeline({}: Props) {
     <VisiblePanel ref={panelRef} {...mouseTrackingProps}>
     <VisiblePanel ref={panelRef} {...mouseTrackingProps}>
       <Stacked
       <Stacked
         style={{
         style={{
-          width: `${timelineWidthPercentage}%`,
+          width: `${size}%`,
           transform: `translate(${translatePercentage}, 0%)`,
           transform: `translate(${translatePercentage}, 0%)`,
         }}
         }}
         ref={stackedRef}
         ref={stackedRef}

+ 34 - 3
static/app/components/replays/replayController.tsx

@@ -12,6 +12,7 @@ import useScrubberMouseTracking from 'sentry/components/replays/player/useScrubb
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {formatTime} from 'sentry/components/replays/utils';
 import {formatTime} from 'sentry/components/replays/utils';
 import {
 import {
+  IconAdd,
   IconContract,
   IconContract,
   IconExpand,
   IconExpand,
   IconNext,
   IconNext,
@@ -20,6 +21,7 @@ import {
   IconPrevious,
   IconPrevious,
   IconRewind10,
   IconRewind10,
   IconSettings,
   IconSettings,
+  IconSubtract,
 } from 'sentry/icons';
 } from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import ConfigStore from 'sentry/stores/configStore';
 import ConfigStore from 'sentry/stores/configStore';
@@ -144,6 +146,29 @@ function ReplayOptionsMenu({speedOptions}: {speedOptions: number[]}) {
   );
   );
 }
 }
 
 
+function TimelineSizeBar({size, setSize}) {
+  return (
+    <ButtonBar merged>
+      <Button
+        size="xs"
+        title={t('Zoom out')}
+        icon={<IconSubtract size="xs" />}
+        borderless
+        onClick={() => setSize(Math.max(size - 50, 100))}
+        aria-label={t('Zoom out')}
+      />
+      <Button
+        size="xs"
+        title={t('Zoom in')}
+        icon={<IconAdd size="xs" />}
+        borderless
+        onClick={() => setSize(Math.min(size + 50, 1000))}
+        aria-label={t('Zoom in')}
+      />
+    </ButtonBar>
+  );
+}
+
 function ReplayControls({
 function ReplayControls({
   toggleFullscreen,
   toggleFullscreen,
   speedOptions = [0.1, 0.25, 0.5, 1, 2, 4, 8, 16],
   speedOptions = [0.1, 0.25, 0.5, 1, 2, 4, 8, 16],
@@ -155,6 +180,7 @@ function ReplayControls({
   const isFullscreen = useIsFullscreen();
   const isFullscreen = useIsFullscreen();
   const {currentTime, replay} = useReplayContext();
   const {currentTime, replay} = useReplayContext();
   const durationMs = replay?.getDurationMs();
   const durationMs = replay?.getDurationMs();
+  const [size, setSize] = useState(300);
 
 
   // If the browser supports going fullscreen or not. iPhone Safari won't do
   // If the browser supports going fullscreen or not. iPhone Safari won't do
   // it. https://caniuse.com/fullscreen
   // it. https://caniuse.com/fullscreen
@@ -195,7 +221,10 @@ function ReplayControls({
           <TimeAndScrubberGrid isCompact={isCompact}>
           <TimeAndScrubberGrid isCompact={isCompact}>
             <Time style={{gridArea: 'currentTime'}}>{formatTime(currentTime)}</Time>
             <Time style={{gridArea: 'currentTime'}}>{formatTime(currentTime)}</Time>
             <div style={{gridArea: 'timeline'}}>
             <div style={{gridArea: 'timeline'}}>
-              <ReplayTimeline />
+              <ReplayTimeline size={size} />
+            </div>
+            <div style={{gridArea: 'timelineSize'}}>
+              <TimelineSizeBar size={size} setSize={setSize} />
             </div>
             </div>
             <StyledScrubber
             <StyledScrubber
               style={{gridArea: 'scrubber'}}
               style={{gridArea: 'scrubber'}}
@@ -246,6 +275,7 @@ const Container = styled('div')`
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   flex: 1 1;
   flex: 1 1;
+  justify-content: center;
 `;
 `;
 
 
 const TimeAndScrubber = styled('div')<{isCompact: boolean}>`
 const TimeAndScrubber = styled('div')<{isCompact: boolean}>`
@@ -268,9 +298,9 @@ const TimeAndScrubberGrid = styled('div')<{isCompact: boolean}>`
   width: 100%;
   width: 100%;
   display: grid;
   display: grid;
   grid-template-areas:
   grid-template-areas:
-    '. timeline .'
+    '. timeline timelineSize'
     'currentTime scrubber duration';
     'currentTime scrubber duration';
-  grid-column-gap: ${space(2)};
+  grid-column-gap: ${space(1)};
   grid-template-columns: max-content auto max-content;
   grid-template-columns: max-content auto max-content;
   align-items: center;
   align-items: center;
   ${p =>
   ${p =>
@@ -285,6 +315,7 @@ const TimeAndScrubberGrid = styled('div')<{isCompact: boolean}>`
 
 
 const Time = styled('span')`
 const Time = styled('span')`
   font-variant-numeric: tabular-nums;
   font-variant-numeric: tabular-nums;
+  padding: 0 ${space(1.5)};
 `;
 `;
 
 
 const StyledScrubber = styled('div')`
 const StyledScrubber = styled('div')`

+ 1 - 1
static/app/views/replays/detail/layout/index.tsx

@@ -41,7 +41,7 @@ function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
 
 
   const timeline = hasNewTimeline ? null : (
   const timeline = hasNewTimeline ? null : (
     <ErrorBoundary mini>
     <ErrorBoundary mini>
-      <ReplayTimeline />
+      <ReplayTimeline size={100} />
     </ErrorBoundary>
     </ErrorBoundary>
   );
   );