refactor(ui/player): refactor more components
This commit is contained in:
parent
e4af3bd6a3
commit
5b97a0c3cf
26 changed files with 1039 additions and 1034 deletions
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { INDEXES } from 'App/constants/zindex';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Loader, Icon } from 'UI';
|
||||
import { initiateCallEnd, releaseRemoteControl } from 'Player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
interface Props {
|
||||
userDisplayName: string;
|
||||
|
|
@ -14,20 +14,38 @@ export enum WindowType {
|
|||
Control,
|
||||
}
|
||||
|
||||
enum Actions {
|
||||
CallEnd,
|
||||
ControlEnd
|
||||
}
|
||||
|
||||
const WIN_VARIANTS = {
|
||||
[WindowType.Call]: {
|
||||
text: 'to accept the call',
|
||||
icon: 'call' as const,
|
||||
action: initiateCallEnd,
|
||||
action: Actions.CallEnd,
|
||||
},
|
||||
[WindowType.Control]: {
|
||||
text: 'to accept remote control request',
|
||||
icon: 'remote-control' as const,
|
||||
action: releaseRemoteControl,
|
||||
action: Actions.ControlEnd,
|
||||
},
|
||||
};
|
||||
|
||||
function RequestingWindow({ userDisplayName, type }: Props) {
|
||||
const { player } = React.useContext(PlayerContext)
|
||||
|
||||
const {
|
||||
assistManager: {
|
||||
initiateCallEnd,
|
||||
releaseRemoteControl,
|
||||
}
|
||||
} = player
|
||||
|
||||
const actions = {
|
||||
[Actions.CallEnd]: initiateCallEnd,
|
||||
[Actions.ControlEnd]: releaseRemoteControl
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="w-full h-full absolute top-0 left-0 flex items-center justify-center"
|
||||
|
|
@ -40,7 +58,7 @@ function RequestingWindow({ userDisplayName, type }: Props) {
|
|||
</div>
|
||||
<span>{WIN_VARIANTS[type].text}</span>
|
||||
<Loader size={30} style={{ minHeight: 60 }} />
|
||||
<Button variant="text-primary" onClick={WIN_VARIANTS[type].action}>
|
||||
<Button variant="text-primary" onClick={actions[WIN_VARIANTS[type].action]}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -48,6 +66,6 @@ function RequestingWindow({ userDisplayName, type }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect((state) => ({
|
||||
export default connect((state: any) => ({
|
||||
userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']),
|
||||
}))(RequestingWindow);
|
||||
|
|
|
|||
|
|
@ -3,15 +3,8 @@ import { Button, Tooltip } from 'UI';
|
|||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { toggleChatWindow } from 'Duck/sessions';
|
||||
import { connectPlayer } from 'Player';
|
||||
import ChatWindow from '../../ChatWindow';
|
||||
import {
|
||||
callPeer,
|
||||
setCallArgs,
|
||||
requestReleaseRemoteControl,
|
||||
toggleAnnotation,
|
||||
toggleUserName,
|
||||
} from 'Player';
|
||||
// state enums
|
||||
import {
|
||||
CallingState,
|
||||
ConnectionStatus,
|
||||
|
|
@ -19,6 +12,8 @@ import {
|
|||
RequestLocalStream,
|
||||
} from 'Player';
|
||||
import type { LocalStream } from 'Player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { toast } from 'react-toastify';
|
||||
import { confirm } from 'UI';
|
||||
import stl from './AassistActions.module.css';
|
||||
|
|
@ -48,17 +43,31 @@ interface Props {
|
|||
|
||||
function AssistActions({
|
||||
userId,
|
||||
calling,
|
||||
annotating,
|
||||
peerConnectionStatus,
|
||||
remoteControlStatus,
|
||||
hasPermission,
|
||||
isEnterprise,
|
||||
isCallActive,
|
||||
agentIds,
|
||||
livePlay,
|
||||
userDisplayName,
|
||||
}: Props) {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
|
||||
const {
|
||||
assistManager: {
|
||||
call: callPeer,
|
||||
setCallArgs,
|
||||
requestReleaseRemoteControl,
|
||||
toggleAnnotation,
|
||||
},
|
||||
toggleUserName,
|
||||
} = player
|
||||
const {
|
||||
calling,
|
||||
annotating,
|
||||
peerConnectionStatus,
|
||||
remoteControl: remoteControlStatus,
|
||||
livePlay,
|
||||
} = store.get()
|
||||
|
||||
const [isPrestart, setPrestart] = useState(false);
|
||||
const [incomeStream, setIncomeStream] = useState<MediaStream[] | null>([]);
|
||||
const [localStream, setLocalStream] = useState<LocalStream | null>(null);
|
||||
|
|
@ -236,7 +245,7 @@ function AssistActions({
|
|||
}
|
||||
|
||||
const con = connect(
|
||||
(state) => {
|
||||
(state: any) => {
|
||||
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
|
||||
return {
|
||||
hasPermission: permissions.includes('ASSIST_CALL'),
|
||||
|
|
@ -248,11 +257,5 @@ const con = connect(
|
|||
);
|
||||
|
||||
export default con(
|
||||
connectPlayer((state) => ({
|
||||
calling: state.calling,
|
||||
annotating: state.annotating,
|
||||
remoteControlStatus: state.remoteControl,
|
||||
peerConnectionStatus: state.peerConnectionStatus,
|
||||
livePlay: state.livePlay,
|
||||
}))(AssistActions)
|
||||
observer(AssistActions)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,20 @@
|
|||
import React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader } from 'UI';
|
||||
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
||||
import { withRequest } from 'HOCs'
|
||||
import {
|
||||
PlayerProvider,
|
||||
connectPlayer,
|
||||
init as initPlayer,
|
||||
clean as cleanPlayer,
|
||||
} from 'Player';
|
||||
import withPermissions from 'HOCs/withPermissions';
|
||||
import { PlayerContext, defaultContextValue } from './playerContext';
|
||||
import { createLiveWebPlayer } from 'Player';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.module.css';
|
||||
|
||||
const InitLoader = connectPlayer(state => ({
|
||||
loading: !state.initialized
|
||||
}))(Loader);
|
||||
|
||||
function LivePlayer ({
|
||||
session,
|
||||
toggleFullscreen,
|
||||
|
|
@ -32,6 +27,8 @@ function LivePlayer ({
|
|||
userEmail,
|
||||
userName
|
||||
}) {
|
||||
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingCredentials) {
|
||||
|
||||
|
|
@ -42,9 +39,16 @@ function LivePlayer ({
|
|||
name: userName,
|
||||
},
|
||||
}
|
||||
initPlayer(sessionWithAgentData, assistCredendials, true);
|
||||
// initPlayer(sessionWithAgentData, assistCredendials, true);
|
||||
const [LivePlayer, LivePlayerStore] = createLiveWebPlayer(
|
||||
sessionWithAgentData,
|
||||
assistCredendials,
|
||||
(state) => makeAutoObservable(state)
|
||||
)
|
||||
setContextValue({ player: LivePlayer, store: LivePlayerStore });
|
||||
|
||||
}
|
||||
return () => cleanPlayer()
|
||||
return () => LivePlayer.clean()
|
||||
}, [ session.sessionId, loadingCredentials, assistCredendials ]);
|
||||
|
||||
// LAYOUT (TODO: local layout state - useContext or something..)
|
||||
|
|
@ -64,15 +68,17 @@ function LivePlayer ({
|
|||
}
|
||||
const [activeTab, setActiveTab] = useState('');
|
||||
|
||||
if (!contextValue.player) return null;
|
||||
|
||||
return (
|
||||
<PlayerProvider>
|
||||
<InitLoader className="flex-1 p-3">
|
||||
<PlayerBlockHeader activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} fullscreen={fullscreen}/>
|
||||
<div className={ styles.session } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
</div>
|
||||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
<PlayerProvider>
|
||||
<PlayerBlockHeader activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} fullscreen={fullscreen}/>
|
||||
<div className={ styles.session } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
</div>
|
||||
</PlayerProvider>
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
|||
import { fetchList } from 'Duck/integrations';
|
||||
import { PlayerProvider, createWebPlayer } from 'Player';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import withLocationHandlers from 'HOCs/withLocationHandlers';
|
||||
import { useStore } from 'App/mstore';
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
|
|
@ -85,6 +84,7 @@ function WebPlayer(props: any) {
|
|||
<PlayerProvider>
|
||||
<>
|
||||
<PlayerBlockHeader
|
||||
// @ts-ignore TODO?
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tabs={TABS}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { tagProps, iTag, Note } from 'App/services/NotesService';
|
||||
import { tagProps, Note } from 'App/services/NotesService';
|
||||
import { formatTimeOrDate } from 'App/date';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
|
|
|||
|
|
@ -55,16 +55,6 @@ export default class Exceptions extends React.PureComponent {
|
|||
|
||||
const filtered = exceptions.filter((e) => filterRE.test(e.name) || filterRE.test(e.message));
|
||||
|
||||
// let lastIndex = -1;
|
||||
// filtered.forEach((item, index) => {
|
||||
// if (
|
||||
// this.props.exceptionsNow.length > 0 &&
|
||||
// item.time <= this.props.exceptionsNow[this.props.exceptionsNow.length - 1].time
|
||||
// ) {
|
||||
// lastIndex = index;
|
||||
// }
|
||||
// });
|
||||
|
||||
return (
|
||||
<>
|
||||
<SlideModal
|
||||
|
|
@ -140,8 +130,6 @@ export default class Exceptions extends React.PureComponent {
|
|||
onJump={() => jump(e.time)}
|
||||
error={e}
|
||||
key={e.key}
|
||||
// selected={lastIndex === index}
|
||||
// inactive={index > lastIndex}
|
||||
onErrorClick={(jsEvent) => {
|
||||
jsEvent.stopPropagation();
|
||||
jsEvent.preventDefault();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function OverviewPanel({ issuesList }: { issuesList: any[] }) {
|
|||
const resourceList = resourceListUnmap
|
||||
.filter((r: any) => r.isRed() || r.isYellow())
|
||||
.concat(fetchList.filter((i: any) => parseInt(i.status) >= 400))
|
||||
.concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)),
|
||||
.concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400))
|
||||
|
||||
const resources: any = React.useMemo(() => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import stl from './SelectorCard.module.css';
|
||||
import cn from 'classnames';
|
||||
import type { MarkedTarget } from 'Player';
|
||||
import { activeTarget } from 'Player';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
interface Props {
|
||||
index?: number;
|
||||
|
|
@ -12,7 +12,11 @@ interface Props {
|
|||
}
|
||||
|
||||
export default function SelectorCard({ index = 1, target, showContent }: Props) {
|
||||
const { player } = React.useContext(PlayerContext)
|
||||
const activeTarget = player.setActiveTarget
|
||||
|
||||
return (
|
||||
// @ts-ignore TODO for Alex
|
||||
<div className={cn(stl.wrapper, { [stl.active]: showContent })} onClick={() => activeTarget(index)}>
|
||||
<div className={stl.top}>
|
||||
{/* @ts-ignore */}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const ControlButton = ({
|
|||
hasErrors = false,
|
||||
active = false,
|
||||
size = 20,
|
||||
noLabel,
|
||||
noLabel = false,
|
||||
labelClassName,
|
||||
containerClassName,
|
||||
noIcon,
|
||||
|
|
|
|||
|
|
@ -1,434 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
connectPlayer,
|
||||
STORAGE_TYPES,
|
||||
selectStorageType,
|
||||
selectStorageListNow,
|
||||
jumpToLive,
|
||||
toggleInspectorMode,
|
||||
} from 'Player';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import {
|
||||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
OVERVIEW,
|
||||
CONSOLE,
|
||||
NETWORK,
|
||||
STACKEVENTS,
|
||||
STORAGE,
|
||||
PROFILER,
|
||||
PERFORMANCE,
|
||||
GRAPHQL,
|
||||
INSPECTOR,
|
||||
} from 'Duck/components/player';
|
||||
import { AssistDuration } from './Time';
|
||||
import Timeline from './Timeline';
|
||||
import ControlButton from './ControlButton';
|
||||
import PlayerControls from './components/PlayerControls';
|
||||
|
||||
import styles from './controls.module.css';
|
||||
import XRayButton from 'Shared/XRayButton';
|
||||
|
||||
const SKIP_INTERVALS = {
|
||||
2: 2e3,
|
||||
5: 5e3,
|
||||
10: 1e4,
|
||||
15: 15e3,
|
||||
20: 2e4,
|
||||
30: 3e4,
|
||||
60: 6e4,
|
||||
};
|
||||
|
||||
function getStorageName(type) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
@connectPlayer((state) => ({
|
||||
time: state.time,
|
||||
endTime: state.endTime,
|
||||
live: state.live,
|
||||
livePlay: state.livePlay,
|
||||
playing: state.playing,
|
||||
completed: state.completed,
|
||||
skip: state.skip,
|
||||
skipToIssue: state.skipToIssue,
|
||||
speed: state.speed,
|
||||
disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets,
|
||||
inspectorMode: state.inspectorMode,
|
||||
fullscreenDisabled: state.messagesLoading,
|
||||
// logCount: state.logList.length,
|
||||
logRedCount: state.logRedCount,
|
||||
showExceptions: state.exceptionsList.length > 0,
|
||||
resourceRedCount: state.resourceRedCount,
|
||||
fetchRedCount: state.fetchRedCount,
|
||||
showStack: state.stackList.length > 0,
|
||||
stackCount: state.stackList.length,
|
||||
stackRedCount: state.stackRedCount,
|
||||
profilesCount: state.profilesList.length,
|
||||
storageCount: selectStorageListNow(state).length,
|
||||
storageType: selectStorageType(state),
|
||||
showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE,
|
||||
showProfiler: state.profilesList.length > 0,
|
||||
showGraphql: state.graphqlList.length > 0,
|
||||
showFetch: state.fetchCount > 0,
|
||||
fetchCount: state.fetchCount,
|
||||
graphqlCount: state.graphqlList.length,
|
||||
liveTimeTravel: state.liveTimeTravel,
|
||||
}))
|
||||
@connect(
|
||||
(state, props) => {
|
||||
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
|
||||
const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee';
|
||||
return {
|
||||
disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')),
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
bottomBlock: state.getIn(['components', 'player', 'bottomBlock']),
|
||||
showStorage:
|
||||
props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
||||
showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']),
|
||||
skipInterval: state.getIn(['components', 'player', 'skipInterval']),
|
||||
};
|
||||
},
|
||||
{
|
||||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
}
|
||||
)
|
||||
export default class Controls extends React.Component {
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.onKeyDown);
|
||||
//this.props.toggleInspectorMode(false);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (
|
||||
nextProps.fullscreen !== this.props.fullscreen ||
|
||||
nextProps.bottomBlock !== this.props.bottomBlock ||
|
||||
nextProps.live !== this.props.live ||
|
||||
nextProps.livePlay !== this.props.livePlay ||
|
||||
nextProps.playing !== this.props.playing ||
|
||||
nextProps.completed !== this.props.completed ||
|
||||
nextProps.skip !== this.props.skip ||
|
||||
nextProps.skipToIssue !== this.props.skipToIssue ||
|
||||
nextProps.speed !== this.props.speed ||
|
||||
nextProps.disabled !== this.props.disabled ||
|
||||
nextProps.fullscreenDisabled !== this.props.fullscreenDisabled ||
|
||||
// nextProps.inspectorMode !== this.props.inspectorMode ||
|
||||
// nextProps.logCount !== this.props.logCount ||
|
||||
nextProps.logRedCount !== this.props.logRedCount ||
|
||||
nextProps.showExceptions !== this.props.showExceptions ||
|
||||
nextProps.resourceRedCount !== this.props.resourceRedCount ||
|
||||
nextProps.fetchRedCount !== this.props.fetchRedCount ||
|
||||
nextProps.showStack !== this.props.showStack ||
|
||||
nextProps.stackCount !== this.props.stackCount ||
|
||||
nextProps.stackRedCount !== this.props.stackRedCount ||
|
||||
nextProps.profilesCount !== this.props.profilesCount ||
|
||||
nextProps.storageCount !== this.props.storageCount ||
|
||||
nextProps.storageType !== this.props.storageType ||
|
||||
nextProps.showStorage !== this.props.showStorage ||
|
||||
nextProps.showProfiler !== this.props.showProfiler ||
|
||||
nextProps.showGraphql !== this.props.showGraphql ||
|
||||
nextProps.showFetch !== this.props.showFetch ||
|
||||
nextProps.fetchCount !== this.props.fetchCount ||
|
||||
nextProps.graphqlCount !== this.props.graphqlCount ||
|
||||
nextProps.liveTimeTravel !== this.props.liveTimeTravel ||
|
||||
nextProps.skipInterval !== this.props.skipInterval
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
if (this.props.inspectorMode) {
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
toggleInspectorMode(false);
|
||||
}
|
||||
}
|
||||
// if (e.key === ' ') {
|
||||
// document.activeElement.blur();
|
||||
// this.props.togglePlay();
|
||||
// }
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
this.props.fullscreenOff();
|
||||
}
|
||||
if (e.key === 'ArrowRight') {
|
||||
this.forthTenSeconds();
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
this.backTenSeconds();
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
this.props.speedDown();
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
this.props.speedUp();
|
||||
}
|
||||
};
|
||||
|
||||
forthTenSeconds = () => {
|
||||
const { time, endTime, jump, skipInterval } = this.props;
|
||||
jump(Math.min(endTime, time + SKIP_INTERVALS[skipInterval]));
|
||||
};
|
||||
|
||||
backTenSeconds = () => {
|
||||
//shouldComponentUpdate
|
||||
const { time, jump, skipInterval } = this.props;
|
||||
jump(Math.max(0, time - SKIP_INTERVALS[skipInterval]));
|
||||
};
|
||||
|
||||
goLive = () => this.props.jump(this.props.endTime);
|
||||
|
||||
renderPlayBtn = () => {
|
||||
const { completed, playing } = this.props;
|
||||
let label;
|
||||
let icon;
|
||||
if (completed) {
|
||||
icon = 'arrow-clockwise';
|
||||
label = 'Replay this session';
|
||||
} else if (playing) {
|
||||
icon = 'pause-fill';
|
||||
label = 'Pause';
|
||||
} else {
|
||||
icon = 'play-fill-new';
|
||||
label = 'Pause';
|
||||
label = 'Play';
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={label}
|
||||
className="mr-4"
|
||||
>
|
||||
<div
|
||||
onClick={this.props.togglePlay}
|
||||
className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade"
|
||||
>
|
||||
<Icon name={icon} size="36" color="inherit" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
controlIcon = (icon, size, action, isBackwards, additionalClasses) => (
|
||||
<div
|
||||
onClick={action}
|
||||
className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', additionalClasses)}
|
||||
style={{ transform: isBackwards ? 'rotate(180deg)' : '' }}
|
||||
>
|
||||
<Icon name={icon} size={size} color="inherit" />
|
||||
</div>
|
||||
);
|
||||
|
||||
render() {
|
||||
const {
|
||||
bottomBlock,
|
||||
toggleBottomBlock,
|
||||
live,
|
||||
livePlay,
|
||||
skip,
|
||||
speed,
|
||||
disabled,
|
||||
logRedCount,
|
||||
showExceptions,
|
||||
resourceRedCount,
|
||||
showStack,
|
||||
stackRedCount,
|
||||
showStorage,
|
||||
storageType,
|
||||
showProfiler,
|
||||
showGraphql,
|
||||
fullscreen,
|
||||
inspectorMode,
|
||||
closedLive,
|
||||
toggleSpeed,
|
||||
toggleSkip,
|
||||
liveTimeTravel,
|
||||
changeSkipInterval,
|
||||
skipInterval,
|
||||
} = this.props;
|
||||
|
||||
const toggleBottomTools = (blockName) => {
|
||||
if (blockName === INSPECTOR) {
|
||||
toggleInspectorMode();
|
||||
bottomBlock && toggleBottomBlock();
|
||||
} else {
|
||||
toggleInspectorMode(false);
|
||||
toggleBottomBlock(blockName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.controls}>
|
||||
<Timeline
|
||||
live={live}
|
||||
jump={this.props.jump}
|
||||
liveTimeTravel={liveTimeTravel}
|
||||
pause={this.props.pause}
|
||||
togglePlay={this.props.togglePlay}
|
||||
/>
|
||||
{!fullscreen && (
|
||||
<div className={cn(styles.buttons, { '!px-5 !pt-0': live })} data-is-live={live}>
|
||||
<div className="flex items-center">
|
||||
{!live && (
|
||||
<>
|
||||
<PlayerControls
|
||||
live={live}
|
||||
skip={skip}
|
||||
speed={speed}
|
||||
disabled={disabled}
|
||||
backTenSeconds={this.backTenSeconds}
|
||||
forthTenSeconds={this.forthTenSeconds}
|
||||
toggleSpeed={toggleSpeed}
|
||||
toggleSkip={toggleSkip}
|
||||
playButton={this.renderPlayBtn()}
|
||||
controlIcon={this.controlIcon}
|
||||
ref={this.speedRef}
|
||||
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 isLivePlay={livePlay} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center h-full">
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(CONSOLE)}
|
||||
active={bottomBlock === CONSOLE && !inspectorMode}
|
||||
label="CONSOLE"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
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(GRAPHQL)}
|
||||
active={bottomBlock === GRAPHQL && !inspectorMode}
|
||||
label="GRAPHQL"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && showStorage && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(STORAGE)}
|
||||
active={bottomBlock === STORAGE && !inspectorMode}
|
||||
label={getStorageName(storageType)}
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
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(PROFILER)}
|
||||
active={bottomBlock === PROFILER && !inspectorMode}
|
||||
label="PROFILER"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && (
|
||||
<Tooltip title="Fullscreen" delay={0} position="top-end" className="mx-4">
|
||||
{this.controlIcon(
|
||||
'arrows-angle-extend',
|
||||
16,
|
||||
this.props.fullscreenOn,
|
||||
false,
|
||||
'rounded hover:bg-gray-light-shade color-gray-medium'
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
420
frontend/app/components/Session_/Player/Controls/Controls.tsx
Normal file
420
frontend/app/components/Session_/Player/Controls/Controls.tsx
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { STORAGE_TYPES, selectStorageType, selectStorageListNow } from 'Player';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import {
|
||||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
OVERVIEW,
|
||||
CONSOLE,
|
||||
NETWORK,
|
||||
STACKEVENTS,
|
||||
STORAGE,
|
||||
PROFILER,
|
||||
PERFORMANCE,
|
||||
GRAPHQL,
|
||||
INSPECTOR,
|
||||
} from 'Duck/components/player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { AssistDuration } from './Time';
|
||||
import Timeline from './Timeline';
|
||||
import ControlButton from './ControlButton';
|
||||
import PlayerControls from './components/PlayerControls';
|
||||
|
||||
import styles from './controls.module.css';
|
||||
import XRayButton from 'Shared/XRayButton';
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
showStackRedux,
|
||||
} = props;
|
||||
|
||||
const storageType = selectStorageType(store.get());
|
||||
const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets;
|
||||
const stackCount = stackList.length;
|
||||
const profilesCount = profilesList.length;
|
||||
const graphqlCount = graphqlList.length;
|
||||
const showGraphql = graphqlCount > 0;
|
||||
const fetchCount = fetchList.length;
|
||||
const showProfiler = profilesCount > 0;
|
||||
const showExceptions = exceptionsList.length > 0;
|
||||
const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux;
|
||||
// const showStack = stackCount > 0 || showStackRedux UPDATE
|
||||
// const showFetch = fetchCount > 0 UPDATE
|
||||
|
||||
const onKeyDown = (e: any) => {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
if (inspectorMode) {
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
player.toggleInspectorMode(false);
|
||||
}
|
||||
}
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
props.fullscreenOff();
|
||||
}
|
||||
if (e.key === 'ArrowRight') {
|
||||
forthTenSeconds();
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
backTenSeconds();
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
props.speedDown();
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
props.speedUp();
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const forthTenSeconds = () => {
|
||||
// @ts-ignore
|
||||
player.jumpInterval(SKIP_INTERVALS[skipInterval])
|
||||
};
|
||||
|
||||
const backTenSeconds = () => {
|
||||
// @ts-ignore
|
||||
player.jumpInterval(-SKIP_INTERVALS[skipInterval])
|
||||
};
|
||||
|
||||
const renderPlayBtn = () => {
|
||||
let label;
|
||||
let icon;
|
||||
if (completed) {
|
||||
icon = 'arrow-clockwise' as const;
|
||||
label = 'Replay this session';
|
||||
} else if (playing) {
|
||||
icon = 'pause-fill' as const;
|
||||
label = 'Pause';
|
||||
} else {
|
||||
icon = 'play-fill-new' as const;
|
||||
label = 'Pause';
|
||||
label = 'Play';
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={label} className="mr-4">
|
||||
<div
|
||||
onClick={() => player.togglePlay()}
|
||||
className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade"
|
||||
>
|
||||
<Icon name={icon} size="36" color="inherit" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const controlIcon = (
|
||||
icon: string,
|
||||
size: number,
|
||||
action: (args: any) => any,
|
||||
isBackwards: boolean,
|
||||
additionalClasses: string
|
||||
) => (
|
||||
<div
|
||||
onClick={action}
|
||||
className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', additionalClasses)}
|
||||
style={{ transform: isBackwards ? 'rotate(180deg)' : '' }}
|
||||
>
|
||||
<Icon
|
||||
// @ts-ignore
|
||||
name={icon}
|
||||
size={size}
|
||||
color="inherit"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const toggleBottomTools = (blockName: number) => {
|
||||
if (blockName === INSPECTOR) {
|
||||
toggleInspectorMode(false);
|
||||
bottomBlock && toggleBottomBlock();
|
||||
} else {
|
||||
toggleInspectorMode(false);
|
||||
toggleBottomBlock(blockName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.controls}>
|
||||
<Timeline
|
||||
live={live}
|
||||
jump={(t: number) => player.jump(t)}
|
||||
liveTimeTravel={liveTimeTravel}
|
||||
pause={() => player.pause()}
|
||||
togglePlay={() => player.togglePlay()}
|
||||
/>
|
||||
{!fullscreen && (
|
||||
<div className={cn(styles.buttons, live ? '!px-5 !pt-0' : '!px-2')} data-is-live={live}>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center h-full">
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(CONSOLE)}
|
||||
active={bottomBlock === CONSOLE && !inspectorMode}
|
||||
label="CONSOLE"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
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(GRAPHQL)}
|
||||
active={bottomBlock === GRAPHQL && !inspectorMode}
|
||||
label="GRAPHQL"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
containerClassName="mx-2"
|
||||
/>
|
||||
)}
|
||||
{!live && showStorage && (
|
||||
<ControlButton
|
||||
disabled={disabled && !inspectorMode}
|
||||
onClick={() => toggleBottomTools(STORAGE)}
|
||||
active={bottomBlock === STORAGE && !inspectorMode}
|
||||
label={getStorageName(storageType)}
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
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(PROFILER)}
|
||||
active={bottomBlock === PROFILER && !inspectorMode}
|
||||
label="PROFILER"
|
||||
noIcon
|
||||
labelClassName="!text-base font-semibold"
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ControlPlayer = observer(Controls);
|
||||
|
||||
export default connect(
|
||||
(state: any) => {
|
||||
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
|
||||
const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee';
|
||||
return {
|
||||
disabledRedux: isEnterprise && !permissions.includes('DEV_TOOLS'),
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
bottomBlock: state.getIn(['components', 'player', 'bottomBlock']),
|
||||
showStorageRedux: !state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
||||
showStackRedux: !state.getIn(['components', 'player', 'hiddenHints', 'stack']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']),
|
||||
skipInterval: state.getIn(['components', 'player', 'skipInterval']),
|
||||
};
|
||||
},
|
||||
{
|
||||
fullscreenOn,
|
||||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
}
|
||||
)(ControlPlayer);
|
||||
|
||||
// shouldComponentUpdate(nextProps) {
|
||||
// if (
|
||||
// nextProps.fullscreen !== props.fullscreen ||
|
||||
// nextProps.bottomBlock !== props.bottomBlock ||
|
||||
// nextProps.live !== props.live ||
|
||||
// nextProps.livePlay !== props.livePlay ||
|
||||
// nextProps.playing !== props.playing ||
|
||||
// nextProps.completed !== props.completed ||
|
||||
// nextProps.skip !== props.skip ||
|
||||
// nextProps.skipToIssue !== props.skipToIssue ||
|
||||
// nextProps.speed !== props.speed ||
|
||||
// nextProps.disabled !== props.disabled ||
|
||||
// nextProps.fullscreenDisabled !== props.fullscreenDisabled ||
|
||||
// // nextProps.inspectorMode !== props.inspectorMode ||
|
||||
// // nextProps.logCount !== props.logCount ||
|
||||
// nextProps.logRedCount !== props.logRedCount ||
|
||||
// nextProps.showExceptions !== props.showExceptions ||
|
||||
// nextProps.resourceRedCount !== props.resourceRedCount ||
|
||||
// nextProps.fetchRedCount !== props.fetchRedCount ||
|
||||
// nextProps.showStack !== props.showStack ||
|
||||
// nextProps.stackCount !== props.stackCount ||
|
||||
// nextProps.stackRedCount !== props.stackRedCount ||
|
||||
// nextProps.profilesCount !== props.profilesCount ||
|
||||
// nextProps.storageCount !== props.storageCount ||
|
||||
// nextProps.storageType !== props.storageType ||
|
||||
// nextProps.showStorage !== props.showStorage ||
|
||||
// nextProps.showProfiler !== props.showProfiler ||
|
||||
// nextProps.showGraphql !== props.showGraphql ||
|
||||
// nextProps.showFetch !== props.showFetch ||
|
||||
// nextProps.fetchCount !== props.fetchCount ||
|
||||
// nextProps.graphqlCount !== props.graphqlCount ||
|
||||
// nextProps.liveTimeTravel !== props.liveTimeTravel ||
|
||||
// nextProps.skipInterval !== props.skipInterval
|
||||
// )
|
||||
// return true;
|
||||
// return false;
|
||||
// }
|
||||
|
|
@ -12,21 +12,17 @@ const Time = ({ time, isCustom, format = 'm:ss', }) => (
|
|||
|
||||
Time.displayName = "Time";
|
||||
|
||||
const ReduxTime = observer(({ format, name }) => {
|
||||
const ReduxTime = observer(({ format, name, isCustom }) => {
|
||||
const { store } = React.useContext(PlayerContext)
|
||||
const time = store.get()[name]
|
||||
|
||||
return <Time format={format} time={time} />
|
||||
return <Time format={format} time={time} isCustom={isCustom} />
|
||||
})
|
||||
|
||||
const AssistDurationCont = connectPlayer(
|
||||
state => {
|
||||
const assistStart = state.assistStart;
|
||||
return {
|
||||
assistStart,
|
||||
}
|
||||
}
|
||||
)(({ assistStart }) => {
|
||||
const AssistDurationCont = () => {
|
||||
const { store } = React.useContext(PlayerContext)
|
||||
const { assistStart } = store.get()
|
||||
|
||||
const [assistDuration, setAssistDuration] = React.useState('00:00');
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
|
|
@ -40,9 +36,9 @@ const AssistDurationCont = connectPlayer(
|
|||
Elapsed {assistDuration}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const AssistDuration = React.memo(AssistDurationCont);
|
||||
const AssistDuration = observer(AssistDurationCont)
|
||||
|
||||
ReduxTime.displayName = "ReduxTime";
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ function PlayerControls(props: Props) {
|
|||
|
||||
<div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip title="Rewind 10s" position="top">
|
||||
<Tooltip anchorClassName='h-full' title={`Rewind ${currentInterval}s`} position="top">
|
||||
<button
|
||||
ref={arrowBackRef}
|
||||
className="h-full hover:border-active-blue-border focus:border focus:border-blue border-borderColor-transparent"
|
||||
|
|
@ -111,8 +111,6 @@ function PlayerControls(props: Props) {
|
|||
<div className="p-1 border-l border-r bg-active-blue-border border-active-blue-border">
|
||||
<OutsideClickDetectingDiv onClickOutside={handleClickOutside}>
|
||||
<Popover
|
||||
// open={showTooltip}
|
||||
// interactive
|
||||
// @ts-ignore
|
||||
theme="nopadding"
|
||||
animation="none"
|
||||
|
|
@ -145,7 +143,7 @@ function PlayerControls(props: Props) {
|
|||
>
|
||||
<div onClick={toggleTooltip} ref={skipRef}>
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip disabled={showTooltip} title="Set default skip duration">
|
||||
<Tooltip anchorClassName='cursor-pointer' disabled={showTooltip} title="Set default skip duration">
|
||||
{currentInterval}s
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
@ -153,7 +151,7 @@ function PlayerControls(props: Props) {
|
|||
</OutsideClickDetectingDiv>
|
||||
</div>
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip title="Forward 10s" position="top">
|
||||
<Tooltip anchorClassName='h-full' title={`Forward ${currentInterval}s`} position="top">
|
||||
<button
|
||||
ref={arrowForwardRef}
|
||||
className="h-full hover:border-active-blue-border focus:border focus:border-blue border-borderColor-transparent"
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { sessions as sessionsRoute, assist as assistRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
|
||||
import { Icon, BackLink, Link } from 'UI';
|
||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import cn from 'classnames';
|
||||
import { connectPlayer, toggleEvents } from 'Player';
|
||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||
import UserCard from './EventsBlock/UserCard';
|
||||
import Tabs from 'Components/Session/Tabs';
|
||||
|
||||
import stl from './playerBlockHeader.module.css';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
import AssistTabs from '../Assist/components/AssistTabs';
|
||||
|
||||
const SESSIONS_ROUTE = sessionsRoute();
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
@connectPlayer(
|
||||
(state) => ({
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
live: state.live,
|
||||
loading: state.cssLoading || state.messagesLoading,
|
||||
showEvents: state.showEvents,
|
||||
}),
|
||||
{ toggleEvents }
|
||||
)
|
||||
@connect(
|
||||
(state, props) => {
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
const session = state.getIn(['sessions', 'current']);
|
||||
|
||||
return {
|
||||
isAssist,
|
||||
session,
|
||||
sessionPath: state.getIn(['sessions', 'sessionPath']),
|
||||
loading: state.getIn(['sessions', 'toggleFavoriteRequest', 'loading']),
|
||||
disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']) || props.loading,
|
||||
local: state.getIn(['sessions', 'timezone']),
|
||||
funnelRef: state.getIn(['funnels', 'navRef']),
|
||||
siteId: state.getIn(['site', 'siteId']),
|
||||
metaList: state.getIn(['customFields', 'list']).map((i) => i.key),
|
||||
closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live),
|
||||
};
|
||||
},
|
||||
{
|
||||
toggleFavorite,
|
||||
setSessionPath,
|
||||
}
|
||||
)
|
||||
@withRouter
|
||||
export default class PlayerBlockHeader extends React.PureComponent {
|
||||
state = {
|
||||
hideBack: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { location } = this.props;
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
this.setState({ hideBack: queryParams.has('iframe') && queryParams.get('iframe') === 'true' });
|
||||
}
|
||||
|
||||
getDimension = (width, height) => {
|
||||
return width && height ? (
|
||||
<div className="flex items-center">
|
||||
{width || 'x'} <Icon name="close" size="12" className="mx-1" /> {height || 'x'}
|
||||
</div>
|
||||
) : (
|
||||
<span className="">Resolution N/A</span>
|
||||
);
|
||||
};
|
||||
|
||||
backHandler = () => {
|
||||
const { history, siteId, sessionPath, isAssist } = this.props;
|
||||
if (sessionPath.pathname === history.location.pathname || sessionPath.pathname.includes('/session/') || isAssist) {
|
||||
history.push(withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId));
|
||||
} else {
|
||||
history.push(sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId));
|
||||
}
|
||||
};
|
||||
|
||||
toggleFavorite = () => {
|
||||
const { session } = this.props;
|
||||
this.props.toggleFavorite(session.sessionId);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
session,
|
||||
fullscreen,
|
||||
metaList,
|
||||
closedLive = false,
|
||||
siteId,
|
||||
isAssist,
|
||||
setActiveTab,
|
||||
activeTab,
|
||||
showEvents,
|
||||
toggleEvents,
|
||||
} = this.props;
|
||||
// const _live = isAssist;
|
||||
|
||||
const { hideBack } = this.state;
|
||||
|
||||
const { sessionId, userId, userNumericHash, live, metadata, isCallActive, agentIds } = session;
|
||||
let _metaList = Object.keys(metadata)
|
||||
.filter((i) => metaList.includes(i))
|
||||
.map((key) => {
|
||||
const value = metadata[key];
|
||||
return { label: key, value };
|
||||
});
|
||||
|
||||
const TABS = [this.props.tabs.EVENTS, this.props.tabs.HEATMAPS].map((tab) => ({ text: tab, key: tab }));
|
||||
return (
|
||||
<div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}>
|
||||
<div className="flex w-full items-center">
|
||||
{!hideBack && (
|
||||
<div className="flex items-center h-full" onClick={this.backHandler}>
|
||||
<BackLink label="Back" className="h-full" />
|
||||
<div className={stl.divider} />
|
||||
</div>
|
||||
)}
|
||||
<UserCard className="" width={width} height={height} />
|
||||
{isAssist && <AssistTabs userId={userId} userNumericHash={userNumericHash} />}
|
||||
|
||||
<div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}>
|
||||
{live && !isAssist && (
|
||||
<>
|
||||
<div className={cn(stl.liveSwitchButton, 'pr-4')}>
|
||||
<Link to={withSiteId(liveSessionRoute(sessionId), siteId)}>This Session is Now Continuing Live</Link>
|
||||
</div>
|
||||
{_metaList.length > 0 && <div className={stl.divider} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{_metaList.length > 0 && (
|
||||
<div className="border-l h-full flex items-center px-2">
|
||||
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAssist && <AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} />}
|
||||
</div>
|
||||
</div>
|
||||
{!isAssist && (
|
||||
<div className="relative border-l" style={{ minWidth: '270px' }}>
|
||||
<Tabs
|
||||
tabs={TABS}
|
||||
active={activeTab}
|
||||
onClick={(tab) => {
|
||||
if (activeTab === tab) {
|
||||
setActiveTab('');
|
||||
toggleEvents();
|
||||
} else {
|
||||
setActiveTab(tab);
|
||||
!showEvents && toggleEvents(true);
|
||||
}
|
||||
}}
|
||||
border={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
161
frontend/app/components/Session_/PlayerBlockHeader.tsx
Normal file
161
frontend/app/components/Session_/PlayerBlockHeader.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {
|
||||
sessions as sessionsRoute,
|
||||
assist as assistRoute,
|
||||
liveSession as liveSessionRoute,
|
||||
withSiteId,
|
||||
} from 'App/routes';
|
||||
import { BackLink, Link } from 'UI';
|
||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import cn from 'classnames';
|
||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||
import UserCard from './EventsBlock/UserCard';
|
||||
import Tabs from 'Components/Session/Tabs';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import stl from './playerBlockHeader.module.css';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
import AssistTabs from '../Assist/components/AssistTabs';
|
||||
|
||||
const SESSIONS_ROUTE = sessionsRoute();
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
// TODO props
|
||||
function PlayerBlockHeader(props: any) {
|
||||
const [hideBack, setHideBack] = React.useState(false);
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
|
||||
const { width, height, showEvents } = store.get();
|
||||
const toggleEvents = player.toggleEvents;
|
||||
|
||||
const {
|
||||
session,
|
||||
fullscreen,
|
||||
metaList,
|
||||
closedLive = false,
|
||||
siteId,
|
||||
isAssist,
|
||||
setActiveTab,
|
||||
activeTab,
|
||||
location,
|
||||
history,
|
||||
sessionPath,
|
||||
} = props;
|
||||
|
||||
React.useEffect(() => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true');
|
||||
}, []);
|
||||
|
||||
const backHandler = () => {
|
||||
if (
|
||||
sessionPath.pathname === history.location.pathname ||
|
||||
sessionPath.pathname.includes('/session/') ||
|
||||
isAssist
|
||||
) {
|
||||
history.push(withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId));
|
||||
} else {
|
||||
history.push(
|
||||
sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const { sessionId, userId, userNumericHash, live, metadata, isCallActive, agentIds } = session;
|
||||
let _metaList = Object.keys(metadata)
|
||||
.filter((i) => metaList.includes(i))
|
||||
.map((key) => {
|
||||
const value = metadata[key];
|
||||
return { label: key, value };
|
||||
});
|
||||
|
||||
const TABS = [props.tabs.EVENTS, props.tabs.HEATMAPS].map((tab) => ({
|
||||
text: tab,
|
||||
key: tab,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}>
|
||||
<div className="flex w-full items-center">
|
||||
{!hideBack && (
|
||||
<div className="flex items-center h-full" onClick={backHandler}>
|
||||
{/* @ts-ignore TODO */}
|
||||
<BackLink label="Back" className="h-full" />
|
||||
<div className={stl.divider} />
|
||||
</div>
|
||||
)}
|
||||
<UserCard className="" width={width} height={height} />
|
||||
{isAssist && <AssistTabs userId={userId} userNumericHash={userNumericHash} />}
|
||||
|
||||
<div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}>
|
||||
{live && !isAssist && (
|
||||
<>
|
||||
<div className={cn(stl.liveSwitchButton, 'pr-4')}>
|
||||
<Link to={withSiteId(liveSessionRoute(sessionId), siteId)}>
|
||||
This Session is Now Continuing Live
|
||||
</Link>
|
||||
</div>
|
||||
{_metaList.length > 0 && <div className={stl.divider} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{_metaList.length > 0 && (
|
||||
<div className="border-l h-full flex items-center px-2">
|
||||
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAssist && (
|
||||
// @ts-ignore TODO
|
||||
<AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isAssist && (
|
||||
<div className="relative border-l" style={{ minWidth: '270px' }}>
|
||||
<Tabs
|
||||
tabs={TABS}
|
||||
active={activeTab}
|
||||
onClick={(tab) => {
|
||||
if (activeTab === tab) {
|
||||
setActiveTab('');
|
||||
toggleEvents();
|
||||
} else {
|
||||
setActiveTab(tab);
|
||||
!showEvents && toggleEvents();
|
||||
}
|
||||
}}
|
||||
border={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const PlayerHeaderCont = connect(
|
||||
(state: any) => {
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
const session = state.getIn(['sessions', 'current']);
|
||||
|
||||
return {
|
||||
isAssist,
|
||||
session,
|
||||
sessionPath: state.getIn(['sessions', 'sessionPath']),
|
||||
local: state.getIn(['sessions', 'timezone']),
|
||||
funnelRef: state.getIn(['funnels', 'navRef']),
|
||||
siteId: state.getIn(['site', 'siteId']),
|
||||
metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key),
|
||||
closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live),
|
||||
};
|
||||
},
|
||||
{
|
||||
toggleFavorite,
|
||||
setSessionPath,
|
||||
}
|
||||
)(observer(PlayerBlockHeader));
|
||||
|
||||
export default withRouter(PlayerHeaderCont);
|
||||
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
shades: Record<string, string>;
|
||||
pathRoot: string;
|
||||
shades?: Record<string, string>;
|
||||
pathRoot?: string;
|
||||
path: string;
|
||||
diff: Record<string, any>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,321 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hideHint } from 'Duck/components/player';
|
||||
// import {
|
||||
// connectPlayer,
|
||||
// selectStorageType,
|
||||
// STORAGE_TYPES,
|
||||
// selectStorageListNow,
|
||||
// selectStorageList,
|
||||
// } from 'Player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { JSONTree, NoContent, Tooltip } from 'UI';
|
||||
import { formatMs } from 'App/date';
|
||||
import { diff } from 'deep-diff';
|
||||
import { jump } from 'Player';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import BottomBlock from '../BottomBlock/index';
|
||||
import DiffRow from './DiffRow';
|
||||
import cn from 'classnames';
|
||||
import stl from './storage.module.css';
|
||||
|
||||
// const STATE = 'STATE';
|
||||
// const DIFF = 'DIFF';
|
||||
// const TABS = [ DIFF, STATE ].map(tab => ({ text: tab, key: tab }));
|
||||
|
||||
function getActionsName(type) {
|
||||
switch (type) {
|
||||
case STORAGE_TYPES.MOBX:
|
||||
case STORAGE_TYPES.VUEX:
|
||||
return 'MUTATIONS';
|
||||
default:
|
||||
return 'ACTIONS';
|
||||
}
|
||||
}
|
||||
|
||||
// @connectPlayer((state) => ({
|
||||
// type: selectStorageType(state),
|
||||
// list: selectStorageList(state),
|
||||
// listNow: selectStorageListNow(state),
|
||||
// }))
|
||||
@connect(
|
||||
(state) => ({
|
||||
hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
||||
}),
|
||||
{
|
||||
hideHint,
|
||||
}
|
||||
)
|
||||
//@withEnumToggle('activeTab', 'setActiveTab', DIFF)
|
||||
export default class Storage extends React.PureComponent {
|
||||
lastBtnRef = React.createRef();
|
||||
state = { showDiffs: false };
|
||||
|
||||
focusNextButton() {
|
||||
if (this.lastBtnRef.current) {
|
||||
this.lastBtnRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.focusNextButton();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.listNow.length !== this.props.listNow.length) {
|
||||
this.focusNextButton();
|
||||
}
|
||||
}
|
||||
|
||||
renderDiff(item, prevItem) {
|
||||
if (!prevItem) {
|
||||
// we don't have state before first action
|
||||
return <div style={{ flex: 1 }} className="p-1" />;
|
||||
}
|
||||
|
||||
const stateDiff = diff(prevItem.state, item.state);
|
||||
|
||||
if (!stateDiff) {
|
||||
return (
|
||||
<div style={{ flex: 3 }} className="flex flex-col p-2 pr-0 font-mono text-disabled-text">
|
||||
No diff
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ flex: 3 }} className="flex flex-col p-1 font-mono">
|
||||
{stateDiff.map((d, i) => this.renderDiffs(d, i))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDiffs(diff, i) {
|
||||
const path = this.createPath(diff);
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<DiffRow shades={this.pathShades} path={path} diff={diff} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
createPath = (diff) => {
|
||||
let path = [];
|
||||
|
||||
if (diff.path) {
|
||||
path = path.concat(diff.path);
|
||||
}
|
||||
if (typeof diff.index !== 'undefined') {
|
||||
path.push(diff.index);
|
||||
}
|
||||
|
||||
const pathStr = path.length ? path.join('.') : '';
|
||||
return pathStr;
|
||||
};
|
||||
|
||||
ensureString(actionType) {
|
||||
if (typeof actionType === 'string') return actionType;
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
|
||||
goNext = () => {
|
||||
const { list, listNow } = this.props;
|
||||
jump(list[listNow.length].time, list[listNow.length]._index);
|
||||
};
|
||||
|
||||
renderTab() {
|
||||
const { listNow } = this.props;
|
||||
if (listNow.length === 0) {
|
||||
return 'Not initialized'; //?
|
||||
}
|
||||
return <JSONTree collapsed={2} src={listNow[listNow.length - 1].state} />;
|
||||
}
|
||||
|
||||
renderItem(item, i, prevItem) {
|
||||
const { type, listNow, list } = this.props;
|
||||
let src;
|
||||
let name;
|
||||
|
||||
switch (type) {
|
||||
case STORAGE_TYPES.REDUX:
|
||||
case STORAGE_TYPES.NGRX:
|
||||
src = item.action;
|
||||
name = src && src.type;
|
||||
break;
|
||||
case STORAGE_TYPES.VUEX:
|
||||
src = item.mutation;
|
||||
name = src && src.type;
|
||||
break;
|
||||
case STORAGE_TYPES.MOBX:
|
||||
src = item.payload;
|
||||
name = `@${item.type} ${src && src.type}`;
|
||||
break;
|
||||
case STORAGE_TYPES.ZUSTAND:
|
||||
src = null;
|
||||
name = item.mutation.join('');
|
||||
}
|
||||
|
||||
if (src !== null && !this.state.showDiffs) {
|
||||
this.setState({ showDiffs: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex justify-between items-start', src !== null ? 'border-b' : '')}
|
||||
key={`store-${i}`}
|
||||
>
|
||||
{src === null ? (
|
||||
<div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}>
|
||||
{name}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{this.renderDiff(item, prevItem)}
|
||||
<div style={{ flex: 2 }} className="flex pl-10 pt-2">
|
||||
<JSONTree
|
||||
name={this.ensureString(name)}
|
||||
src={src}
|
||||
collapsed
|
||||
collapseStringsAfterLength={7}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div style={{ flex: 1 }} className="flex-1 flex gap-2 pt-2 items-center justify-end self-start">
|
||||
{typeof item.duration === 'number' && (
|
||||
<div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div>
|
||||
)}
|
||||
<div className="w-12">
|
||||
{i + 1 < listNow.length && (
|
||||
<button className={stl.button} onClick={() => jump(item.time, item._index)}>
|
||||
{'JUMP'}
|
||||
</button>
|
||||
)}
|
||||
{i + 1 === listNow.length && i + 1 < list.length && (
|
||||
<button className={stl.button} ref={this.lastBtnRef} onClick={this.goNext}>
|
||||
{'NEXT'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { type, listNow, list, hintIsHidden } = this.props;
|
||||
|
||||
const showStore = type !== STORAGE_TYPES.MOBX;
|
||||
return (
|
||||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
{list.length > 0 && (
|
||||
<div className="flex w-full">
|
||||
{showStore && <h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold">{'STATE'}</h3>}
|
||||
{this.state.showDiffs ? (
|
||||
<h3 style={{ width: '39%'}} className="font-semibold">
|
||||
DIFFS
|
||||
</h3>
|
||||
) : null}
|
||||
<h3 style={{ width: '30%' }} className="font-semibold">{getActionsName(type)}</h3>
|
||||
<h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold">
|
||||
<Tooltip title="Time to execute">
|
||||
TTE
|
||||
</Tooltip>
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content className="flex">
|
||||
<NoContent
|
||||
title="Nothing to display yet."
|
||||
subtext={
|
||||
!hintIsHidden ? (
|
||||
<>
|
||||
{
|
||||
'Inspect your application state while you’re replaying your users sessions. OpenReplay supports '
|
||||
}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/redux"
|
||||
target="_blank"
|
||||
>
|
||||
Redux
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/vuex"
|
||||
target="_blank"
|
||||
>
|
||||
VueX
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/pinia"
|
||||
target="_blank"
|
||||
>
|
||||
Pinia
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/zustand"
|
||||
target="_blank"
|
||||
>
|
||||
Zustand
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/mobx"
|
||||
target="_blank"
|
||||
>
|
||||
MobX
|
||||
</a>
|
||||
{' and '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/ngrx"
|
||||
target="_blank"
|
||||
>
|
||||
NgRx
|
||||
</a>
|
||||
.
|
||||
<br />
|
||||
<br />
|
||||
<button className="color-teal" onClick={() => this.props.hideHint('storage')}>
|
||||
Got It!
|
||||
</button>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
size="small"
|
||||
show={listNow.length === 0}
|
||||
>
|
||||
{showStore && (
|
||||
<div className="ph-10 scroll-y" style={{ width: '25%' }}>
|
||||
{listNow.length === 0 ? (
|
||||
<div className="color-gray-light font-size-16 mt-20 text-center">
|
||||
{'Empty state.'}
|
||||
</div>
|
||||
) : (
|
||||
this.renderTab()
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex" style={{ width: showStore ? '75%' : '100%' }}>
|
||||
<Autoscroll className="ph-10">
|
||||
{listNow.map((item, i) =>
|
||||
this.renderItem(item, i, i > 0 ? listNow[i - 1] : undefined)
|
||||
)}
|
||||
</Autoscroll>
|
||||
</div>
|
||||
</NoContent>
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
);
|
||||
}
|
||||
}
|
||||
315
frontend/app/components/Session_/Storage/Storage.tsx
Normal file
315
frontend/app/components/Session_/Storage/Storage.tsx
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hideHint } from 'Duck/components/player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { JSONTree, NoContent, Tooltip } from 'UI';
|
||||
import { formatMs } from 'App/date';
|
||||
import { diff } from 'deep-diff';
|
||||
import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import BottomBlock from '../BottomBlock/index';
|
||||
import DiffRow from './DiffRow';
|
||||
import cn from 'classnames';
|
||||
import stl from './storage.module.css';
|
||||
|
||||
function getActionsName(type: string) {
|
||||
switch (type) {
|
||||
case STORAGE_TYPES.MOBX:
|
||||
case STORAGE_TYPES.VUEX:
|
||||
return 'MUTATIONS';
|
||||
default:
|
||||
return 'ACTIONS';
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
hideHint: (args: string) => void;
|
||||
hintIsHidden: boolean;
|
||||
}
|
||||
function Storage(props: Props) {
|
||||
const lastBtnRef = React.useRef<HTMLButtonElement>();
|
||||
const [showDiffs, setShowDiffs] = React.useState(false);
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
const state = store.get();
|
||||
|
||||
const listNow = selectStorageListNow(state);
|
||||
const list = selectStorageList(state);
|
||||
const type = selectStorageType(state);
|
||||
|
||||
const focusNextButton = () => {
|
||||
if (lastBtnRef.current) {
|
||||
lastBtnRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
focusNextButton();
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
focusNextButton();
|
||||
}, [listNow]);
|
||||
|
||||
const renderDiff = (item: Record<string, any>, prevItem: Record<string, any>) => {
|
||||
if (!prevItem) {
|
||||
// we don't have state before first action
|
||||
return <div style={{ flex: 1 }} className="p-1" />;
|
||||
}
|
||||
|
||||
const stateDiff = diff(prevItem.state, item.state);
|
||||
|
||||
if (!stateDiff) {
|
||||
return (
|
||||
<div style={{ flex: 3 }} className="flex flex-col p-2 pr-0 font-mono text-disabled-text">
|
||||
No diff
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ flex: 3 }} className="flex flex-col p-1 font-mono">
|
||||
{stateDiff.map((d: Record<string, any>, i: number) => renderDiffs(d, i))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDiffs = (diff: Record<string, any>, i: number) => {
|
||||
const path = createPath(diff);
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<DiffRow path={path} diff={diff} />
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const createPath = (diff: Record<string, any>) => {
|
||||
let path: string[] = [];
|
||||
|
||||
if (diff.path) {
|
||||
path = path.concat(diff.path);
|
||||
}
|
||||
if (typeof diff.index !== 'undefined') {
|
||||
path.push(diff.index);
|
||||
}
|
||||
|
||||
const pathStr = path.length ? path.join('.') : '';
|
||||
return pathStr;
|
||||
};
|
||||
|
||||
const ensureString = (actionType: string) => {
|
||||
if (typeof actionType === 'string') return actionType;
|
||||
return 'UNKNOWN';
|
||||
};
|
||||
|
||||
const goNext = () => {
|
||||
// , list[listNow.length]._index
|
||||
player.jump(list[listNow.length].time);
|
||||
};
|
||||
|
||||
const renderTab = () => {
|
||||
if (listNow.length === 0) {
|
||||
return 'Not initialized'; //?
|
||||
}
|
||||
return <JSONTree collapsed={2} src={listNow[listNow.length - 1].state} />;
|
||||
};
|
||||
|
||||
const renderItem = (item: Record<string, any>, i: number, prevItem: Record<string, any>) => {
|
||||
let src;
|
||||
let name;
|
||||
|
||||
switch (type) {
|
||||
case STORAGE_TYPES.REDUX:
|
||||
case STORAGE_TYPES.NGRX:
|
||||
src = item.action;
|
||||
name = src && src.type;
|
||||
break;
|
||||
case STORAGE_TYPES.VUEX:
|
||||
src = item.mutation;
|
||||
name = src && src.type;
|
||||
break;
|
||||
case STORAGE_TYPES.MOBX:
|
||||
src = item.payload;
|
||||
name = `@${item.type} ${src && src.type}`;
|
||||
break;
|
||||
case STORAGE_TYPES.ZUSTAND:
|
||||
src = null;
|
||||
name = item.mutation.join('');
|
||||
}
|
||||
|
||||
if (src !== null && !showDiffs) {
|
||||
setShowDiffs(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex justify-between items-start', src !== null ? 'border-b' : '')}
|
||||
key={`store-${i}`}
|
||||
>
|
||||
{src === null ? (
|
||||
<div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}>
|
||||
{name}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{renderDiff(item, prevItem)}
|
||||
<div style={{ flex: 2 }} className="flex pl-10 pt-2">
|
||||
<JSONTree
|
||||
name={ensureString(name)}
|
||||
src={src}
|
||||
collapsed
|
||||
collapseStringsAfterLength={7}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{ flex: 1 }}
|
||||
className="flex-1 flex gap-2 pt-2 items-center justify-end self-start"
|
||||
>
|
||||
{typeof item.duration === 'number' && (
|
||||
<div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div>
|
||||
)}
|
||||
<div className="w-12">
|
||||
{i + 1 < listNow.length && (
|
||||
<button className={stl.button} onClick={() => jump(item.time, item._index)}>
|
||||
{'JUMP'}
|
||||
</button>
|
||||
)}
|
||||
{i + 1 === listNow.length && i + 1 < list.length && (
|
||||
<button className={stl.button} ref={lastBtnRef} onClick={goNext}>
|
||||
{'NEXT'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { hintIsHidden } = props;
|
||||
|
||||
const showStore = type !== STORAGE_TYPES.MOBX;
|
||||
return (
|
||||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
{list.length > 0 && (
|
||||
<div className="flex w-full">
|
||||
{showStore && (
|
||||
<h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold">
|
||||
{'STATE'}
|
||||
</h3>
|
||||
)}
|
||||
{showDiffs ? (
|
||||
<h3 style={{ width: '39%' }} className="font-semibold">
|
||||
DIFFS
|
||||
</h3>
|
||||
) : null}
|
||||
<h3 style={{ width: '30%' }} className="font-semibold">
|
||||
{getActionsName(type)}
|
||||
</h3>
|
||||
<h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold">
|
||||
<Tooltip title="Time to execute">TTE</Tooltip>
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content className="flex">
|
||||
<NoContent
|
||||
title="Nothing to display yet."
|
||||
subtext={
|
||||
!hintIsHidden ? (
|
||||
<>
|
||||
{
|
||||
'Inspect your application state while you’re replaying your users sessions. OpenReplay supports '
|
||||
}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/redux"
|
||||
target="_blank"
|
||||
>
|
||||
Redux
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/vuex"
|
||||
target="_blank"
|
||||
>
|
||||
VueX
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/pinia"
|
||||
target="_blank"
|
||||
>
|
||||
Pinia
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/zustand"
|
||||
target="_blank"
|
||||
>
|
||||
Zustand
|
||||
</a>
|
||||
{', '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/mobx"
|
||||
target="_blank"
|
||||
>
|
||||
MobX
|
||||
</a>
|
||||
{' and '}
|
||||
<a
|
||||
className="underline color-teal"
|
||||
href="https://docs.openreplay.com/plugins/ngrx"
|
||||
target="_blank"
|
||||
>
|
||||
NgRx
|
||||
</a>
|
||||
.
|
||||
<br />
|
||||
<br />
|
||||
<button className="color-teal" onClick={() => props.hideHint('storage')}>
|
||||
Got It!
|
||||
</button>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
size="small"
|
||||
show={listNow.length === 0}
|
||||
>
|
||||
{showStore && (
|
||||
<div className="ph-10 scroll-y" style={{ width: '25%' }}>
|
||||
{listNow.length === 0 ? (
|
||||
<div className="color-gray-light font-size-16 mt-20 text-center">
|
||||
{'Empty state.'}
|
||||
</div>
|
||||
) : (
|
||||
renderTab()
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex" style={{ width: showStore ? '75%' : '100%' }}>
|
||||
<Autoscroll className="ph-10">
|
||||
{listNow.map((item: Record<string, any>, i: number) =>
|
||||
renderItem(item, i, i > 0 ? listNow[i - 1] : undefined)
|
||||
)}
|
||||
</Autoscroll>
|
||||
</div>
|
||||
</NoContent>
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
||||
}),
|
||||
{
|
||||
hideHint,
|
||||
}
|
||||
)(observer(Storage));
|
||||
|
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||
import { setCreateNoteTooltip } from 'Duck/sessions';
|
||||
import GuidePopup from 'Shared/GuidePopup';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
function NotePopup({
|
||||
setCreateNoteTooltip,
|
||||
|
|
@ -14,12 +13,11 @@ function NotePopup({
|
|||
tooltipActive: boolean;
|
||||
}) {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
const { time } = store.get();
|
||||
|
||||
const toggleNotePopup = () => {
|
||||
if (tooltipActive) return;
|
||||
player.pause();
|
||||
setCreateNoteTooltip({ time: time, isVisible: true });
|
||||
setCreateNoteTooltip({ time: store.get().time, isVisible: true });
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
|
|
@ -42,11 +40,9 @@ function NotePopup({
|
|||
);
|
||||
}
|
||||
|
||||
const NotePopupPl = observer(NotePopup);
|
||||
|
||||
const NotePopupComp = connect(
|
||||
(state: any) => ({ tooltipActive: state.getIn(['sessions', 'createNoteTooltip', 'isVisible']) }),
|
||||
{ setCreateNoteTooltip }
|
||||
)(NotePopupPl);
|
||||
)(NotePopup);
|
||||
|
||||
export default React.memo(NotePopupComp);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ export default function GuidePopup({ children, title, description }: IProps) {
|
|||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { Placement } from '@floating-ui/react-dom-interactions';
|
|||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
title?: any;
|
||||
title: React.ReactNode;
|
||||
children: any;
|
||||
disabled?: boolean;
|
||||
open?: boolean;
|
||||
|
|
@ -13,6 +13,7 @@ interface Props {
|
|||
delay?: number;
|
||||
style?: any;
|
||||
offset?: number;
|
||||
anchorClassName?: string;
|
||||
}
|
||||
function Tooltip(props: Props) {
|
||||
const {
|
||||
|
|
@ -21,6 +22,7 @@ function Tooltip(props: Props) {
|
|||
open = false,
|
||||
placement,
|
||||
className = '',
|
||||
anchorClassName = '',
|
||||
delay = 500,
|
||||
style = {},
|
||||
offset = 5,
|
||||
|
|
@ -38,7 +40,7 @@ function Tooltip(props: Props) {
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
<TooltipAnchor state={state}>{props.children}</TooltipAnchor>
|
||||
<TooltipAnchor className={anchorClassName} state={state}>{props.children}</TooltipAnchor>
|
||||
<FloatingTooltip
|
||||
state={state}
|
||||
className={cn('bg-gray-darkest color-white rounded py-1 px-2 animate-fade', className)}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,26 @@ export default class Animator {
|
|||
}
|
||||
}
|
||||
|
||||
jumpInterval(interval: number) {
|
||||
const { endTime, time } = this.store.get()
|
||||
|
||||
if (interval > 0) {
|
||||
return this.jump(
|
||||
Math.min(
|
||||
endTime,
|
||||
time + interval
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return this.jump(
|
||||
Math.max(
|
||||
0,
|
||||
time - interval
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: clearify logic of live time-travel
|
||||
jumpToLive() {
|
||||
cancelAnimationFrame(this.animationFrameRequestId)
|
||||
|
|
@ -177,4 +197,4 @@ export default class Animator {
|
|||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export default class InspectorController {
|
|||
}
|
||||
}
|
||||
|
||||
enableInspector(clickCallback: (e: { target: Element }) => void): Document | null {
|
||||
enableInspector(clickCallback?: (e: { target: Element }) => void): Document | null {
|
||||
const parent = this.screen.getParentElement()
|
||||
if (!parent) return null;
|
||||
if (!this.substitutor) {
|
||||
|
|
@ -28,7 +28,7 @@ export default class InspectorController {
|
|||
}
|
||||
|
||||
this.substitutor.display(false)
|
||||
|
||||
|
||||
const docElement = this.screen.document?.documentElement // this.substitutor.document?.importNode(
|
||||
const doc = this.substitutor.document
|
||||
if (doc && docElement) {
|
||||
|
|
@ -43,11 +43,11 @@ export default class InspectorController {
|
|||
this.substitutor.display(true);
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
disableInspector() {
|
||||
if (this.substitutor) {
|
||||
const doc = this.substitutor.document;
|
||||
if (doc) {
|
||||
if (doc) {
|
||||
doc.documentElement.innerHTML = "";
|
||||
}
|
||||
this.inspector.clean();
|
||||
|
|
@ -56,4 +56,4 @@ export default class InspectorController {
|
|||
this.screen.display(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type Screen from './Screen'
|
||||
import type Marker from './Marker'
|
||||
|
||||
//import { select } from 'optimal-select';
|
||||
//import { select } from 'optimal-select';
|
||||
|
||||
export default class Inspector {
|
||||
// private captureCallbacks = [];
|
||||
// private bubblingCallbacks = [];
|
||||
constructor(private screen: Screen, private marker: Marker) {}
|
||||
|
||||
private onMouseMove = (e: MouseEvent) => {
|
||||
private onMouseMove = (e: MouseEvent) => {
|
||||
// const { overlay } = this.screen;
|
||||
// if (!overlay.contains(e.target)) {
|
||||
// return;
|
||||
|
|
@ -21,7 +21,7 @@ export default class Inspector {
|
|||
return;
|
||||
}
|
||||
|
||||
this.marker.mark(target);
|
||||
this.marker.mark(target);
|
||||
}
|
||||
|
||||
private onOverlayLeave = () => {
|
||||
|
|
@ -57,7 +57,7 @@ export default class Inspector {
|
|||
// }
|
||||
|
||||
private clickCallback: (e: { target: Element }) => void | null = null
|
||||
enable(clickCallback: Inspector['clickCallback']) {
|
||||
enable(clickCallback?: Inspector['clickCallback']) {
|
||||
this.screen.overlay.addEventListener('mousemove', this.onMouseMove)
|
||||
this.screen.overlay.addEventListener('mouseleave', this.onOverlayLeave)
|
||||
this.screen.overlay.addEventListener('click', this.onMarkClick)
|
||||
|
|
@ -67,4 +67,4 @@ export default class Inspector {
|
|||
this.screen.overlay.removeEventListener('mouseleave', this.onOverlayLeave)
|
||||
this.screen.overlay.removeEventListener('click', this.onMarkClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Player, { State as PlayerState } from '../player/Player'
|
|||
import MessageManager from './MessageManager'
|
||||
import InspectorController from './InspectorController'
|
||||
import TargetMarker from './TargetMarker'
|
||||
import AssistManager, {
|
||||
import AssistManager, {
|
||||
INITIAL_STATE as ASSIST_INITIAL_STATE,
|
||||
} from './assist/AssistManager'
|
||||
import Screen from './Screen/Screen'
|
||||
|
|
@ -40,7 +40,7 @@ export default class WebPlayer extends Player {
|
|||
} : {}
|
||||
|
||||
const screen = new Screen()
|
||||
const messageManager = new MessageManager(session, wpState, screen, initialLists)
|
||||
const messageManager = new MessageManager(session, wpState, screen, initialLists)
|
||||
super(wpState, messageManager)
|
||||
this.screen = screen
|
||||
this.messageManager = messageManager
|
||||
|
|
@ -48,14 +48,14 @@ export default class WebPlayer extends Player {
|
|||
this.targetMarker = new TargetMarker(this.screen, wpState)
|
||||
this.inspectorController = new InspectorController(screen)
|
||||
|
||||
|
||||
|
||||
const endTime = !live && session.duration.valueOf()
|
||||
wpState.update({
|
||||
//@ts-ignore
|
||||
initialized: true,
|
||||
//@ts-ignore
|
||||
session,
|
||||
|
||||
|
||||
live,
|
||||
livePlay: live,
|
||||
endTime, // : 0, //TODO: through initialState
|
||||
|
|
@ -85,7 +85,7 @@ export default class WebPlayer extends Player {
|
|||
mark(e: Element) {
|
||||
this.inspectorController.marker?.mark(e)
|
||||
}
|
||||
toggleInspectorMode(flag: boolean, clickCallback) {
|
||||
toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => any) {
|
||||
if (typeof flag !== 'boolean') {
|
||||
const { inspectorMode } = this.wpState.get()
|
||||
flag = !inspectorMode;
|
||||
|
|
@ -104,6 +104,7 @@ export default class WebPlayer extends Player {
|
|||
setActiveTarget(args: Parameters<TargetMarker['setActiveTarget']>) {
|
||||
this.targetMarker.setActiveTarget(...args)
|
||||
}
|
||||
|
||||
markTargets(args: Parameters<TargetMarker['markTargets']>) {
|
||||
this.pause()
|
||||
this.targetMarker.markTargets(...args)
|
||||
|
|
@ -133,11 +134,10 @@ export default class WebPlayer extends Player {
|
|||
toggleUserName(name?: string) {
|
||||
this.screen.cursor.showTag(name)
|
||||
}
|
||||
|
||||
|
||||
clean() {
|
||||
super.clean()
|
||||
this.assistManager.clean()
|
||||
window.removeEventListener('resize', this.scale)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -461,7 +461,7 @@ export default class AssistManager {
|
|||
onStream: (s: MediaStream)=>void,
|
||||
onCallEnd: () => void,
|
||||
onReject: () => void,
|
||||
onError?: ()=> void,
|
||||
onError?: (e?: any)=> void,
|
||||
) {
|
||||
this.callArgs = {
|
||||
localStream,
|
||||
|
|
@ -472,7 +472,7 @@ export default class AssistManager {
|
|||
}
|
||||
}
|
||||
|
||||
public call(thirdPartyPeers?: string[]): { end: Function } {
|
||||
public call(thirdPartyPeers?: string[]): { end: () => void } {
|
||||
if (thirdPartyPeers && thirdPartyPeers.length > 0) {
|
||||
this.addPeerCall(thirdPartyPeers)
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue