import { STORAGE_TYPES, StorageType, selectStorageType } from 'Player'; import { Switch } from 'antd'; import cn from 'classnames'; import { observer } from 'mobx-react-lite'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { PlayerContext } from 'App/components/Session/playerContext'; import { useStore } from 'App/mstore'; import { FullScreenButton, PlayButton, PlayingState } from 'App/player-ui'; import { session as sessionRoute, withSiteId } from 'App/routes'; import DropdownAudioPlayer from 'Components/Session/Player/ReplayPlayer/AudioPlayer'; import useShortcuts from 'Components/Session/Player/ReplayPlayer/useShortcuts'; import { LaunchConsoleShortcut, LaunchEventsShortcut, LaunchNetworkShortcut, LaunchPerformanceShortcut, LaunchStateShortcut, LaunchXRaShortcut, } from 'Components/Session_/Player/Controls/components/KeyboardHelp'; import { CONSOLE, GRAPHQL, INSPECTOR, NETWORK, OVERVIEW, PERFORMANCE, PROFILER, STACKEVENTS, STORAGE, BACKENDLOGS, } from 'App/mstore/uiPlayerStore'; import { Icon } from 'UI'; import LogsButton from 'App/components/Session/Player/SharedComponents/BackendLogs/LogsButton'; import ControlButton from './ControlButton'; import Timeline from './Timeline'; import PlayerControls from './components/PlayerControls'; import styles from './controls.module.css'; import { useTranslation } from 'react-i18next'; export const SKIP_INTERVALS = { 2: 2e3, 5: 5e3, 10: 1e4, 15: 15e3, 20: 2e4, 30: 3e4, 60: 6e4, }; function getStorageName(type: any) { switch (type) { case STORAGE_TYPES.REDUX: return 'Redux'; case STORAGE_TYPES.MOBX: return 'Mobx'; case STORAGE_TYPES.VUEX: return 'Vuex'; case STORAGE_TYPES.NGRX: return 'NgRx'; case STORAGE_TYPES.ZUSTAND: return 'Zustand'; case STORAGE_TYPES.NONE: return 'State'; default: return 'State'; } } function Controls({ setActiveTab }: any) { const { player, store } = React.useContext(PlayerContext); const { uxtestingStore, uiPlayerStore, projectsStore, sessionStore, userStore, } = useStore(); const permissions = userStore.account.permissions || []; const disableDevtools = userStore.isEnterprise && !( permissions.includes('DEV_TOOLS') || permissions.includes('SERVICE_DEV_TOOLS') ); const { fullscreen } = uiPlayerStore; const { bottomBlock } = uiPlayerStore; const { toggleBottomBlock } = uiPlayerStore; const { fullscreenOn } = uiPlayerStore; const { fullscreenOff } = uiPlayerStore; const { changeSkipInterval } = uiPlayerStore; const { skipInterval } = uiPlayerStore; const showStorageRedux = !uiPlayerStore.hiddenHints.storage; const history = useHistory(); const { siteId } = projectsStore; const { playing, completed, skip, speed, messagesLoading, markedTargets, inspectorMode, } = store.get(); const session = sessionStore.current; const previousSessionId = sessionStore.previousId; const nextSessionId = sessionStore.nextId; const disabled = disableDevtools || messagesLoading || inspectorMode || markedTargets; const sessionTz = session?.timezone; const nextHandler = () => { history.push(withSiteId(sessionRoute(nextSessionId), siteId)); }; const prevHandler = () => { history.push(withSiteId(sessionRoute(previousSessionId), siteId)); }; useShortcuts({ skipInterval, fullScreenOn: fullscreenOn, fullScreenOff: fullscreenOff, toggleBottomBlock, openNextSession: nextHandler, openPrevSession: prevHandler, setActiveTab, disableDevtools, }); const forthTenSeconds = () => { // @ts-ignore player.jumpInterval(SKIP_INTERVALS[skipInterval]); }; const backTenSeconds = () => { // @ts-ignore player.jumpInterval(-SKIP_INTERVALS[skipInterval]); }; const toggleBottomTools = (blockName: number) => { player.toggleInspectorMode(false); toggleBottomBlock(blockName); }; const state = completed ? PlayingState.Completed : playing ? PlayingState.Playing : PlayingState.Paused; const events = session.stackEvents ?? []; return (
{!fullscreen && (
player.toggleSpeed(speedIndex)} toggleSkip={() => player.toggleSkip()} playButton={ } skipIntervals={SKIP_INTERVALS} setSkipInterval={changeSkipInterval} currentInterval={skipInterval} startedAt={session.startedAt} />
{uxtestingStore.hideDevtools && uxtestingStore.isUxt() ? null : ( )}
)}
); } interface IDevtoolsButtons { showStorageRedux: boolean; toggleBottomTools: (blockName: number) => void; bottomBlock: number; disabled: boolean; events: any[]; } const DevtoolsButtons = observer( ({ showStorageRedux, toggleBottomTools, bottomBlock, disabled, events, }: IDevtoolsButtons) => { const { t } = useTranslation(); const { aiSummaryStore, integrationsStore } = useStore(); const { store, player } = React.useContext(PlayerContext); // @ts-ignore const originStr = window.env.ORIGIN || window.location.origin; const isSaas = /app\.openreplay\.com/.test(originStr); const { inspectorMode, currentTab, tabStates } = store.get(); const disableButtons = disabled; const profilesList = tabStates[currentTab]?.profilesList || []; const graphqlList = tabStates[currentTab]?.graphqlList || []; const logRedCount = tabStates[currentTab]?.logMarkedCountNow || 0; const resourceRedCount = tabStates[currentTab]?.resourceMarkedCountNow || 0; const stackRedCount = tabStates[currentTab]?.stackMarkedCountNow || 0; const exceptionsList = tabStates[currentTab]?.exceptionsList || []; const storageType = store.get().tabStates[currentTab] ? selectStorageType(store.get().tabStates[currentTab]) : StorageType.NONE; const profilesCount = profilesList.length; const graphqlCount = graphqlList.length; const showGraphql = graphqlCount > 0; const showProfiler = profilesCount > 0; const showExceptions = exceptionsList.length > 0; const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux; const showSummary = () => { player.pause(); if (bottomBlock !== OVERVIEW) { toggleBottomTools(OVERVIEW); } aiSummaryStore.setToggleSummary(!aiSummaryStore.toggleSummary); }; const possibleAudio = events.filter((e) => e.name.includes('media/audio')); const integratedServices = integrationsStore.integrations.backendLogIntegrations; return ( <> {isSaas ? : null}
{t('Get a quick overview on the issues in this session.')}
} label="X-Ray" onClick={() => toggleBottomTools(OVERVIEW)} active={bottomBlock === OVERVIEW && !inspectorMode} />
{t('Launch Console')}
} disabled={disableButtons} onClick={() => toggleBottomTools(CONSOLE)} active={bottomBlock === CONSOLE && !inspectorMode} label={t('Console')} hasErrors={logRedCount > 0 || showExceptions} />
{t('Launch Network')}
} disabled={disableButtons} onClick={() => toggleBottomTools(NETWORK)} active={bottomBlock === NETWORK && !inspectorMode} label={t('Network')} hasErrors={resourceRedCount > 0} />
{t('Launch Performance')}
} disabled={disableButtons} onClick={() => toggleBottomTools(PERFORMANCE)} active={bottomBlock === PERFORMANCE && !inspectorMode} label="Performance" /> {showGraphql && ( toggleBottomTools(GRAPHQL)} active={bottomBlock === GRAPHQL && !inspectorMode} label="Graphql" /> )} {showStorage && (
{t('Launch State')}
} disabled={disableButtons} onClick={() => toggleBottomTools(STORAGE)} active={bottomBlock === STORAGE && !inspectorMode} label={getStorageName(storageType) as string} /> )}
{t('Launch Events')}
} disabled={disableButtons} onClick={() => toggleBottomTools(STACKEVENTS)} active={bottomBlock === STACKEVENTS && !inspectorMode} label={t('Events')} hasErrors={stackRedCount > 0} /> {showProfiler && ( toggleBottomTools(PROFILER)} active={bottomBlock === PROFILER && !inspectorMode} label={t('Profiler')} /> )} {integratedServices.length ? ( service.name)} onClick={() => toggleBottomTools(BACKENDLOGS)} /> ) : null} {possibleAudio.length ? ( ) : null} ); }, ); export function SummaryButton({ onClick, withToggle, onToggle, toggleValue, }: { onClick?: () => void; withToggle?: boolean; onToggle?: () => void; toggleValue?: boolean; }) { const { t } = useTranslation(); const [isHovered, setHovered] = React.useState(false); return (
setHovered(true)} onMouseLeave={() => setHovered(false)} > {withToggle ? ( ) : null}
{t('Summary AI')}
); } export const gradientButton = { border: 'double 1px transparent', borderRadius: '60px', background: 'linear-gradient(#f6f6f6, #f6f6f6), linear-gradient(to right, #394EFF 0%, #3EAAAF 100%)', backgroundOrigin: 'border-box', backgroundClip: 'content-box, border-box', cursor: 'pointer', height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', }; const onHoverFillStyle = { width: '100%', height: '100%', display: 'flex', borderRadius: '60px', gap: 2, alignItems: 'center', padding: '1px 8px', background: 'linear-gradient(156deg, #E3E6FF 0%, #E4F3F4 69.48%)', }; const fillStyle = { width: '100%', height: '100%', display: 'flex', borderRadius: '60px', gap: 2, alignItems: 'center', padding: '1px 8px', }; export default observer(Controls);