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,