@openreplay/tracker-assist
Version:
Tracker plugin for screen assistance through the WebRTC
184 lines (183 loc) • 7.04 kB
JavaScript
import { hasOpenreplayAttribute } from "./utils.js";
export default class Mouse {
constructor(agentName, onDragCamera) {
this.agentName = agentName;
this.onDragCamera = onDragCamera;
this.position = [0, 0,];
this.isDragging = false;
this.pScrEl = document.scrollingElement || document.documentElement; // Is it always correct
this.lastScrEl = null;
this.resetLastScrEl = () => { this.lastScrEl = null; };
this.handleWScroll = e => {
if (e.target !== this.lastScrEl &&
this.lastScrEl !== 'window') {
this.resetLastScrEl();
}
};
this.mouse = document.createElement('div');
const agentBubble = document.createElement('div');
const svg = '<svg version="1.1" width="20" height="20" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xml:space="" viewBox="8.2 4.9 11.6 18.2"><polygon fill="#FFFFFF" points="8.2,20.9 8.2,4.9 19.8,16.5 13,16.5 12.6,16.6 "></polygon><polygon fill="#FFFFFF" points="17.3,21.6 13.7,23.1 9,12 12.7,10.5 "></polygon><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"></rect><polygon points="9.2,7.3 9.2,18.5 12.2,15.6 12.6,15.5 17.4,15.5 "></polygon></svg>';
this.mouse.innerHTML = svg;
this.mouse.setAttribute('data-openreplay-hidden', '');
Object.assign(agentBubble.style, {
position: 'absolute',
padding: '4px 5px',
borderRadius: '4px',
backgroundColor: '#394EFF',
color: 'white',
bottom: '-20px',
left: '65%',
fontSize: '12px',
whiteSpace: 'nowrap',
});
this.onDragCamera = onDragCamera;
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, {
position: 'absolute',
zIndex: '999998',
pointerEvents: 'none',
// adjusting the svg empty space
marginTop: '-1px',
marginLeft: '-2px',
});
}
mount() {
document.body.appendChild(this.mouse);
window.addEventListener('scroll', this.handleWScroll);
window.addEventListener('resize', this.resetLastScrEl);
}
move(pos) {
if (this.position[0] !== pos[0] || this.position[1] !== pos[1]) {
this.resetLastScrEl();
}
this.position = pos;
Object.assign(this.mouse.style, {
left: `${pos[0] || 0}px`,
top: `${pos[1] || 0}px`,
});
}
getPosition() {
return this.position;
}
click(pos) {
const el = document.elementFromPoint(pos[0], pos[1]);
if (el instanceof HTMLElement || el instanceof SVGElement) {
try {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
clientX: pos[0],
clientY: pos[1]
});
el.dispatchEvent(clickEvent);
}
catch (e) {
console.error(e);
// @ts-ignore
el.click && el.click();
}
el.focus();
return el;
}
return null;
}
startDrag(pos) {
this.move(pos);
const el = document.elementFromPoint(pos[0], pos[1]);
if (el) {
const downEvt = new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
clientX: pos[0],
clientY: pos[1],
buttons: 1,
});
el.dispatchEvent(downEvt);
this.isDragging = true;
}
}
drag(pos) {
const [x, y, dx, dy] = pos;
this.move([x, y]);
if (!this.isDragging)
return;
const el = document.elementFromPoint(x, y);
if (el) {
const moveEvt = new MouseEvent("mousemove", {
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
buttons: 1,
});
el.dispatchEvent(moveEvt);
if (hasOpenreplayAttribute(el, 'draggable') && this.onDragCamera) {
this.onDragCamera(dx, dy);
}
}
}
stopDrag() {
if (!this.isDragging)
return;
const [x, y] = this.position;
const el = document.elementFromPoint(x, y);
if (el) {
const upEvt = new MouseEvent("mouseup", {
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
buttons: 0,
});
el.dispatchEvent(upEvt);
}
this.isDragging = false;
}
scroll(delta) {
// what would be the browser-like logic?
const [mouseX, mouseY,] = this.position;
const [dX, dY,] = delta;
let el = this.lastScrEl;
// Scroll the same one
if (el instanceof Element) {
el.scrollLeft += dX;
el.scrollTop += dY;
return; // TODO: if not scrolled
}
if (el === 'window') {
window.scroll(this.pScrEl.scrollLeft + dX, this.pScrEl.scrollTop + dY);
return;
}
el = document.elementFromPoint(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) {
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;
const est = el.scrollTop;
el.scrollLeft += dX;
el.scrollTop += dY;
if (esl !== el.scrollLeft || est !== el.scrollTop) { // doesn't work if the scroll-behavior is "smooth"
this.lastScrEl = el;
return;
}
}
}
el = el.parentElement;
}
// If not scrolled
window.scroll(this.pScrEl.scrollLeft + dX, this.pScrEl.scrollTop + dY);
this.lastScrEl = 'window';
}
remove() {
if (this.mouse.parentElement) {
document.body.removeChild(this.mouse);
}
window.removeEventListener('scroll', this.handleWScroll);
window.removeEventListener('resize', this.resetLastScrEl);
}
}