Skip to main content
Glama
InteractableDevice.tsx3.62 kB
import React, { MouseEventHandler, useState } from "react"; import { DivProps } from "../../helpers/models"; import { AnnotatedScreenshot } from "./AnnotatedScreenshot"; import { isHotkeyPressed } from "react-hotkeys-hook"; import { useDeviceContext } from "../../context/DeviceContext"; import clsx from "clsx"; import { useRepl } from '../../context/ReplContext'; const useMetaKeyDown = () => { return isHotkeyPressed("meta"); }; export default function InteractableDevice({ enableGestureControl = true, }: { enableGestureControl?: boolean; }) { const { deviceScreen } = useDeviceContext(); const { runCommandYaml } = useRepl(); const metaKeyDown = useMetaKeyDown(); const onTapGesture = async (x: number, y: number) => { await runCommandYaml(`- tapOn: point: "${Math.round(100 * x)}%,${Math.round(100 * y)}%"`); }; const onSwipeGesture = async ( startX: number, startY: number, endX: number, endY: number, duration: number ) => { const startXPercent = Math.round(startX * 100); const startYPercent = Math.round(startY * 100); const endXPercent = Math.round(endX * 100); const endYPercent = Math.round(endY * 100); await runCommandYaml(` swipe: start: "${startXPercent}%,${startYPercent}%" end: "${endXPercent}%,${endYPercent}%" duration: ${Math.round(duration)} `); }; return ( <GestureDiv className={clsx( "rounded-lg overflow-hidden w-full", enableGestureControl ? "border-2 box-content border-pink-500" : "" )} style={{ aspectRatio: deviceScreen ? deviceScreen.width / deviceScreen.height : 1, }} onTap={onTapGesture} onSwipe={onSwipeGesture} gesturesEnabled={enableGestureControl ? metaKeyDown : false} > <AnnotatedScreenshot annotationsEnabled={enableGestureControl ? !metaKeyDown : true} /> </GestureDiv> ); } type GestureEvent = { x: number; y: number; timestamp: number; }; const createGestureEvent = ( e: React.MouseEvent<HTMLDivElement, MouseEvent> ): GestureEvent => { const { top, left } = e.currentTarget.getBoundingClientRect(); return { x: e.pageX - left, y: e.pageY - top, timestamp: e.timeStamp, }; }; const GestureDiv = ({ onTap, onSwipe, gesturesEnabled = true, ...rest }: { onTap: (x: number, y: number) => void; onSwipe: ( startX: number, startY: number, endX: number, endY: number, duration: number ) => void; gesturesEnabled?: boolean; } & DivProps) => { const [start, setStart] = useState<GestureEvent>(); const onStart: MouseEventHandler<HTMLDivElement> = (e) => { if (!gesturesEnabled) return; setStart(createGestureEvent(e)); }; const onEnd: MouseEventHandler<HTMLDivElement> = (e) => { if (!gesturesEnabled || !start) return; const end = createGestureEvent(e); const { width: clientWidth, height: clientHeight } = e.currentTarget.getBoundingClientRect(); const duration = end.timestamp - start.timestamp; const distance = Math.hypot(end.x - start.x, end.y - start.y); if (duration < 100 || distance < 10) { onTap(start.x / clientWidth, start.y / clientHeight); } else { onSwipe( start.x / clientWidth, start.y / clientHeight, end.x / clientWidth, end.y / clientHeight, duration ); } }; const onCancel = () => { setStart(undefined); }; return ( <div {...rest} onMouseDown={onStart} onMouseUp={onEnd} onMouseLeave={onCancel} /> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mobile-dev-inc/Maestro'

If you have feedback or need assistance with the MCP directory API, please join our Discord server