Skip to content

Commit c4ff350

Browse files
authored
973 feature display bitmask options on fla params window (#1136)
* Display bitmask bit values in FLA params window * Refactor param def helpers * Address copilot review comments
1 parent 0f2cd0e commit c4ff350

4 files changed

Lines changed: 166 additions & 26 deletions

File tree

gcs/electron/modules/flaParamsWindow.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ let flaParamsWin: BrowserWindow | null = null
1010
export function openFlaParamsWindow(
1111
paramsData: ParamObject[] | null,
1212
fileName: string | null,
13+
aircraftType: string | null,
14+
firmwareVersion: string | null,
1315
parentWindow?: BrowserWindow,
1416
) {
1517
if (flaParamsWin !== null) {
@@ -61,6 +63,8 @@ export function openFlaParamsWindow(
6163
flaParamsWin?.webContents.send("app:send-fla-params", {
6264
params: paramsData,
6365
fileName: fileName,
66+
aircraftType,
67+
firmwareVersion,
6468
})
6569
})
6670
}
@@ -81,7 +85,13 @@ export default function registerFlaParamsIPC() {
8185

8286
ipcMain.handle("app:open-fla-params-window", (event, data) => {
8387
const parentWindow = BrowserWindow.fromWebContents(event.sender)
84-
openFlaParamsWindow(data.params, data.fileName, parentWindow || undefined)
88+
openFlaParamsWindow(
89+
data.params,
90+
data.fileName,
91+
data.aircraftType ?? null,
92+
data.firmwareVersion ?? null,
93+
parentWindow || undefined,
94+
)
8595
})
8696
ipcMain.handle("app:close-fla-params-window", () => closeFlaParamsWindow())
8797
}

gcs/src/components/fla/flaParamsWindow.jsx

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,111 @@
22

33
import { useEffect, useState } from "react"
44

5-
import { Button, Table, TextInput } from "@mantine/core"
5+
import { Button, Table, TextInput, Tooltip } from "@mantine/core"
66
import { useElementSize, useViewportSize } from "@mantine/hooks"
7+
import { IconInfoCircle } from "@tabler/icons-react"
78
import {
89
showErrorNotification,
910
showSuccessNotification,
1011
} from "../../helpers/notification"
12+
import { loadParamDefinitionsForVersion } from "../../helpers/paramDefinitions"
13+
14+
function getBitmaskInfo(rawValue, paramDef) {
15+
const bitmaskDef = paramDef?.Bitmask
16+
if (!bitmaskDef || typeof bitmaskDef !== "object") {
17+
return {
18+
isBitmask: false,
19+
displayValue: String(rawValue),
20+
selected: [],
21+
}
22+
}
23+
24+
const numericValue = Number(rawValue)
25+
if (!Number.isFinite(numericValue)) {
26+
return {
27+
isBitmask: false,
28+
displayValue: String(rawValue),
29+
selected: [],
30+
}
31+
}
32+
33+
const bitmaskValue = Math.trunc(numericValue) >>> 0
34+
const selected = Object.entries(bitmaskDef)
35+
.map(([bitKey, label]) => ({
36+
bit: Number.parseInt(bitKey, 10),
37+
label,
38+
}))
39+
.filter((item) => Number.isInteger(item.bit) && item.bit >= 0)
40+
.sort((a, b) => a.bit - b.bit)
41+
.filter((item) => item.bit < 32)
42+
.filter((item) => (bitmaskValue & (1 << item.bit)) !== 0)
43+
44+
return {
45+
isBitmask: true,
46+
displayValue: String(bitmaskValue),
47+
selected,
48+
}
49+
}
50+
51+
function renderParamValue(value, paramDef) {
52+
const bitmaskInfo = getBitmaskInfo(value, paramDef)
53+
54+
if (!bitmaskInfo.isBitmask) {
55+
return bitmaskInfo.displayValue
56+
}
57+
58+
return (
59+
<div className="flex items-center gap-1.5">
60+
<span>{bitmaskInfo.displayValue}</span>
61+
{bitmaskInfo.displayValue !== "0" && bitmaskInfo.selected.length > 0 && (
62+
<Tooltip
63+
multiline
64+
w={300}
65+
label={
66+
bitmaskInfo.selected.length > 0 && (
67+
<div className="text-sm">
68+
{bitmaskInfo.selected.map((item) => (
69+
<div key={item.bit}>{`${item.bit}: ${item.label}`}</div>
70+
))}
71+
</div>
72+
)
73+
}
74+
>
75+
<span className="inline-flex">
76+
<IconInfoCircle size={16} color="white" />
77+
</span>
78+
</Tooltip>
79+
)}
80+
</div>
81+
)
82+
}
1183

1284
export default function FlaParamsWindow() {
1385
const [params, setParams] = useState(null)
1486
const [fileName, setFileName] = useState("")
87+
const [aircraftKey, setAircraftKey] = useState(null)
88+
const [firmwareVersion, setFirmwareVersion] = useState(null)
89+
const [paramDefs, setParamDefs] = useState({})
1590
const [paramSearchValue, setParamSearchValue] = useState("")
1691
const { height } = useViewportSize()
1792
const { ref, height: searchBarHeight } = useElementSize()
1893

94+
useEffect(() => {
95+
let cancelled = false
96+
97+
loadParamDefinitionsForVersion(aircraftKey, firmwareVersion).then(
98+
(result) => {
99+
if (!cancelled) {
100+
setParamDefs(result.paramDefs)
101+
}
102+
},
103+
)
104+
105+
return () => {
106+
cancelled = true
107+
}
108+
}, [aircraftKey, firmwareVersion])
109+
19110
async function saveParamsToFile() {
20111
try {
21112
const options = {
@@ -56,6 +147,12 @@ export default function FlaParamsWindow() {
56147
const handler = (_event, data) => {
57148
setParams(data.params)
58149
setFileName(data.fileName)
150+
setAircraftKey(
151+
data.aircraftType === "plane" || data.aircraftType === "copter"
152+
? data.aircraftType
153+
: null,
154+
)
155+
setFirmwareVersion(data.firmwareVersion ?? null)
59156
}
60157
window.ipcRenderer.on("app:send-fla-params", handler)
61158

@@ -105,7 +202,9 @@ export default function FlaParamsWindow() {
105202
.map((param) => (
106203
<Table.Tr key={param.name}>
107204
<Table.Td>{param.name}</Table.Td>
108-
<Table.Td>{param.value}</Table.Td>
205+
<Table.Td>
206+
{renderParamValue(param.value, paramDefs[param.name])}
207+
</Table.Td>
109208
</Table.Tr>
110209
))}
111210
</Table.Tbody>

gcs/src/components/fla/graph.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
selectCanSavePreset,
4444
selectCanShowMapPositionData,
4545
selectFile,
46+
selectFirmwareVersion,
4647
selectFlightModeMessages,
4748
selectLogEvents,
4849
selectMapPositionData,
@@ -113,6 +114,7 @@ export default function Graph({ data, customColors, openPresetModal }) {
113114
const flightModes = useSelector(selectFlightModeMessages)
114115
const canSavePreset = useSelector(selectCanSavePreset)
115116
const aircraftType = useSelector(selectAircraftType)
117+
const firmwareVersion = useSelector(selectFirmwareVersion)
116118
const params = useSelector(selectParams)
117119
const file = useSelector(selectFile)
118120
const fileName = file?.name
@@ -550,6 +552,8 @@ export default function Graph({ data, customColors, openPresetModal }) {
550552
window.ipcRenderer.invoke("app:open-fla-params-window", {
551553
params,
552554
fileName,
555+
aircraftType,
556+
firmwareVersion,
553557
})
554558
} catch (error) {
555559
showErrorNotification(error)

gcs/src/helpers/paramDefinitions.js

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ function getAircraftKey(aircraftType) {
2929
return null
3030
}
3131

32-
function parseMajorMinor(flightSwVersion) {
33-
const match = String(flightSwVersion || "").match(/(\d+)\.(\d+)/)
32+
export function parseMajorMinorVersion(versionString) {
33+
const match = String(versionString || "").match(/(\d+)\.(\d+)/)
3434
if (!match) {
3535
return null
3636
}
@@ -85,6 +85,43 @@ function findModulePath(aircraftKey, version) {
8585
return modulePathByFileName[manifestFileName] || null
8686
}
8787

88+
export function resolveParamDefinitionVersion(aircraftKey, versionString) {
89+
const requestedVersion = parseMajorMinorVersion(versionString)
90+
const resolvedVersion = getResolvedVersion(aircraftKey, requestedVersion)
91+
92+
return {
93+
requestedVersion,
94+
resolvedVersion,
95+
}
96+
}
97+
98+
export async function loadParamDefinitionsForVersion(
99+
aircraftKey,
100+
versionString,
101+
) {
102+
const { requestedVersion, resolvedVersion } = resolveParamDefinitionVersion(
103+
aircraftKey,
104+
versionString,
105+
)
106+
107+
const modulePath = findModulePath(aircraftKey, resolvedVersion)
108+
if (!modulePath) {
109+
return {
110+
paramDefs: {},
111+
requestedVersion,
112+
resolvedVersion,
113+
}
114+
}
115+
116+
const loadedModule = await paramDefinitionModules[modulePath]()
117+
118+
return {
119+
paramDefs: loadedModule.default || {},
120+
requestedVersion,
121+
resolvedVersion,
122+
}
123+
}
124+
88125
export function useParamDefinitions() {
89126
const aircraftType = useSelector(selectAircraftType)
90127
const flightSwVersion = useSelector(selectFlightSwVersion)
@@ -95,36 +132,26 @@ export function useParamDefinitions() {
95132
[aircraftType],
96133
)
97134

98-
const requestedVersion = useMemo(
99-
() => parseMajorMinor(flightSwVersion),
100-
[flightSwVersion],
101-
)
102-
103-
const resolvedVersion = useMemo(
104-
() => getResolvedVersion(aircraftKey, requestedVersion),
105-
[aircraftKey, requestedVersion],
135+
const { requestedVersion, resolvedVersion } = useMemo(
136+
() => resolveParamDefinitionVersion(aircraftKey, flightSwVersion),
137+
[aircraftKey, flightSwVersion],
106138
)
107139

108140
useEffect(() => {
109-
const modulePath = findModulePath(aircraftKey, resolvedVersion)
110-
111-
if (!modulePath) {
112-
setParamDefs({})
113-
return
114-
}
115-
116141
let cancelled = false
117142

118-
paramDefinitionModules[modulePath]().then((loadedModule) => {
119-
if (!cancelled) {
120-
setParamDefs(loadedModule.default || {})
121-
}
122-
})
143+
loadParamDefinitionsForVersion(aircraftKey, flightSwVersion).then(
144+
(result) => {
145+
if (!cancelled) {
146+
setParamDefs(result.paramDefs)
147+
}
148+
},
149+
)
123150

124151
return () => {
125152
cancelled = true
126153
}
127-
}, [aircraftKey, resolvedVersion])
154+
}, [aircraftKey, flightSwVersion])
128155

129156
return {
130157
paramDefs,

0 commit comments

Comments
 (0)