🚀 Auto-deploy: melhoria no snap e medição AR em 31/05/2026 23:46:46
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
|||||||
} from './three/viewCubeBus';
|
} from './three/viewCubeBus';
|
||||||
import { useModelStore } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
import { getModelLocalGroup } from '@/lib/modelTransforms';
|
import { getModelLocalGroup } from '@/lib/modelTransforms';
|
||||||
import { Check, RotateCcw } from 'lucide-react';
|
import { Check, RotateCcw, Maximize2 } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
/** Builds a square canvas texture with a label centered on it. */
|
/** Builds a square canvas texture with a label centered on it. */
|
||||||
@@ -296,6 +296,23 @@ export function ViewCube() {
|
|||||||
<RotateCcw className="h-2.5 w-2.5" /> reset
|
<RotateCcw className="h-2.5 w-2.5" /> reset
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Botão de Tela Cheia */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const container = document.getElementById("canvas-container");
|
||||||
|
if (container) {
|
||||||
|
container.requestFullscreen().catch((err) => {
|
||||||
|
toast.error("Não foi possível entrar em tela cheia: " + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex h-7 w-full items-center justify-center gap-1 rounded border border-primary/40 bg-background/40 font-mono text-[10px] uppercase tracking-widest text-primary hover:bg-primary/10 transition-colors"
|
||||||
|
title="Entrar em modo tela cheia"
|
||||||
|
>
|
||||||
|
<Maximize2 className="h-3 w-3" /> Tela Cheia
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-6
@@ -1,6 +1,6 @@
|
|||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
import { ArrowLeft, Glasses, Box, Ruler, FileText, Upload, Loader2, Users } from "lucide-react";
|
import { ArrowLeft, Glasses, Box, Ruler, FileText, Upload, Loader2, Users, Menu, Target } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useModelStore } from "@/stores/useModelStore";
|
import { useModelStore } from "@/stores/useModelStore";
|
||||||
import { ModelViewerCanvas } from "@/components/three/ModelViewer";
|
import { ModelViewerCanvas } from "@/components/three/ModelViewer";
|
||||||
@@ -21,6 +21,7 @@ import { Separator } from "@/components/ui/separator";
|
|||||||
import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from "@/lib/convertToGLB";
|
import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from "@/lib/convertToGLB";
|
||||||
import { validateModelFile } from "@/lib/validateModelFile";
|
import { validateModelFile } from "@/lib/validateModelFile";
|
||||||
import { convertIFCtoGLB } from "@/lib/convertIFC";
|
import { convertIFCtoGLB } from "@/lib/convertIFC";
|
||||||
|
import { mainCameraRef, mainControlsRef } from "@/components/three/viewCubeBus";
|
||||||
|
|
||||||
const Viewer = () => {
|
const Viewer = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -39,6 +40,44 @@ const Viewer = () => {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
|
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
setIsFullscreen(!!document.fullscreenElement);
|
||||||
|
};
|
||||||
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRecenter = useCallback(() => {
|
||||||
|
const camera = mainCameraRef.current;
|
||||||
|
const controls = mainControlsRef.current;
|
||||||
|
if (camera) {
|
||||||
|
camera.position.set(2, 2, 2);
|
||||||
|
camera.up.set(0, 1, 0);
|
||||||
|
camera.lookAt(0, 0, 0);
|
||||||
|
}
|
||||||
|
if (controls) {
|
||||||
|
// @ts-ignore
|
||||||
|
controls.target.set(0, 0, 0);
|
||||||
|
// @ts-ignore
|
||||||
|
controls.update();
|
||||||
|
}
|
||||||
|
toast.success("Visualização centralizada");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exitFullscreen = useCallback(() => {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen().catch((err) => {
|
||||||
|
console.error("Erro ao sair de tela cheia:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const roomId = searchParams.get("room");
|
const roomId = searchParams.get("room");
|
||||||
const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
|
const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
|
||||||
@@ -325,15 +364,51 @@ const Viewer = () => {
|
|||||||
|
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
{/* 3D Canvas with floating controls */}
|
{/* 3D Canvas with floating controls */}
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1" id="canvas-container">
|
||||||
<ModelViewerCanvas />
|
<ModelViewerCanvas />
|
||||||
<ViewCube />
|
{isFullscreen ? (
|
||||||
<ViewerControls />
|
<div className="absolute top-4 left-4 z-50 flex gap-2">
|
||||||
<SectionCutPanel />
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 bg-background/80 border-primary/40 hover:bg-primary/20 text-foreground font-mono shadow-md backdrop-blur-sm h-9"
|
||||||
|
onClick={handleRecenter}
|
||||||
|
>
|
||||||
|
<Target className="h-4 w-4 text-primary animate-pulse" />
|
||||||
|
Centralizar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 bg-background/80 border-destructive/40 hover:bg-destructive/20 text-foreground font-mono shadow-md backdrop-blur-sm h-9"
|
||||||
|
onClick={exitFullscreen}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4 text-destructive" />
|
||||||
|
Retornar (ESC)
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Botão de Toggle do Sidebar */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
className={`absolute z-30 h-8 w-8 rounded-md bg-background/80 border-primary/30 text-primary shadow-md hover:bg-primary/10 transition-all duration-300 ${
|
||||||
|
sidebarOpen ? "right-[332px]" : "right-4"
|
||||||
|
} top-[205px]`}
|
||||||
|
title={sidebarOpen ? "Recolher menu" : "Expandir menu"}
|
||||||
|
>
|
||||||
|
<Menu className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ViewCube />
|
||||||
|
<ViewerControls />
|
||||||
|
<SectionCutPanel />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Side panel */}
|
{/* Side panel */}
|
||||||
<aside className="w-80 shrink-0 border-l bg-card">
|
<aside className={`transition-all duration-300 border-l bg-card flex flex-col ${sidebarOpen ? 'w-80 opacity-100' : 'w-0 opacity-0 pointer-events-none border-l-0 overflow-hidden'}`}>
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<div className="p-4 space-y-5">
|
<div className="p-4 space-y-5">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user