feat(ui): add recording request to ui side, add recording state and request window

This commit is contained in:
sylenien 2022-11-07 16:49:13 +01:00 committed by Delirium
parent d071d6d2cd
commit 249e731569
10 changed files with 170 additions and 40 deletions

View file

@ -6,17 +6,19 @@ import { PlayerContext } from 'App/components/Session/playerContext';
interface Props {
userDisplayName: string;
type: WindowType;
getWindowType: () => WindowType | null;
}
export enum WindowType {
Call,
Control,
Record,
}
enum Actions {
CallEnd,
ControlEnd
ControlEnd,
RecordingEnd,
}
const WIN_VARIANTS = {
@ -24,27 +26,40 @@ const WIN_VARIANTS = {
text: 'to accept the call',
icon: 'call' as const,
action: Actions.CallEnd,
iconColor: 'teal',
},
[WindowType.Control]: {
text: 'to accept remote control request',
icon: 'remote-control' as const,
action: Actions.ControlEnd,
iconColor: 'teal',
},
[WindowType.Record]: {
text: 'to accept recording request',
icon: 'record-circle' as const,
iconColor: 'red',
action: Actions.RecordingEnd,
}
};
function RequestingWindow({ userDisplayName, type }: Props) {
function RequestingWindow({ userDisplayName, getWindowType }: Props) {
const windowType = getWindowType()
if (!windowType) return;
const { player } = React.useContext(PlayerContext)
const {
assistManager: {
initiateCallEnd,
releaseRemoteControl,
stopRecording,
}
} = player
const actions = {
[Actions.CallEnd]: initiateCallEnd,
[Actions.ControlEnd]: releaseRemoteControl
[Actions.ControlEnd]: releaseRemoteControl,
[Actions.RecordingEnd]: stopRecording,
}
return (
<div
@ -52,13 +67,13 @@ function RequestingWindow({ userDisplayName, type }: Props) {
style={{ background: 'rgba(0,0,0, 0.30)', zIndex: INDEXES.PLAYER_REQUEST_WINDOW }}
>
<div className="rounded bg-white pt-4 pb-2 px-8 flex flex-col text-lg items-center max-w-lg text-center">
<Icon size={40} color="teal" name={WIN_VARIANTS[type].icon} className="mb-4" />
<Icon size={40} color={WIN_VARIANTS[windowType].iconColor} name={WIN_VARIANTS[windowType].icon} className="mb-4" />
<div>
Waiting for <span className="font-semibold">{userDisplayName}</span>
</div>
<span>{WIN_VARIANTS[type].text}</span>
<span>{WIN_VARIANTS[windowType].text}</span>
<Loader size={30} style={{ minHeight: 60 }} />
<Button variant="text-primary" onClick={actions[WIN_VARIANTS[type].action]}>
<Button variant="text-primary" onClick={actions[WIN_VARIANTS[windowType].action]}>
Cancel
</Button>
</div>

View file

@ -17,6 +17,7 @@ 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';
function onReject() {
toast.info(`Call was rejected.`);
@ -184,6 +185,10 @@ function AssistActions({
</>
)}
{/* @ts-ignore wtf? */}
<ScreenRecorder />
<div className={stl.divider} />
{/* @ts-ignore */}
<Tooltip title="Go live to initiate remote control" disabled={livePlay}>
<div

View file

@ -1,5 +1,11 @@
import React from 'react';
import { CallingState, ConnectionStatus, RemoteControlStatus, getStatusText } from 'Player';
import {
SessionRecordingStatus,
getStatusText,
CallingState,
ConnectionStatus,
RemoteControlStatus,
} from 'Player';
import AutoplayTimer from './Overlay/AutoplayTimer';
import PlayIconLayer from './Overlay/PlayIconLayer';
@ -36,6 +42,7 @@ function Overlay({
livePlay,
calling,
remoteControl,
recordingState,
} = store.get()
const loading = messagesLoading || cssLoading
const liveStatusText = getStatusText(peerConnectionStatus)
@ -45,25 +52,41 @@ function Overlay({
const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer;
const showLiveStatusText = live && livePlay && liveStatusText && !loading;
const showRequestWindow = live && (calling === CallingState.Connecting || remoteControl === RemoteControlStatus.Requesting)
const requestWindowType = calling === CallingState.Connecting ? WindowType.Call : remoteControl === RemoteControlStatus.Requesting ? WindowType.Control : null
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;
}
return (
<>
{showRequestWindow ? <RequestingWindow type={requestWindowType} /> : 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}/>
}
{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} />}
</>
);
}
export default observer(Overlay);

View file

@ -1,7 +1,11 @@
import React from 'react';
import { screenRecorder } from 'App/utils/screenRecorder';
import { Tooltip } from 'react-tippy'
import { Button } from 'UI'
import { requestRecording, stopRecording, connectPlayer } from 'Player'
import {
SessionRecordingStatus,
} from 'Player/MessageDistributor/managers/AssistManager';
let stopRecorderCb: () => void
/**
@ -22,35 +26,60 @@ function isSupported() {
return false
}
function ScreenRecorder() {
const supportedBrowsers = ["Chrome v91+", "Edge v90+"]
const supportedMessage = `Supported Browsers: ${supportedBrowsers.join(', ')}`
function ScreenRecorder({ recordingState }: { recordingState: SessionRecordingStatus }) {
const [isRecording, setRecording] = React.useState(false);
const toggleRecording = async () => {
console.log(isRecording);
if (isRecording) {
stopRecorderCb?.();
setRecording(false);
} else {
React.useEffect(() => {
return () => stopRecorderCb?.()
}, [])
React.useEffect(() => {
if (!isRecording && recordingState === SessionRecordingStatus.Recording) {
startRecording();
}
if (isRecording && recordingState !== SessionRecordingStatus.Recording) {
stopRecordingHandler();
}
}, [recordingState, isRecording])
const startRecording = async () => {
const stop = await screenRecorder();
stopRecorderCb = stop;
setRecording(true);
}
};
const isSupportedBrowser = isSupported()
if (!isSupportedBrowser) return (
<div className="p-3">
const stopRecordingHandler = () => {
stopRecording()
stopRecorderCb?.();
setRecording(false);
}
const recordingRequest = () => {
requestRecording()
// startRecording()
}
if (!isSupported()) return (
<div className="p-2">
{/* @ts-ignore */}
<Tooltip title="Supported browsers: Chrome v91+; Edge v90+">
<div className="p-1 text-disabled-text cursor-not-allowed">Record</div>
<Tooltip title={supportedMessage}>
<Button icon="record-circle" disabled variant={isRecording ? "text-red" : "text-primary"}>
Record Activity
</Button>
</Tooltip>
</div>
)
return (
<div onClick={toggleRecording} className="p-3">
<div className="p-1 font-semibold cursor-pointer hover:text-main">{isRecording ? 'STOP' : 'RECORD'}</div>
<div onClick={!isRecording ? recordingRequest : stopRecordingHandler} className="p-2">
<Button icon={!isRecording ? 'stop-record-circle' : 'record-circle'} variant={isRecording ? "text-red" : "text-primary"}>
{isRecording ? 'Stop Recording' : 'Record Activity'}
</Button>
</div>
);
}
export default ScreenRecorder
// @ts-ignore
export default connectPlayer(state => ({ recordingState: state.recordingState}))(ScreenRecorder)

File diff suppressed because one or more lines are too long

View file

@ -111,6 +111,11 @@ export default class Screen {
return Object.assign(this.screen.style, styles)
}
toggleRecordingStatus(isEnabled: boolean) {
const styles = isEnabled ? { border: '2px dashed red' } : { border: 'unset'}
return Object.assign(this.screen.style, styles)
}
get window(): WindowProxy | null {
return this.iframe.contentWindow;
}

View file

@ -34,6 +34,12 @@ export enum RemoteControlStatus {
Enabled,
}
export enum SessionRecordingStatus {
Off,
Requesting,
Recording
}
export function getStatusText(status: ConnectionStatus): string {
switch(status) {
@ -58,6 +64,7 @@ export interface State {
calling: CallingState;
peerConnectionStatus: ConnectionStatus;
remoteControl: RemoteControlStatus;
recordingState: SessionRecordingStatus;
annotating: boolean;
assistStart: number;
}
@ -66,6 +73,7 @@ export const INITIAL_STATE: State = {
calling: CallingState.NoCall,
peerConnectionStatus: ConnectionStatus.Connecting,
remoteControl: RemoteControlStatus.Disabled,
recordingState: SessionRecordingStatus.Off,
annotating: false,
assistStart: 0,
}
@ -245,12 +253,45 @@ export default class AssistManager {
this.toggleRemoteControl(false)
})
socket.on('call_end', this.onRemoteCallEnd)
socket.on('recording_accepted', () => {
this.toggleRecording(true)
})
socket.on('recording_denied', () => {
this.toggleRecording(false)
})
document.addEventListener('visibilitychange', this.onVisChange)
})
}
/* ==== Recording the session ==== */
public requestRecording = () => {
const recordingState = getState().recordingState
if (!this.socket || recordingState === SessionRecordingStatus.Requesting) return;
update({ recordingState: SessionRecordingStatus.Requesting })
this.socket.emit("request_recording", JSON.stringify({
...this.session.agentInfo,
query: document.location.search,
}))
}
public stopRecording = () => {
const recordingState = getState().recordingState
if (!this.socket || recordingState === SessionRecordingStatus.Off) return;
this.socket.emit("stop_recording")
this.toggleRecording(false)
}
private toggleRecording = (isAccepted: boolean) => {
this.md.toggleRecordingStatus(isAccepted)
update({ recordingStatus: isAccepted ? SessionRecordingStatus.Recording : SessionRecordingStatus.Off })
}
/* ==== Remote Control ==== */
private onMouseMove = (e: MouseEvent): void => {

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 201 B

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenoddCustomFill" d="M8 16H16V8H8V16ZM12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2Z" />
</svg>

After

Width:  |  Height:  |  Size: 232 B

View file

@ -90,6 +90,9 @@ ${icons.map(icon => {
.replace(/xmlns\:xlink/g, 'xmlnsXlink')
.replace(/clip-path/g, 'clipPath')
.replace(/clip-rule/g, 'clipRule')
// hack to keep fill rule for some icons like stop recording square
.replace(/clipRule="evenoddCustomFill"/g, 'clipRule="evenodd" fillRule="evenodd"')
.replace(/fill-rule/g, 'fillRule')
.replace(/fill-opacity/g, 'fillOpacity')
.replace(/stop-color/g, 'stopColor')
.replace(/xml:space="preserve"/g, '')};`;