diff --git a/static/app/components/replays/player/styles.tsx b/static/app/components/replays/player/styles.tsx new file mode 100644 index 00000000000000..a19bb907758972 --- /dev/null +++ b/static/app/components/replays/player/styles.tsx @@ -0,0 +1,126 @@ +import {css, type Theme} from '@emotion/react'; + +// Base styles, to make the Replayer instance work +export const baseReplayerCss = css` + .replayer-wrapper { + user-select: none; + } + + .replayer-wrapper > .replayer-mouse { + pointer-events: none; + } + .replayer-wrapper > .replayer-mouse-tail { + position: absolute; + pointer-events: none; + } + + /* Override default user-agent styles */ + .replayer-wrapper > iframe { + border: none; + background: white; + + /* Set pointer-events to make it easier to right-click & inspect */ + pointer-events: initial !important; + } +`; + +// Sentry-specific styles for the player. +// The elements we have to work with are: +// ```css +// div.replayer-wrapper {} +// div.replayer-wrapper > div.replayer-mouse {} +// div.replayer-wrapper > canvas.replayer-mouse-tail {} +// div.replayer-wrapper > iframe {} +// ``` +// The mouse-tail is also configured for color/size in `app/components/replays/replayContext.tsx` +export const sentryReplayerCss = (theme: Theme) => css` + .replayer-mouse { + position: absolute; + width: 32px; + height: 32px; + transition: + left 0.05s linear, + top 0.05s linear; + background-size: contain; + background-repeat: no-repeat; + background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTkiIHZpZXdCb3g9IjAgMCAxMiAxOSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAgMTZWMEwxMS42IDExLjZINC44TDQuNCAxMS43TDAgMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOS4xIDE2LjdMNS41IDE4LjJMMC43OTk5OTkgNy4xTDQuNSA1LjZMOS4xIDE2LjdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNC42NzQ1MSA4LjYxODUxTDIuODMwMzEgOS4zOTI3MUw1LjkyNzExIDE2Ljc2OTVMNy43NzEzMSAxNS45OTUzTDQuNjc0NTEgOC42MTg1MVoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik0xIDIuNFYxMy42TDQgMTAuN0w0LjQgMTAuNkg5LjJMMSAyLjRaIiBmaWxsPSJibGFjayIvPgo8L3N2Zz4K'); + border-color: transparent; + } + .replayer-mouse:after { + content: ''; + display: inline-block; + width: 32px; + height: 32px; + background: ${theme.purple300}; + border-radius: 100%; + transform: translate(-50%, -50%); + opacity: 0.3; + } + .replayer-mouse.active:after { + animation: click 0.2s ease-in-out 1; + } + .replayer-mouse.touch-device { + background-image: none; + width: 70px; + height: 70px; + border-radius: 100%; + margin-left: -37px; + margin-top: -37px; + border: 4px solid rgba(73, 80, 246, 0); + transition: + left 0s linear, + top 0s linear, + border-color 0.2s ease-in-out; + } + .replayer-mouse.touch-device.touch-active { + border-color: ${theme.purple200}; + transition: + left 0.25s linear, + top 0.25s linear, + border-color 0.2s ease-in-out; + } + .replayer-mouse.touch-device:after { + opacity: 0; + } + .replayer-mouse.touch-device.active:after { + animation: touch-click 0.2s ease-in-out 1; + } + @keyframes click { + 0% { + opacity: 0.3; + width: 20px; + height: 20px; + } + 50% { + opacity: 0.5; + width: 10px; + height: 10px; + } + } + @keyframes touch-click { + 0% { + opacity: 0; + width: 20px; + height: 20px; + } + 50% { + opacity: 0.5; + width: 10px; + height: 10px; + } + } + + /* Correctly positions the canvas for video replays and shows the purple "mousetails" */ + &.video-replayer { + .replayer-wrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + .replayer-wrapper > iframe { + opacity: 0; + } + } +`; diff --git a/static/app/components/replays/replayPlayer.tsx b/static/app/components/replays/replayPlayer.tsx index b62a9f5e106861..28a2d79f034719 100644 --- a/static/app/components/replays/replayPlayer.tsx +++ b/static/app/components/replays/replayPlayer.tsx @@ -6,6 +6,10 @@ import NegativeSpaceContainer from 'sentry/components/container/negativeSpaceCon import LoadingIndicator from 'sentry/components/loadingIndicator'; import BufferingOverlay from 'sentry/components/replays/player/bufferingOverlay'; import FastForwardBadge from 'sentry/components/replays/player/fastForwardBadge'; +import { + baseReplayerCss, + sentryReplayerCss, +} from 'sentry/components/replays/player/styles'; import {useReplayContext} from 'sentry/components/replays/replayContext'; import {trackAnalytics} from 'sentry/utils/analytics'; import useOrganization from 'sentry/utils/useOrganization'; @@ -177,127 +181,12 @@ const PositionedLoadingIndicator = styled(LoadingIndicator)` // Base styles, to make the Replayer instance work const PlayerRoot = styled(BasePlayerRoot)` - .replayer-wrapper { - user-select: none; - } - - .replayer-wrapper > .replayer-mouse { - pointer-events: none; - } - .replayer-wrapper > .replayer-mouse-tail { - position: absolute; - pointer-events: none; - } - - /* Override default user-agent styles */ - .replayer-wrapper > iframe { - border: none; - background: white; - - /* Set pointer-events to make it easier to right-click & inspect */ - pointer-events: initial !important; - } + ${baseReplayerCss} `; // Sentry-specific styles for the player. -// The elements we have to work with are: -// ```css -// div.replayer-wrapper {} -// div.replayer-wrapper > div.replayer-mouse {} -// div.replayer-wrapper > canvas.replayer-mouse-tail {} -// div.replayer-wrapper > iframe {} -// ``` -// The mouse-tail is also configured for color/size in `app/components/replays/replayContext.tsx` const SentryPlayerRoot = styled(PlayerRoot)` - .replayer-mouse { - position: absolute; - width: 32px; - height: 32px; - transition: - left 0.05s linear, - top 0.05s linear; - background-size: contain; - background-repeat: no-repeat; - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTkiIHZpZXdCb3g9IjAgMCAxMiAxOSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAgMTZWMEwxMS42IDExLjZINC44TDQuNCAxMS43TDAgMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOS4xIDE2LjdMNS41IDE4LjJMMC43OTk5OTkgNy4xTDQuNSA1LjZMOS4xIDE2LjdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNC42NzQ1MSA4LjYxODUxTDIuODMwMzEgOS4zOTI3MUw1LjkyNzExIDE2Ljc2OTVMNy43NzEzMSAxNS45OTUzTDQuNjc0NTEgOC42MTg1MVoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik0xIDIuNFYxMy42TDQgMTAuN0w0LjQgMTAuNkg5LjJMMSAyLjRaIiBmaWxsPSJibGFjayIvPgo8L3N2Zz4K'); - border-color: transparent; - } - .replayer-mouse:after { - content: ''; - display: inline-block; - width: 32px; - height: 32px; - background: ${p => p.theme.purple300}; - border-radius: 100%; - transform: translate(-50%, -50%); - opacity: 0.3; - } - .replayer-mouse.active:after { - animation: click 0.2s ease-in-out 1; - } - .replayer-mouse.touch-device { - background-image: none; - width: 70px; - height: 70px; - border-radius: 100%; - margin-left: -37px; - margin-top: -37px; - border: 4px solid rgba(73, 80, 246, 0); - transition: - left 0s linear, - top 0s linear, - border-color 0.2s ease-in-out; - } - .replayer-mouse.touch-device.touch-active { - border-color: ${p => p.theme.purple200}; - transition: - left 0.25s linear, - top 0.25s linear, - border-color 0.2s ease-in-out; - } - .replayer-mouse.touch-device:after { - opacity: 0; - } - .replayer-mouse.touch-device.active:after { - animation: touch-click 0.2s ease-in-out 1; - } - @keyframes click { - 0% { - opacity: 0.3; - width: 20px; - height: 20px; - } - 50% { - opacity: 0.5; - width: 10px; - height: 10px; - } - } - @keyframes touch-click { - 0% { - opacity: 0; - width: 20px; - height: 20px; - } - 50% { - opacity: 0.5; - width: 10px; - height: 10px; - } - } - - /* Correctly positions the canvas for video replays and shows the purple "mousetails" */ - &.video-replayer { - .replayer-wrapper { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - .replayer-wrapper > iframe { - opacity: 0; - } - } + ${p => sentryReplayerCss(p.theme)} `; const Overlay = styled('div')`