import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSocketStore } from '../../context/socket'; import { getMappedCoordinates } from "../../helpers/inputHelpers"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { GenericModal } from '../atoms/GenericModal'; import { Box, Button, Typography } from '@mui/material'; interface CreateRefCallback { (ref: React.RefObject): void; } /** * Interface for mouse's x,y coordinates */ interface CanvasProps { width: number; height: number; onCreateRef: CreateRefCallback; highlighterData: { rect: DOMRect, selector: string } | null; } export interface Coordinates { x: number; y: number; } const ConfirmationBox = ({ selector, onYes, onNo }: { selector: string; onYes: () => void; onNo: () => void }) => { return ( Confirmation Do you want to interact with the element: {selector}? ); }; const Canvas = ({ width, height, onCreateRef, highlighterData }: CanvasProps) => { const canvasRef = useRef(null); const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); const [showConfirmation, setShowConfirmation] = useState(false); const [pendingClick, setPendingClick] = useState(null); const lastMousePosition = useRef({ x: 0, y: 0 }); const notifyLastAction = (action: string) => { if (lastAction !== action) { setLastAction(action); } }; const onMouseEvent = useCallback((event: MouseEvent) => { if (socket && canvasRef.current) { const coordinates = getMappedCoordinates(event, canvasRef.current, width, height); switch (event.type) { case 'mousemove': if (lastMousePosition.current.x !== coordinates.x || lastMousePosition.current.y !== coordinates.y) { lastMousePosition.current = { x: coordinates.x, y: coordinates.y, }; socket.emit('input:mousemove', { x: coordinates.x, y: coordinates.y, }); notifyLastAction('move'); } break; case 'mousedown': if (highlighterData) { const highlightRect = highlighterData.rect; if ( coordinates.x >= highlightRect.left && coordinates.x <= highlightRect.right && coordinates.y >= highlightRect.top && coordinates.y <= highlightRect.bottom ) { setPendingClick(coordinates); setShowConfirmation(true); } else { socket.emit('input:mousedown', coordinates); notifyLastAction('click'); } } else { socket.emit('input:mousedown', coordinates); notifyLastAction('click'); } break; case 'wheel': const wheelEvent = event as WheelEvent; const deltas = { deltaX: Math.round(wheelEvent.deltaX), deltaY: Math.round(wheelEvent.deltaY), }; socket.emit('input:wheel', deltas); notifyLastAction('scroll'); break; } } }, [socket, width, height, setLastAction, highlighterData]); const onKeyboardEvent = useCallback((event: KeyboardEvent) => { if (socket) { switch (event.type) { case 'keydown': socket.emit('input:keydown', { key: event.key, coordinates: lastMousePosition.current }); notifyLastAction(`${event.key} pressed`); break; case 'keyup': socket.emit('input:keyup', event.key); break; } } }, [socket, setLastAction]); const handleConfirmation = (confirmed: boolean) => { if (confirmed && pendingClick && socket) { socket.emit('input:mousedown', pendingClick); notifyLastAction('click'); } setShowConfirmation(false); setPendingClick(null); }; useEffect(() => { if (canvasRef.current) { onCreateRef(canvasRef); canvasRef.current.addEventListener('mousedown', onMouseEvent); canvasRef.current.addEventListener('mousemove', onMouseEvent); canvasRef.current.addEventListener('wheel', onMouseEvent, { passive: true }); canvasRef.current.addEventListener('keydown', onKeyboardEvent); canvasRef.current.addEventListener('keyup', onKeyboardEvent); return () => { if (canvasRef.current) { canvasRef.current.removeEventListener('mousedown', onMouseEvent); canvasRef.current.removeEventListener('mousemove', onMouseEvent); canvasRef.current.removeEventListener('wheel', onMouseEvent); canvasRef.current.removeEventListener('keydown', onKeyboardEvent); canvasRef.current.removeEventListener('keyup', onKeyboardEvent); } }; } }, [onMouseEvent, onKeyboardEvent, onCreateRef]); return ( <> setShowConfirmation(false)} canBeClosed={false} > handleConfirmation(true)} onNo={() => handleConfirmation(false)} /> ); }; export default Canvas;