diff --git a/gcs/src/components/mapComponents/contextMenuSpecificCommandItems.jsx b/gcs/src/components/mapComponents/contextMenuSpecificCommandItems.jsx
new file mode 100644
index 000000000..36844c71e
--- /dev/null
+++ b/gcs/src/components/mapComponents/contextMenuSpecificCommandItems.jsx
@@ -0,0 +1,91 @@
+import { useDispatch, useSelector } from "react-redux"
+import { coordToInt } from "../../helpers/dataFormatters"
+import { COPTER_MISSION_ITEM_COMMANDS_LIST } from "../../helpers/mavlinkConstants"
+import {
+ createNewSpecificMissionItem,
+ selectActiveTab,
+ selectContextMenu,
+} from "../../redux/slices/missionSlice"
+import ContextMenuItem from "./contextMenuItem"
+
+function getMissionCommandIdByName(name) {
+ const key = Object.keys(COPTER_MISSION_ITEM_COMMANDS_LIST).find(
+ (k) => COPTER_MISSION_ITEM_COMMANDS_LIST[k] === name,
+ )
+ if (key === undefined) {
+ // Command name not found; return null to indicate error
+ return null
+ }
+ return parseInt(key)
+}
+
+const defaultCommandAltitude = 30 // TODO: make this user configurable
+
+export default function ContextMenuSpecificCommandItems() {
+ const dispatch = useDispatch()
+ const activeTab = useSelector(selectActiveTab)
+ const contextMenuState = useSelector(selectContextMenu)
+
+ function addSpecificMissionItem(commandData) {
+ dispatch(createNewSpecificMissionItem(commandData))
+ }
+
+ // TODO: Add support for loiter commands in sub-menu, as well as modal input for parameters e.g. takeoff altitude
+
+ if (activeTab === "mission") {
+ return (
+ <>
+
+ addSpecificMissionItem({
+ command: getMissionCommandIdByName("MAV_CMD_NAV_WAYPOINT"),
+ x: coordToInt(contextMenuState.gpsCoords.lat),
+ y: coordToInt(contextMenuState.gpsCoords.lng),
+ z: defaultCommandAltitude,
+ })
+ }
+ >
+ Add waypoint
+
+
+ addSpecificMissionItem({
+ command: getMissionCommandIdByName("MAV_CMD_NAV_TAKEOFF"),
+ x: 0,
+ y: 0,
+ z: defaultCommandAltitude,
+ })
+ }
+ >
+ Add takeoff
+
+
+ addSpecificMissionItem({
+ command: getMissionCommandIdByName("MAV_CMD_NAV_LAND"),
+ x: coordToInt(contextMenuState.gpsCoords.lat),
+ y: coordToInt(contextMenuState.gpsCoords.lng),
+ z: 1,
+ })
+ }
+ >
+ Add land
+
+
+ addSpecificMissionItem({
+ command: getMissionCommandIdByName(
+ "MAV_CMD_NAV_RETURN_TO_LAUNCH",
+ ),
+ x: 0,
+ y: 0,
+ z: 0,
+ })
+ }
+ >
+ Add RTL
+
+ >
+ )
+ }
+}
diff --git a/gcs/src/components/missions/missionsMap.jsx b/gcs/src/components/missions/missionsMap.jsx
index 7aa661322..c4c13edd5 100644
--- a/gcs/src/components/missions/missionsMap.jsx
+++ b/gcs/src/components/missions/missionsMap.jsx
@@ -51,7 +51,7 @@ import {
import {
clearDrawingItems,
createFencePolygon,
- createNewDrawingItem,
+ createNewDefaultDrawingItem,
getFrameKey,
removeDrawingItem,
selectActiveTab,
@@ -61,6 +61,7 @@ import {
setPlannedHomePositionToDronesHomePositionThunk,
updateContextMenuState,
} from "../../redux/slices/missionSlice"
+import ContextMenuSpecificCommandItems from "../mapComponents/contextMenuSpecificCommandItems"
const tailwindColors = resolveConfig(tailwindConfig).theme.colors
@@ -321,7 +322,7 @@ function MapSectionNonMemo({
addNewPolygonVertex(lat, lon)
} else {
dispatch(
- createNewDrawingItem({ x: coordToInt(lat), y: coordToInt(lon) }),
+ createNewDefaultDrawingItem({ x: coordToInt(lat), y: coordToInt(lon) }),
)
}
}}
@@ -446,6 +447,7 @@ function MapSectionNonMemo({
>
)}
+
Zoom to drone
diff --git a/gcs/src/redux/slices/missionSlice.js b/gcs/src/redux/slices/missionSlice.js
index 7e060555b..1973582fe 100644
--- a/gcs/src/redux/slices/missionSlice.js
+++ b/gcs/src/redux/slices/missionSlice.js
@@ -213,7 +213,7 @@ const missionInfoSlice = createSlice({
[state.activeTab]: true,
}
},
- createNewDrawingItem: (state, action) => {
+ createNewDefaultDrawingItem: (state, action) => {
const { x, y } = action.payload
const drawingItem = newMissionItem(x, y, state.targetInfo)
@@ -238,6 +238,38 @@ const missionInfoSlice = createSlice({
[state.activeTab]: true,
}
},
+ createNewSpecificMissionItem: (state, action) => {
+ const { x, y, z, command } = action.payload
+
+ if (!command) {
+ console.error("Invalid command for new mission item:", action.payload)
+ return
+ }
+
+ const drawingItem = newMissionItem(x, y, state.targetInfo)
+ const _type = `${state.activeTab}Items`
+
+ drawingItem.seq = state.drawingItems[_type].length
+ drawingItem.z = z
+ drawingItem.command = command
+ drawingItem.mission_type = { mission: 0, fence: 1, rally: 2 }[
+ state.activeTab
+ ]
+ if (action.payload.frame) {
+ drawingItem.frame = getFrameKey(action.payload.frame)
+ }
+ if (action.payload.params) {
+ Object.keys(action.payload.params).forEach((key) => {
+ drawingItem[key] = action.payload.params[key]
+ })
+ }
+
+ state.drawingItems[_type].push(drawingItem)
+ state.unwrittenChanges = {
+ ...state.unwrittenChanges,
+ [state.activeTab]: true,
+ }
+ },
clearDrawingItems: (state) => {
const _type = `${state.activeTab}Items`
@@ -514,7 +546,8 @@ export const {
updateDrawingItem,
removeDrawingItem,
reorderDrawingItem,
- createNewDrawingItem,
+ createNewDefaultDrawingItem,
+ createNewSpecificMissionItem,
clearDrawingItems,
createFencePolygon,
setDrawingMissionItems,