This commit is contained in:
gpt-engineer-app[bot]
2026-02-28 00:15:34 +00:00
parent 1d38e0f474
commit 113f961ed4
+57 -7
View File
@@ -2,23 +2,26 @@ import { useState } from 'react';
import {
Eye, LayoutGrid, Grid3X3, Box, Ruler, Trash2, Camera,
Move, RotateCw, RefreshCw, ChevronUp, ChevronDown, Grip,
ClipboardCheck, X,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Slider } from '@/components/ui/slider';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useModelStore } from '@/stores/useModelStore';
import { InspectionChecklist } from '@/components/InspectionChecklist';
import { toast } from 'sonner';
const POSITION_STEP = 0.001;
const ROTATION_STEP = 0.1;
interface XRHudProps {
/** Whether joystick moves model freely (without grip) */
freeMove: boolean;
onToggleFreeMove: () => void;
}
export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
const [expanded, setExpanded] = useState(false);
const [checklistOpen, setChecklistOpen] = useState(false);
const {
opacity, setOpacity,
@@ -28,8 +31,13 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
measurements, clearMeasurements, measurePoints,
fineTuning, setFineTuning, resetFineTuning,
addScreenshot,
checklist,
} = useModelStore();
const approved = checklist.filter(i => i.status === 'approved').length;
const rejected = checklist.filter(i => i.status === 'rejected').length;
const total = checklist.length;
const handleScreenshot = () => {
const canvas = document.querySelector('canvas');
if (!canvas) return;
@@ -39,6 +47,38 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
};
return (
<>
{/* ── Slide-out checklist panel ── */}
<div
className={`absolute top-0 right-0 z-30 h-full w-80 max-w-[85vw] transition-transform duration-300 ease-in-out ${
checklistOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
<div className="h-full border-l bg-card/95 backdrop-blur-md shadow-2xl flex flex-col">
<div className="flex items-center justify-between border-b px-4 py-3">
<div className="flex items-center gap-2">
<ClipboardCheck className="h-4 w-4 text-primary" />
<span className="font-mono text-xs font-semibold text-foreground">Inspeção</span>
</div>
<div className="flex items-center gap-2">
{/* Progress badge */}
<span className="font-mono text-[10px] text-muted-foreground">
{approved + rejected}/{total}
</span>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => setChecklistOpen(false)}>
<X className="h-4 w-4" />
</Button>
</div>
</div>
<ScrollArea className="flex-1">
<div className="p-4">
<InspectionChecklist />
</div>
</ScrollArea>
</div>
</div>
{/* ── Bottom HUD ── */}
<div className="absolute bottom-0 left-0 right-0 z-20 pointer-events-none p-3">
<div className="pointer-events-auto mx-auto max-w-lg space-y-2">
{/* Expanded panel */}
@@ -103,12 +143,10 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
{/* Main toolbar */}
<div className="flex items-center justify-between rounded-xl border bg-card/90 backdrop-blur-md p-2 shadow-2xl">
<div className="flex items-center gap-1.5 flex-wrap">
{/* Expand/collapse */}
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setExpanded(!expanded)}>
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronUp className="h-4 w-4" />}
</Button>
{/* Free move toggle */}
<Button
variant={freeMove ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono"
@@ -118,7 +156,6 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
{freeMove ? 'Mover ON' : 'Mover'}
</Button>
{/* Grid */}
<Button
variant={showGrid ? 'default' : 'outline'}
size="icon" className="h-8 w-8"
@@ -128,7 +165,6 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
<LayoutGrid className="h-3.5 w-3.5" />
</Button>
{/* Render mode */}
<Button
variant={renderMode !== 'solid' ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono"
@@ -141,7 +177,6 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
{renderMode === 'solid' ? 'Sólido' : renderMode === 'wireframe' ? 'Wire' : 'Bordas'}
</Button>
{/* Measure */}
<Button
variant={measureMode ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono"
@@ -159,13 +194,28 @@ export function XRHud({ freeMove, onToggleFreeMove }: XRHudProps) {
</Button>
)}
{/* Screenshot */}
<Button variant="outline" size="icon" className="h-8 w-8" onClick={handleScreenshot} title="Screenshot">
<Camera className="h-3.5 w-3.5" />
</Button>
{/* Checklist toggle */}
<Button
variant={checklistOpen ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono relative"
onClick={() => setChecklistOpen(!checklistOpen)}
>
<ClipboardCheck className="h-3.5 w-3.5" />
Inspeção
{(approved + rejected) > 0 && (
<span className={`ml-1 font-mono text-[9px] ${rejected > 0 ? 'text-destructive' : 'text-primary'}`}>
{approved + rejected}/{total}
</span>
)}
</Button>
</div>
</div>
</div>
</div>
</>
);
}