Changes
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 { Button } from '@/components/ui/button';
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
import { useCallback } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
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 (
|
||||
<div className="absolute bottom-4 left-4 z-10 flex items-end gap-3">
|
||||
@@ -69,6 +82,17 @@ export function ViewerControls() {
|
||||
</Button>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
return (
|
||||
<Canvas
|
||||
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"
|
||||
className="!bg-background"
|
||||
onCreated={({ gl, invalidate }) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ViewerControls } from '@/components/ViewerControls';
|
||||
import { InspectionChecklist } from '@/components/InspectionChecklist';
|
||||
import { FineTuningControls } from '@/components/FineTuningControls';
|
||||
import { MeasurementsList } from '@/components/MeasurementsList';
|
||||
import { ScreenshotGallery } from '@/components/ScreenshotGallery';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
@@ -96,6 +97,9 @@ const Viewer = () => {
|
||||
|
||||
<InspectionChecklist />
|
||||
|
||||
<Separator />
|
||||
|
||||
<ScreenshotGallery />
|
||||
<div className="rounded-lg border bg-muted/50 p-3">
|
||||
<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.
|
||||
|
||||
@@ -81,6 +81,11 @@ interface ModelStore {
|
||||
measurements: Measurement[];
|
||||
clearMeasurements: () => 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 };
|
||||
@@ -152,4 +157,11 @@ export const useModelStore = create<ModelStore>((set) => ({
|
||||
removeMeasurement: (id) => set((state) => ({
|
||||
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: [] }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user