Skip to content

Commit 97b4276

Browse files
committed
feat: Add gray overlay (sunglasses) support to car config UI and home page (#106)
* Working mode * Adjust warnings * refactor: move car config state into shared useCarConfig context - Extract CarConfig, Capabilities, CarConfigResponse interfaces into use-car-config.ts so they are shared across the app - useCarConfigProvider now stores full config + capabilities and exposes a refresh() callback (increments a tick, re-triggers the fetch effect) - car-config-container no longer calls ApiHelper.get directly; it reads config/capabilities from the context via useCarConfig() - After Save, component calls refresh() to sync the shared context so home.tsx isGrayOverlayEnabled stays up to date without a page reload - Refresh button calls handleRefresh() which discards local draft changes immediately and triggers a context re-fetch - Remove isGrayOverlaySupported / isGrayOverlayEnabled from useSupportedApis; they belong in useCarConfig - All 474 tests pass * fix: clear local draft state on null context; fix provider name in error message - car-config-container: clear draft and savedConfig when contextConfig becomes null (logout / API unsupported) so the form doesn't show stale settings with Save still enabled - use-car-config: correct error message to name CarConfigInnerProvider (the actual provider used in context-provider.tsx) Eslint fix
1 parent 2efb6cb commit 97b4276

9 files changed

Lines changed: 359 additions & 326 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { createContext, useCallback, useContext, useEffect, useState } from "react";
2+
import { ApiHelper } from "../helpers/api-helper";
3+
import { useAuth } from "./use-authentication";
4+
import { useSupportedApis } from "./use-supported-apis";
5+
6+
// ── Shared types ─────────────────────────────────────────────────────────────
7+
8+
export interface CarConfig {
9+
logging: {
10+
mode: string;
11+
provider: string;
12+
};
13+
camera: {
14+
mode: string;
15+
enable_gray_overlay?: boolean;
16+
};
17+
inference: {
18+
engine: string;
19+
device: string;
20+
};
21+
steering: {
22+
mode: string;
23+
};
24+
}
25+
26+
export interface Capabilities {
27+
camera_modes: string[];
28+
logging_modes: string[];
29+
logging_providers: string[];
30+
inference_engines: string[];
31+
inference_devices: Record<string, string[]>;
32+
steering_modes: string[];
33+
gray_overlay?: boolean;
34+
}
35+
36+
export interface CarConfigResponse {
37+
success: boolean;
38+
config: CarConfig;
39+
capabilities?: Capabilities;
40+
reason?: string;
41+
}
42+
43+
// ── Context ──────────────────────────────────────────────────────────────────
44+
45+
interface CarConfigState {
46+
config: CarConfig | null;
47+
capabilities: Capabilities | null;
48+
isGrayOverlaySupported: boolean;
49+
isGrayOverlayEnabled: boolean;
50+
isLoading: boolean;
51+
refresh: () => void;
52+
}
53+
54+
export const CarConfigContext = createContext<CarConfigState | null>(null);
55+
56+
export const useCarConfig = () => {
57+
const context = useContext(CarConfigContext);
58+
if (!context) {
59+
throw new Error("useCarConfig must be used within CarConfigContext.Provider");
60+
}
61+
return context;
62+
};
63+
64+
export const useCarConfigProvider = () => {
65+
const [config, setConfig] = useState<CarConfig | null>(null);
66+
const [capabilities, setCapabilities] = useState<Capabilities | null>(null);
67+
const [isLoading, setIsLoading] = useState<boolean>(true);
68+
const [refreshTick, setRefreshTick] = useState(0);
69+
70+
const { isAuthenticated } = useAuth();
71+
const { isCarConfigSupported } = useSupportedApis();
72+
73+
const refresh = useCallback(() => setRefreshTick((n) => n + 1), []);
74+
75+
useEffect(() => {
76+
if (!isAuthenticated || !isCarConfigSupported) {
77+
setConfig(null);
78+
setCapabilities(null);
79+
setIsLoading(false);
80+
return;
81+
}
82+
83+
let isSubscribed = true;
84+
85+
const fetchCarConfig = async () => {
86+
setIsLoading(true);
87+
try {
88+
const data = await ApiHelper.get<CarConfigResponse>("car_config");
89+
if (!isSubscribed) return;
90+
if (data?.success) {
91+
setConfig(data.config);
92+
setCapabilities(data.capabilities ?? null);
93+
} else {
94+
setConfig(null);
95+
setCapabilities(null);
96+
}
97+
} catch {
98+
if (isSubscribed) {
99+
setConfig(null);
100+
setCapabilities(null);
101+
}
102+
} finally {
103+
if (isSubscribed) setIsLoading(false);
104+
}
105+
};
106+
107+
fetchCarConfig();
108+
109+
return () => {
110+
isSubscribed = false;
111+
};
112+
}, [isAuthenticated, isCarConfigSupported, refreshTick]);
113+
114+
const isGrayOverlaySupported = capabilities?.gray_overlay === true;
115+
const isGrayOverlayEnabled = config?.camera?.enable_gray_overlay === true;
116+
117+
return { config, capabilities, isGrayOverlaySupported, isGrayOverlayEnabled, isLoading, refresh };
118+
};

website/src/common/hooks/use-supported-apis.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export const useSupportedApisProvider = () => {
5959
setIsEmergencyStopSupported(response.apis_supported.includes("/api/emergency_stop"));
6060
setIsDeviceStatusSupported(response.apis_supported.includes("/api/get_device_status"));
6161
setIsTimeApiSupported(response.apis_supported.includes("/api/get_time"));
62-
setIsCarConfigSupported(response.apis_supported.includes("/api/car_config"));
62+
const carConfigSupported = response.apis_supported.includes("/api/car_config");
63+
setIsCarConfigSupported(carConfigSupported);
6364
setHasError(false);
6465
} else if (isSubscribed) {
6566
setSupportedApis([]);

website/src/components/context-provider.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { BatteryContext, useBatteryProvider } from "../common/hooks/use-battery";
33
import { NetworkContext, useNetworkProvider } from "../common/hooks/use-network";
44
import { SupportedApisContext, useSupportedApisProvider } from "../common/hooks/use-supported-apis";
5+
import { CarConfigContext, useCarConfigProvider } from "../common/hooks/use-car-config";
56
import { ModelsContext, useModelsProvider } from "../common/hooks/use-models";
67
import { AuthContext, useAuthProvider } from "../common/hooks/use-authentication";
78
import { PreferencesContext, usePreferencesProvider } from "../common/hooks/use-preferences";
@@ -19,17 +20,27 @@ export const ContextProvider: React.FC<{
1920

2021
return (
2122
<SupportedApisContext.Provider value={supportedApisContextValue}>
22-
<PreferencesContext.Provider value={preferencesContextValue}>
23-
<BatteryContext.Provider value={batteryContextValue}>
24-
<NetworkContext.Provider value={networkContextValue}>
25-
<ModelsContext.Provider value={modelsContextValue}>{children}</ModelsContext.Provider>
26-
</NetworkContext.Provider>
27-
</BatteryContext.Provider>
28-
</PreferencesContext.Provider>
23+
<CarConfigInnerProvider>
24+
<PreferencesContext.Provider value={preferencesContextValue}>
25+
<BatteryContext.Provider value={batteryContextValue}>
26+
<NetworkContext.Provider value={networkContextValue}>
27+
<ModelsContext.Provider value={modelsContextValue}>{children}</ModelsContext.Provider>
28+
</NetworkContext.Provider>
29+
</BatteryContext.Provider>
30+
</PreferencesContext.Provider>
31+
</CarConfigInnerProvider>
2932
</SupportedApisContext.Provider>
3033
);
3134
};
3235

36+
// Inner provider that can consume SupportedApisContext
37+
const CarConfigInnerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
38+
const carConfigContextValue = useCarConfigProvider();
39+
return (
40+
<CarConfigContext.Provider value={carConfigContextValue}>{children}</CarConfigContext.Provider>
41+
);
42+
};
43+
3344
export const ApiProvider: React.FC<{
3445
children: React.ReactNode;
3546
}> = ({ children }) => {

website/src/components/device-status-panel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ const DeviceStatusPanel = ({ isInferenceRunning, setNotifications }: DeviceStatu
6868
cpu: {
6969
usage: { warning: 90, error: 99, compare: "gt" as ComparisonOperator },
7070
temperature: { warning: 75, error: 90, compare: "gt" as ComparisonOperator },
71-
frequency: { warning: 85, error: 75, compare: "lt" as ComparisonOperator }, // Note: For CPU frequency, higher is better
71+
frequency: { warning: 80, error: 65, compare: "lt" as ComparisonOperator }, // Note: For CPU frequency, higher is better
7272
},
7373
memory: { warning: 85, error: 90, compare: "gt" as ComparisonOperator },
7474
disk: { warning: 90, error: 95, compare: "gt" as ComparisonOperator },
7575
performance: {
7676
latency_mean: { warning: 20.0, error: 30.0, compare: "gt" as ComparisonOperator },
77-
latency_p95: { warning: 1.35, error: 1.75, compare: "gt" as ComparisonOperator },
77+
latency_p95: { warning: 1.5, error: 2.0, compare: "gt" as ComparisonOperator },
7878
fps_mean: { warning: 1.05, error: 1.1, compare: "gt" as ComparisonOperator },
7979
},
8080
}),

0 commit comments

Comments
 (0)