Skip to content

Commit d025405

Browse files
committed
more tidy up for weather and added animations
1 parent e73c15b commit d025405

File tree

8 files changed

+118
-97
lines changed

8 files changed

+118
-97
lines changed

src/frontend/components/Weather/Weather.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,9 @@ import { WindDirection } from './WindDirection/WindDirection';
88
export const Weather = () => {
99
const [parent] = useAutoAnimate();
1010
const weather = useTrackWeather();
11-
const trackWetnessPct = Math.floor(
12-
(Number(weather.trackMoisture?.value?.[0] ?? 0) / 7) * 100
13-
);
14-
1511
const trackTemp = useTrackTemperature();
16-
const windSpeed = weather.windVelo?.value[0] * (18 / 5);
17-
const windDirectionValue =
18-
weather.windDirection?.value[0] - weather.windYaw?.value[0];
12+
const windSpeed = weather.windVelocity;
13+
const relativeWindDirection = (weather.windDirection ?? 0) - (weather.windYaw ?? 0);
1914

2015
return (
2116
<div
@@ -25,11 +20,8 @@ export const Weather = () => {
2520
<div className="flex flex-col p-2 w-full rounded-sm gap-2">
2621
<WeatherTemp title="Track" value={trackTemp.trackTemp} />
2722
<WeatherTemp title="Air" value={trackTemp.airTemp} />
28-
<WindDirection speed={windSpeed} direction={windDirectionValue} />
29-
<WeatherTrackWetness
30-
trackWetnessPct={trackWetnessPct}
31-
trackState={weather.trackState}
32-
/>
23+
<WindDirection speedMs={windSpeed} direction={relativeWindDirection} />
24+
<WeatherTrackWetness trackMoisture={weather.trackMoisture} />
3325
</div>
3426
</div>
3527
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render, screen } from '@testing-library/react';
3+
import { WeatherTrackWetness } from './WeatherTrackWetness';
4+
5+
describe('WeatherTrackWetness', () => {
6+
it('renders "Unknown" for invalid moisture values', () => {
7+
render(<WeatherTrackWetness trackMoisture={8} />);
8+
expect(screen.getByText('Unknown')).toBeInTheDocument();
9+
});
10+
11+
it('displays correct wetness states for different moisture levels', () => {
12+
const testCases = [
13+
{ moisture: 1, expected: 'Dry' },
14+
{ moisture: 2, expected: 'Mostly Dry' },
15+
{ moisture: 3, expected: 'Very Lightly Wet' },
16+
{ moisture: 4, expected: 'Lightly Wet' },
17+
{ moisture: 5, expected: 'Moderately Wet' },
18+
{ moisture: 6, expected: 'Very Wet' },
19+
{ moisture: 7, expected: 'Extremely Wet' },
20+
];
21+
22+
testCases.forEach(({ moisture, expected }) => {
23+
const { rerender } = render(<WeatherTrackWetness trackMoisture={moisture} />);
24+
expect(screen.getByText(expected)).toBeInTheDocument();
25+
rerender(<></>); // Clean up between tests
26+
});
27+
});
28+
29+
it('calculates correct wetness percentage', () => {
30+
const testCases = [
31+
{ moisture: 1, expected: '0%' }, // Min wetness = 0%
32+
{ moisture: 4, expected: '50%' }, // Middle wetness = 50%
33+
{ moisture: 7, expected: '100%' }, // Max wetness = 100%
34+
];
35+
36+
testCases.forEach(({ moisture, expected }) => {
37+
const { rerender, container } = render(<WeatherTrackWetness trackMoisture={moisture} />);
38+
const progressBar = container.querySelector('[style*="width"]');
39+
expect(progressBar).toHaveStyle(`width: ${expected}`);
40+
rerender(<></>); // Clean up between tests
41+
});
42+
});
43+
44+
it('renders Sun and Drop icons', () => {
45+
const { container } = render(<WeatherTrackWetness />);
46+
// Since Phosphor icons are rendered as SVGs, we can check for their presence
47+
expect(container.querySelectorAll('svg')).toHaveLength(2);
48+
});
49+
});

src/frontend/components/Weather/WeatherTrackWetness/WeatherTrackWetness.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ type Story = StoryObj<typeof WeatherTrackWetness>;
99

1010
export const Primary: Story = {
1111
args: {
12-
trackWetnessPct: 5,
13-
trackState: 'Dry',
12+
trackMoisture: 5,
1413
},
1514
};
Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,54 @@
11
import { Drop, Sun } from '@phosphor-icons/react';
22

3+
// Track wetness constants
4+
const MIN_WETNESS = 1;
5+
const MAX_WETNESS = 7; // Extremely Wet
6+
const DEFAULT_WETNESS = 0;
7+
const WETNESS_LEVELS: Record<number, string> = {
8+
0: '',
9+
1: 'Dry',
10+
2: 'Mostly Dry',
11+
3: 'Very Lightly Wet',
12+
4: 'Lightly Wet',
13+
5: 'Moderately Wet',
14+
6: 'Very Wet',
15+
7: 'Extremely Wet',
16+
};
17+
318
export interface WeatherTrackWetnessProps {
4-
trackWetnessPct: number;
5-
trackState: string;
19+
trackMoisture?: number;
620
}
721

822
export const WeatherTrackWetness = ({
9-
trackWetnessPct,
10-
trackState,
23+
trackMoisture,
1124
}: WeatherTrackWetnessProps) => {
25+
// Calculate wetness percentage (0-100%)
26+
// Input range is 1-7, so we normalize it to 0-100%
27+
const normalizedMoisture = trackMoisture || MIN_WETNESS;
28+
const wetnessScale = MAX_WETNESS - MIN_WETNESS;
29+
const trackWetnessPct = ((normalizedMoisture - MIN_WETNESS) / wetnessScale) * 100;
30+
31+
// Get the descriptive state of track wetness
32+
const safeTrackMoisture = trackMoisture ?? DEFAULT_WETNESS;
33+
const trackState = WETNESS_LEVELS[safeTrackMoisture] ?? 'Unknown';
34+
1235
return (
1336
<div className="bg-slate-800/70 p-2 rounded-sm">
1437
<div className="flex items-center flex-row gap-x-1 mt-1">
1538
<Sun />
1639
<div className="w-full bg-gray-700 rounded-full h-2.5">
1740
<div
41+
role="progressbar"
42+
aria-valuenow={trackWetnessPct}
43+
aria-valuemin={0}
44+
aria-valuemax={100}
1845
style={{ width: `${trackWetnessPct}%` }}
19-
className="bg-blue-600 h-2.5 rounded-full"
46+
className="bg-blue-600 h-2.5 rounded-full transition-all duration-1000 ease-in-out"
2047
></div>
2148
</div>
2249
<Drop />
2350
</div>
24-
<div className="text-center text-sm mt-1">{trackState ?? 'Unknown'}</div>
51+
<div className="text-center text-sm mt-1">{trackState}</div>
2552
</div>
2653
);
2754
};

src/frontend/components/Weather/WindDirection/WindDirection.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default {
1212
step: 0.1,
1313
},
1414
},
15-
speed: {
15+
speedMs: {
1616
control: {
1717
type: 'range',
1818
min: 0,
@@ -28,6 +28,6 @@ type Story = StoryObj<typeof WindDirection>;
2828
export const Primary: Story = {
2929
args: {
3030
direction: 0,
31-
speed: 0,
31+
speedMs: 0,
3232
},
3333
};

src/frontend/components/Weather/WindDirection/WindDirection.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { useAutoAnimate } from '@formkit/auto-animate/react';
22
import { Wind } from '@phosphor-icons/react';
33

44
export interface WindDirectionProps {
5-
speed: number;
6-
direction: number;
5+
speedMs?: number;
6+
direction?: number;
77
}
88

9-
export const WindDirection = ({ speed, direction }: WindDirectionProps) => {
9+
export const WindDirection = ({ speedMs, direction }: WindDirectionProps) => {
10+
const speed = speedMs !== undefined ? speedMs * (18 / 5) : undefined;
1011
const [parent] = useAutoAnimate();
1112
return (
1213
<div className="bg-slate-800/70 p-2 rounded-sm">
@@ -22,15 +23,15 @@ export const WindDirection = ({ speed, direction }: WindDirectionProps) => {
2223
<svg
2324
xmlns="http://www.w3.org/2000/svg"
2425
viewBox="0 0 60 60"
25-
className="absolute stroke-current stroke-[3] w-full h-full box-border fill-none origin-center transform-gpu"
26+
className="absolute stroke-current stroke-[3] w-full h-full box-border fill-none origin-center transform-gpu transition-transform duration-1000 ease-out"
2627
style={{
27-
rotate: `calc(${direction} * 1rad + 0.5turn)`,
28+
rotate: `calc(${direction ?? 0} * 1rad + 0.5turn)`,
2829
}}
2930
>
3031
<path d="M48 8A28 28 90 0158 30c0 15.464-12.536 28-28 28S2 45.464 2 30A28 28 90 0112 8M22 9 30 1l8 8" />
3132
</svg>
3233
<div className="absolute w-full h-full flex justify-center items-center text-[32px]">
33-
{speed.toFixed()}
34+
{speed !== undefined ? speed.toFixed(1) : '-'}
3435
</div>
3536
</div>
3637
</div>
Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { useTelemetry } from '@irdashies/context';
2-
import { TelemetryVar } from '@irdashies/types';
1+
import { useTelemetry, useTelemetryValue } from '@irdashies/context';
32
import { renderHook } from '@testing-library/react';
43
import { describe, beforeEach, vi, it, expect } from 'vitest';
54
import { useTrackWeather } from './useTrackWeather';
@@ -11,35 +10,24 @@ describe('useTrackWeather', () => {
1110
vi.resetAllMocks();
1211
});
1312

14-
const mockTelemetryVar = (value: number[]): TelemetryVar<number[]> => ({
15-
value,
16-
name: 'Mock',
17-
description: 'Mock telemetry variable',
18-
unit: '',
19-
countAsTime: false,
20-
length: value.length,
21-
varType: 0,
22-
});
23-
2413
it('should return track weather data from telemetry values', () => {
25-
vi.mocked(useTelemetry).mockImplementation((key) => {
14+
vi.mocked(useTelemetryValue).mockImplementation((key) => {
2615
const mockValues = {
27-
TrackWetness: mockTelemetryVar([2]),
28-
YawNorth: mockTelemetryVar([45]),
29-
WindDir: mockTelemetryVar([270]),
30-
WindVel: mockTelemetryVar([8.3]),
16+
TrackWetness: 2,
17+
YawNorth: 45,
18+
WindDir: 270,
19+
WindVel: 8.3,
3120
};
3221
return mockValues[key as keyof typeof mockValues];
3322
});
3423

3524
const { result } = renderHook(() => useTrackWeather());
3625

3726
expect(result.current).toEqual({
38-
trackState: 'Mostly Dry',
39-
trackMoisture: mockTelemetryVar([2]),
40-
windDirection: mockTelemetryVar([270]),
41-
windVelo: mockTelemetryVar([8.3]),
42-
windYaw: mockTelemetryVar([45]),
27+
trackMoisture: 2,
28+
windDirection: 270,
29+
windVelocity: 8.3,
30+
windYaw: 45,
4331
});
4432
});
4533

@@ -51,34 +39,10 @@ describe('useTrackWeather', () => {
5139
const { result } = renderHook(() => useTrackWeather());
5240

5341
expect(result.current).toEqual({
54-
trackState: '',
5542
trackMoisture: undefined,
5643
windDirection: undefined,
57-
windVelo: undefined,
44+
windVelocity: undefined,
5845
windYaw: undefined,
5946
});
6047
});
61-
62-
it('should return correct track state for different wetness levels', () => {
63-
const testCases = [
64-
{ wetness: 0, expected: '' },
65-
{ wetness: 1, expected: 'Dry' },
66-
{ wetness: 2, expected: 'Mostly Dry' },
67-
{ wetness: 3, expected: 'Very Lightly Wet' },
68-
{ wetness: 4, expected: 'Lightly Wet' },
69-
{ wetness: 5, expected: 'Moderately Wet' },
70-
{ wetness: 6, expected: 'Very Wet' },
71-
{ wetness: 7, expected: 'Extremely Wet' },
72-
{ wetness: 8, expected: '' }, // Invalid wetness level
73-
];
74-
75-
testCases.forEach(({ wetness, expected }) => {
76-
vi.mocked(useTelemetry).mockImplementation((key) => {
77-
return key === 'TrackWetness' ? mockTelemetryVar([wetness]) : mockTelemetryVar([0]);
78-
});
79-
80-
const { result } = renderHook(() => useTrackWeather());
81-
expect(result.current.trackState).toBe(expected);
82-
});
83-
});
8448
});
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
1-
import { useTelemetry } from '@irdashies/context';
2-
import { useMemo } from 'react';
3-
4-
const wetnessLevels: Record<number, string> = {
5-
0: '',
6-
1: 'Dry',
7-
2: 'Mostly Dry',
8-
3: 'Very Lightly Wet',
9-
4: 'Lightly Wet',
10-
5: 'Moderately Wet',
11-
6: 'Very Wet',
12-
7: 'Extremely Wet',
13-
};
1+
import { useTelemetryValue } from '@irdashies/context';
142

153
export const useTrackWeather = () => {
16-
const trackMoisture = useTelemetry('TrackWetness');
17-
const windYaw = useTelemetry('YawNorth');
18-
const windDirection = useTelemetry('WindDir');
19-
const windVelo = useTelemetry('WindVel');
20-
const trackState = useMemo(() => {
21-
const wetnessLevel = trackMoisture?.value[0] ?? '';
22-
return wetnessLevels[wetnessLevel] ?? '';
23-
}, [trackMoisture?.value]);
4+
const trackMoisture = useTelemetryValue('TrackWetness');
5+
const windYaw = useTelemetryValue('YawNorth');
6+
const windDirection = useTelemetryValue('WindDir');
7+
const windVelocity = useTelemetryValue('WindVel');
248

25-
return { trackState, trackMoisture, windDirection, windVelo, windYaw };
9+
return {
10+
trackMoisture,
11+
windDirection,
12+
windVelocity,
13+
windYaw,
14+
};
2615
};

0 commit comments

Comments
 (0)