0147fa80fa
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import { Maximize, Check } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
import { useModelStore, SCALE_PRESETS, type ScaleRatio } from '@/stores/useModelStore';
|
|
import { toast } from 'sonner';
|
|
|
|
interface ScaleSelectorProps {
|
|
/** "compact" = small icon-style for XR HUD, "default" = labeled button for 2D viewer */
|
|
variant?: 'default' | 'compact';
|
|
}
|
|
|
|
export function ScaleSelector({ variant = 'default' }: ScaleSelectorProps) {
|
|
const { scaleRatio, setScaleRatio, resetFineTuning } = useModelStore();
|
|
|
|
const reductions = SCALE_PRESETS.filter(p => p.factor < 1);
|
|
const enlargements = SCALE_PRESETS.filter(p => p.factor > 1);
|
|
const oneToOne = SCALE_PRESETS.find(p => p.label === '1:1')!;
|
|
|
|
const apply = (r: ScaleRatio) => {
|
|
setScaleRatio(r);
|
|
// Reset two-hand scale gesture so it doesn't fight the preset.
|
|
if (r.label !== '1:1') resetFineTuning();
|
|
toast.success(`Escala ${r.label}`, {
|
|
description: 'Medições e ajuste fino mostrados em mm reais.',
|
|
});
|
|
};
|
|
|
|
const Trigger = variant === 'compact' ? (
|
|
<Button variant="outline" size="sm" className="h-8 gap-1.5 text-[10px] font-mono" title="Escala do modelo">
|
|
<Maximize className="h-3.5 w-3.5" />
|
|
{scaleRatio.label}
|
|
</Button>
|
|
) : (
|
|
<Button variant={scaleRatio.label !== '1:1' ? 'default' : 'outline'} size="sm" className="gap-2 h-9">
|
|
<Maximize className="h-3.5 w-3.5" />
|
|
<span className="font-mono text-xs">Escala {scaleRatio.label}</span>
|
|
</Button>
|
|
);
|
|
|
|
return (
|
|
<Popover>
|
|
<PopoverTrigger asChild>{Trigger}</PopoverTrigger>
|
|
<PopoverContent className="w-72 space-y-3" side="top" align="start">
|
|
<div>
|
|
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1">
|
|
Escala atual
|
|
</p>
|
|
<p className="font-mono text-sm font-bold text-primary">{scaleRatio.label}</p>
|
|
<p className="text-[10px] text-muted-foreground mt-1">
|
|
Medições, ajuste fino e snap são compensados — todos os valores exibidos continuam em mm reais.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
|
|
Real
|
|
</p>
|
|
<PresetButton current={scaleRatio} preset={oneToOne} onSelect={apply} />
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
|
|
Reduzir (modelo menor)
|
|
</p>
|
|
<div className="grid grid-cols-3 gap-1.5">
|
|
{reductions.map(p => (
|
|
<PresetButton key={p.label} current={scaleRatio} preset={p} onSelect={apply} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
|
|
Ampliar (modelo maior)
|
|
</p>
|
|
<div className="grid grid-cols-3 gap-1.5">
|
|
{enlargements.map(p => (
|
|
<PresetButton key={p.label} current={scaleRatio} preset={p} onSelect={apply} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
}
|
|
|
|
function PresetButton({
|
|
preset, current, onSelect,
|
|
}: { preset: ScaleRatio; current: ScaleRatio; onSelect: (r: ScaleRatio) => void }) {
|
|
const active = preset.label === current.label;
|
|
return (
|
|
<Button
|
|
variant={active ? 'default' : 'outline'}
|
|
size="sm"
|
|
className="h-8 font-mono text-[11px] gap-1"
|
|
onClick={() => onSelect(preset)}
|
|
>
|
|
{active && <Check className="h-3 w-3" />}
|
|
{preset.label}
|
|
</Button>
|
|
);
|
|
}
|