This commit is contained in:
gpt-engineer-app[bot]
2026-02-27 01:14:49 +00:00
parent 8cced1a146
commit cb912b8f62
5 changed files with 92 additions and 3 deletions
+49
View File
@@ -0,0 +1,49 @@
import { Camera, X, Download } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useModelStore } from '@/stores/useModelStore';
export function ScreenshotGallery() {
const { screenshots, removeScreenshot, clearScreenshots } = useModelStore();
if (screenshots.length === 0) return null;
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="font-mono text-xs font-semibold uppercase tracking-widest text-muted-foreground">
Capturas ({screenshots.length})
</h3>
<Button variant="ghost" size="sm" className="h-6 text-[10px]" onClick={clearScreenshots}>
Limpar
</Button>
</div>
<div className="grid grid-cols-2 gap-2">
{screenshots.map((src, i) => (
<div key={i} className="group relative rounded-lg border overflow-hidden bg-muted/30">
<img src={src} alt={`Captura ${i + 1}`} className="w-full aspect-video object-cover" />
<div className="absolute top-1 right-1 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<a
href={src}
download={`TrackkSteelXR_capture_${i + 1}.png`}
className="h-6 w-6 rounded bg-card/90 flex items-center justify-center"
>
<Download className="h-3 w-3 text-primary" />
</a>
<button
onClick={() => removeScreenshot(i)}
className="h-6 w-6 rounded bg-card/90 flex items-center justify-center"
>
<X className="h-3 w-3 text-destructive" />
</button>
</div>
<div className="absolute bottom-1 left-1">
<span className="font-mono text-[9px] bg-card/80 rounded px-1 py-0.5 text-muted-foreground">
#{i + 1}
</span>
</div>
</div>
))}
</div>
</div>
);
}
+26 -2
View File
@@ -1,10 +1,23 @@
import { Eye, Grid3X3, Ruler, Trash2 } from 'lucide-react'; import { Eye, Grid3X3, Ruler, Trash2, Camera } from 'lucide-react';
import { Slider } from '@/components/ui/slider'; import { Slider } from '@/components/ui/slider';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useModelStore } from '@/stores/useModelStore'; import { useModelStore } from '@/stores/useModelStore';
import { useCallback } from 'react';
import { toast } from 'sonner';
export function ViewerControls() { export function ViewerControls() {
const { opacity, setOpacity, renderMode, setRenderMode, measureMode, setMeasureMode, measurements, clearMeasurements, measurePoints } = useModelStore(); const { opacity, setOpacity, renderMode, setRenderMode, measureMode, setMeasureMode, measurements, clearMeasurements, measurePoints, addScreenshot } = useModelStore();
const handleScreenshot = useCallback(() => {
const canvas = document.querySelector('canvas');
if (!canvas) {
toast.error('Canvas não encontrado');
return;
}
const dataUrl = canvas.toDataURL('image/png');
addScreenshot(dataUrl);
toast.success('Screenshot capturado!');
}, [addScreenshot]);
return ( return (
<div className="absolute bottom-4 left-4 z-10 flex items-end gap-3"> <div className="absolute bottom-4 left-4 z-10 flex items-end gap-3">
@@ -69,6 +82,17 @@ export function ViewerControls() {
</Button> </Button>
)} )}
</div> </div>
{/* Screenshot */}
<Button
variant="outline"
size="sm"
className="gap-2 h-9"
onClick={handleScreenshot}
>
<Camera className="h-3.5 w-3.5" />
<span className="font-mono text-xs">Capturar</span>
</Button>
</div> </div>
); );
} }
+1 -1
View File
@@ -192,7 +192,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
return ( return (
<Canvas <Canvas
camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }} camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }}
gl={{ antialias: true, alpha: true, powerPreference: 'high-performance' }} gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }}
frameloop="demand" frameloop="demand"
className="!bg-background" className="!bg-background"
onCreated={({ gl, invalidate }) => { onCreated={({ gl, invalidate }) => {
+4
View File
@@ -7,6 +7,7 @@ import { ViewerControls } from '@/components/ViewerControls';
import { InspectionChecklist } from '@/components/InspectionChecklist'; import { InspectionChecklist } from '@/components/InspectionChecklist';
import { FineTuningControls } from '@/components/FineTuningControls'; import { FineTuningControls } from '@/components/FineTuningControls';
import { MeasurementsList } from '@/components/MeasurementsList'; import { MeasurementsList } from '@/components/MeasurementsList';
import { ScreenshotGallery } from '@/components/ScreenshotGallery';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
@@ -96,6 +97,9 @@ const Viewer = () => {
<InspectionChecklist /> <InspectionChecklist />
<Separator />
<ScreenshotGallery />
<div className="rounded-lg border bg-muted/50 p-3"> <div className="rounded-lg border bg-muted/50 p-3">
<p className="font-mono text-xs text-muted-foreground"> <p className="font-mono text-xs text-muted-foreground">
Use o mouse para orbitar, scroll para zoom. Controle a opacidade e o modo de renderização nos controles flutuantes. Use o mouse para orbitar, scroll para zoom. Controle a opacidade e o modo de renderização nos controles flutuantes.
+12
View File
@@ -81,6 +81,11 @@ interface ModelStore {
measurements: Measurement[]; measurements: Measurement[];
clearMeasurements: () => void; clearMeasurements: () => void;
removeMeasurement: (id: string) => void; removeMeasurement: (id: string) => void;
screenshots: string[];
addScreenshot: (dataUrl: string) => void;
removeScreenshot: (index: number) => void;
clearScreenshots: () => void;
} }
const defaultFineTuning: FineTuning = { posX: 0, posY: 0, posZ: 0, rotY: 0 }; const defaultFineTuning: FineTuning = { posX: 0, posY: 0, posZ: 0, rotY: 0 };
@@ -152,4 +157,11 @@ export const useModelStore = create<ModelStore>((set) => ({
removeMeasurement: (id) => set((state) => ({ removeMeasurement: (id) => set((state) => ({
measurements: state.measurements.filter(m => m.id !== id), measurements: state.measurements.filter(m => m.id !== id),
})), })),
screenshots: [],
addScreenshot: (dataUrl) => set((state) => ({ screenshots: [...state.screenshots, dataUrl] })),
removeScreenshot: (index) => set((state) => ({
screenshots: state.screenshots.filter((_, i) => i !== index),
})),
clearScreenshots: () => set({ screenshots: [] }),
})); }));