-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
1,479 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
34 changes: 34 additions & 0 deletions
34
static/app/components/replays/player/__stories__/jumpToOffsetButtonBar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
26
static/app/components/replays/player/__stories__/providers.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
42 changes: 42 additions & 0 deletions
42
static/app/components/replays/player/__stories__/replaySlugChooser.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
55
static/app/components/replays/player/replayCurrentTime.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
12
static/app/components/replays/player/replayCurrentTime.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']} />; | ||
} |
69 changes: 69 additions & 0 deletions
69
static/app/components/replays/player/replayPlayPauseButton.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
37
static/app/components/replays/player/replayPlayPauseButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
50
static/app/components/replays/player/replayPlayer.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} />; | ||
} |
Oops, something went wrong.