diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png index 9d259c96a06cd..b2d4c263e841a 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png index 67a3c75bc8661..600363cbe918f 100644 Binary files a/frontend/__snapshots__/replay-player-success--recent-recordings--light.png and b/frontend/__snapshots__/replay-player-success--recent-recordings--light.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index 8ca07124d50f9..085e1712a86b5 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png differ diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png index fcf8f4fd07417..0aca8202e0d92 100644 Binary files a/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png and b/frontend/__snapshots__/replay-player-success--second-recording-in-list--light.png differ diff --git a/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx b/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx index dae06c8ba2eb1..8a5c2cb68f166 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerMetaLinks.tsx @@ -10,10 +10,11 @@ import { IconTrash, } from '@posthog/icons' import { LemonButton, LemonButtonProps, LemonDialog, LemonMenu, LemonMenuItems } from '@posthog/lemon-ui' +import clsx from 'clsx' import { useActions, useValues } from 'kea' import { FEATURE_FLAGS } from 'lib/constants' import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' -import { IconComment } from 'lib/lemon-ui/icons' +import { IconComment, IconFullScreen } from 'lib/lemon-ui/icons' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { Fragment } from 'react' import { useNotebookNode } from 'scenes/notebooks/Nodes/NotebookNodeContext' @@ -78,8 +79,9 @@ function PinToPlaylistButton({ } export function PlayerMetaLinks({ iconsOnly }: { iconsOnly: boolean }): JSX.Element { - const { sessionRecordingId, logicProps } = useValues(sessionRecordingPlayerLogic) + const { sessionRecordingId, logicProps, isFullScreen } = useValues(sessionRecordingPlayerLogic) const { setPause, setIsFullScreen } = useActions(sessionRecordingPlayerLogic) + const nodeLogic = useNotebookNode() const { closeSessionPlayer } = useActions(sessionPlayerModalLogic()) @@ -112,6 +114,11 @@ export function PlayerMetaLinks({ iconsOnly }: { iconsOnly: boolean }): JSX.Elem
{![SessionRecordingPlayerMode.Sharing].includes(mode) ? ( <> + {sessionRecordingId && ( +
+ {mode === SessionRecordingPlayerMode.Standard && } +
+ )} } @@ -159,11 +166,13 @@ export function PlayerMetaLinks({ iconsOnly }: { iconsOnly: boolean }): JSX.Elem - {sessionRecordingId && ( -
- {mode === SessionRecordingPlayerMode.Standard && } -
- )} + setIsFullScreen(!isFullScreen)} + tooltip={`${!isFullScreen ? 'Go' : 'Exit'} full screen (F)`} + > + + ) : null}
diff --git a/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx b/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx index 615cda9768b4f..c5ac8b967f20a 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerSettings.tsx @@ -1,109 +1,96 @@ -import { IconEllipsis, IconFastForward } from '@posthog/icons' +import { IconFastForward, IconGear } from '@posthog/icons' import { LemonButton, LemonMenu, LemonSelect, LemonSwitch } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { playerSettingsLogic } from './playerSettingsLogic' -import { PLAYBACK_SPEEDS } from './sessionRecordingPlayerLogic' export const PlayerSettings = (): JSX.Element => { - const { speed, autoplayDirection, skipInactivitySetting, showMouseTail, showSeekbarTicks } = - useValues(playerSettingsLogic) - const { setSpeed, setAutoplayDirection, setSkipInactivitySetting, setShowMouseTail, setShowSeekbarTicks } = + const { autoplayDirection, skipInactivitySetting, showMouseTail, showSeekbarTicks } = useValues(playerSettingsLogic) + const { setAutoplayDirection, setSkipInactivitySetting, setShowMouseTail, setShowSeekbarTicks } = useActions(playerSettingsLogic) return ( -
- ( -
- Autoplay + ( +
+ Autoplay - - Autoplay next recording -
({!autoplayDirection ? 'off' : autoplayDirection}) -
- } - value={autoplayDirection} - aria-label="Autoplay next recording" - onChange={setAutoplayDirection} - dropdownPlacement="bottom-end" - dropdownMatchSelectWidth={false} - options={[ - { value: null, label: 'Off' }, - { value: 'newer', label: 'Newer recordings' }, - { value: 'older', label: 'Older recordings' }, - ]} - size="small" - /> -
- ), - }, - { - custom: true, - label: () => ( - - ), - }, - { - custom: true, - label: () => ( - - ), - }, - { - custom: true, - label: () => ( - + + Autoplay next recording +
({!autoplayDirection ? 'off' : autoplayDirection}) +
} - fullWidth - data-attr="skip-inactivity" - className="px-2 py-1" - checked={skipInactivitySetting} - onChange={setSkipInactivitySetting} - label="Skip inactivity" + value={autoplayDirection} + aria-label="Autoplay next recording" + onChange={setAutoplayDirection} + dropdownPlacement="bottom-end" + dropdownMatchSelectWidth={false} + options={[ + { value: null, label: 'Off' }, + { value: 'newer', label: 'Newer recordings' }, + { value: 'older', label: 'Older recordings' }, + ]} + size="small" /> - ), - }, - { - label: `Playback speed (${speed}x)`, - items: PLAYBACK_SPEEDS.map((speedToggle) => ({ - label: `${speedToggle}x`, - onClick: () => setSpeed(speedToggle), - active: speedToggle === speed, - })), - placement: 'right-end', - }, - ]} - > - } /> - - + + ), + }, + { + custom: true, + label: () => ( + + ), + }, + { + custom: true, + label: () => ( + + ), + }, + { + custom: true, + label: () => ( + + } + fullWidth + data-attr="skip-inactivity" + className="px-2 py-1" + checked={skipInactivitySetting} + onChange={setSkipInactivitySetting} + label="Skip inactivity" + /> + ), + }, + ]} + > + } /> + ) } diff --git a/frontend/src/scenes/session-recordings/player/PlayerSidebar.tsx b/frontend/src/scenes/session-recordings/player/PlayerSidebar.tsx index c6fa9522cbfb9..eaa19d22260c2 100644 --- a/frontend/src/scenes/session-recordings/player/PlayerSidebar.tsx +++ b/frontend/src/scenes/session-recordings/player/PlayerSidebar.tsx @@ -9,7 +9,7 @@ import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { capitalizeFirstLetter } from 'lib/utils' import { useRef } from 'react' -import { SessionRecordingSidebarTab } from '~/types' +import { SessionRecordingSidebarStacking, SessionRecordingSidebarTab } from '~/types' import { TabToIcon } from './inspector/PlayerInspectorControls' import { PlayerPersonMeta } from './PlayerPersonMeta' @@ -17,20 +17,14 @@ import { playerSettingsLogic } from './playerSettingsLogic' import { playerSidebarLogic } from './sidebar/playerSidebarLogic' import { PlayerSidebarTab } from './sidebar/PlayerSidebarTab' -export function PlayerSidebar({ - isVerticallyStacked, - toggleLayoutStacking, -}: { - isVerticallyStacked: boolean - toggleLayoutStacking?: () => void -}): JSX.Element { +export function PlayerSidebar(): JSX.Element { const ref = useRef(null) const { featureFlags } = useValues(featureFlagLogic) const { activeTab } = useValues(playerSidebarLogic) const { setTab } = useActions(playerSidebarLogic) - const { sidebarOpen } = useValues(playerSettingsLogic) - const { setSidebarOpen } = useActions(playerSettingsLogic) + const { sidebarOpen, preferredSidebarStacking, isVerticallyStacked } = useValues(playerSettingsLogic) + const { setSidebarOpen, setPreferredSidebarStacking } = useActions(playerSettingsLogic) const logicKey = `player-sidebar-${isVerticallyStacked ? 'vertical' : 'horizontal'}` @@ -87,15 +81,19 @@ export function PlayerSidebar({ barClassName="mb-0" />
-
- {toggleLayoutStacking && ( - : } - onClick={toggleLayoutStacking} - tooltip={`Dock to ${isVerticallyStacked ? 'right' : 'bottom'}`} - /> - )} +
+ : } + onClick={() => + setPreferredSidebarStacking( + preferredSidebarStacking === SessionRecordingSidebarStacking.Vertical + ? SessionRecordingSidebarStacking.Horizontal + : SessionRecordingSidebarStacking.Vertical + ) + } + tooltip={`Dock to ${isVerticallyStacked ? 'right' : 'bottom'}`} + /> } diff --git a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx index 63dd749db6228..3dbeb3008c7f9 100644 --- a/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx +++ b/frontend/src/scenes/session-recordings/player/SessionRecordingPlayer.tsx @@ -15,8 +15,6 @@ import { RecordingNotFound } from 'scenes/session-recordings/player/RecordingNot import { MatchingEventsMatchType } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic' import { urls } from 'scenes/urls' -import { SessionRecordingSidebarStacking } from '~/types' - import { NetworkView } from '../apm/NetworkView' import { PlayerController } from './controller/PlayerController' import { PlayerFrame } from './PlayerFrame' @@ -95,8 +93,7 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. sessionRecordingPlayerLogic(logicProps) ) const speedHotkeys = useMemo(() => createPlaybackSpeedKey(setSpeed), [setSpeed]) - const { preferredSidebarStacking, sidebarOpen, playbackMode } = useValues(playerSettingsLogic) - const { setPreferredSidebarStacking } = useActions(playerSettingsLogic) + const { isVerticallyStacked, sidebarOpen, playbackMode } = useValues(playerSettingsLogic) useKeyboardHotkeys( { @@ -160,10 +157,6 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. } ) - const compactLayout = size === 'small' - const layoutStacking = compactLayout ? SessionRecordingSidebarStacking.Vertical : preferredSidebarStacking - const isVerticallyStacked = layoutStacking === SessionRecordingSidebarStacking.Vertical - const lessThanFiveMinutesOld = dayjs().diff(start, 'minute') <= 5 const cannotPlayback = snapshotsInvalid && lessThanFiveMinutesOld && !messageTooLargeWarnings @@ -239,22 +232,7 @@ export function SessionRecordingPlayer(props: SessionRecordingPlayerProps): JSX. )}
- {!noInspector && ( - - setPreferredSidebarStacking( - preferredSidebarStacking === - SessionRecordingSidebarStacking.Vertical - ? SessionRecordingSidebarStacking.Horizontal - : SessionRecordingSidebarStacking.Vertical - ) - } - /> - )} + {!noInspector && } )} diff --git a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx index fe44dfe3a444b..894aca7cd2fbc 100644 --- a/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx +++ b/frontend/src/scenes/session-recordings/player/controller/PlayerController.tsx @@ -1,9 +1,12 @@ import { IconPause, IconPlay } from '@posthog/icons' -import clsx from 'clsx' import { useActions, useValues } from 'kea' -import { IconFullScreen, IconSync } from 'lib/lemon-ui/icons' +import { IconSync } from 'lib/lemon-ui/icons' import { LemonButton } from 'lib/lemon-ui/LemonButton' -import { sessionRecordingPlayerLogic } from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' +import { LemonMenu } from 'lib/lemon-ui/LemonMenu' +import { + PLAYBACK_SPEEDS, + sessionRecordingPlayerLogic, +} from 'scenes/session-recordings/player/sessionRecordingPlayerLogic' import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut' import { SessionPlayerState } from '~/types' @@ -13,50 +16,66 @@ import { PlayerSettings } from '../PlayerSettings' import { SeekSkip, Timestamp } from './PlayerControllerTime' import { Seekbar } from './Seekbar' -export function PlayerController({ iconsOnly }: { iconsOnly: boolean }): JSX.Element { - const { playingState, isFullScreen, endReached } = useValues(sessionRecordingPlayerLogic) - const { togglePlayPause, setIsFullScreen } = useActions(sessionRecordingPlayerLogic) +function SetPlaybackSpeed(): JSX.Element { + const { speed } = useValues(sessionRecordingPlayerLogic) + const { setSpeed } = useActions(sessionRecordingPlayerLogic) + return ( + ({ + label: `${speedToggle}x`, + onClick: () => setSpeed(speedToggle), + }))} + > + + {speed}x + + + ) +} + +function PlayPauseButton(): JSX.Element { + const { playingState, endReached } = useValues(sessionRecordingPlayerLogic) + const { togglePlayPause } = useActions(sessionRecordingPlayerLogic) const showPause = playingState === SessionPlayerState.PLAY + return ( + + {showPause ? 'Pause' : endReached ? 'Restart' : 'Play'} + +
+ } + > + {showPause ? ( + + ) : endReached ? ( + + ) : ( + + )} + + ) +} + +export function PlayerController({ iconsOnly }: { iconsOnly: boolean }): JSX.Element { return (
-
+
-
- - {showPause ? 'Pause' : endReached ? 'Restart' : 'Play'} - -
- } - > - {showPause ? ( - - ) : endReached ? ( - - ) : ( - - )} - +
+ - setIsFullScreen(!isFullScreen)} - tooltip={`${!isFullScreen ? 'Go' : 'Exit'} full screen (F)`} - > - - + +
-
diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 67561520bc1f6..b5d1ada994eba 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -349,6 +349,10 @@ export const playerSettingsLogic = kea([ })), selectors({ + isVerticallyStacked: [ + (s) => [s.preferredSidebarStacking], + (preferredSidebarStacking) => preferredSidebarStacking === SessionRecordingSidebarStacking.Vertical, + ], miniFilters: [ (s) => [s.tab, s.selectedMiniFilters], (tab, selectedMiniFilters): SharedListMiniFilter[] => {