* start moving ui to redux tlk * remove unused reducer * changes for gdpr and site types * ui: migrating duck/roles to mobx * ui: drop unreferenced types * ui: drop unreferenced types * ui: move player slice reducer to mobx family * ui: move assignments to issueReportingStore.ts * remove issues store * some fixes after issues store * remove errors reducer, drop old components * finish removing errors reducer * start moving integrations state to mobx * change(ui): funnel duck cleanup * change(ui): custom fields * change(ui): customMetrics cleanup * change(ui): customMetrics cleanup * change(ui): duck/filters minor cleanup * change(ui): duck/filters cleanup * change(ui): duck/customMetrics cleanup and upgrades * fix integrations service, fix babel config to >.25 + not ie * refactoring integrations reducers etc WIP * finish removing integrations state * some fixes for integrated check * start of projects refactoring * move api and "few" files to new project store * new batch for site -> projects * fix setid context * move all critical components, drop site duck * remove all duck/site refs, remove old components * fixup for SessionTags.tsx, remove duck/sources (?) * move session store * init sessionstore outside of context * fix userfilter * replace simple actions for session store * sessions sotre * Rtm temp (#2597) * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): search states * change(ui): search states * change(ui): search states * change(ui): fix savedSearch store * change(ui): fix savedSearch store * some fixes for session connector * change(ui): fix savedSearch store * change(ui): fix searchLive * change(ui): fix searchLive * fixes for session replay * change(ui): bookmark fetch * last components for sessions * add fetchautoplaylist * finish session reducer, remove deleted reducers * change(ui): fix the search fetch * change(ui): fix the search fetch * fix integrations call ctx * ensure ctx for sessionstore * fix(ui): checking for latest sessions path * start removing user reducer * removing user reducer pt2... * finish user store * remove rand log * fix crashes * tinkering workflow file for tracker test * making sure prefetched sessions work properly * fix conflict * fix router redirects during loading --------- Co-authored-by: Shekar Siri <sshekarsiri@gmail.com>
291 lines
8.1 KiB
TypeScript
291 lines
8.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Button, Tooltip } from 'UI';
|
|
import cn from 'classnames';
|
|
import ChatWindow from '../../ChatWindow';
|
|
import { CallingState, ConnectionStatus, RemoteControlStatus, RequestLocalStream } from 'Player';
|
|
import type { LocalStream } from 'Player';
|
|
import { PlayerContext, ILivePlayerContext } 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';
|
|
import ScreenRecorder from 'App/components/Session_/ScreenRecorder/ScreenRecorder';
|
|
import { audioContextManager } from 'App/utils/screenRecorder';
|
|
import { useStore } from "App/mstore";
|
|
|
|
function onReject() {
|
|
toast.info(`Call was rejected.`);
|
|
}
|
|
|
|
function onControlReject() {
|
|
toast.info('Remote control request was rejected by user');
|
|
}
|
|
function onControlBusy() {
|
|
toast.info('Remote control busy');
|
|
}
|
|
|
|
function onError(e: any) {
|
|
console.log(e);
|
|
toast.error(typeof e === 'string' ? e : e.message);
|
|
}
|
|
|
|
interface Props {
|
|
userId: string;
|
|
isCallActive: boolean;
|
|
agentIds: string[];
|
|
userDisplayName: string;
|
|
}
|
|
|
|
const AssistActionsPing = {
|
|
control: {
|
|
start: 's_control_started',
|
|
end: 's_control_ended'
|
|
},
|
|
call: {
|
|
start: 's_call_started',
|
|
end: 's_call_ended'
|
|
},
|
|
} as const
|
|
|
|
function AssistActions({
|
|
userId,
|
|
isCallActive,
|
|
agentIds,
|
|
}: Props) {
|
|
// @ts-ignore ???
|
|
const { player, store } = React.useContext<ILivePlayerContext>(PlayerContext);
|
|
const { sessionStore, userStore } = useStore();
|
|
const permissions = userStore.account.permissions || [];
|
|
const hasPermission = permissions.includes('ASSIST_CALL') || permissions.includes('SERVICE_ASSIST_CALL');
|
|
const isEnterprise = userStore.isEnterprise;
|
|
const agentId = userStore.account.id;
|
|
const userDisplayName = sessionStore.current.userDisplayName;
|
|
|
|
const {
|
|
assistManager: {
|
|
call: callPeer,
|
|
setCallArgs,
|
|
requestReleaseRemoteControl,
|
|
toggleAnnotation,
|
|
setRemoteControlCallbacks,
|
|
},
|
|
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);
|
|
const [callObject, setCallObject] = useState<{ end: () => void } | null>(null);
|
|
|
|
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting;
|
|
const callRequesting = calling === CallingState.Connecting;
|
|
const cannotCall =
|
|
peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission);
|
|
|
|
const remoteRequesting = remoteControlStatus === RemoteControlStatus.Requesting;
|
|
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled;
|
|
|
|
useEffect(() => {
|
|
if (!onCall && isCallActive && agentIds) {
|
|
setPrestart(true);
|
|
// call(agentIds); do not autocall on prestart, can change later
|
|
}
|
|
}, [agentIds, isCallActive]);
|
|
|
|
useEffect(() => {
|
|
if (!livePlay) {
|
|
if (annotating) {
|
|
toggleAnnotation(false);
|
|
}
|
|
if (remoteActive) {
|
|
requestReleaseRemoteControl();
|
|
}
|
|
}
|
|
}, [livePlay]);
|
|
|
|
useEffect(() => {
|
|
if (remoteActive) {
|
|
toggleUserName(userDisplayName);
|
|
} else {
|
|
// higher than waiting for messages
|
|
if (peerConnectionStatus > 1) {
|
|
toggleUserName();
|
|
}
|
|
}
|
|
}, [remoteActive]);
|
|
|
|
useEffect(() => {
|
|
return callObject?.end();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
|
|
toast.info(`Live session was closed.`);
|
|
}
|
|
}, [peerConnectionStatus]);
|
|
|
|
const addIncomeStream = (stream: MediaStream) => {
|
|
setIncomeStream((oldState) => {
|
|
if (oldState === null) return [stream];
|
|
if (!oldState.find((existingStream) => existingStream.id === stream.id)) {
|
|
audioContextManager.mergeAudioStreams(stream);
|
|
return [...oldState, stream];
|
|
}
|
|
return oldState;
|
|
});
|
|
};
|
|
|
|
function call(additionalAgentIds?: string[]) {
|
|
RequestLocalStream()
|
|
.then((lStream) => {
|
|
setLocalStream(lStream);
|
|
audioContextManager.mergeAudioStreams(lStream.stream);
|
|
setCallArgs(
|
|
lStream,
|
|
addIncomeStream,
|
|
() => {
|
|
player.assistManager.ping(AssistActionsPing.call.end, agentId)
|
|
lStream.stop.bind(lStream);
|
|
},
|
|
onReject,
|
|
onError
|
|
);
|
|
setCallObject(callPeer());
|
|
if (additionalAgentIds) {
|
|
callPeer(additionalAgentIds);
|
|
}
|
|
})
|
|
.catch(onError);
|
|
}
|
|
|
|
const confirmCall = async () => {
|
|
if (callRequesting || remoteRequesting) return;
|
|
|
|
if (
|
|
await confirm({
|
|
header: 'Start Call',
|
|
confirmButton: 'Call',
|
|
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`,
|
|
})
|
|
) {
|
|
call(agentIds);
|
|
}
|
|
};
|
|
|
|
const requestControl = () => {
|
|
const onStart = () => {
|
|
player.assistManager.ping(AssistActionsPing.control.start, agentId)
|
|
}
|
|
const onEnd = () => {
|
|
player.assistManager.ping(AssistActionsPing.control.end, agentId)
|
|
}
|
|
setRemoteControlCallbacks({
|
|
onReject: onControlReject,
|
|
onStart: onStart,
|
|
onEnd: onEnd,
|
|
onBusy: onControlBusy,
|
|
});
|
|
requestReleaseRemoteControl();
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
if (onCall) {
|
|
player.assistManager.ping(AssistActionsPing.call.start, agentId)
|
|
}
|
|
}, [onCall])
|
|
|
|
return (
|
|
<div className="flex items-center">
|
|
{(onCall || remoteActive) && (
|
|
<>
|
|
<div
|
|
className={cn('cursor-pointer p-2 flex items-center', {
|
|
[stl.disabled]: cannotCall || !livePlay,
|
|
})}
|
|
onClick={() => toggleAnnotation(!annotating)}
|
|
role="button"
|
|
>
|
|
<Button
|
|
icon={annotating ? 'pencil-stop' : 'pencil'}
|
|
variant={annotating ? 'text-red' : 'text-primary'}
|
|
style={{ height: '28px' }}
|
|
>
|
|
Annotate
|
|
</Button>
|
|
</div>
|
|
<div className={stl.divider} />
|
|
</>
|
|
)}
|
|
|
|
{/* @ts-ignore wtf? */}
|
|
<ScreenRecorder />
|
|
<div className={stl.divider} />
|
|
|
|
{/* @ts-ignore */}
|
|
<Tooltip title="Go live to initiate remote control" disabled={livePlay}>
|
|
<div
|
|
className={cn('cursor-pointer p-2 flex items-center', {
|
|
[stl.disabled]: cannotCall || !livePlay || callRequesting || remoteRequesting,
|
|
})}
|
|
onClick={requestControl}
|
|
role="button"
|
|
>
|
|
<Button
|
|
icon={remoteActive ? 'window-x' : 'remote-control'}
|
|
variant={remoteActive ? 'text-red' : 'text-primary'}
|
|
style={{ height: '28px' }}
|
|
>
|
|
Remote Control
|
|
</Button>
|
|
</div>
|
|
</Tooltip>
|
|
<div className={stl.divider} />
|
|
|
|
<Tooltip
|
|
title={
|
|
cannotCall
|
|
? `You don't have the permissions to perform this action.`
|
|
: `Call ${userId ? userId : 'User'}`
|
|
}
|
|
disabled={onCall}
|
|
>
|
|
<div
|
|
className={cn('cursor-pointer p-2 flex items-center', {
|
|
[stl.disabled]: cannotCall || callRequesting || remoteRequesting,
|
|
})}
|
|
onClick={onCall ? callObject?.end : confirmCall}
|
|
role="button"
|
|
>
|
|
<Button
|
|
icon="headset"
|
|
variant={onCall ? 'text-red' : isPrestart ? 'green' : 'primary'}
|
|
style={{ height: '28px' }}
|
|
>
|
|
{onCall ? 'End' : isPrestart ? 'Join Call' : 'Call'}
|
|
</Button>
|
|
</div>
|
|
</Tooltip>
|
|
|
|
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
|
|
{onCall && callObject && (
|
|
<ChatWindow
|
|
endCall={callObject.end}
|
|
userId={userId}
|
|
incomeStream={incomeStream}
|
|
localStream={localStream}
|
|
isPrestart={isPrestart}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default observer(AssistActions);
|