Skip to content

Commit

Permalink
ref - WIP - replay all the things
Browse files Browse the repository at this point in the history
  • Loading branch information
ryan953 committed Jul 8, 2024
1 parent d724281 commit 3eab339
Show file tree
Hide file tree
Showing 30 changed files with 1,479 additions and 14 deletions.
18 changes: 18 additions & 0 deletions static/app/components/duration/duration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {css} from '@emotion/react';

import formatDuration from 'sentry/utils/duration/formatDuration';
import type {Duration as TDuration} from 'sentry/utils/duration/types';

export default function Duration({duration}: {duration: TDuration}) {
return (
<time
css={css`
font-variant-numeric: tabular-nums;
`}
dateTime={formatDuration({duration, precision: 'ms', style: 'HTML duration'})}
title={formatDuration({duration, precision: 'ms', style: 'hh:mm:ss.sss'})}
>
{formatDuration({duration, precision: 'sec', style: 'h:mm:ss'})}
</time>
);
}
Empty file removed static/app/components/parser
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Button} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import formatDuration from 'sentry/utils/duration/formatDuration';
import {intervalToMilliseconds} from 'sentry/utils/duration/intervalToMilliseconds';
import {useReplayUserAction} from 'sentry/utils/replays/playback/providers/useReplayPlayerState';

interface Props {
intervals: string[];
}

export default function JumpToOffsetButtonBar({intervals}: Props) {
const userAction = useReplayUserAction();

return (
<ButtonBar merged>
{intervals.map(interval => {
const intervalMs = intervalToMilliseconds(interval);
return (
<Button
key={interval}
onClick={() => userAction({type: 'jumpToOffset', offsetMs: intervalMs})}
size="sm"
>
{formatDuration({
duration: [intervalMs, 'ms'],
style: 'h:mm:ss',
precision: 'sec',
})}
</Button>
);
})}
</ButtonBar>
);
}
26 changes: 26 additions & 0 deletions static/app/components/replays/player/__stories__/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type {ReactNode} from 'react';

import {StaticNoSkipReplayPreferences} from 'sentry/components/replays/preferences/replayPreferences';
import {ReplayPlayerEventsContextProvider} from 'sentry/utils/replays/playback/providers/useReplayPlayerEvents';
import {ReplayPlayerPluginsContextProvider} from 'sentry/utils/replays/playback/providers/useReplayPlayerPlugins';
import {ReplayPlayerStateContextProvider} from 'sentry/utils/replays/playback/providers/useReplayPlayerState';
import {ReplayPreferencesContextProvider} from 'sentry/utils/replays/playback/providers/useReplayPrefs';
import type ReplayReader from 'sentry/utils/replays/replayReader';

export default function Providers({
children,
replay,
}: {
children: ReactNode;
replay: ReplayReader;
}) {
return (
<ReplayPreferencesContextProvider prefsStrategy={StaticNoSkipReplayPreferences}>
<ReplayPlayerPluginsContextProvider>
<ReplayPlayerEventsContextProvider replay={replay}>
<ReplayPlayerStateContextProvider>{children}</ReplayPlayerStateContextProvider>
</ReplayPlayerEventsContextProvider>
</ReplayPlayerPluginsContextProvider>
</ReplayPreferencesContextProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {Fragment, type ReactNode} from 'react';
import {css} from '@emotion/react';

import Providers from 'sentry/components/replays/player/__stories__/providers';
import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader';
import useOrganization from 'sentry/utils/useOrganization';
import {useSessionStorage} from 'sentry/utils/useSessionStorage';

export default function ReplaySlugChooser({children}: {children: ReactNode}) {
const [replaySlug, setReplaySlug] = useSessionStorage('stories:replaySlug', '');

return (
<Fragment>
<input
defaultValue={replaySlug}
onChange={event => {
setReplaySlug(event.target.value);
}}
placeholder="Paste a replaySlug"
css={css`
font-variant-numeric: tabular-nums;
`}
size={34}
/>
{replaySlug ? <LoadReplay replaySlug={replaySlug}>{children}</LoadReplay> : null}
</Fragment>
);
}

function LoadReplay({children, replaySlug}: {children: ReactNode; replaySlug: string}) {
const organization = useOrganization();
const {replay, fetching} = useReplayReader({
orgSlug: organization.slug,
replaySlug,
});

if (!replay || fetching) {
return 'Loading...';
}

return <Providers replay={replay}>{children}</Providers>;
}
55 changes: 55 additions & 0 deletions static/app/components/replays/player/replayCurrentTime.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import NegativeSpaceContainer from 'sentry/components/container/negativeSpaceContainer';
import JumpToOffsetButtonBar from 'sentry/components/replays/player/__stories__/jumpToOffsetButtonBar';
import ReplaySlugChooser from 'sentry/components/replays/player/__stories__/replaySlugChooser';
import ReplayCurrentTime from 'sentry/components/replays/player/replayCurrentTime';
import ReplayPlayer from 'sentry/components/replays/player/replayPlayer';
import ReplayPlayerContainment from 'sentry/components/replays/player/replayPlayerContainment';
import ReplayPlayPauseButton from 'sentry/components/replays/player/replayPlayPauseButton';
import SideBySide from 'sentry/components/stories/sideBySide';
import storyBook from 'sentry/stories/storyBook';

export default storyBook(ReplayCurrentTime, story => {
story('Default', () => {
function Example() {
return (
<SideBySide>
<ReplayPlayPauseButton />
<ReplayCurrentTime />
<NegativeSpaceContainer style={{height: 300}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
</SideBySide>
);
}
return (
<ReplaySlugChooser>
<Example />
</ReplaySlugChooser>
);
});

story('Jumping to different times', () => {
function Example() {
return (
<SideBySide>
<ReplayPlayPauseButton />
<ReplayCurrentTime />
<JumpToOffsetButtonBar intervals={['0m', '1m', '12m']} />

<NegativeSpaceContainer style={{height: 300}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
</SideBySide>
);
}
return (
<ReplaySlugChooser>
<Example />
</ReplaySlugChooser>
);
});
});
12 changes: 12 additions & 0 deletions static/app/components/replays/player/replayCurrentTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {useState} from 'react';

import Duration from 'sentry/components/duration/duration';
import useReplayCurrentTime from 'sentry/utils/replays/playback/hooks/useReplayCurrentTime';

export default function ReplayCurrentTime() {
const [currentTime, setCurrentTime] = useState({timeMs: 0});

useReplayCurrentTime({callback: setCurrentTime});

return <Duration duration={[currentTime.timeMs, 'ms']} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {Fragment} from 'react';

import NegativeSpaceContainer from 'sentry/components/container/negativeSpaceContainer';
import ReplaySlugChooser from 'sentry/components/replays/player/__stories__/replaySlugChooser';
import ReplayPlayer from 'sentry/components/replays/player/replayPlayer';
import ReplayPlayerContainment from 'sentry/components/replays/player/replayPlayerContainment';
import ReplayPlayPauseButton from 'sentry/components/replays/player/replayPlayPauseButton';
import JSXNode from 'sentry/components/stories/jsxNode';
import storyBook from 'sentry/stories/storyBook';

export default storyBook(ReplayPlayer, story => {
story('Default', () => {
function Example() {
return (
<Fragment>
<p>
Include <JSXNode name="ReplayPlayPauseButton" /> inside a{' '}
<JSXNode name="ReplayPlayerStateContextProvider" /> to control the play/pause
state of the replay.
</p>

<NegativeSpaceContainer style={{height: 400}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
<ReplayPlayPauseButton />
</Fragment>
);
}
return (
<ReplaySlugChooser>
<Example />
</ReplaySlugChooser>
);
});

story('Multiple Players', () => {
function Example() {
return (
<Fragment>
<p>
All <JSXNode name="ReplayPlayer" /> instances within the{' '}
<JSXNode name="ReplayPlayerStateContextProvider" /> will play & pause
together.
</p>

<NegativeSpaceContainer style={{height: 200}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
<hr />
<NegativeSpaceContainer style={{height: 200}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
<ReplayPlayPauseButton />
</Fragment>
);
}
return (
<ReplaySlugChooser>
<Example />
</ReplaySlugChooser>
);
});
});
37 changes: 37 additions & 0 deletions static/app/components/replays/player/replayPlayPauseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type {BaseButtonProps} from 'sentry/components/button';
import {Button} from 'sentry/components/button';
import {IconPause, IconPlay, IconRefresh} from 'sentry/icons';
import {t} from 'sentry/locale';
import useReplayPlayerState, {
useReplayUserAction,
} from 'sentry/utils/replays/playback/providers/useReplayPlayerState';

export default function ReplayPlayPauseButton(props: BaseButtonProps) {
const userAction = useReplayUserAction();
const {playerState, isFinished} = useReplayPlayerState();

const isPlaying = playerState === 'playing';

return isFinished ? (
<Button
title={t('Restart Replay')}
icon={<IconRefresh />}
onClick={() => {
userAction({type: 'jumpToOffset', offsetMs: 0});
userAction({type: 'play'});
}}
aria-label={t('Restart Replay')}
priority="primary"
{...props}
/>
) : (
<Button
title={isPlaying ? t('Pause') : t('Play')}
icon={isPlaying ? <IconPause /> : <IconPlay />}
onClick={() => userAction(isPlaying ? {type: 'pause'} : {type: 'play'})}
aria-label={isPlaying ? t('Pause') : t('Play')}
priority="primary"
{...props}
/>
);
}
50 changes: 50 additions & 0 deletions static/app/components/replays/player/replayPlayer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import NegativeSpaceContainer from 'sentry/components/container/negativeSpaceContainer';
import ObjectInspector from 'sentry/components/objectInspector';
import JumpToOffsetButtonBar from 'sentry/components/replays/player/__stories__/jumpToOffsetButtonBar';
import ReplaySlugChooser from 'sentry/components/replays/player/__stories__/replaySlugChooser';
import ReplayCurrentTime from 'sentry/components/replays/player/replayCurrentTime';
import ReplayPlayer from 'sentry/components/replays/player/replayPlayer';
import ReplayPlayerContainment from 'sentry/components/replays/player/replayPlayerContainment';
import ReplayPlayPauseButton from 'sentry/components/replays/player/replayPlayPauseButton';
import ReplayPreferenceDropdown from 'sentry/components/replays/preferences/replayPreferenceDropdown';
import SideBySide from 'sentry/components/stories/sideBySide';
import storyBook from 'sentry/stories/storyBook';
import useReplayPlayerState from 'sentry/utils/replays/playback/providers/useReplayPlayerState';
import useReplayPrefs from 'sentry/utils/replays/playback/providers/useReplayPrefs';

export default storyBook(ReplayPlayer, story => {
story('Default', () => {
function Example() {
return (
<SideBySide>
<ReplayPlayPauseButton />
<ReplayCurrentTime />
<ReplayPreferenceDropdown speedOptions={[0.5, 1, 2, 8]} />
<JumpToOffsetButtonBar intervals={['0m', '1ms', '1m', '8m', '12m']} />
<DebugReplayPlayerState />
<DebugReplayPrefsState />
<NegativeSpaceContainer style={{height: 500}}>
<ReplayPlayerContainment measure="both">
{style => <ReplayPlayer style={style} />}
</ReplayPlayerContainment>
</NegativeSpaceContainer>
</SideBySide>
);
}
return (
<ReplaySlugChooser>
<Example />
</ReplaySlugChooser>
);
});
});

function DebugReplayPlayerState() {
const state = useReplayPlayerState();
return <ObjectInspector data={state} expandLevel={1} />;
}

function DebugReplayPrefsState() {
const [prefs] = useReplayPrefs();
return <ObjectInspector data={prefs} expandLevel={1} />;
}
Loading

0 comments on commit 3eab339

Please sign in to comment.