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 { 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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: [] }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user