148 lines
4.3 KiB
TypeScript
148 lines
4.3 KiB
TypeScript
import Mouse from './Mouse.js'
|
|
import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js'
|
|
import { controlConfirmDefault, } from './ConfirmWindow/defaults.js'
|
|
import type { Options as AssistOptions, } from './Assist.js'
|
|
|
|
export enum RCStatus {
|
|
Disabled,
|
|
Requesting,
|
|
Enabled,
|
|
}
|
|
|
|
|
|
let setInputValue = function(this: HTMLInputElement | HTMLTextAreaElement, value: string) { this.value = value }
|
|
const nativeInputValueDescriptor = typeof window !== 'undefined' && Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
|
|
if (nativeInputValueDescriptor && nativeInputValueDescriptor.set) {
|
|
setInputValue = nativeInputValueDescriptor.set
|
|
}
|
|
|
|
|
|
export default class RemoteControl {
|
|
private mouse: Mouse | null = null
|
|
public status: RCStatus = RCStatus.Disabled
|
|
private agentID: string | null = null
|
|
|
|
constructor(
|
|
private readonly options: AssistOptions,
|
|
private readonly onGrand: (id: string) => string | undefined,
|
|
private readonly onRelease: (id?: string | null, isDenied?: boolean) => void,
|
|
private readonly onBusy: (id?: string) => void) {}
|
|
|
|
reconnect(ids: string[]) {
|
|
const storedID = sessionStorage.getItem(this.options.session_control_peer_key)
|
|
if (storedID !== null && ids.indexOf(storedID) !== -1) {
|
|
this.grantControl(storedID)
|
|
} else {
|
|
sessionStorage.removeItem(this.options.session_control_peer_key)
|
|
}
|
|
}
|
|
|
|
private confirm: ConfirmWindow | null = null
|
|
requestControl = (id: string) => {
|
|
if (this.status === RCStatus.Enabled) {
|
|
return this.onBusy(id)
|
|
}
|
|
|
|
if (this.agentID !== null) {
|
|
this.releaseControl()
|
|
return
|
|
}
|
|
setTimeout(() => {
|
|
if (this.status === RCStatus.Requesting) {
|
|
this.releaseControl()
|
|
}
|
|
}, 30000)
|
|
this.agentID = id
|
|
this.status = RCStatus.Requesting
|
|
this.confirm = new ConfirmWindow(controlConfirmDefault(this.options.controlConfirm))
|
|
this.confirm.mount().then(allowed => {
|
|
if (allowed) {
|
|
this.grantControl(id)
|
|
} else {
|
|
this.confirm?.remove()
|
|
this.releaseControl(true)
|
|
}
|
|
})
|
|
.then(() => {
|
|
this.confirm?.remove()
|
|
})
|
|
.catch(e => {
|
|
this.confirm?.remove()
|
|
console.error(e)
|
|
})
|
|
}
|
|
|
|
releaseControl = (isDenied?: boolean, keepId?: boolean) => {
|
|
if (this.confirm) {
|
|
this.confirm.remove()
|
|
this.confirm = null
|
|
}
|
|
this.resetMouse()
|
|
this.status = RCStatus.Disabled
|
|
if (!keepId) {
|
|
sessionStorage.removeItem(this.options.session_control_peer_key)
|
|
}
|
|
this.onRelease(this.agentID, isDenied)
|
|
this.agentID = null
|
|
}
|
|
|
|
grantControl = (id: string) => {
|
|
this.agentID = id
|
|
this.status = RCStatus.Enabled
|
|
sessionStorage.setItem(this.options.session_control_peer_key, id)
|
|
const agentName = this.onGrand(id)
|
|
if (this.mouse) {
|
|
this.resetMouse()
|
|
}
|
|
this.mouse = new Mouse(agentName, this.options.onDragCamera)
|
|
this.mouse.mount()
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) this.releaseControl(false, true)
|
|
else {
|
|
if (this.status === RCStatus.Disabled) {
|
|
this.reconnect([id,])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
resetMouse = () => {
|
|
this.mouse?.remove()
|
|
this.mouse = null
|
|
}
|
|
|
|
scroll = (id, d) => { id === this.agentID && this.mouse?.scroll(d) }
|
|
move = (id, xy) => {
|
|
return id === this.agentID && this.mouse?.move(xy)
|
|
}
|
|
private focused: HTMLElement | null = null
|
|
click = (id, xy) => {
|
|
if (id !== this.agentID || !this.mouse) { return }
|
|
this.focused = this.mouse.click(xy)
|
|
}
|
|
focus = (id, el: HTMLElement) => {
|
|
this.focused = el
|
|
}
|
|
startDrag = (id, xy) => {
|
|
this.mouse?.startDrag(xy)
|
|
}
|
|
drag = (id, xydxdy) => {
|
|
this.mouse?.drag(xydxdy);
|
|
}
|
|
stopDrag = (id) => {
|
|
this.mouse?.stopDrag();
|
|
}
|
|
input = (id, value: string) => {
|
|
if (id !== this.agentID || !this.mouse || !this.focused) { return }
|
|
if (this.focused instanceof HTMLTextAreaElement
|
|
|| this.focused instanceof HTMLInputElement
|
|
|| this.focused.tagName === 'INPUT'
|
|
|| this.focused.tagName === 'TEXTAREA') {
|
|
setInputValue.call(this.focused, value)
|
|
const ev = new Event('input', { bubbles: true,})
|
|
this.focused.dispatchEvent(ev)
|
|
} else if (this.focused.isContentEditable) {
|
|
this.focused.innerText = value
|
|
}
|
|
}
|
|
}
|