| import { Button, Paper, Select, Slider, Stack, Text, Group, Title } from '@mantine/core' |
| import type { Band, Precip } from '../types' |
| import { PRESETS, matchPreset } from '../presets' |
|
|
| export interface ControlsState { |
| dm: number |
| logNw: number |
| mu: number |
| band: Band |
| cantingStd: number |
| precip: Precip |
| } |
|
|
| interface Props { |
| value: ControlsState |
| onChange: (next: ControlsState) => void |
| } |
|
|
| function SliderField({ |
| label, |
| value, |
| min, |
| max, |
| step, |
| decimals, |
| marks, |
| onChange, |
| }: { |
| label: React.ReactNode |
| value: number |
| min: number |
| max: number |
| step: number |
| decimals: number |
| marks?: { value: number; label?: string }[] |
| onChange: (v: number) => void |
| }) { |
| return ( |
| <div> |
| <Group justify="space-between" mb={4} gap="xs"> |
| <Text size="sm" fw={500} c="dimmed"> |
| {label} |
| </Text> |
| <Text size="sm" fw={600} ff="monospace"> |
| {value.toFixed(decimals)} |
| </Text> |
| </Group> |
| <Slider |
| value={value} |
| onChange={onChange} |
| min={min} |
| max={max} |
| step={step} |
| marks={marks} |
| color="dropCyan" |
| size="md" |
| label={null} |
| /> |
| </div> |
| ) |
| } |
|
|
| export function Controls({ value, onChange }: Props) { |
| const set = <K extends keyof ControlsState>(k: K, v: ControlsState[K]) => |
| onChange({ ...value, [k]: v }) |
|
|
| return ( |
| <Paper withBorder p="md" radius="md" shadow="xs"> |
| <Stack gap="lg"> |
| <Select |
| label="Precipitation type" |
| value={value.precip} |
| onChange={(v) => v && set('precip', v as Precip)} |
| data={[ |
| { value: 'rain', label: 'Rain' }, |
| { value: 'hail', label: 'Hail' }, |
| { value: 'snow_sector', label: 'Sector snowflake' }, |
| { value: 'snow_rosette', label: '6-point bullet rosette' }, |
| { value: 'snow_aggregate', label: 'Bullet-rosette aggregate' }, |
| ]} |
| allowDeselect={false} |
| /> |
| |
| <Select |
| label="Radar wavelength" |
| value={value.band} |
| onChange={(v) => v && set('band', v as Band)} |
| data={[ |
| { value: 'S', label: 'S band (10 cm)' }, |
| { value: 'C', label: 'C band (5 cm)' }, |
| { value: 'X', label: 'X band (3 cm)' }, |
| ]} |
| allowDeselect={false} |
| /> |
| |
| <SliderField |
| label={<>D<sub>m</sub> (mm)</>} |
| value={value.dm} |
| min={0.5} |
| max={20.0} |
| step={0.1} |
| decimals={1} |
| marks={[ |
| { value: 1, label: '1' }, |
| { value: 5, label: '5' }, |
| { value: 10, label: '10' }, |
| { value: 15, label: '15' }, |
| { value: 20, label: '20' }, |
| ]} |
| onChange={(v) => set('dm', v)} |
| /> |
| |
| <SliderField |
| label={<>log<sub>10</sub> N<sub>w</sub> (mm⁻¹ m⁻³)</>} |
| value={value.logNw} |
| min={0.5} |
| max={6.0} |
| step={0.1} |
| decimals={2} |
| marks={[ |
| { value: 1, label: '1' }, |
| { value: 3, label: '3' }, |
| { value: 5, label: '5' }, |
| ]} |
| onChange={(v) => set('logNw', v)} |
| /> |
|
|
| <SliderField |
| label={<>μ (shape)</>} |
| value={value.mu} |
| min={-3} |
| max={80} |
| step={1} |
| decimals={0} |
| marks={[ |
| { value: 0, label: '0' }, |
| { value: 20, label: '20' }, |
| { value: 40, label: '40' }, |
| { value: 60, label: '60' }, |
| ]} |
| onChange={(v) => set('mu', v)} |
| /> |
|
|
| <SliderField |
| label={<>Canting σ (deg)</>} |
| value={value.cantingStd} |
| min={0} |
| max={40} |
| step={1} |
| decimals={0} |
| marks={[ |
| { value: 0, label: '0' }, |
| { value: 20, label: '20' }, |
| { value: 40, label: '40' }, |
| ]} |
| onChange={(v) => set('cantingStd', v)} |
| /> |
| </Stack> |
|
|
| <Paper withBorder p="sm" radius="md" mt="lg" bg="gray.0"> |
| <Title order={5} mb="xs"> |
| Presets |
| </Title> |
| <Stack gap={6}> |
| {PRESETS.map((p) => { |
| const active = matchPreset(value) === p.key |
| return ( |
| <Button |
| key={p.key} |
| variant={active ? 'filled' : 'light'} |
| color="climasBlue" |
| size="sm" |
| fullWidth |
| styles={{ |
| root: { height: 'auto', paddingTop: 6, paddingBottom: 6 }, |
| label: { whiteSpace: 'normal' }, |
| }} |
| onClick={() => onChange({ ...value, ...p.values })} |
| > |
| <Stack gap={0} align="center"> |
| <Text size="sm" fw={600}> |
| {p.label} |
| </Text> |
| <Text size="xs" c={active ? 'white' : 'dimmed'} fs="italic"> |
| {p.source} |
| </Text> |
| </Stack> |
| </Button> |
| ) |
| })} |
| </Stack> |
| </Paper> |
| </Paper> |
| ) |
| } |
|
|