Skip to content

Commit ef20377

Browse files
bensgilbertNexInfinite1BlademasterKwash67Jopat2409
committed
Alpha 0.1.9/518 add altitude warnings (#585)
* Added kush's stuff * Fixing kush's branch (#539) * fixes * Fix map.jsx --------- Co-authored-by: Kush Makkapati <kush.makkapati@icloud.com> * Alpha 0.1.9/523 new missions page (#545) * added map, resizable boxes, and added hotkeys to switch to page * display spotlight action for missions * allow user to decide whether maps should be in sync * removing usused vars and formatting * fixing lint errors * format * this better work * Separate missions map component, update general stuff * Fix linting issues --------- Co-authored-by: Kush Makkapati <kush.makkapati@icloud.com> * 542 show error stack on error boundary (#543) * changed release version * Showed stack log * formatted * removed comments * styalised code block * updated bug message * improving readability * Update bug_report.md --------- Co-authored-by: Kwashie A. <104215256+Kwash67@users.noreply.github.com> * Alpha 0.1.9/517 popout camera feed from dashboard (#541) * Moved maincontent to separate component to make fast refresh work * Added webcam picture-in-picture window * Create API for webcam window * Create webcam route * Update camera tab section to allow for camera popout * Fixed bug with multiple resize handlers stacking * Added nullify webcam window on close * Fixed resize issues * Added some coments * Fixed buge where camera breaks when no video stream * Fixed webcam being always on in the background * Explicitly prevent fullscreen * Oops I removed fullscreen on the wrong window * improved styling * formatted * Added errorboundaryfallback from #306382b * Fixed import error --------- Co-authored-by: Julian Jones <julianjones663@gmail.com> * Fix socket connection state not updating * changed tab message (#549) * Alpha 0.1.9/544 Support for displaying multiple batteries (#555) * trivial * multiple batteries * formatting * styling * revert styling * Alpha 0.1.9/551 update mission centre to center on full mission (#552) * fixed issue with Battery preset * missions route was missing for some reason * removed annoying run command message when you switch tabs with spotlight * allow center map on mission * removed unused import * commented out unused function * removed comments --------- Co-authored-by: Julian Jones <julianjones663@gmail.com> Co-authored-by: Julian Jones <37962677+NexInfinite@users.noreply.github.com> * alert system * added descriptions to settings + new extendableNumber setting * altitude alert system * formatting * slight style change for new alert button * update alert settings location * minor improvements --------- Co-authored-by: Julian Jones <julianjones663@gmail.com> Co-authored-by: Julian Jones <37962677+NexInfinite@users.noreply.github.com> Co-authored-by: Kush Makkapati <kush.makkapati@icloud.com> Co-authored-by: Kwashie A. <104215256+Kwash67@users.noreply.github.com> Co-authored-by: Joe <joantpat@gmail.com>
1 parent 2edb5f1 commit ef20377

9 files changed

Lines changed: 266 additions & 21 deletions

File tree

gcs/data/default_settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@
3535
"display": "Sync Dashboard and Missions Map Viewstate"
3636
}
3737
},
38+
"Dashboard": {
39+
"altitudeAlerts": {
40+
"description": "Add as many altitude alerts as you want, they'll show when you fall below the set limit",
41+
"default": [100, 90, 80],
42+
"type": "extendableNumber",
43+
"range": [
44+
0,
45+
100000
46+
],
47+
"display": "Altitude Alerts"
48+
}
49+
},
3850
"Params": {},
3951
"Config": {},
4052
"FGCS": {}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Alert } from "@mantine/core"
2+
import { IconAlertTriangle } from "@tabler/icons-react"
3+
import { useMemo } from "react"
4+
import { useAlerts } from "./alertProvider"
5+
6+
export const AlertCategory = {
7+
Altitude: "Altitude",
8+
Speed: "Speed",
9+
}
10+
11+
export const AlertSeverity = {
12+
Yellow: 1,
13+
Orange: 2,
14+
Red: 3,
15+
}
16+
17+
const SeverityColor = {
18+
[AlertSeverity.Yellow]: "yellow",
19+
[AlertSeverity.Orange]: "orange",
20+
[AlertSeverity.Red]: "red",
21+
}
22+
23+
export default function AlertSection() {
24+
const { alerts, dismissAlert } = useAlerts()
25+
26+
const sortedAlerts = useMemo(() => {
27+
return alerts.toSorted((a1, a2) => a2.severity - a1.severity)
28+
}, [alerts])
29+
30+
return (
31+
<div className="space-y-2 max-w-sm">
32+
{sortedAlerts.map((alert) => (
33+
<div
34+
className="bg-falcongrey-900/90"
35+
key={alert.category}
36+
style={{ borderRadius: "var(--mantine-radius-default)" }}
37+
>
38+
<Alert
39+
variant="outline"
40+
color={SeverityColor[alert.severity]}
41+
withCloseButton
42+
title={alert.category}
43+
icon={<IconAlertTriangle />}
44+
onClose={() => dismissAlert(alert.category, true)}
45+
>
46+
{alert.jsx}
47+
</Alert>
48+
</div>
49+
))}
50+
</div>
51+
)
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* Alert
2+
* {
3+
* category: AlertCategory,
4+
* severity: AlertSeverity,
5+
* jsx: <></>
6+
* }
7+
*/
8+
9+
import { createContext, useContext, useRef, useState } from "react"
10+
11+
const AlertContext = createContext()
12+
13+
export default function AlertProvider({ children }) {
14+
const [alerts, setAlerts] = useState([])
15+
const dismissedAlerts = useRef(new Map())
16+
17+
function dispatchAlert(alert) {
18+
if (dismissedAlerts.current.get(alert.category) >= alert.severity) return
19+
dismissedAlerts.current.delete(alert.category)
20+
21+
const existingAlertIndex = alerts.findIndex(
22+
(existingAlert) => existingAlert.category == alert.category,
23+
)
24+
if (existingAlertIndex >= 0) {
25+
alerts[existingAlertIndex] = alert
26+
setAlerts([...alerts])
27+
} else {
28+
setAlerts([...alerts, alert])
29+
}
30+
}
31+
32+
function dismissAlert(category, manual) {
33+
setAlerts((prevAlerts) => {
34+
const alert = prevAlerts.find((a) => a.category === category)
35+
36+
if (manual) {
37+
dismissedAlerts.current.set(category, alert.severity)
38+
} else {
39+
dismissedAlerts.current.delete(category)
40+
}
41+
42+
return prevAlerts.filter((a) => a.category !== category)
43+
})
44+
}
45+
46+
return (
47+
<AlertContext.Provider value={{ alerts, dispatchAlert, dismissAlert }}>
48+
{children}
49+
</AlertContext.Provider>
50+
)
51+
}
52+
53+
export const useAlerts = () => useContext(AlertContext)

gcs/src/components/dashboard/statusBar.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { IconClock, IconNetwork, IconNetworkOff } from "@tabler/icons-react"
1515
// Helper imports
1616
import { socket } from "../../helpers/socket"
1717
import GetOutsideVisibilityColor from "../../helpers/outsideVisibility"
18+
import AlertSection from "./alert"
1819

1920
export function StatusSection({ icon, value, tooltip }) {
2021
return (
@@ -66,6 +67,9 @@ export default function StatusBar(props) {
6667
<p className="text-sm text-blue-200">Current heading</p>
6768
<p className="text-sm text-red-200">Desired heading</p>
6869
</div>
70+
<div className="m-2">
71+
<AlertSection />
72+
</div>
6973
</div>
7074
)
7175
}

gcs/src/components/dashboard/telemetry.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Telemetry. This file holds all the telemetry indicators and is part of the resizable info box
2+
Telemetry. This file holds all the telemetry indicators and is part of the resizable info box
33
section, found in the top half.
44
*/
55

gcs/src/components/mainContent.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Dashboard from "../dashboard"
2121
import { store } from "../redux/store"
2222
import { Provider } from "react-redux"
2323
import { ErrorBoundary } from "react-error-boundary"
24+
import AlertProvider from "./dashboard/alertProvider"
2425
import ErrorBoundaryFallback from "./error/errorBoundary"
2526

2627
export default function AppContent() {
@@ -34,7 +35,14 @@ export default function AppContent() {
3435
<ErrorBoundary fallbackRender={ErrorBoundaryFallback}>
3536
<SettingsModal />
3637
<Routes>
37-
<Route path="/" element={<Dashboard />} />
38+
<Route
39+
path="/"
40+
element={
41+
<AlertProvider>
42+
<Dashboard />
43+
</AlertProvider>
44+
}
45+
/>
3846
<Route path="/missions" element={<Missions />} />
3947
<Route path="/graphs" element={<Graphs />} />
4048
<Route path="/params" element={<Params />} />
@@ -48,7 +56,6 @@ export default function AppContent() {
4856
</Provider>
4957
}
5058
/>
51-
<Route path="/missions" element={<Missions />} />
5259
</Routes>
5360
{renderUI && <Commands />}
5461
</ErrorBoundary>

gcs/src/components/settingsModal.jsx

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
import { Checkbox, Input, Modal, NativeSelect, Tabs } from "@mantine/core"
1+
import {
2+
Button,
3+
Checkbox,
4+
Input,
5+
Modal,
6+
NativeSelect,
7+
NumberInput,
8+
Tabs,
9+
} from "@mantine/core"
210
import { useSettings } from "../helpers/settings"
311

12+
import { IconTrash } from "@tabler/icons-react"
13+
import { memo, useEffect, useState } from "react"
414
import DefaultSettings from "../../data/default_settings.json"
5-
import { memo } from "react"
15+
16+
const isValidNumber = (num, range) => {
17+
return (
18+
num &&
19+
parseInt(num) &&
20+
(range === null || (range[0] <= num && num <= range[1]))
21+
)
22+
}
623

724
function TextSetting({ settingName, hidden }) {
825
const { getSetting, setSetting } = useSettings()
@@ -38,31 +55,94 @@ function OptionSetting({ settingName, options }) {
3855

3956
function NumberSetting({ settingName, range }) {
4057
const { getSetting, setSetting } = useSettings()
41-
const setIfValid = (num) => {
42-
if (
43-
!num ||
44-
(parseInt(num) &&
45-
(range === null || (range[0] <= num && num <= range[1])))
46-
)
47-
setSetting(settingName, num)
48-
}
4958

5059
return (
5160
<Input
5261
value={getSetting(settingName)}
53-
onChange={(e) => setIfValid(e.currentTarget.value)}
62+
onChange={(e) => {
63+
const num = e.currentTarget.value
64+
if (isValidNumber(num, range)) setSetting(settingName, num)
65+
}}
5466
/>
5567
)
5668
}
5769

70+
const generateId = () => Math.random().toString(36).slice(8)
71+
72+
function ExtendableNumberSetting({ settingName, range }) {
73+
const { getSetting, setSetting } = useSettings()
74+
75+
const [values, setValues] = useState(
76+
getSetting(settingName).map((val) => ({ id: generateId(), value: val })),
77+
)
78+
79+
useEffect(() => {
80+
setSetting(
81+
settingName,
82+
values.map((a) => a.value),
83+
)
84+
}, [values])
85+
86+
const updateValue = (id, value) => {
87+
setValues((prev) => prev.map((a) => (a.id === id ? { ...a, value } : a)))
88+
}
89+
90+
const removeValue = (id) => {
91+
setValues((prev) => prev.filter((a) => a.id !== id))
92+
}
93+
94+
return (
95+
<div className="flex flex-col shrink-0 items-end gap-2">
96+
{values.map(({ id, value }) => (
97+
<div key={id} className="flex gap-2 items-center">
98+
<button
99+
className="text-falconred-600 hover:text-falconred-700 p-1 rounded-full"
100+
onClick={() => removeValue(id)}
101+
>
102+
<IconTrash size={20} />
103+
</button>
104+
<NumberInput
105+
value={value}
106+
onChange={(num) => {
107+
if (!isValidNumber(num, range)) return
108+
updateValue(id, parseInt(num))
109+
}}
110+
suffix="m"
111+
/>
112+
</div>
113+
))}
114+
<div className="w-full pl-9">
115+
<Button
116+
fullWidth
117+
onClick={() =>
118+
setValues([...values, { id: generateId(), value: 0 }])
119+
}
120+
>
121+
Add new Alert
122+
</Button>
123+
</div>
124+
</div>
125+
)
126+
}
127+
58128
function Setting({ settingName, df }) {
59129
return (
60-
<div className="flex flex-row justify-between items-center h-[5vh] px-10 ">
61-
<div>{df.display}:</div>
62-
{df.type == "number" ? (
130+
<div
131+
className={`flex flex-row gap-8 justify-between ${df.type != "extendableNumber" && "items-center"} px-10 `}
132+
>
133+
<div className="space-y-px">
134+
<div>{df.display}:</div>
135+
<p className="text-gray-400 text-sm">{df.description}</p>
136+
</div>
137+
{df.type == "extendableNumber" ? (
138+
<ExtendableNumberSetting
139+
settingName={settingName}
140+
range={df.range || null}
141+
/>
142+
) : df.type == "number" ? (
63143
<NumberSetting settingName={settingName} range={df.range || null} />
64144
) : df.type == "boolean" ? (
65-
<BoolSetting settingName={settingName} />
145+
<BoolSetting settingName={settingName} options={df.options} />
66146
) : df.type == "option" ? (
67147
<OptionSetting settingName={settingName} options={df.options} />
68148
) : (
@@ -111,7 +191,7 @@ function SettingsModal() {
111191
</Tabs.List>
112192
{settingTabs.map((t) => {
113193
return (
114-
<Tabs.Panel value={t} key={t}>
194+
<Tabs.Panel className="space-y-4" value={t} key={t}>
115195
{Object.keys(DefaultSettings[t]).map((s) => {
116196
return (
117197
<Setting

gcs/src/dashboard.jsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ const tailwindColors = resolveConfig(tailwindConfig).theme.colors
6161
// Sounds
6262
import armSound from "./assets/sounds/armed.mp3"
6363
import disarmSound from "./assets/sounds/disarmed.mp3"
64+
import { AlertCategory, AlertSeverity } from "./components/dashboard/alert"
65+
import { useAlerts } from "./components/dashboard/alertProvider"
66+
import { useSettings } from "./helpers/settings"
6467

6568
export default function Dashboard() {
6669
// Local Storage
@@ -148,9 +151,43 @@ export default function Dashboard() {
148151
defaultValue: defaultDataMessages,
149152
})
150153

154+
const { getSetting } = useSettings()
155+
156+
// Alerts
157+
const { dispatchAlert, dismissAlert } = useAlerts()
158+
const highestAltitudeRef = useRef(0)
159+
160+
function updateAltitudeAlert(msg) {
161+
if (msg.alt > highestAltitudeRef.current)
162+
return (highestAltitudeRef.current = msg.alt)
163+
const altitudes = getSetting("Dashboard.altitudeAlerts")
164+
altitudes.sort((a1, a2) => a1 - a2)
165+
166+
for (const [i, altitude] of altitudes.entries()) {
167+
if (highestAltitudeRef.current > altitude && msg.alt < altitude) {
168+
dispatchAlert({
169+
category: AlertCategory.Altitude,
170+
severity:
171+
i == 0
172+
? AlertSeverity.Red
173+
: i == altitudes.length - 1
174+
? AlertSeverity.Yellow
175+
: AlertSeverity.Orange,
176+
jsx: <>Caution! You've fallen below {altitude}m</>,
177+
})
178+
return
179+
}
180+
}
181+
182+
dismissAlert(AlertCategory.Altitude)
183+
}
184+
151185
const incomingMessageHandler = useCallback(
152186
() => ({
153-
VFR_HUD: (msg) => setTelemetryData(msg),
187+
VFR_HUD: (msg) => {
188+
setTelemetryData(msg)
189+
updateAltitudeAlert(msg)
190+
},
154191
BATTERY_STATUS: (msg) => {
155192
const battery = localBatteryData.filter(
156193
(battery) => battery.id == msg.id,

gcs/src/missions.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
The missions screen.
2+
The missions screen.
33
*/
44

55
// Base imports

0 commit comments

Comments
 (0)