Adicionou popover de cores
X-Lovable-Edit-ID: edt-ad717e5e-9246-4f93-b1d9-9c99bc76b631 Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,16 @@
|
|||||||
import { Eye, EyeOff, Lock, Unlock, Copy, Trash2, Box, Layers } from 'lucide-react';
|
import { Eye, EyeOff, Lock, Unlock, Copy, Trash2, Box, Layers, Check } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { useModelStore } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
const COLOR_PALETTE = [
|
||||||
|
'#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16', '#22c55e',
|
||||||
|
'#10b981', '#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1',
|
||||||
|
'#8b5cf6', '#a855f7', '#d946ef', '#ec4899', '#f43f5e', '#64748b',
|
||||||
|
'#ffffff', '#1f2937',
|
||||||
|
];
|
||||||
|
|
||||||
interface SceneModelListProps {
|
interface SceneModelListProps {
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
@@ -15,6 +23,7 @@ export function SceneModelList({ compact = false }: SceneModelListProps) {
|
|||||||
const toggleVisible = useModelStore((s) => s.toggleModelVisible);
|
const toggleVisible = useModelStore((s) => s.toggleModelVisible);
|
||||||
const toggleLocked = useModelStore((s) => s.toggleModelLocked);
|
const toggleLocked = useModelStore((s) => s.toggleModelLocked);
|
||||||
const duplicate = useModelStore((s) => s.duplicateModel);
|
const duplicate = useModelStore((s) => s.duplicateModel);
|
||||||
|
const setModelColor = useModelStore((s) => s.setModelColor);
|
||||||
const max = useModelStore((s) => s.maxModels);
|
const max = useModelStore((s) => s.maxModels);
|
||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
@@ -56,11 +65,47 @@ export function SceneModelList({ compact = false }: SceneModelListProps) {
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => setActive(m.id)}
|
onClick={() => setActive(m.id)}
|
||||||
>
|
>
|
||||||
<div
|
<Popover>
|
||||||
className="h-2.5 w-2.5 shrink-0 rounded-full ring-1 ring-foreground/20"
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="h-2.5 w-2.5 shrink-0 rounded-full ring-1 ring-foreground/20 hover:ring-2 hover:ring-primary cursor-pointer"
|
||||||
style={{ backgroundColor: m.color }}
|
style={{ backgroundColor: m.color }}
|
||||||
title="Cor da peça"
|
title="Trocar cor da peça"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-auto p-2"
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-2">
|
||||||
|
Cor da peça
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-5 gap-1.5">
|
||||||
|
{COLOR_PALETTE.map((c) => {
|
||||||
|
const selected = c.toLowerCase() === m.color.toLowerCase();
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={c}
|
||||||
|
type="button"
|
||||||
|
className="h-6 w-6 rounded-md ring-1 ring-foreground/20 hover:scale-110 transition-transform flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: c }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setModelColor(m.id, c);
|
||||||
|
}}
|
||||||
|
title={c}
|
||||||
|
>
|
||||||
|
{selected && <Check className="h-3 w-3" style={{ color: c === '#ffffff' ? '#000' : '#fff' }} />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Box className={`h-3 w-3 shrink-0 ${isActive ? 'text-primary' : 'text-muted-foreground'}`} />
|
<Box className={`h-3 w-3 shrink-0 ${isActive ? 'text-primary' : 'text-muted-foreground'}`} />
|
||||||
<span className={`flex-1 truncate font-mono ${textSize} ${isActive ? 'text-foreground font-semibold' : 'text-muted-foreground'}`}>
|
<span className={`flex-1 truncate font-mono ${textSize} ${isActive ? 'text-foreground font-semibold' : 'text-muted-foreground'}`}>
|
||||||
{m.fileName}
|
{m.fileName}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { convertIFCtoGLB } from "@/lib/convertIFC";
|
|||||||
|
|
||||||
const Viewer = () => {
|
const Viewer = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { model, xrSupported, addModel, models, maxModels } = useModelStore();
|
const { model, xrSupported, addModel, models, maxModels, scaleRatio } = useModelStore();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ const Viewer = () => {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<InfoItem icon={FileText} label="Arquivo" value={model.fileName} />
|
<InfoItem icon={FileText} label="Arquivo" value={model.fileName} />
|
||||||
<InfoItem icon={Box} label="Tamanho" value={`${(model.fileSize / (1024 * 1024)).toFixed(2)} MB`} />
|
<InfoItem icon={Box} label="Tamanho" value={`${(model.fileSize / (1024 * 1024)).toFixed(2)} MB`} />
|
||||||
<InfoItem icon={Ruler} label="Escala" value="1:1 (mm)" />
|
<InfoItem icon={Ruler} label="Escala" value={`${scaleRatio.label} (mm)`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ interface ModelStore {
|
|||||||
setActiveModel: (id: string | null) => void;
|
setActiveModel: (id: string | null) => void;
|
||||||
toggleModelVisible: (id: string) => void;
|
toggleModelVisible: (id: string) => void;
|
||||||
toggleModelLocked: (id: string) => void;
|
toggleModelLocked: (id: string) => void;
|
||||||
|
setModelColor: (id: string, color: string) => void;
|
||||||
duplicateModel: (id: string) => void;
|
duplicateModel: (id: string) => void;
|
||||||
clearAllModels: () => void;
|
clearAllModels: () => void;
|
||||||
maxModels: number;
|
maxModels: number;
|
||||||
@@ -243,6 +244,10 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
models: state.models.map(m => m.id === id ? { ...m, locked: !m.locked } : m),
|
models: state.models.map(m => m.id === id ? { ...m, locked: !m.locked } : m),
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
setModelColor: (id, color) => set((state) => ({
|
||||||
|
models: state.models.map(m => m.id === id ? { ...m, color } : m),
|
||||||
|
})),
|
||||||
|
|
||||||
duplicateModel: (id) => {
|
duplicateModel: (id) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
if (state.models.length >= MAX_MODELS) return;
|
if (state.models.length >= MAX_MODELS) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user