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 { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
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 {
|
||||
compact?: boolean;
|
||||
}
|
||||
@@ -15,6 +23,7 @@ export function SceneModelList({ compact = false }: SceneModelListProps) {
|
||||
const toggleVisible = useModelStore((s) => s.toggleModelVisible);
|
||||
const toggleLocked = useModelStore((s) => s.toggleModelLocked);
|
||||
const duplicate = useModelStore((s) => s.duplicateModel);
|
||||
const setModelColor = useModelStore((s) => s.setModelColor);
|
||||
const max = useModelStore((s) => s.maxModels);
|
||||
|
||||
if (models.length === 0) {
|
||||
@@ -56,11 +65,47 @@ export function SceneModelList({ compact = false }: SceneModelListProps) {
|
||||
}`}
|
||||
onClick={() => setActive(m.id)}
|
||||
>
|
||||
<div
|
||||
className="h-2.5 w-2.5 shrink-0 rounded-full ring-1 ring-foreground/20"
|
||||
style={{ backgroundColor: m.color }}
|
||||
title="Cor da peça"
|
||||
/>
|
||||
<Popover>
|
||||
<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 }}
|
||||
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'}`} />
|
||||
<span className={`flex-1 truncate font-mono ${textSize} ${isActive ? 'text-foreground font-semibold' : 'text-muted-foreground'}`}>
|
||||
{m.fileName}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { convertIFCtoGLB } from "@/lib/convertIFC";
|
||||
|
||||
const Viewer = () => {
|
||||
const navigate = useNavigate();
|
||||
const { model, xrSupported, addModel, models, maxModels } = useModelStore();
|
||||
const { model, xrSupported, addModel, models, maxModels, scaleRatio } = useModelStore();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [converting, setConverting] = useState(false);
|
||||
|
||||
@@ -177,7 +177,7 @@ const Viewer = () => {
|
||||
<div className="space-y-3">
|
||||
<InfoItem icon={FileText} label="Arquivo" value={model.fileName} />
|
||||
<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>
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ interface ModelStore {
|
||||
setActiveModel: (id: string | null) => void;
|
||||
toggleModelVisible: (id: string) => void;
|
||||
toggleModelLocked: (id: string) => void;
|
||||
setModelColor: (id: string, color: string) => void;
|
||||
duplicateModel: (id: string) => void;
|
||||
clearAllModels: () => void;
|
||||
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),
|
||||
})),
|
||||
|
||||
setModelColor: (id, color) => set((state) => ({
|
||||
models: state.models.map(m => m.id === id ? { ...m, color } : m),
|
||||
})),
|
||||
|
||||
duplicateModel: (id) => {
|
||||
const state = get();
|
||||
if (state.models.length >= MAX_MODELS) return;
|
||||
|
||||
Reference in New Issue
Block a user