"use client"; import { useState, useCallback } from "react"; import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Circle, X, Video } from "lucide-react"; type Direction = "up" | "down" | "left" | "right" | "front"; // --- Shared D-Pad + Camera Image --- interface CameraFeedProps { base64: string; } /** * Pure camera feed with D-pad controls — no positioning wrapper. * Used by DashboardGrid's bento flip and can be embedded anywhere. */ export function CameraFeed({ base64 }: CameraFeedProps) { const [isMoving, setIsMoving] = useState(false); const [lastDirection, setLastDirection] = useState(null); const moveHead = useCallback(async (direction: Direction) => { if (isMoving) return; setIsMoving(true); setLastDirection(direction); try { const response = await fetch("http://localhost:7860/api/move-head", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ direction }), }); if (!response.ok) { console.error("Move head failed:", await response.text()); } } catch (error) { console.error("Move head error:", error); } finally { setTimeout(() => { setIsMoving(false); setLastDirection(null); }, 300); } }, [isMoving]); return (
{/* Header bar — muted, consistent with bento cards */}
{/* Video feed — fills remaining space */}
Robot camera view {/* D-pad overlay */}
} position="top-0 left-1/2 -translate-x-1/2" onClick={() => moveHead("up")} isActive={lastDirection === "up"} disabled={isMoving && lastDirection !== "up"} /> } position="bottom-0 left-1/2 -translate-x-1/2" onClick={() => moveHead("down")} isActive={lastDirection === "down"} disabled={isMoving && lastDirection !== "down"} /> } position="left-0 top-1/2 -translate-y-1/2" onClick={() => moveHead("left")} isActive={lastDirection === "left"} disabled={isMoving && lastDirection !== "left"} /> } position="right-0 top-1/2 -translate-y-1/2" onClick={() => moveHead("right")} isActive={lastDirection === "right"} disabled={isMoving && lastDirection !== "right"} /> {/* Center button (front) */}
{/* Footer hint */}
Tap arrows to move robot's view
); } // --- Legacy floating overlay (kept for backwards compat) --- interface CameraViewProps { base64: string; onClose: () => void; } export function CameraView({ base64, onClose }: CameraViewProps) { return (
{/* Close button on the old overlay */}
); } // --- D-Pad Button --- interface DPadButtonProps { direction: Direction; icon: React.ReactNode; position: string; onClick: () => void; isActive: boolean; disabled: boolean; } function DPadButton({ direction, icon, position, onClick, isActive, disabled }: DPadButtonProps) { return ( ); }