Skip to content

Commit 3f87150

Browse files
committed
partially wire up controls to pad state
1 parent aa9ae74 commit 3f87150

File tree

10 files changed

+107
-77
lines changed

10 files changed

+107
-77
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"typescript.preferences.preferTypeOnlyAutoImports": true,
1212
"editor.codeActionsOnSave": {
1313
"source.fixAll": "explicit"
14-
}
14+
},
15+
"editor.defaultFormatter": "biomejs.biome"
1516
}

ui/common/panel-meters.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
flex-wrap: wrap;
2222
justify-content: center;
2323
gap: 32px;
24-
}
24+
}

ui/common/panel-meters.tsx

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,61 @@
1+
import { useState, useCallback } from "react";
2+
import { useAtomValue } from "jotai";
13

2-
import { useState, useCallback } from "react"
3-
import { SensorMeterInput } from "./sensor-meter-input"
4-
import { ToggleSwitch } from "./toggle-switch"
4+
import { useTestData } from "../stage/hooks";
5+
import { selectedStage$, selectedPanelIdx$ } from "../state";
6+
import { SensorMeterInput } from "./sensor-meter-input";
7+
import { ToggleSwitch } from "./toggle-switch";
58
import classes from "./panel-meters.module.css";
69

710
export function PanelMeters() {
11+
const stage = useAtomValue(selectedStage$);
12+
const panelIdx = useAtomValue(selectedPanelIdx$);
13+
const testData = useTestData(stage);
14+
const panelData = panelIdx === undefined ? null : testData?.panels[panelIdx];
815
const [sensors, setSensors] = useState([
9-
{ id: 1, value: 50, activationThreshold: 70, releaseThreshold: 30 },
10-
{ id: 2, value: 30, activationThreshold: 60, releaseThreshold: 20 },
11-
{ id: 3, value: 70, activationThreshold: 80, releaseThreshold: 40 },
12-
{ id: 4, value: 40, activationThreshold: 75, releaseThreshold: 25 },
13-
])
16+
{ id: 1, activationThreshold: 70, releaseThreshold: 30 },
17+
{ id: 2, activationThreshold: 60, releaseThreshold: 20 },
18+
{ id: 3, activationThreshold: 80, releaseThreshold: 40 },
19+
{ id: 4, activationThreshold: 75, releaseThreshold: 25 },
20+
]);
1421

15-
const [isLocked, setIsLocked] = useState(false)
22+
const [isLocked, setIsLocked] = useState(false);
1623

1724
const updateSensorThreshold = useCallback(
1825
(id: number, type: "activation" | "release", value: number) => {
1926
setSensors((prevSensors) => {
2027
const updatedSensors = prevSensors.map((sensor) =>
2128
sensor.id === id ? { ...sensor, [`${type}Threshold`]: value } : sensor,
22-
)
29+
);
2330

2431
if (isLocked) {
2532
// If locked, update all sensors with the same threshold
2633
return updatedSensors.map((sensor) => ({
2734
...sensor,
2835
[`${type}Threshold`]: value,
29-
}))
36+
}));
3037
}
3138

32-
return updatedSensors
33-
})
39+
return updatedSensors;
40+
});
3441
},
3542
[isLocked],
36-
)
43+
);
3744

3845
const toggleLock = () => {
39-
setIsLocked((prev) => !prev)
46+
setIsLocked((prev) => !prev);
4047
if (!isLocked) {
4148
// When locking, set all thresholds to the values of the first sensor
42-
const { activationThreshold, releaseThreshold } = sensors[0]
49+
const { activationThreshold, releaseThreshold } = sensors[0];
4350
setSensors((prevSensors) =>
4451
prevSensors.map((sensor) => ({
4552
...sensor,
4653
activationThreshold,
4754
releaseThreshold,
4855
})),
49-
)
56+
);
5057
}
51-
}
58+
};
5259

5360
return (
5461
<div className={classes.panelWrapper}>
@@ -60,7 +67,7 @@ export function PanelMeters() {
6067
{sensors.map((sensor, index) => (
6168
<SensorMeterInput
6269
key={sensor.id}
63-
value={sensor.value}
70+
value={panelData?.sensor_level[index]}
6471
id={sensor.id}
6572
activationThreshold={sensor.activationThreshold}
6673
releaseThreshold={sensor.releaseThreshold}
@@ -71,6 +78,5 @@ export function PanelMeters() {
7178
))}
7279
</div>
7380
</div>
74-
)
81+
);
7582
}
76-

ui/common/sensor-meter-input.tsx

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ function clamp(value: number, min: number, max: number) {
88

99
interface SensorProps {
1010
id: number;
11-
value: number;
11+
value?: number | undefined;
1212
activationThreshold: number;
1313
releaseThreshold: number;
1414
maxValue: number;
15-
updateThreshold?: (
16-
id: number,
17-
type: "activation" | "release",
18-
value: number,
19-
) => void;
15+
updateThreshold?: (id: number, type: "activation" | "release", value: number) => void;
2016
showControls?: boolean;
2117
}
2218

@@ -46,13 +42,11 @@ export function SensorMeterInput({
4642
updateThreshold,
4743
showControls,
4844
}: SensorProps) {
49-
const valuePct = 100 * value / maxValue;
50-
const releaseThresholdPct = 100 * releaseThreshold / maxValue;
51-
const activationThresholdPct = 100 * activationThreshold / maxValue;
52-
const [currentDraggingHandle, setIsDragging] = useState<"activation" | "release" | null>(
53-
null,
54-
);
55-
const isActive = useSensorActive(value, activationThreshold, releaseThreshold);
45+
const valuePct = (100 * (value || 0)) / maxValue;
46+
const releaseThresholdPct = (100 * releaseThreshold) / maxValue;
47+
const activationThresholdPct = (100 * activationThreshold) / maxValue;
48+
const [currentDraggingHandle, setIsDragging] = useState<"activation" | "release" | null>(null);
49+
const isActive = useSensorActive(value || 0, activationThreshold, releaseThreshold);
5650

5751
const handleMouseDown = (type: "activation" | "release") => {
5852
setIsDragging(type);
@@ -73,7 +67,7 @@ export function SensorMeterInput({
7367

7468
updateThreshold?.(id, currentDraggingHandle, Math.round(percentage));
7569
},
76-
[id, currentDraggingHandle, updateThreshold],
70+
[id, currentDraggingHandle, updateThreshold, maxValue],
7771
);
7872

7973
return (
@@ -100,40 +94,24 @@ export function SensorMeterInput({
10094
onMouseUp={handleMouseUp}
10195
onMouseLeave={handleMouseUp}
10296
>
103-
<div
104-
className={classes.dragHandleContainer}
105-
style={{ bottom: `calc(${activationThresholdPct}% - 8px)` }}
106-
>
97+
<div className={classes.dragHandleContainer} style={{ bottom: `calc(${activationThresholdPct}% - 8px)` }}>
10798
<div
10899
className={classNames(classes.dragHandle, classes.atkColorBg)}
109100
onMouseDown={() => handleMouseDown("activation")}
110101
/>
111-
<span
112-
className={classNames(classes.handleLabel, classes.atkColorFg)}
113-
>
114-
{activationThreshold}
115-
</span>
102+
<span className={classNames(classes.handleLabel, classes.atkColorFg)}>{activationThreshold}</span>
116103
</div>
117-
<div
118-
className={classes.dragHandleContainer}
119-
style={{ bottom: `calc(${releaseThresholdPct}% - 8px)` }}
120-
>
104+
<div className={classes.dragHandleContainer} style={{ bottom: `calc(${releaseThresholdPct}% - 8px)` }}>
121105
<div
122106
className={classNames(classes.dragHandle, classes.rlsColorBg)}
123107
onMouseDown={() => handleMouseDown("release")}
124108
/>
125-
<span
126-
className={classNames(classes.handleLabel, classes.rlsColorFg)}
127-
>
128-
{releaseThreshold}
129-
</span>
109+
<span className={classNames(classes.handleLabel, classes.rlsColorFg)}>{releaseThreshold}</span>
130110
</div>
131111
</div>
132112
)}
133113
</div>
134-
<div className={classes.bottomLabel}>
135-
<p>Value: {value}</p>
136-
</div>
114+
<div className={classes.bottomLabel}>{value === undefined ? null : <p>Value: {value}</p>}</div>
137115
</div>
138116
);
139117
}

ui/common/toggle-switch.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@ import classNames from "classnames";
22
import classes from "./toggle-switch.module.css";
33

44
interface ToggleProps {
5-
isOn: boolean
6-
onToggle: () => void
7-
label: string
5+
isOn: boolean;
6+
onToggle: () => void;
7+
label: string;
88
}
99

1010
export function ToggleSwitch({ isOn, onToggle, label }: ToggleProps) {
1111
return (
1212
<label className={classes.wrapper}>
1313
<div className={classes.inputParent}>
1414
<input type="checkbox" className={classes.screenReaderOnly} checked={isOn} onChange={onToggle} />
15-
<div className={classNames(classes.background, { [classes.bgOn]: isOn })}></div>
16-
<div
17-
className={classNames(classes.handle, { [classes.handleOn]: isOn })}
18-
></div>
15+
<div className={classNames(classes.background, { [classes.bgOn]: isOn })} />
16+
<div className={classNames(classes.handle, { [classes.handleOn]: isOn })} />
1917
</div>
2018
<div className={classes.labelText}>{label}</div>
2119
</label>
22-
)
20+
);
2321
}

ui/stage/fsr-panel.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import cn from "classnames";
22
import { FsrSensor, type SMXPanelTestData } from "../../sdk";
3+
import { useCallback } from "react";
4+
import { selectedPanelIdx$, selectedStageSerial$ } from "../state";
5+
import { useAtomValue } from "jotai";
36

47
interface EnabledProps {
58
testData: SMXPanelTestData | undefined;
69
active: boolean | undefined;
710
disabled: boolean | undefined;
11+
index: number;
12+
stageSerial: string;
13+
onClick(index: number): void;
814
}
915

10-
export function FsrPanel({ testData, active, disabled }: EnabledProps) {
16+
export function FsrPanel({ testData, active, disabled, index, onClick, stageSerial }: EnabledProps) {
17+
const handleClick = useCallback(() => onClick(index), [index, onClick]);
18+
const selectedPanelIdx = useAtomValue(selectedPanelIdx$);
19+
const selectedStageSerial = useAtomValue(selectedStageSerial$);
1120
if (disabled) {
12-
return <div className={cn("panel", {})} />;
21+
return <div className={cn("panel disabled", {})} />;
1322
}
23+
const selected = stageSerial === selectedStageSerial && selectedPanelIdx === index;
1424
return (
25+
// biome-ignore lint/a11y/useKeyWithClickEvents: not doing a11y right now :/
1526
<div
27+
onClick={handleClick}
1628
className={cn("panel", {
1729
commErr: testData && !testData.have_data_from_panel,
1830
active: active,
31+
selected,
1932
})}
2033
>
2134
<Fsr

ui/stage/load-cell-panel.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import cn from "classnames";
22
import type { SMXPanelTestData } from "../../sdk";
3+
import { useCallback } from "react";
4+
import { useAtomValue } from "jotai";
5+
import { selectedPanelIdx$, selectedStageSerial$ } from "../state";
36

47
interface EnabledProps {
58
testData: SMXPanelTestData | undefined;
69
active: boolean | undefined;
710
disabled: boolean | undefined;
11+
index: number;
12+
stageSerial: string;
13+
onClick(index: number): void;
814
}
915

10-
export function LoadCellPanel({ testData, active, disabled }: EnabledProps) {
16+
export function LoadCellPanel({ testData, active, disabled, onClick, index, stageSerial }: EnabledProps) {
17+
const handleClick = useCallback(() => onClick(index), [index, onClick]);
18+
const selectedPanelIdx = useAtomValue(selectedPanelIdx$);
19+
const selectedStageSerial = useAtomValue(selectedStageSerial$);
1120
if (disabled) {
12-
return <div className={cn("panel", {})} />;
21+
return <div className={cn("panel disabled", {})} />;
1322
}
23+
const selected = stageSerial === selectedStageSerial && selectedPanelIdx === index;
1424
return (
25+
// biome-ignore lint/a11y/useKeyWithClickEvents: not doing a11y right now :/
1526
<div
27+
onClick={handleClick}
1628
className={cn("panel", {
1729
commErr: testData && !testData.have_data_from_panel,
1830
active: active,
31+
selected,
1932
})}
2033
>
2134
{/* TODO: load cells don't have inherent placement, so this UI layout should become more ambiguous soon */}

ui/stage/stage-test.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import cn from "classnames";
2-
import { useAtomValue, type Atom } from "jotai";
2+
import { useAtomValue, type Atom, useSetAtom } from "jotai";
33
import type React from "react";
44
import { FsrPanel } from "./fsr-panel";
55
import type { SMXStage } from "../../sdk/";
66
import { timez } from "./util";
77
import { LoadCellPanel } from "./load-cell-panel";
88
import { useTestData, useInputState, useConfig } from "./hooks";
9+
import { selectedPanelIdx$, selectedStageSerial$ } from "../state";
10+
import { useCallback } from "react";
911

1012
export function StageTest({
1113
stageAtom,
@@ -16,6 +18,15 @@ export function StageTest({
1618
const testData = useTestData(stage);
1719
const inputState = useInputState(stage);
1820
const config = useConfig(stage);
21+
const setSelectedPanelIdx = useSetAtom(selectedPanelIdx$);
22+
const setSelectedStateSerial = useSetAtom(selectedStageSerial$);
23+
const handleSelectPanel = useCallback(
24+
(panelIdx: number) => {
25+
setSelectedPanelIdx(panelIdx);
26+
setSelectedStateSerial(stage?.info?.serial);
27+
},
28+
[setSelectedPanelIdx, setSelectedStateSerial, stage],
29+
);
1930

2031
let panels: React.ReactNode;
2132
if (stage?.config?.flags.PlatformFlags_FSR) {
@@ -24,16 +35,22 @@ export function StageTest({
2435
disabled={config?.enabledSensors[idx].every((enabled) => !enabled)}
2536
active={inputState?.[idx]}
2637
key={idx}
38+
index={idx}
2739
testData={testData?.panels[idx]}
40+
onClick={handleSelectPanel}
41+
stageSerial={stage?.info?.serial || ""}
2842
/>
2943
));
3044
} else {
3145
panels = timez(9, (idx) => (
3246
<LoadCellPanel
33-
disabled={config?.enabledSensors[idx].every((enabled) => !enabled)}
47+
disabled={!config || config?.enabledSensors[idx].every((enabled) => !enabled)}
3448
active={inputState?.[idx]}
3549
key={idx}
50+
index={idx}
3651
testData={testData?.panels[idx]}
52+
onClick={handleSelectPanel}
53+
stageSerial={stage?.info?.serial || ""}
3754
/>
3855
));
3956
}

ui/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export const selectedStage$ = atom<SMXStage | undefined>((get) => {
1818
return stages[serial];
1919
});
2020

21+
export const selectedPanelIdx$ = atom<number | undefined>();
22+
2123
export const displayTestData$ = atom<"" | "raw" | "calibrated" | "noise" | "tare">("");
2224

2325
export const statusText$ = atom(

ui/styles.css

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,7 @@ footer {
292292
bottom: 0em;
293293
padding: 1em 0;
294294
width: 100%;
295-
background-image: linear-gradient(
296-
to bottom,
297-
rgba(255, 255, 255, 0),
298-
#ffffff 2em
299-
);
295+
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0), #ffffff 2em);
300296
}
301297

302298
button,
@@ -374,6 +370,12 @@ button:focus {
374370
outline: 2px solid #ddd;
375371
position: relative;
376372
}
373+
.panel.selected {
374+
outline-color: var(--color-green-900);
375+
}
376+
.panel:not(.disabled) {
377+
cursor: pointer;
378+
}
377379
.panel.active {
378380
background-color: #00ff00;
379381
}

0 commit comments

Comments
 (0)