change(ui): change cursor icon for assist, add usernames to cursors for user and agent
This commit is contained in:
parent
3d2107fe98
commit
c56a0da63b
9 changed files with 292 additions and 197 deletions
|
|
@ -5,8 +5,18 @@ import cn from 'classnames';
|
|||
import { toggleChatWindow } from 'Duck/sessions';
|
||||
import { connectPlayer } from 'Player/store';
|
||||
import ChatWindow from '../../ChatWindow';
|
||||
import { callPeer, setCallArgs, requestReleaseRemoteControl, toggleAnnotation } from 'Player';
|
||||
import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player/MessageDistributor/managers/AssistManager';
|
||||
import {
|
||||
callPeer,
|
||||
setCallArgs,
|
||||
requestReleaseRemoteControl,
|
||||
toggleAnnotation,
|
||||
toggleUserName,
|
||||
} from 'Player';
|
||||
import {
|
||||
CallingState,
|
||||
ConnectionStatus,
|
||||
RemoteControlStatus,
|
||||
} from 'Player/MessageDistributor/managers/AssistManager';
|
||||
import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream';
|
||||
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';
|
||||
|
||||
|
|
@ -15,199 +25,228 @@ import { confirm } from 'UI';
|
|||
import stl from './AassistActions.module.css';
|
||||
|
||||
function onReject() {
|
||||
toast.info(`Call was rejected.`);
|
||||
toast.info(`Call was rejected.`);
|
||||
}
|
||||
|
||||
function onError(e: any) {
|
||||
console.log(e)
|
||||
toast.error(typeof e === 'string' ? e : e.message);
|
||||
console.log(e);
|
||||
toast.error(typeof e === 'string' ? e : e.message);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
userId: string;
|
||||
calling: CallingState;
|
||||
annotating: boolean;
|
||||
peerConnectionStatus: ConnectionStatus;
|
||||
remoteControlStatus: RemoteControlStatus;
|
||||
hasPermission: boolean;
|
||||
isEnterprise: boolean;
|
||||
isCallActive: boolean;
|
||||
agentIds: string[];
|
||||
livePlay: boolean;
|
||||
userId: string;
|
||||
calling: CallingState;
|
||||
annotating: boolean;
|
||||
peerConnectionStatus: ConnectionStatus;
|
||||
remoteControlStatus: RemoteControlStatus;
|
||||
hasPermission: boolean;
|
||||
isEnterprise: boolean;
|
||||
isCallActive: boolean;
|
||||
agentIds: string[];
|
||||
livePlay: boolean;
|
||||
userDisplayName: string;
|
||||
}
|
||||
|
||||
function AssistActions({
|
||||
userId,
|
||||
calling,
|
||||
annotating,
|
||||
peerConnectionStatus,
|
||||
remoteControlStatus,
|
||||
hasPermission,
|
||||
isEnterprise,
|
||||
isCallActive,
|
||||
agentIds,
|
||||
livePlay
|
||||
userId,
|
||||
calling,
|
||||
annotating,
|
||||
peerConnectionStatus,
|
||||
remoteControlStatus,
|
||||
hasPermission,
|
||||
isEnterprise,
|
||||
isCallActive,
|
||||
agentIds,
|
||||
livePlay,
|
||||
userDisplayName,
|
||||
}: Props) {
|
||||
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 [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 remoteActive = remoteControlStatus === RemoteControlStatus.Enabled;
|
||||
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting;
|
||||
const callRequesting = calling === CallingState.Connecting;
|
||||
const cannotCall =
|
||||
peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission);
|
||||
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
return callObject?.end()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
|
||||
toast.info(`Live session was closed.`);
|
||||
}
|
||||
}, [peerConnectionStatus]);
|
||||
|
||||
const addIncomeStream = (stream: MediaStream) => {
|
||||
setIncomeStream(oldState => {
|
||||
if (!oldState.find(existingStream => existingStream.id === stream.id)) {
|
||||
return [...oldState, stream]
|
||||
}
|
||||
return oldState
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!onCall && isCallActive && agentIds) {
|
||||
setPrestart(true);
|
||||
// call(agentIds); do not autocall on prestart, can change later
|
||||
}
|
||||
}, [agentIds, isCallActive]);
|
||||
|
||||
function call(additionalAgentIds?: string[]) {
|
||||
RequestLocalStream().then(lStream => {
|
||||
setLocalStream(lStream);
|
||||
setCallArgs(
|
||||
lStream,
|
||||
addIncomeStream,
|
||||
lStream.stop.bind(lStream),
|
||||
onReject,
|
||||
onError
|
||||
)
|
||||
setCallObject(callPeer());
|
||||
if (additionalAgentIds) {
|
||||
callPeer(additionalAgentIds)
|
||||
}
|
||||
}).catch(onError)
|
||||
useEffect(() => {
|
||||
if (!livePlay) {
|
||||
if (annotating) {
|
||||
toggleAnnotation(false);
|
||||
}
|
||||
if (remoteActive) {
|
||||
requestReleaseRemoteControl();
|
||||
}
|
||||
}
|
||||
}, [livePlay]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!onCall && isCallActive && agentIds) {
|
||||
setPrestart(true);
|
||||
// call(agentIds); do not autocall on prestart, can change later
|
||||
}
|
||||
}, [agentIds, isCallActive])
|
||||
|
||||
const confirmCall = async () => {
|
||||
if (callRequesting) return;
|
||||
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Start Call',
|
||||
confirmButton: 'Call',
|
||||
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`,
|
||||
})
|
||||
) {
|
||||
call(agentIds);
|
||||
}
|
||||
};
|
||||
|
||||
const requestControl = () => {
|
||||
if (callRequesting) return;
|
||||
requestReleaseRemoteControl()
|
||||
useEffect(() => {
|
||||
if (remoteActive) {
|
||||
toggleUserName(userDisplayName);
|
||||
} else {
|
||||
toggleUserName();
|
||||
}
|
||||
}, [remoteActive]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!livePlay) {
|
||||
if (annotating) {
|
||||
toggleAnnotation(false);
|
||||
}
|
||||
if (remoteActive) {
|
||||
requestReleaseRemoteControl()
|
||||
}
|
||||
useEffect(() => {
|
||||
return callObject?.end();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
|
||||
toast.info(`Live session was closed.`);
|
||||
}
|
||||
}, [peerConnectionStatus]);
|
||||
|
||||
const addIncomeStream = (stream: MediaStream) => {
|
||||
setIncomeStream((oldState) => {
|
||||
if (!oldState.find((existingStream) => existingStream.id === stream.id)) {
|
||||
return [...oldState, stream];
|
||||
}
|
||||
return oldState;
|
||||
});
|
||||
};
|
||||
|
||||
function call(additionalAgentIds?: string[]) {
|
||||
RequestLocalStream()
|
||||
.then((lStream) => {
|
||||
setLocalStream(lStream);
|
||||
setCallArgs(lStream, addIncomeStream, lStream.stop.bind(lStream), onReject, onError);
|
||||
setCallObject(callPeer());
|
||||
if (additionalAgentIds) {
|
||||
callPeer(additionalAgentIds);
|
||||
}
|
||||
}, [livePlay])
|
||||
})
|
||||
.catch(onError);
|
||||
}
|
||||
|
||||
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>
|
||||
{/* <IconButton label={`Annotate`} icon={annotating ? 'pencil-stop' : 'pencil'} primaryText redText={annotating} /> */}
|
||||
</div>
|
||||
<div className={stl.divider} />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={cn('cursor-pointer p-2 flex items-center', { [stl.disabled]: cannotCall || !livePlay || callRequesting })}
|
||||
onClick={requestControl}
|
||||
role="button"
|
||||
const confirmCall = async () => {
|
||||
if (callRequesting) return;
|
||||
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Start Call',
|
||||
confirmButton: 'Call',
|
||||
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`,
|
||||
})
|
||||
) {
|
||||
call(agentIds);
|
||||
}
|
||||
};
|
||||
|
||||
const requestControl = () => {
|
||||
if (callRequesting) return;
|
||||
requestReleaseRemoteControl();
|
||||
};
|
||||
|
||||
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' }}
|
||||
>
|
||||
<Button
|
||||
icon={remoteActive ? 'window-x' : 'remote-control'}
|
||||
variant={remoteActive ? 'text-red' : 'text-primary'}
|
||||
style={{ height: '28px' }}
|
||||
>
|
||||
Remote Control
|
||||
</Button>
|
||||
{/* <IconButton label={`Remote Control`} icon={remoteActive ? 'window-x' : 'remote-control'} primaryText redText={remoteActive} /> */}
|
||||
</div>
|
||||
<div className={stl.divider} />
|
||||
Annotate
|
||||
</Button>
|
||||
{/* <IconButton label={`Annotate`} icon={annotating ? 'pencil-stop' : 'pencil'} primaryText redText={annotating} /> */}
|
||||
</div>
|
||||
<div className={stl.divider} />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={cn('cursor-pointer p-2 flex items-center', {
|
||||
[stl.disabled]: cannotCall || !livePlay || callRequesting,
|
||||
})}
|
||||
onClick={requestControl}
|
||||
role="button"
|
||||
>
|
||||
<Button
|
||||
icon={remoteActive ? 'window-x' : 'remote-control'}
|
||||
variant={remoteActive ? 'text-red' : 'text-primary'}
|
||||
style={{ height: '28px' }}
|
||||
>
|
||||
Remote Control
|
||||
</Button>
|
||||
{/* <IconButton label={`Remote Control`} icon={remoteActive ? 'window-x' : 'remote-control'} primaryText redText={remoteActive} /> */}
|
||||
</div>
|
||||
<div className={stl.divider} />
|
||||
|
||||
<Popup content={cannotCall ? `You don't have the permissions to perform this action.` : `Call ${userId ? userId : 'User'}`}>
|
||||
<div
|
||||
className={cn('cursor-pointer p-2 flex items-center', { [stl.disabled]: cannotCall || callRequesting })}
|
||||
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>
|
||||
{/* <IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" /> */}
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
<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>
|
||||
<Popup
|
||||
content={
|
||||
cannotCall
|
||||
? `You don't have the permissions to perform this action.`
|
||||
: `Call ${userId ? userId : 'User'}`
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cn('cursor-pointer p-2 flex items-center', {
|
||||
[stl.disabled]: cannotCall || callRequesting,
|
||||
})}
|
||||
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>
|
||||
{/* <IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" /> */}
|
||||
</div>
|
||||
);
|
||||
</Popup>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
const con = connect(
|
||||
(state) => {
|
||||
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
|
||||
return {
|
||||
hasPermission: permissions.includes('ASSIST_CALL'),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
};
|
||||
},
|
||||
{ toggleChatWindow }
|
||||
(state) => {
|
||||
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
|
||||
return {
|
||||
hasPermission: permissions.includes('ASSIST_CALL'),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']),
|
||||
};
|
||||
},
|
||||
{ toggleChatWindow }
|
||||
);
|
||||
|
||||
export default con(
|
||||
connectPlayer((state) => ({
|
||||
calling: state.calling,
|
||||
annotating: state.annotating,
|
||||
remoteControlStatus: state.remoteControl,
|
||||
peerConnectionStatus: state.peerConnectionStatus,
|
||||
livePlay: state.livePlay,
|
||||
}))(AssistActions)
|
||||
connectPlayer((state) => ({
|
||||
calling: state.calling,
|
||||
annotating: state.annotating,
|
||||
remoteControlStatus: state.remoteControl,
|
||||
peerConnectionStatus: state.peerConnectionStatus,
|
||||
livePlay: state.livePlay,
|
||||
}))(AssistActions)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import styles from './cursor.module.css';
|
|||
|
||||
export default class Cursor {
|
||||
private readonly cursor: HTMLDivElement;
|
||||
private nameElement: HTMLDivElement;
|
||||
private readonly position: Point = { x: -1, y: -1 }
|
||||
constructor(overlay: HTMLDivElement) {
|
||||
this.cursor = document.createElement('div');
|
||||
|
|
@ -19,6 +20,32 @@ export default class Cursor {
|
|||
}
|
||||
}
|
||||
|
||||
toggleUserName(name?: string) {
|
||||
if (!this.nameElement) {
|
||||
this.nameElement = document.createElement('div')
|
||||
Object.assign(this.nameElement.style, {
|
||||
position: 'absolute',
|
||||
padding: '4px 6px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'rgb(57, 78, 255)',
|
||||
color: 'white',
|
||||
bottom: '-30px',
|
||||
left: '100%',
|
||||
fontSize: '16px',
|
||||
whiteSpace: 'nowrap',
|
||||
})
|
||||
this.cursor.appendChild(this.nameElement)
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
this.nameElement.style.display = 'none'
|
||||
} else {
|
||||
this.nameElement.style.display = 'block'
|
||||
const nameStr = name ? name.length > 10 ? name.slice(0, 9) + '...' : name : 'User'
|
||||
this.nameElement.innerHTML = `<span>${nameStr}</span>`
|
||||
}
|
||||
}
|
||||
|
||||
move({ x, y }: Point) {
|
||||
this.position.x = x;
|
||||
this.position.y = y;
|
||||
|
|
@ -41,4 +68,4 @@ export default class Cursor {
|
|||
return { x: this.position.x, y: this.position.y };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.cursor {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
width: 13px;
|
||||
height: 20px;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M302.189 329.126H196.105l55.831 135.993c3.889 9.428-.555 19.999-9.444 23.999l-49.165 21.427c-9.165 4-19.443-.571-23.332-9.714l-53.053-129.136-86.664 89.138C18.729 472.71 0 463.554 0 447.977V18.299C0 1.899 19.921-6.096 30.277 5.443l284.412 292.542c11.472 11.179 3.007 31.141-12.5 31.141z"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
|
||||
/* ====== *
|
||||
Source: https://github.com/codrops/ClickEffects/blob/master/css/component.css
|
||||
Source: https://github.com/codrops/ClickEffects/blob/master/css/component.css
|
||||
* ======= */
|
||||
.cursor::after {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -263,7 +263,11 @@ export default class Player extends MessageDistributor {
|
|||
this._setTime(getState().endTime);
|
||||
this._startAnimation();
|
||||
update({ livePlay: true });
|
||||
}
|
||||
}
|
||||
|
||||
toggleUserName(name?: string) {
|
||||
this.cursor.toggleUserName(name)
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.pause();
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const toggleAnnotation = initCheck((...args) => instance.assistManager.to
|
|||
/** @type {Player.toggleTimetravel} */
|
||||
export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args))
|
||||
export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args))
|
||||
export const toggleUserName = initCheck((...args) => instance.toggleUserName(...args))
|
||||
|
||||
export const Controls = {
|
||||
jump,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default class AnnotationCanvas {
|
|||
})
|
||||
}
|
||||
|
||||
private resizeCanvas = () => {
|
||||
private readonly resizeCanvas = () => {
|
||||
this.canvas.width = window.innerWidth
|
||||
this.canvas.height = window.innerHeight
|
||||
}
|
||||
|
|
@ -78,4 +78,4 @@ export default class AnnotationCanvas {
|
|||
}
|
||||
window.removeEventListener('resize', this.resizeCanvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ export default class Assist {
|
|||
this.emit('control_granted', id)
|
||||
annot = new AnnotationCanvas()
|
||||
annot.mount()
|
||||
return callingAgents.get(id)
|
||||
},
|
||||
id => {
|
||||
const cb = this.agents[id].onControlReleased
|
||||
|
|
@ -211,7 +212,7 @@ export default class Assist {
|
|||
})
|
||||
|
||||
socket.on('AGENT_DISCONNECTED', (id) => {
|
||||
remoteControl.releaseControl(id)
|
||||
remoteControl.releaseControl()
|
||||
|
||||
this.agents[id]?.onDisconnect?.()
|
||||
delete this.agents[id]
|
||||
|
|
@ -298,7 +299,9 @@ export default class Assist {
|
|||
const handleCallEnd = () => { // Completle stop and clear all calls
|
||||
// Streams
|
||||
Object.values(calls).forEach(call => call.close())
|
||||
Object.keys(calls).forEach(peerId => delete calls[peerId])
|
||||
Object.keys(calls).forEach(peerId => {
|
||||
delete calls[peerId]
|
||||
})
|
||||
Object.values(lStreams).forEach((stream) => { stream.stop() })
|
||||
Object.keys(lStreams).forEach((peerId: string) => { delete lStreams[peerId] })
|
||||
|
||||
|
|
@ -312,6 +315,9 @@ export default class Assist {
|
|||
this.emit('UPDATE_SESSION', { agentIds: [], isCallActive: false, })
|
||||
this.setCallingState(CallingState.False)
|
||||
sessionStorage.removeItem(this.options.session_calling_peer_key)
|
||||
|
||||
remoteControl.releaseControl()
|
||||
|
||||
callEndCallback?.()
|
||||
}
|
||||
const initiateCallEnd = () => {
|
||||
|
|
|
|||
|
|
@ -4,16 +4,33 @@ type XY = [number, number]
|
|||
export default class Mouse {
|
||||
private readonly mouse: HTMLDivElement
|
||||
private position: [number,number] = [0,0,]
|
||||
constructor() {
|
||||
constructor(private readonly agentName?: string) {
|
||||
this.mouse = document.createElement('div')
|
||||
const agentBubble = document.createElement('div')
|
||||
const svg ='<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="32" height="32" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 28 28" enable-background="new 0 0 28 28" xml:space="preserve"><polygon fill="#FFFFFF" points="8.2,20.9 8.2,4.9 19.8,16.5 13,16.5 12.6,16.6 "/><polygon fill="#FFFFFF" points="17.3,21.6 13.7,23.1 9,12 12.7,10.5 "/><rect x="12.5" y="13.6" transform="matrix(0.9221 -0.3871 0.3871 0.9221 -5.7605 6.5909)" width="2" height="8"/><polygon points="9.2,7.3 9.2,18.5 12.2,15.6 12.6,15.5 17.4,15.5 "/></svg>'
|
||||
|
||||
this.mouse.innerHTML = svg
|
||||
|
||||
Object.assign(agentBubble.style, {
|
||||
position: 'absolute',
|
||||
padding: '4px 6px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'green',
|
||||
color: 'white',
|
||||
bottom: '-18px',
|
||||
left: '50%',
|
||||
fontSize: '16px',
|
||||
whiteSpace: 'nowrap',
|
||||
})
|
||||
|
||||
const agentNameStr = this.agentName ? this.agentName.length > 10 ? this.agentName.slice(0, 9) + '...' : this.agentName : 'Agent'
|
||||
agentBubble.innerHTML = `<span>${agentNameStr}</span>`
|
||||
|
||||
this.mouse.appendChild(agentBubble)
|
||||
|
||||
Object.assign(this.mouse.style, {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
opacity: '.4',
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
zIndex: '999998',
|
||||
background: 'radial-gradient(red, transparent)',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +83,7 @@ export default class Mouse {
|
|||
|
||||
let el = this.lastScrEl
|
||||
|
||||
// Scroll the same one
|
||||
// Scroll the same one
|
||||
if (el instanceof Element) {
|
||||
el.scrollLeft += dX
|
||||
el.scrollTop += dY
|
||||
|
|
@ -78,12 +95,12 @@ export default class Mouse {
|
|||
}
|
||||
|
||||
el = document.elementFromPoint(
|
||||
mouseX-this.pScrEl.scrollLeft,
|
||||
mouseX-this.pScrEl.scrollLeft,
|
||||
mouseY-this.pScrEl.scrollTop,
|
||||
)
|
||||
while (el) {
|
||||
// el.scrollTopMax > 0 // available in firefox
|
||||
if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) {
|
||||
if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) {
|
||||
const styles = getComputedStyle(el)
|
||||
if (styles.overflow.indexOf('scroll') >= 0 || styles.overflow.indexOf('auto') >= 0) { // returns true for body in habr.com but it's not scrollable
|
||||
const esl = el.scrollLeft
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ export default class RemoteControl {
|
|||
|
||||
constructor(
|
||||
private readonly options: AssistOptions,
|
||||
private readonly onGrand: (sting?) => void,
|
||||
private readonly onRelease: (sting?) => void) {}
|
||||
private readonly onGrand: (string?) => string | undefined,
|
||||
private readonly onRelease: (string?) => void) {}
|
||||
|
||||
reconnect(ids: string[]) {
|
||||
const storedID = sessionStorage.getItem(this.options.session_control_peer_key)
|
||||
|
|
@ -39,12 +39,12 @@ export default class RemoteControl {
|
|||
private confirm: ConfirmWindow | null = null
|
||||
requestControl = (id: string) => {
|
||||
if (this.agentID !== null) {
|
||||
this.releaseControl(id)
|
||||
this.releaseControl()
|
||||
return
|
||||
}
|
||||
setTimeout(() =>{
|
||||
if (this.status === RCStatus.Requesting) {
|
||||
this.releaseControl(id)
|
||||
this.releaseControl()
|
||||
}
|
||||
}, 30000)
|
||||
this.agentID = id
|
||||
|
|
@ -54,7 +54,7 @@ export default class RemoteControl {
|
|||
if (allowed) {
|
||||
this.grantControl(id)
|
||||
} else {
|
||||
this.releaseControl(id)
|
||||
this.releaseControl()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
|
@ -65,23 +65,24 @@ export default class RemoteControl {
|
|||
console.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
grantControl = (id: string) => {
|
||||
this.agentID = id
|
||||
this.status = RCStatus.Enabled
|
||||
this.mouse = new Mouse()
|
||||
this.mouse.mount()
|
||||
sessionStorage.setItem(this.options.session_control_peer_key, id)
|
||||
this.onGrand(id)
|
||||
const agentName = this.onGrand(id)
|
||||
this.mouse = new Mouse(agentName)
|
||||
this.mouse.mount()
|
||||
}
|
||||
|
||||
releaseControl = (id: string) => {
|
||||
if (this.agentID !== id) { return }
|
||||
releaseControl = () => {
|
||||
if (!this.agentID) { return }
|
||||
this.mouse?.remove()
|
||||
this.mouse = null
|
||||
this.status = RCStatus.Disabled
|
||||
this.agentID = null
|
||||
sessionStorage.removeItem(this.options.session_control_peer_key)
|
||||
this.onRelease(id)
|
||||
this.onRelease(this.agentID)
|
||||
this.agentID = null
|
||||
}
|
||||
|
||||
scroll = (id, d) => { id === this.agentID && this.mouse?.scroll(d) }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue