change(ui): remove live player refs inside replay player, extract some components
This commit is contained in:
parent
6334f10888
commit
5f5efe18e6
17 changed files with 320 additions and 340 deletions
|
|
@ -28,9 +28,6 @@ function LiveSession({
|
|||
} else {
|
||||
console.error('No sessionID in route.');
|
||||
}
|
||||
return () => {
|
||||
if (!session.exists()) return;
|
||||
};
|
||||
}, [sessionId, hasSessionsPath]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import Player from './PlayerInst';
|
||||
import SubHeader from 'Components/Session_/Subheader';
|
||||
|
||||
import styles from 'Components/Session_/playerBlock.module.css';
|
||||
|
||||
interface IProps {
|
||||
fullscreen: boolean;
|
||||
sessionId: string;
|
||||
disabled: boolean;
|
||||
activeTab: string;
|
||||
jiraConfig: Record<string, any>
|
||||
fullView?: boolean
|
||||
isClickmap?: boolean
|
||||
}
|
||||
|
||||
function PlayerBlock(props: IProps) {
|
||||
const {
|
||||
fullscreen,
|
||||
sessionId,
|
||||
disabled,
|
||||
activeTab,
|
||||
jiraConfig,
|
||||
fullView = false,
|
||||
isClickmap
|
||||
} = props;
|
||||
|
||||
const shouldShowSubHeader = !fullscreen && !fullView && !isClickmap
|
||||
return (
|
||||
<div
|
||||
className={cn(styles.playerBlock, 'flex flex-col', !isClickmap ? 'overflow-x-hidden' : 'overflow-visible')}
|
||||
style={{ zIndex: isClickmap ? 1 : undefined, minWidth: isClickmap ? '100%' : undefined }}
|
||||
>
|
||||
{shouldShowSubHeader ? (
|
||||
<SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} />
|
||||
) : null}
|
||||
<Player
|
||||
activeTab={activeTab}
|
||||
fullView={fullView}
|
||||
isClickmap={isClickmap}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']),
|
||||
jiraConfig: state.getIn(['issues', 'list'])[0],
|
||||
}))(PlayerBlock)
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
import React from 'react';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.module.css';
|
||||
import { countDaysFrom } from 'App/date';
|
||||
import cn from 'classnames';
|
||||
import RightBlock from './RightBlock';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import styles from 'Components/Session_/session.module.css';
|
||||
import { countDaysFrom } from 'App/date';
|
||||
import RightBlock from 'Components/Session/RightBlock';
|
||||
import { PlayerContext } from 'Components/Session/playerContext';
|
||||
import Session from 'Types/session'
|
||||
import PlayerBlock from './PlayerBlock';
|
||||
|
||||
const TABS = {
|
||||
EVENTS: 'User Steps',
|
||||
HEATMAPS: 'Click Map',
|
||||
};
|
||||
|
||||
function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isClickmap }) {
|
||||
interface IProps {
|
||||
fullscreen: boolean;
|
||||
activeTab: string;
|
||||
setActiveTab: (tab: string) => void;
|
||||
isClickmap: boolean;
|
||||
session: Session
|
||||
}
|
||||
|
||||
function PlayerContent({ session, fullscreen, activeTab, setActiveTab, isClickmap }: IProps) {
|
||||
const { store } = React.useContext(PlayerContext)
|
||||
|
||||
const {
|
||||
|
|
@ -60,7 +69,6 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isC
|
|||
setActiveTab={setActiveTab}
|
||||
fullscreen={fullscreen}
|
||||
tabs={TABS}
|
||||
live={live}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -69,10 +77,9 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, isC
|
|||
);
|
||||
}
|
||||
|
||||
function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
|
||||
function RightMenu({ tabs, activeTab, setActiveTab, fullscreen }: any) {
|
||||
return (
|
||||
!live &&
|
||||
!fullscreen && <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} />
|
||||
!fullscreen ? <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} /> : null
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import cn from 'classnames';
|
||||
import { EscapeButton } from 'UI';
|
||||
import {
|
||||
NONE,
|
||||
CONSOLE,
|
||||
NETWORK,
|
||||
STACKEVENTS,
|
||||
STORAGE,
|
||||
PROFILER,
|
||||
PERFORMANCE,
|
||||
GRAPHQL,
|
||||
EXCEPTIONS,
|
||||
INSPECTOR,
|
||||
OVERVIEW,
|
||||
fullscreenOff,
|
||||
} from 'Duck/components/player';
|
||||
import NetworkPanel from 'Shared/DevTools/NetworkPanel';
|
||||
import Storage from 'Components/Session_/Storage';
|
||||
import { ConnectedPerformance } from 'Components/Session_/Performance';
|
||||
import GraphQL from 'Components/Session_/GraphQL';
|
||||
import Exceptions from 'Components/Session_/Exceptions/Exceptions';
|
||||
import Inspector from 'Components/Session_/Inspector';
|
||||
import Controls from 'Components/Session_/Player/Controls';
|
||||
import Overlay from 'Components/Session_/Player/Overlay';
|
||||
import stl from 'Components/Session_/Player/player.module.css';
|
||||
import { updateLastPlayedSession } from 'Duck/sessions';
|
||||
import OverviewPanel from 'Components/Session_/OverviewPanel';
|
||||
import ConsolePanel from 'Shared/DevTools/ConsolePanel';
|
||||
import ProfilerPanel from 'Shared/DevTools/ProfilerPanel';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import StackEventPanel from 'Shared/DevTools/StackEventPanel';
|
||||
|
||||
|
||||
interface IProps {
|
||||
fullView: boolean;
|
||||
isMultiview?: boolean;
|
||||
bottomBlock: number;
|
||||
fullscreen: boolean;
|
||||
fullscreenOff: () => any;
|
||||
nextId: string;
|
||||
sessionId: string;
|
||||
activeTab: string;
|
||||
isClickmap?: boolean;
|
||||
updateLastPlayedSession: (id: string) => void
|
||||
}
|
||||
|
||||
function Player(props: IProps) {
|
||||
const {
|
||||
fullscreen,
|
||||
fullscreenOff,
|
||||
nextId,
|
||||
bottomBlock,
|
||||
activeTab,
|
||||
fullView,
|
||||
isClickmap,
|
||||
} = props;
|
||||
const playerContext = React.useContext(PlayerContext);
|
||||
const screenWrapper = React.useRef<HTMLDivElement>(null);
|
||||
const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE;
|
||||
|
||||
React.useEffect(() => {
|
||||
props.updateLastPlayedSession(props.sessionId);
|
||||
const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture
|
||||
if (parentElement) {
|
||||
playerContext.player.attach(parentElement);
|
||||
playerContext.player.play();
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
playerContext.player.scale();
|
||||
}, [props.bottomBlock, props.fullscreen, playerContext.player]);
|
||||
|
||||
if (!playerContext.player) return null;
|
||||
|
||||
const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw';
|
||||
return (
|
||||
<div
|
||||
className={cn(stl.playerBody, 'flex-1 flex flex-col relative', fullscreen && 'pb-2')}
|
||||
data-bottom-block={bottomBlockIsActive}
|
||||
>
|
||||
{fullscreen && <EscapeButton onClose={fullscreenOff} />}
|
||||
<div className={cn("relative flex-1", isClickmap ? 'overflow-visible' : 'overflow-hidden')}>
|
||||
<Overlay nextId={nextId} isClickmap={isClickmap} />
|
||||
<div className={cn(stl.screenWrapper, isClickmap && '!overflow-y-scroll')} ref={screenWrapper} />
|
||||
</div>
|
||||
{!fullscreen && !!bottomBlock && (
|
||||
<div style={{ maxWidth, width: '100%' }}>
|
||||
{bottomBlock === OVERVIEW && <OverviewPanel />}
|
||||
{bottomBlock === CONSOLE && <ConsolePanel />}
|
||||
{bottomBlock === NETWORK && <NetworkPanel />}
|
||||
{bottomBlock === STACKEVENTS && <StackEventPanel />}
|
||||
{bottomBlock === STORAGE && <Storage />}
|
||||
{bottomBlock === PROFILER && <ProfilerPanel />}
|
||||
{bottomBlock === PERFORMANCE && <ConnectedPerformance />}
|
||||
{bottomBlock === GRAPHQL && <GraphQL />}
|
||||
{bottomBlock === EXCEPTIONS && <Exceptions />}
|
||||
{bottomBlock === INSPECTOR && <Inspector />}
|
||||
</div>
|
||||
)}
|
||||
{!fullView && !isClickmap ? (
|
||||
<Controls
|
||||
speedDown={playerContext.player.speedDown}
|
||||
speedUp={playerContext.player.speedUp}
|
||||
jump={playerContext.player.jump}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
nextId: state.getIn(['sessions', 'nextId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
bottomBlock: state.getIn(['components', 'player', 'bottomBlock']),
|
||||
}),
|
||||
{
|
||||
fullscreenOff,
|
||||
updateLastPlayedSession,
|
||||
}
|
||||
)(Player);
|
||||
|
|
@ -7,10 +7,10 @@ import { createWebPlayer } from 'Player';
|
|||
import { makeAutoObservable } from 'mobx';
|
||||
import withLocationHandlers from 'HOCs/withLocationHandlers';
|
||||
import { useStore } from 'App/mstore';
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import PlayerBlockHeader from './Player/ReplayPlayer/PlayerBlockHeader';
|
||||
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
|
||||
import { fetchList as fetchMembers } from 'Duck/member';
|
||||
import PlayerContent from './PlayerContent';
|
||||
import PlayerContent from './Player/ReplayPlayer/PlayerContent';
|
||||
import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Note } from "App/services/NotesService";
|
||||
|
|
|
|||
|
|
@ -2,15 +2,12 @@ import React from 'react';
|
|||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { STORAGE_TYPES, selectStorageType } from 'Player';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
import AssistSessionsTabs from './AssistSessionsTabs';
|
||||
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import {
|
||||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
OVERVIEW,
|
||||
CONSOLE,
|
||||
NETWORK,
|
||||
|
|
@ -25,7 +22,6 @@ import { PlayerContext } from 'App/components/Session/playerContext';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { fetchSessions } from 'Duck/liveSearch';
|
||||
|
||||
import { AssistDuration } from './Time';
|
||||
import Timeline from './Timeline';
|
||||
import ControlButton from './ControlButton';
|
||||
import PlayerControls from './components/PlayerControls';
|
||||
|
|
@ -63,47 +59,32 @@ function getStorageName(type: any) {
|
|||
function Controls(props: any) {
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
|
||||
const { jumpToLive, toggleInspectorMode } = player;
|
||||
const {
|
||||
live,
|
||||
livePlay,
|
||||
playing,
|
||||
completed,
|
||||
skip,
|
||||
// skipToIssue, UPDATE
|
||||
speed,
|
||||
cssLoading,
|
||||
messagesLoading,
|
||||
inspectorMode,
|
||||
markedTargets,
|
||||
// messagesLoading: fullscreenDisabled, UPDATE
|
||||
// stackList,
|
||||
exceptionsList,
|
||||
profilesList,
|
||||
graphqlList,
|
||||
// fetchList,
|
||||
liveTimeTravel,
|
||||
logMarkedCountNow: logRedCount,
|
||||
resourceMarkedCountNow: resourceRedCount,
|
||||
stackMarkedCountNow: stackRedCount,
|
||||
} = store.get();
|
||||
// const storageCount = selectStorageListNow(store.get()).length UPDATE
|
||||
const {
|
||||
bottomBlock,
|
||||
toggleBottomBlock,
|
||||
fullscreen,
|
||||
closedLive,
|
||||
changeSkipInterval,
|
||||
skipInterval,
|
||||
disabledRedux,
|
||||
showStorageRedux,
|
||||
session,
|
||||
// showStackRedux,
|
||||
fetchSessions: fetchAssistSessions,
|
||||
totalAssistSessions,
|
||||
} = props;
|
||||
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
const storageType = selectStorageType(store.get());
|
||||
const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets;
|
||||
const profilesCount = profilesList.length;
|
||||
|
|
@ -112,10 +93,6 @@ function Controls(props: any) {
|
|||
const showProfiler = profilesCount > 0;
|
||||
const showExceptions = exceptionsList.length > 0;
|
||||
const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux;
|
||||
// const fetchCount = fetchList.length;
|
||||
// const stackCount = stackList.length;
|
||||
// const showStack = stackCount > 0 || showStackRedux UPDATE
|
||||
// const showFetch = fetchCount > 0 UPDATE
|
||||
|
||||
const onKeyDown = (e: any) => {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
|
|
@ -145,9 +122,6 @@ function Controls(props: any) {
|
|||
|
||||
React.useEffect(() => {
|
||||
document.addEventListener('keydown', onKeyDown.bind(this));
|
||||
if (isAssist && totalAssistSessions === 0) {
|
||||
fetchAssistSessions();
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown.bind(this));
|
||||
};
|
||||
|
|
@ -223,57 +197,31 @@ function Controls(props: any) {
|
|||
|
||||
return (
|
||||
<div className={styles.controls}>
|
||||
<Timeline
|
||||
live={live}
|
||||
jump={(t: number) => player.jump(t)}
|
||||
liveTimeTravel={liveTimeTravel}
|
||||
pause={() => player.pause()}
|
||||
togglePlay={() => player.togglePlay()}
|
||||
/>
|
||||
<Timeline />
|
||||
{!fullscreen && (
|
||||
<div className={cn(styles.buttons, live ? '!px-5 !pt-0' : '!px-2')} data-is-live={live}>
|
||||
<div className={cn(styles.buttons, '!px-2')}>
|
||||
<div className="flex items-center">
|
||||
{!live && (
|
||||
<>
|
||||
<PlayerControls
|
||||
live={live}
|
||||
skip={skip}
|
||||
speed={speed}
|
||||
disabled={disabled}
|
||||
backTenSeconds={backTenSeconds}
|
||||
forthTenSeconds={forthTenSeconds}
|
||||
toggleSpeed={() => player.toggleSpeed()}
|
||||
toggleSkip={() => player.toggleSkip()}
|
||||
playButton={renderPlayBtn()}
|
||||
controlIcon={controlIcon}
|
||||
skipIntervals={SKIP_INTERVALS}
|
||||
setSkipInterval={changeSkipInterval}
|
||||
currentInterval={skipInterval}
|
||||
/>
|
||||
<div className={cn('mx-2')} />
|
||||
<XRayButton
|
||||
isActive={bottomBlock === OVERVIEW && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(OVERVIEW)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{live && !closedLive && (
|
||||
<div className={styles.buttonsLeft}>
|
||||
<LiveTag isLive={livePlay} onClick={() => (livePlay ? null : jumpToLive())} />
|
||||
<div className="font-semibold px-2">
|
||||
<AssistDuration />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<PlayerControls
|
||||
skip={skip}
|
||||
speed={speed}
|
||||
disabled={disabled}
|
||||
backTenSeconds={backTenSeconds}
|
||||
forthTenSeconds={forthTenSeconds}
|
||||
toggleSpeed={() => player.toggleSpeed()}
|
||||
toggleSkip={() => player.toggleSkip()}
|
||||
playButton={renderPlayBtn()}
|
||||
controlIcon={controlIcon}
|
||||
skipIntervals={SKIP_INTERVALS}
|
||||
setSkipInterval={changeSkipInterval}
|
||||
currentInterval={skipInterval}
|
||||
/>
|
||||
<div className={cn('mx-2')} />
|
||||
<XRayButton
|
||||
isActive={bottomBlock === OVERVIEW && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(OVERVIEW)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isAssist && totalAssistSessions > 1 ? (
|
||||
<div>
|
||||
<AssistSessionsTabs session={session} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex items-center h-full">
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
|
|
@ -285,30 +233,29 @@ function Controls(props: any) {
|
|||
hasErrors={logRedCount > 0 || showExceptions}
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
{!live && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(NETWORK)}
|
||||
active={bottomBlock === NETWORK && !inspectorMode}
|
||||
label="NETWORK"
|
||||
hasErrors={resourceRedCount > 0}
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(PERFORMANCE)}
|
||||
active={bottomBlock === PERFORMANCE && !inspectorMode}
|
||||
label="PERFORMANCE"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && showGraphql && (
|
||||
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(NETWORK)}
|
||||
active={bottomBlock === NETWORK && !inspectorMode}
|
||||
label="NETWORK"
|
||||
hasErrors={resourceRedCount > 0}
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(PERFORMANCE)}
|
||||
active={bottomBlock === PERFORMANCE && !inspectorMode}
|
||||
label="PERFORMANCE"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
|
||||
{showGraphql && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(GRAPHQL)}
|
||||
|
|
@ -319,7 +266,8 @@ function Controls(props: any) {
|
|||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && showStorage && (
|
||||
|
||||
{showStorage && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(STORAGE)}
|
||||
|
|
@ -330,19 +278,17 @@ function Controls(props: any) {
|
|||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(STACKEVENTS)}
|
||||
active={bottomBlock === STACKEVENTS && !inspectorMode}
|
||||
label="EVENTS"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
hasErrors={stackRedCount > 0}
|
||||
/>
|
||||
)}
|
||||
{!live && showProfiler && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(STACKEVENTS)}
|
||||
active={bottomBlock === STACKEVENTS && !inspectorMode}
|
||||
label="EVENTS"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
hasErrors={stackRedCount > 0}
|
||||
/>
|
||||
{showProfiler && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(PROFILER)}
|
||||
|
|
@ -353,17 +299,16 @@ function Controls(props: any) {
|
|||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && (
|
||||
<Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4">
|
||||
{controlIcon(
|
||||
'arrows-angle-extend',
|
||||
16,
|
||||
props.fullscreenOn,
|
||||
false,
|
||||
'rounded hover:bg-gray-light-shade color-gray-medium'
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4">
|
||||
{controlIcon(
|
||||
'arrows-angle-extend',
|
||||
16,
|
||||
props.fullscreenOn,
|
||||
false,
|
||||
'rounded hover:bg-gray-light-shade color-gray-medium'
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -385,8 +330,6 @@ export default connect(
|
|||
showStackRedux: !state.getIn(['components', 'player', 'hiddenHints', 'stack']),
|
||||
session: state.getIn(['sessions', 'current']),
|
||||
totalAssistSessions: state.getIn(['liveSearch', 'total']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current']).live,
|
||||
skipInterval: state.getIn(['components', 'player', 'skipInterval']),
|
||||
};
|
||||
},
|
||||
|
|
@ -394,7 +337,6 @@ export default connect(
|
|||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
fetchSessions,
|
||||
}
|
||||
)(ControlPlayer);
|
||||
|
|
|
|||
|
|
@ -19,28 +19,7 @@ const ReduxTime = observer(({ format, name, isCustom }) => {
|
|||
return <Time format={format} time={time} isCustom={isCustom} />
|
||||
})
|
||||
|
||||
const AssistDurationCont = () => {
|
||||
const { store } = React.useContext(PlayerContext)
|
||||
const { assistStart } = store.get()
|
||||
|
||||
const [assistDuration, setAssistDuration] = React.useState('00:00');
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss'));
|
||||
}
|
||||
, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
Elapsed {assistDuration}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AssistDuration = observer(AssistDurationCont)
|
||||
|
||||
ReduxTime.displayName = "ReduxTime";
|
||||
|
||||
export default React.memo(Time);
|
||||
export { ReduxTime, AssistDuration };
|
||||
export { ReduxTime };
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|||
import styles from './timeTracker.module.css';
|
||||
import cn from 'classnames'
|
||||
|
||||
const TimeTracker = ({ scale, live, left }) => {
|
||||
const TimeTracker = ({ scale, live = false, left }) => {
|
||||
const { store } = React.useContext(PlayerContext)
|
||||
const time = store.get().time
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ function Timeline(props: IProps) {
|
|||
skipToIssue,
|
||||
ready,
|
||||
endTime,
|
||||
live,
|
||||
liveTimeTravel,
|
||||
} = store.get()
|
||||
const { issues } = props;
|
||||
const notes = notesStore.sessionNotes
|
||||
|
|
@ -64,16 +62,13 @@ function Timeline(props: IProps) {
|
|||
const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), [])
|
||||
|
||||
const onDragEnd = () => {
|
||||
if (live && !liveTimeTravel) return;
|
||||
|
||||
if (wasPlaying) {
|
||||
player.togglePlay();
|
||||
}
|
||||
};
|
||||
|
||||
const onDrag: OnDragCallback = (offset) => {
|
||||
if ((live && !liveTimeTravel) || !progressRef.current) return;
|
||||
|
||||
// @ts-ignore react mismatch
|
||||
const p = (offset.x) / progressRef.current.offsetWidth;
|
||||
const time = Math.max(Math.round(p * endTime), 0);
|
||||
debouncedJump(time);
|
||||
|
|
@ -84,39 +79,20 @@ function Timeline(props: IProps) {
|
|||
}
|
||||
};
|
||||
|
||||
const getLiveTime = (e: React.MouseEvent) => {
|
||||
const duration = new Date().getTime() - props.startedAt;
|
||||
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
|
||||
const time = Math.max(Math.round(p * duration), 0);
|
||||
|
||||
return [time, duration];
|
||||
};
|
||||
|
||||
const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target !== progressRef.current && e.target !== timelineRef.current) {
|
||||
return props.tooltipVisible && hideTimeTooltip();
|
||||
}
|
||||
|
||||
let timeLineTooltip;
|
||||
|
||||
if (live) {
|
||||
const [time, duration] = getLiveTime(e);
|
||||
timeLineTooltip = {
|
||||
time: Duration.fromMillis(duration - time).toFormat(`-mm:ss`),
|
||||
offset: e.nativeEvent.offsetX,
|
||||
isVisible: true,
|
||||
};
|
||||
} else {
|
||||
const time = getTime(e);
|
||||
const tz = settingsStore.sessionSettings.timezone.value
|
||||
const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`)
|
||||
timeLineTooltip = {
|
||||
time: Duration.fromMillis(time).toFormat(`mm:ss`),
|
||||
timeStr,
|
||||
offset: e.nativeEvent.offsetX,
|
||||
isVisible: true,
|
||||
};
|
||||
}
|
||||
const time = getTime(e);
|
||||
const tz = settingsStore.sessionSettings.timezone.value
|
||||
const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`)
|
||||
const timeLineTooltip = {
|
||||
time: Duration.fromMillis(time).toFormat(`mm:ss`),
|
||||
timeStr,
|
||||
offset: e.nativeEvent.offsetX,
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
debouncedTooltipChange(timeLineTooltip);
|
||||
}
|
||||
|
|
@ -132,29 +108,16 @@ function Timeline(props: IProps) {
|
|||
hideTimeTooltip();
|
||||
};
|
||||
|
||||
const loadAndSeek = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.persist();
|
||||
await player.toggleTimetravel();
|
||||
|
||||
setTimeout(() => {
|
||||
seekProgress(e);
|
||||
});
|
||||
};
|
||||
|
||||
const jumpToTime = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (live && !liveTimeTravel) {
|
||||
loadAndSeek(e);
|
||||
} else {
|
||||
seekProgress(e);
|
||||
}
|
||||
seekProgress(e);
|
||||
};
|
||||
|
||||
const getTime = (e: React.MouseEvent<HTMLDivElement>, customEndTime?: number) => {
|
||||
// @ts-ignore react mismatch
|
||||
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
|
||||
const targetTime = customEndTime || endTime;
|
||||
const time = Math.max(Math.round(p * targetTime), 0);
|
||||
|
||||
return time;
|
||||
return Math.max(Math.round(p * targetTime), 0);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -176,21 +139,19 @@ function Timeline(props: IProps) {
|
|||
onMouseEnter={showTimeTooltip}
|
||||
onMouseLeave={hideTimeTooltip}
|
||||
>
|
||||
<TooltipContainer live={live} />
|
||||
{/* custo color is live */}
|
||||
<TooltipContainer />
|
||||
<DraggableCircle
|
||||
left={time * scale}
|
||||
onDrop={onDragEnd}
|
||||
live={live}
|
||||
/>
|
||||
<CustomDragLayer
|
||||
onDrag={onDrag}
|
||||
minX={0}
|
||||
maxX={progressRef.current ? progressRef.current.offsetWidth : 0}
|
||||
/>
|
||||
<TimeTracker scale={scale} live={live} left={time * scale} />
|
||||
<TimeTracker scale={scale} left={time * scale} />
|
||||
|
||||
{!live && skip ?
|
||||
{skip ?
|
||||
skipIntervals.map((interval) => (
|
||||
<div
|
||||
key={interval.start}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const ItemTypes = {
|
|||
|
||||
interface Props {
|
||||
left: number
|
||||
live: boolean
|
||||
live?: boolean
|
||||
onDrop?: () => void
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Icon, Tooltip, Popover } from 'UI';
|
||||
import cn from 'classnames';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import { ReduxTime } from '../Time';
|
||||
// @ts-ignore
|
||||
import styles from '../controls.module.css';
|
||||
|
||||
interface Props {
|
||||
live: boolean;
|
||||
skip: boolean;
|
||||
speed: number;
|
||||
disabled: boolean;
|
||||
|
|
@ -30,7 +28,6 @@ interface Props {
|
|||
|
||||
function PlayerControls(props: Props) {
|
||||
const {
|
||||
live,
|
||||
skip,
|
||||
speed,
|
||||
disabled,
|
||||
|
|
@ -45,10 +42,10 @@ function PlayerControls(props: Props) {
|
|||
controlIcon,
|
||||
} = props;
|
||||
const [showTooltip, setShowTooltip] = React.useState(false);
|
||||
const speedRef = React.useRef(null);
|
||||
const arrowBackRef = React.useRef(null);
|
||||
const arrowForwardRef = React.useRef(null);
|
||||
const skipRef = React.useRef<HTMLDivElement>();
|
||||
const speedRef = React.useRef<HTMLButtonElement>(null);
|
||||
const arrowBackRef = React.useRef<HTMLButtonElement>(null);
|
||||
const arrowForwardRef = React.useRef<HTMLButtonElement>(null);
|
||||
const skipRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyboard = (e: KeyboardEvent) => {
|
||||
|
|
@ -56,16 +53,16 @@ function PlayerControls(props: Props) {
|
|||
return;
|
||||
}
|
||||
if (e.key === 'ArrowRight') {
|
||||
arrowForwardRef.current.focus();
|
||||
arrowForwardRef.current?.focus();
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
arrowBackRef.current.focus();
|
||||
arrowBackRef.current?.focus();
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
speedRef.current.focus();
|
||||
speedRef.current?.focus();
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
speedRef.current.focus();
|
||||
speedRef.current?.focus();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyboard);
|
||||
|
|
@ -75,22 +72,20 @@ function PlayerControls(props: Props) {
|
|||
const toggleTooltip = () => {
|
||||
setShowTooltip(!showTooltip);
|
||||
};
|
||||
const handleClickOutside = () => {
|
||||
setShowTooltip(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{playButton}
|
||||
<div className="mx-1" />
|
||||
{!live && (
|
||||
<div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}>
|
||||
{/* @ts-ignore */}
|
||||
<ReduxTime isCustom name="time" format="mm:ss" />
|
||||
<span className="px-1">/</span>
|
||||
{/* @ts-ignore */}
|
||||
<ReduxTime isCustom name="endTime" format="mm:ss" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}>
|
||||
{/* @ts-ignore */}
|
||||
<ReduxTime isCustom name="time" format="mm:ss" />
|
||||
<span className="px-1">/</span>
|
||||
{/* @ts-ignore */}
|
||||
<ReduxTime isCustom name="endTime" format="mm:ss" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch">
|
||||
{/* @ts-ignore */}
|
||||
|
|
@ -115,8 +110,6 @@ function PlayerControls(props: Props) {
|
|||
|
||||
<div className="p-1 border-l border-r bg-active-blue-border border-active-blue-border flex items-center">
|
||||
<Popover
|
||||
// open={showTooltip}
|
||||
// interactive
|
||||
// @ts-ignore
|
||||
theme="nopadding"
|
||||
animation="none"
|
||||
|
|
@ -176,7 +169,7 @@ function PlayerControls(props: Props) {
|
|||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{!live && (
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="mx-2" />
|
||||
{/* @ts-ignore */}
|
||||
|
|
@ -203,7 +196,7 @@ function PlayerControls(props: Props) {
|
|||
{'Skip Inactivity'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import CreateNote from './CreateNote';
|
|||
import store from 'App/store';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
function TooltipContainer({ live }: { live: boolean }) {
|
||||
function TooltipContainer() {
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<>
|
||||
<TimeTooltip liveTimeTravel={live} />
|
||||
<TimeTooltip />
|
||||
<CreateNote />
|
||||
</>
|
||||
</Provider>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,8 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
SessionRecordingStatus,
|
||||
getStatusText,
|
||||
CallingState,
|
||||
ConnectionStatus,
|
||||
RemoteControlStatus,
|
||||
} from 'Player';
|
||||
|
||||
import AutoplayTimer from './Overlay/AutoplayTimer';
|
||||
import PlayIconLayer from './Overlay/PlayIconLayer';
|
||||
import LiveStatusText from './Overlay/LiveStatusText';
|
||||
import Loader from './Overlay/Loader';
|
||||
import ElementsMarker from './Overlay/ElementsMarker';
|
||||
import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
|
|
@ -24,7 +14,6 @@ interface Props {
|
|||
|
||||
function Overlay({
|
||||
nextId,
|
||||
closedLive,
|
||||
isClickmap,
|
||||
}: Props) {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
|
|
@ -37,53 +26,17 @@ function Overlay({
|
|||
completed,
|
||||
autoplay,
|
||||
inspectorMode,
|
||||
live,
|
||||
peerConnectionStatus,
|
||||
markedTargets,
|
||||
activeTargetIndex,
|
||||
livePlay,
|
||||
calling,
|
||||
remoteControl,
|
||||
recordingState,
|
||||
} = store.get()
|
||||
const loading = messagesLoading || cssLoading
|
||||
const liveStatusText = getStatusText(peerConnectionStatus)
|
||||
const concetionStatus = peerConnectionStatus
|
||||
|
||||
const showAutoplayTimer = !live && completed && autoplay && nextId
|
||||
const showPlayIconLayer = !isClickmap && !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer;
|
||||
const showLiveStatusText = live && livePlay && liveStatusText && !loading;
|
||||
|
||||
const showRequestWindow =
|
||||
live &&
|
||||
(calling === CallingState.Connecting ||
|
||||
remoteControl === RemoteControlStatus.Requesting ||
|
||||
recordingState === SessionRecordingStatus.Requesting);
|
||||
|
||||
const getRequestWindowType = () => {
|
||||
if (calling === CallingState.Connecting) {
|
||||
return WindowType.Call
|
||||
}
|
||||
if (remoteControl === RemoteControlStatus.Requesting) {
|
||||
return WindowType.Control
|
||||
}
|
||||
if (recordingState === SessionRecordingStatus.Requesting) {
|
||||
return WindowType.Record
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
const showAutoplayTimer = completed && autoplay && nextId
|
||||
const showPlayIconLayer = !isClickmap && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showRequestWindow ? <RequestingWindow getWindowType={getRequestWindowType} /> : null}
|
||||
{showAutoplayTimer && <AutoplayTimer />}
|
||||
{showLiveStatusText && (
|
||||
<LiveStatusText
|
||||
text={liveStatusText}
|
||||
concetionStatus={closedLive ? ConnectionStatus.Closed : concetionStatus}
|
||||
/>
|
||||
)}
|
||||
{loading ? <Loader /> : null}
|
||||
{showPlayIconLayer && <PlayIconLayer playing={playing} togglePlay={togglePlay} />}
|
||||
{markedTargets && <ElementsMarker targets={markedTargets} activeIndex={activeTargetIndex} />}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,14 @@ import Marker from './ElementsMarker/Marker';
|
|||
import type { MarkedTarget } from 'Player';
|
||||
|
||||
export default function ElementsMarker({ targets, activeIndex }: { targets: MarkedTarget[], activeIndex: number }) {
|
||||
return targets && targets.map(t => <React.Fragment key={t.index}><Marker target={t} active={activeIndex === t.index}/></React.Fragment>)
|
||||
return targets ? <>
|
||||
{targets.map(
|
||||
t => <React.Fragment key={t.index}>
|
||||
<Marker
|
||||
target={t}
|
||||
active={activeIndex === t.index}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</> : null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import Player from './Player';
|
||||
import SubHeader from './Subheader';
|
||||
|
||||
import styles from './playerBlock.module.css';
|
||||
|
||||
@connect((state) => ({
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']),
|
||||
jiraConfig: state.getIn(['issues', 'list'])[0],
|
||||
}))
|
||||
export default class PlayerBlock extends React.PureComponent {
|
||||
render() {
|
||||
const { fullscreen, sessionId, disabled, activeTab, jiraConfig, fullView = false, isMultiview, isClickmap } = this.props;
|
||||
|
||||
const shouldShowSubHeader = !fullscreen && !fullView && !isMultiview && !isClickmap
|
||||
return (
|
||||
<div className={cn(styles.playerBlock, 'flex flex-col', !isClickmap ? 'overflow-x-hidden' : 'overflow-visible')} style={{ zIndex: isClickmap ? 1 : undefined, minWidth: isMultiview || isClickmap ? '100%' : undefined }}>
|
||||
{shouldShowSubHeader ? (
|
||||
<SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} />
|
||||
) : null}
|
||||
<Player
|
||||
className="flex-1"
|
||||
fullscreen={fullscreen}
|
||||
activeTab={activeTab}
|
||||
fullView={fullView}
|
||||
isMultiview={isMultiview}
|
||||
isClickmap={isClickmap}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import { useModal } from 'App/components/Modal';
|
|||
import BugReportModal from './BugReport/BugReportModal';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from 'App/mstore';
|
||||
import AutoplayToggle from 'Shared/AutoplayToggle';
|
||||
|
||||
function SubHeader(props) {
|
||||
|
|
@ -35,7 +34,6 @@ function SubHeader(props) {
|
|||
|
||||
const [isCopied, setCopied] = React.useState(false);
|
||||
const { showModal, hideModal } = useModal();
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
|
||||
const location =
|
||||
currentLocation && currentLocation.length > 60
|
||||
|
|
@ -72,7 +70,6 @@ function SubHeader(props) {
|
|||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{!isAssist ? (
|
||||
<div
|
||||
className="ml-auto text-sm flex items-center color-gray-medium gap-2"
|
||||
style={{ width: 'max-content' }}
|
||||
|
|
@ -111,7 +108,6 @@ function SubHeader(props) {
|
|||
<QueueControls />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { Icon } from 'UI';
|
||||
import stl from './escapeButton.module.css'
|
||||
|
||||
function EscapeButton({ onClose = null}) {
|
||||
function EscapeButton({ onClose = () => null }) {
|
||||
return (
|
||||
<div className={ stl.closeWrapper } onClick={ onClose }>
|
||||
<Icon name="close" size="16" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue