1// The controls for one lab, declared by the lab and laid out in a single row
2// above its figures.
3import type { Params, ParamValue } from '../engine/types.ts'
4import type { Control } from './types.ts'
6export default function Controls({
7 controls,
8 params,
9 onChange,
10}: {
11 controls: Control[]
12 params: Params
13 onChange: (key: string, value: ParamValue) => void
14}) {
15 if (controls.length === 0) return null
16 return (
17 <div className="controls">
18 {controls.map((c) => {
19 if (c.kind === 'select') {
20 return (
21 <label className="control" key={c.key}>
22 <span className="control-label">{c.label}</span>
23 <select
24 value={String(params[c.key])}
25 onChange={(e) => {
26 const raw = e.target.value
27 const opt = c.options.find((o) => String(o.value) === raw)
28 onChange(c.key, opt ? opt.value : raw)
29 }}
30 >
31 {c.options.map((o) => (
32 <option key={String(o.value)} value={String(o.value)}>
33 {o.label}
34 </option>
35 ))}
36 </select>
37 </label>
38 )
39 }
40 if (c.kind === 'toggle') {
41 return (
42 <label className="control control-toggle" key={c.key}>
43 <input
44 type="checkbox"
45 checked={Boolean(params[c.key])}
46 onChange={(e) => onChange(c.key, e.target.checked)}
47 />
48 <span className="control-label">{c.label}</span>
49 </label>
50 )
51 }
52 const value = Number(params[c.key])
53 const fmt = c.format ?? ((v: number) => String(v))
54 if (c.values) {
55 const idx = Math.max(
56 0,
57 c.values.findIndex((v) => v === value),
58 )
59 return (
60 <label className="control" key={c.key}>
61 <span className="control-label">
62 {c.label} <span className="control-value">{fmt(value)}</span>
63 </span>
64 <input
65 type="range"
66 min={0}
67 max={c.values.length - 1}
68 step={1}
69 value={idx}
70 onChange={(e) => onChange(c.key, c.values![Number(e.target.value)])}
71 />
72 </label>
73 )
74 }
75 return (
76 <label className="control" key={c.key}>
77 <span className="control-label">
78 {c.label} <span className="control-value">{fmt(value)}</span>
79 </span>
80 <input
81 type="range"
82 min={c.min}
83 max={c.max}
84 step={c.step ?? 1}
85 value={value}
86 onChange={(e) => onChange(c.key, Number(e.target.value))}
87 />
88 </label>
89 )
90 })}
91 </div>
92 )
93}