diff --git a/gcs/src/components/mapComponents/contextMenuItem.jsx b/gcs/src/components/mapComponents/contextMenuItem.jsx
index 970d8c1b3..03fa547ac 100644
--- a/gcs/src/components/mapComponents/contextMenuItem.jsx
+++ b/gcs/src/components/mapComponents/contextMenuItem.jsx
@@ -4,7 +4,7 @@ export default function ContextMenuItem({ children, onClick }) {
className="hover:bg-falcongrey-800 hover:cursor-pointer py-1 px-4 rounded"
onClick={onClick}
>
- {children}
+
{children}
)
}
diff --git a/gcs/src/components/mapComponents/contextMenuSubMenuItem.jsx b/gcs/src/components/mapComponents/contextMenuSubMenuItem.jsx
new file mode 100644
index 000000000..5f29e2eca
--- /dev/null
+++ b/gcs/src/components/mapComponents/contextMenuSubMenuItem.jsx
@@ -0,0 +1,54 @@
+import { useEffect, useRef, useState } from "react"
+
+export default function ContextMenuSubMenuItem({ children, title }) {
+ const [isHovered, setIsHovered] = useState(false)
+ const [submenuPosition, setSubmenuPosition] = useState({ top: 0, left: 0 })
+ const parentRef = useRef(null)
+ const subMenuRef = useRef(null)
+
+ useEffect(() => {
+ if (isHovered && parentRef.current && subMenuRef.current) {
+ const parentRect = parentRef.current.getBoundingClientRect()
+ const submenuRect = subMenuRef.current.getBoundingClientRect()
+ const viewportWidth = window.innerWidth
+
+ // Calculate whether to position the submenu to the right or left
+ const shouldAppearToLeft =
+ parentRect.right + submenuRect.width > viewportWidth
+
+ setSubmenuPosition({
+ top: parentRect.top,
+ left: shouldAppearToLeft
+ ? parentRect.left - submenuRect.width
+ : parentRect.right,
+ })
+ }
+ }, [isHovered])
+
+ return (
+ setIsHovered(true)}
+ onMouseOut={() => setIsHovered(false)}
+ >
+
+ {isHovered && (
+
+ {children}
+
+ )}
+
+ )
+}
diff --git a/gcs/src/components/mapComponents/drawLineCoordinates.jsx b/gcs/src/components/mapComponents/drawLineCoordinates.jsx
index 28e72176a..1f9f75aa8 100644
--- a/gcs/src/components/mapComponents/drawLineCoordinates.jsx
+++ b/gcs/src/components/mapComponents/drawLineCoordinates.jsx
@@ -9,6 +9,8 @@ export default function DrawLineCoordinates({
colour,
width = 1,
lineProps = {},
+ fillLayer = false,
+ fillOpacity = 0.5,
}) {
return (
+ {fillLayer && (
+
+ )}
)
}
diff --git a/gcs/src/components/mapComponents/fenceItems.jsx b/gcs/src/components/mapComponents/fenceItems.jsx
new file mode 100644
index 000000000..795a35b1e
--- /dev/null
+++ b/gcs/src/components/mapComponents/fenceItems.jsx
@@ -0,0 +1,181 @@
+/*
+ The FenceItems component returns the markers and lines connecting the
+ markers together on the map to display. It also filters out any
+ items which should not be displayed on the map as markers or not have lines
+ connecting them. It properly parses the type of fence marker.
+*/
+
+import { useEffect, useState } from "react"
+
+// Helper imports
+import { intToCoord } from "../../helpers/dataFormatters"
+
+// Styling imports
+import "maplibre-gl/dist/maplibre-gl.css"
+
+// Component imports
+
+// Tailing styling
+import { circle } from "@turf/turf"
+import { Layer, Source } from "react-map-gl"
+import resolveConfig from "tailwindcss/resolveConfig"
+import tailwindConfig from "../../../tailwind.config"
+import { FENCE_ITEM_COMMANDS_LIST } from "../../helpers/mavlinkConstants"
+import DrawLineCoordinates from "./drawLineCoordinates"
+import MarkerPin from "./markerPin"
+const tailwindColors = resolveConfig(tailwindConfig).theme.colors
+
+function getFenceCommandNumber(value) {
+ return parseInt(
+ Object.keys(FENCE_ITEM_COMMANDS_LIST).filter(
+ (key) => FENCE_ITEM_COMMANDS_LIST[key] === value,
+ ),
+ )
+}
+
+const polygonCommands = [
+ getFenceCommandNumber("MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION"),
+ getFenceCommandNumber("MAV_CMD_NAV_FENCE_POLYGON_VERTEX_EXCLUSION"),
+]
+const circleCommands = [
+ getFenceCommandNumber("MAV_CMD_NAV_FENCE_CIRCLE_INCLUSION"),
+ getFenceCommandNumber("MAV_CMD_NAV_FENCE_CIRCLE_EXCLUSION"),
+]
+
+export default function FenceItems({
+ fenceItems,
+ editable = false,
+ dragEndCallback = () => {},
+}) {
+ const [fencePolygonItems, setFencePolygonItems] = useState([])
+ const [fenceCircleItems, setFenceCircleItems] = useState([])
+
+ useEffect(() => {
+ // Filter out fence items based on their type
+ const polygonItems = fenceItems.filter((item) =>
+ polygonCommands.includes(item.command),
+ )
+ const circleItems = fenceItems.filter((item) =>
+ circleCommands.includes(item.command),
+ )
+
+ setFencePolygonItems(polygonItems)
+ setFenceCircleItems(circleItems)
+ }, [fenceItems])
+
+ return (
+ <>
+ {/* Show mission geo-fence MARKERS */}
+ {fencePolygonItems.map((item, index) => {
+ return (
+
+ )
+ })}
+
+ {/* Group fencePolygonItems into separate polygons */}
+ {(() => {
+ const polygons = []
+ let currentPolygon = []
+ let currentPoints = 0
+
+ fencePolygonItems.forEach((item) => {
+ currentPolygon.push(item)
+ currentPoints++
+
+ if (currentPoints === item.param1) {
+ polygons.push(currentPolygon)
+ currentPolygon = []
+ currentPoints = 0
+ }
+ })
+
+ return polygons.map((polygon, index) => {
+ const lastPolygonItem = polygon[polygon.length - 1]
+
+ const color =
+ lastPolygonItem.command === 5002
+ ? tailwindColors.red[500]
+ : tailwindColors.blue[200]
+
+ return (
+ [
+ intToCoord(item.y),
+ intToCoord(item.x),
+ ]),
+ [intToCoord(polygon[0].y), intToCoord(polygon[0].x)],
+ ]}
+ colour={color}
+ lineProps={{ "line-width": 2, "line-dasharray": [4, 6] }}
+ fillLayer={true}
+ fillOpacity={lastPolygonItem.command === 5002 ? 0.2 : 0}
+ />
+ )
+ })
+ })()}
+
+ {fenceCircleItems.map((item, index) => {
+ return (
+
+ )
+ })}
+
+
+ circle([intToCoord(item.y), intToCoord(item.x)], item.param1, {
+ steps: 64, // Number of points to create the circle
+ units: "meters", // Units for the radius
+ properties: {
+ color:
+ item.command === 5004
+ ? tailwindColors.red[500]
+ : tailwindColors.blue[200],
+ fillOpacity: item.command === 5004 ? 0.2 : 0, // No fill if inclusion
+ },
+ }),
+ ),
+ }}
+ >
+
+
+
+ >
+ )
+}
diff --git a/gcs/src/components/mapComponents/polygon.jsx b/gcs/src/components/mapComponents/polygon.jsx
new file mode 100644
index 000000000..d4f87302f
--- /dev/null
+++ b/gcs/src/components/mapComponents/polygon.jsx
@@ -0,0 +1,54 @@
+/*
+ The PolygonItems component returns the markers and lines connecting the
+ polygon together on the map to display.
+*/
+
+// Helper imports
+
+// Styling imports
+import "maplibre-gl/dist/maplibre-gl.css"
+
+// Component imports
+import DrawLineCoordinates from "./drawLineCoordinates"
+import MarkerPin from "./markerPin"
+
+// Tailing styling
+import resolveConfig from "tailwindcss/resolveConfig"
+import tailwindConfig from "../../../tailwind.config"
+const tailwindColors = resolveConfig(tailwindConfig).theme.colors
+
+export default function Polygon({
+ polygonPoints,
+ editable = false,
+ dragEndCallback = () => {},
+}) {
+ return (
+ <>
+ {/* Show polygon MARKERS */}
+ {polygonPoints.map((item, index) => {
+ return (
+
+ )
+ })}
+
+ {/* Show polygon outlines */}
+ {polygonPoints.length > 0 && (
+ [item.lon, item.lat]),
+ [polygonPoints[0].lon, polygonPoints[0].lat],
+ ]}
+ colour={tailwindColors.red[200]}
+ />
+ )}
+ >
+ )
+}
diff --git a/gcs/src/components/missions/fenceItemsTable.jsx b/gcs/src/components/missions/fenceItemsTable.jsx
new file mode 100644
index 000000000..0037890af
--- /dev/null
+++ b/gcs/src/components/missions/fenceItemsTable.jsx
@@ -0,0 +1,55 @@
+/*
+ This table displays all the fence items.
+*/
+
+import { Table } from "@mantine/core"
+import React from "react"
+import FenceItemsTableRow from "./fenceItemsTableRow"
+
+function FenceItemsTableNonMemo({
+ fenceItems,
+ updateMissionItem,
+ deleteMissionItem,
+ updateMissionItemOrder,
+}) {
+ return (
+
+
+
+
+ Command
+ Param 1
+
+
+
+ Lat
+ Lng
+
+ Frame
+
+
+
+
+ {fenceItems.map((fenceItem, idx) => {
+ return (
+
+ )
+ })}
+
+
+ )
+}
+
+function propsAreEqual(prev, next) {
+ return JSON.stringify(prev) === JSON.stringify(next)
+}
+const FenceItemsTable = React.memo(FenceItemsTableNonMemo, propsAreEqual)
+
+export default FenceItemsTable
diff --git a/gcs/src/components/missions/fenceItemsTableRow.jsx b/gcs/src/components/missions/fenceItemsTableRow.jsx
new file mode 100644
index 000000000..5ba378300
--- /dev/null
+++ b/gcs/src/components/missions/fenceItemsTableRow.jsx
@@ -0,0 +1,142 @@
+/*
+ This component displays the row for a fence item in a table.
+*/
+
+import {
+ ActionIcon,
+ NumberInput,
+ Select,
+ TableTd,
+ TableTr,
+} from "@mantine/core"
+import { IconArrowDown, IconArrowUp, IconTrash } from "@tabler/icons-react"
+import { useEffect, useState } from "react"
+import { coordToInt, intToCoord } from "../../helpers/dataFormatters"
+import {
+ FENCE_ITEM_COMMANDS_LIST,
+ MAV_FRAME_LIST,
+} from "../../helpers/mavlinkConstants"
+
+const coordsFractionDigits = 9
+
+function getDisplayCommandName(commandName) {
+ if (commandName.startsWith("MAV_CMD_NAV_")) {
+ commandName = commandName.replace("MAV_CMD_NAV_", "")
+ } else if (commandName.startsWith("MAV_CMD_")) {
+ commandName = commandName.replace("MAV_CMD_", "")
+ }
+
+ return commandName
+}
+
+function getAvailableCommands() {
+ var commandsList = FENCE_ITEM_COMMANDS_LIST
+
+ return Object.entries(commandsList).map(([key, value]) => ({
+ value: key,
+ label: getDisplayCommandName(value),
+ }))
+}
+
+function getFrameName(frameId) {
+ var frameName = MAV_FRAME_LIST[frameId]
+
+ if (frameName.startsWith("MAV_FRAME_")) {
+ frameName = frameName.replace("MAV_FRAME_", "")
+ }
+
+ return frameName || "UNKNOWN"
+}
+
+export default function FenceItemsTableRow({
+ index,
+ fenceItem,
+ updateMissionItem,
+ deleteMissionItem,
+ updateMissionItemOrder,
+}) {
+ const [fenceItemData, setFenceItemData] = useState(fenceItem)
+
+ useEffect(() => {
+ setFenceItemData(fenceItem)
+ }, [fenceItem])
+
+ useEffect(() => {
+ updateMissionItem(fenceItemData)
+ }, [fenceItemData])
+
+ function updateFenceItemData(key, newVal) {
+ setFenceItemData({
+ ...fenceItemData,
+ [key]: newVal,
+ })
+ }
+
+ return (
+
+ {index}
+
+
+
+ updateFenceItemData("param1", val)}
+ hideControls
+ />
+
+
+
+
+
+
+
+
+
+
+
+ updateFenceItemData("x", coordToInt(val))}
+ hideControls
+ />
+
+
+ updateFenceItemData("y", coordToInt(val))}
+ hideControls
+ />
+
+
+ updateFenceItemData("z", val)}
+ hideControls
+ />
+
+ {getFrameName(fenceItemData.frame)}
+
+ updateMissionItemOrder(fenceItemData.id, -1)}
+ >
+
+
+ updateMissionItemOrder(fenceItemData.id, 1)}>
+
+
+ deleteMissionItem(fenceItemData.id)}
+ color="red"
+ >
+
+
+
+
+ )
+}
diff --git a/gcs/src/components/missions/missionsMap.jsx b/gcs/src/components/missions/missionsMap.jsx
index ba5dc84c6..09167b223 100644
--- a/gcs/src/components/missions/missionsMap.jsx
+++ b/gcs/src/components/missions/missionsMap.jsx
@@ -20,19 +20,23 @@ import "maplibre-gl/dist/maplibre-gl.css"
import Map from "react-map-gl/maplibre"
// Helper scripts
-import { intToCoord } from "../../helpers/dataFormatters"
+import { v4 as uuidv4 } from "uuid"
+import { coordToInt, intToCoord } from "../../helpers/dataFormatters"
import { filterMissionItems } from "../../helpers/filterMissions"
import { showNotification } from "../../helpers/notification"
import { useSettings } from "../../helpers/settings"
// Other dashboard imports
import ContextMenuItem from "../mapComponents/contextMenuItem"
-import DrawLineCoordinates from "../mapComponents/drawLineCoordinates"
+import ContextMenuSubMenuItem from "../mapComponents/contextMenuSubMenuItem"
import DroneMarker from "../mapComponents/droneMarker"
+import FenceItems from "../mapComponents/fenceItems"
import HomeMarker from "../mapComponents/homeMarker"
import MarkerPin from "../mapComponents/markerPin"
import MissionItems from "../mapComponents/missionItems"
+import Polygon from "../mapComponents/polygon"
import useContextMenu from "../mapComponents/useContextMenu"
+import Divider from "../toolbar/menus/divider"
// Tailwind styling
import resolveConfig from "tailwindcss/resolveConfig"
@@ -55,6 +59,7 @@ function MapSectionNonMemo({
addNewMissionItem,
updateMissionHomePosition,
clearMissionItems,
+ addFencePolygon,
mapId = "dashboard",
}) {
const [connected] = useSessionStorage({
@@ -99,6 +104,9 @@ function MapSectionNonMemo({
const clipboard = useClipboard({ timeout: 500 })
+ const [polygonDrawMode, setPolygonDrawMode] = useState(false)
+ const [polygonPoints, setPolygonPoints] = useState([])
+
useEffect(() => {
return () => {}
}, [connected])
@@ -171,6 +179,53 @@ function MapSectionNonMemo({
}
}, [homePosition])
+ function addNewPolygonVertex(lat, lon) {
+ if (!polygonDrawMode) return
+
+ // Add new point to polygon points
+ setPolygonPoints((prevPoints) => [
+ ...prevPoints,
+ { id: uuidv4(), lat: lat, lon: lon },
+ ])
+ }
+
+ function updatePolygonVertex(updatedPolygonVertex) {
+ setPolygonPoints((prevPoints) =>
+ prevPoints.map((item) =>
+ item.id === updatedPolygonVertex.id
+ ? {
+ ...item,
+ lat: intToCoord(updatedPolygonVertex.x),
+ lon: intToCoord(updatedPolygonVertex.y),
+ }
+ : item,
+ ),
+ )
+ }
+
+ function convertPolygonToFenceItems(fenceType) {
+ if (polygonPoints.length < 3) {
+ showNotification("Polygon must have at least 3 points to be valid")
+ return
+ }
+
+ const fenceItems = polygonPoints.map((point, index) => ({
+ id: point.id,
+ command: fenceType === "inclusion" ? 5001 : 5002, // 5001 for inclusion, 5002 for exclusion
+ param1: polygonPoints.length, // Number of points in the polygon
+ param2: 0,
+ param3: 0,
+ param4: 0,
+ x: coordToInt(point.lat),
+ y: coordToInt(point.lon),
+ z: index,
+ }))
+
+ addFencePolygon(fenceItems)
+ setPolygonPoints([])
+ setPolygonDrawMode(false)
+ }
+
return (
)}
diff --git a/gcs/src/components/missions/rallyItemsTable.jsx b/gcs/src/components/missions/rallyItemsTable.jsx
index dc1ea9820..6666742a7 100644
--- a/gcs/src/components/missions/rallyItemsTable.jsx
+++ b/gcs/src/components/missions/rallyItemsTable.jsx
@@ -16,10 +16,10 @@ function RallyItemsTableNonMemo({
Command
- Param 1
- Param 2
- Param 3
- Param 4
+
+
+
+
Lat
Lng
Alt
diff --git a/gcs/src/helpers/mavlinkConstants.js b/gcs/src/helpers/mavlinkConstants.js
index d26a19843..bf87d7086 100644
--- a/gcs/src/helpers/mavlinkConstants.js
+++ b/gcs/src/helpers/mavlinkConstants.js
@@ -444,12 +444,11 @@ export const COPTER_MISSION_ITEM_COMMANDS_LIST = {
526: "MAV_CMD_STORAGE_FORMAT",
}
-// TODO: implement
export const FENCE_ITEM_COMMANDS_LIST = {
- // 5001: "MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION",
- // 5002: "MAV_CMD_NAV_FENCE_POLYGON_VERTEX_EXCLUSION",
- // 5003: 'MAV_CMD_NAV_FENCE_CIRCLE_INCLUSION',
- // 5004: 'MAV_CMD_NAV_FENCE_CIRCLE_EXCLUSION',
+ 5001: "MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION",
+ 5002: "MAV_CMD_NAV_FENCE_POLYGON_VERTEX_EXCLUSION",
+ 5003: "MAV_CMD_NAV_FENCE_CIRCLE_INCLUSION",
+ 5004: "MAV_CMD_NAV_FENCE_CIRCLE_EXCLUSION",
}
export const MAV_FRAME_LIST = {
diff --git a/gcs/src/missions.jsx b/gcs/src/missions.jsx
index fd0b1181f..2f2f8066e 100644
--- a/gcs/src/missions.jsx
+++ b/gcs/src/missions.jsx
@@ -26,6 +26,7 @@ import {
} from "@mantine/core"
import { IconInfoCircle } from "@tabler/icons-react"
import Layout from "./components/layout"
+import FenceItemsTable from "./components/missions/fenceItemsTable"
import MissionItemsTable from "./components/missions/missionItemsTable"
import MissionsMapSection from "./components/missions/missionsMap"
import RallyItemsTable from "./components/missions/rallyItemsTable"
@@ -43,6 +44,11 @@ import {
} from "./helpers/notification"
import { socket } from "./helpers/socket"
+// Tailwind styling
+import resolveConfig from "tailwindcss/resolveConfig"
+import tailwindConfig from "../tailwind.config"
+const tailwindColors = resolveConfig(tailwindConfig).theme.colors
+
const coordsFractionDigits = 7
export default function Missions() {
@@ -57,7 +63,7 @@ export default function Missions() {
const [activeTab, setActiveTab] = useState("mission")
- // Mission
+ // Mission data
const [missionItems, setMissionItems] = useSessionStorage({
key: "missionItems",
defaultValue: [],
@@ -78,11 +84,12 @@ export default function Missions() {
key: "targetInfo",
defaultValue: { target_component: 0, target_system: 255 },
})
+
+ // File import handling
const [importFile, setImportFile] = useState(null)
const importFileResetRef = useRef(null)
- const newMissionItemAltitude = 30 // TODO: Make this configurable
-
+ // Modal for mission progress
const [
missionProgressModalOpened,
{ open: openMissionProgressModal, close: closeMissionProgressModal },
@@ -92,17 +99,14 @@ export default function Missions() {
)
const [missionProgressModalData, setMissionProgressModalData] = useState({})
- // Heartbeat data
+ // Drone data
const [heartbeatData, setHeartbeatData] = useState({ system_status: 0 })
-
- // GPS and Telemetry
const [gpsData, setGpsData] = useState({})
+ const [navControllerOutputData, setNavControllerOutputData] = useState({})
- // Map and messages
const mapRef = useRef()
- // System data
- const [navControllerOutputData, setNavControllerOutputData] = useState({})
+ const newMissionItemAltitude = 30 // TODO: Make this configurable
const incomingMessageHandler = useCallback(
() => ({
@@ -117,6 +121,10 @@ export default function Missions() {
[],
)
+ useEffect(() => {
+ setActiveTab("mission") // Default to mission tab on load
+ }, [])
+
useEffect(() => {
if (!connected) {
return
@@ -162,7 +170,11 @@ export default function Missions() {
updateHomePositionBasedOnWaypoints(missionItemsWithIds)
setMissionItems(missionItemsWithIds)
} else if (data.mission_type === "fence") {
- setFenceItems(data.items)
+ const fenceItemsWithIds = []
+ for (let fence of data.items) {
+ fenceItemsWithIds.push(addIdToItem(fence))
+ }
+ setFenceItems(fenceItemsWithIds)
} else if (data.mission_type === "rally") {
const rallyItemsWithIds = []
for (let rallyItem of data.items) {
@@ -196,7 +208,11 @@ export default function Missions() {
setMissionItems(missionItemsWithIds)
} else if (data.mission_type === "fence") {
- setFenceItems(data.items)
+ const fenceItemsWithIds = []
+ for (let fence of data.items) {
+ fenceItemsWithIds.push(addIdToItem(fence))
+ }
+ setFenceItems(fenceItemsWithIds)
} else if (data.mission_type === "rally") {
const rallyItemsWithIds = []
for (let rallyItem of data.items) {
@@ -305,7 +321,7 @@ export default function Missions() {
),
),
command: null,
- param1: 0,
+ param1: activeTab === "fence" ? 5 : 0,
param2: 0,
param3: 0,
param4: 0,
@@ -325,7 +341,7 @@ export default function Missions() {
setMissionItems((prevItems) => [...prevItems, newMissionItem])
} else if (activeTab === "fence") {
newMissionItem.seq = fenceItems.length
- newMissionItem.command = 5100 // MAV_CMD_NAV_FENCE_POINT
+ newMissionItem.command = 5004 // MAV_CMD_NAV_FENCE_CIRCLE_EXCLUSION
newMissionItem.mission_type = 1 // Fence type
setFenceItems((prevItems) => [...prevItems, newMissionItem])
@@ -390,7 +406,7 @@ export default function Missions() {
if (activeTab === "mission") {
setMissionItems((prevItems) => getUpdatedItems(prevItems))
} else if (activeTab === "fence") {
- // TODO: Implement fence item update logic
+ setFenceItems((prevItems) => getUpdatedItems(prevItems))
} else if (activeTab === "rally") {
setRallyItems((prevItems) => getUpdatedItems(prevItems))
}
@@ -409,14 +425,14 @@ export default function Missions() {
if (activeTab === "mission") {
setMissionItems((prevItems) => getUpdatedItems(prevItems))
} else if (activeTab === "fence") {
- // TODO: Implement fence item deletion logic
+ setFenceItems((prevItems) => getUpdatedItems(prevItems))
} else if (activeTab === "rally") {
setRallyItems((prevItems) => getUpdatedItems(prevItems))
}
}
function updateMissionItemOrder(missionItemId, indexIncrement) {
- setMissionItems((prevItems) => {
+ function updateItemOrder(prevItems) {
const currentIndex = prevItems.findIndex(
(item) => item.id === missionItemId,
)
@@ -446,7 +462,13 @@ export default function Missions() {
updatedItems[newIndex].seq = newIndex
return updatedItems
- })
+ }
+
+ if (activeTab === "mission") {
+ setMissionItems((prevItems) => updateItemOrder(prevItems))
+ } else if (activeTab === "fence") {
+ setFenceItems((prevItems) => updateItemOrder(prevItems))
+ }
}
function readMissionFromDrone() {
@@ -501,6 +523,17 @@ export default function Missions() {
items = [...missionItems]
} else if (activeTab === "fence") {
items = [...fenceItems]
+
+ const newHomeItem = createHomePositionItem()
+ if (newHomeItem) {
+ items.unshift(newHomeItem) // Add home item at the beginning
+ }
+
+ // Ensure all sequence values are updated
+ items = items.map((item, index) => ({
+ ...item,
+ seq: index,
+ }))
} else if (activeTab === "rally") {
items = [...rallyItems]
@@ -589,6 +622,43 @@ export default function Missions() {
}
}
+ function addFencePolygon(newFenceItems) {
+ let seqNumber =
+ fenceItems.length > 0 ? fenceItems[fenceItems.length - 1].seq + 1 : 0
+
+ const newFenceMissionItems = newFenceItems.map((item) => {
+ const newFenceMissionItem = {
+ id: item.id,
+ seq: seqNumber,
+ x: item.x,
+ y: item.y,
+ z: item.z,
+ frame: parseInt(
+ Object.keys(MAV_FRAME_LIST).find(
+ (key) => MAV_FRAME_LIST[key] === "MAV_FRAME_GLOBAL_RELATIVE_ALT",
+ ),
+ ),
+ command: item.command,
+ param1: item.param1,
+ param2: item.param2,
+ param3: item.param3,
+ param4: item.param4,
+ current: 0,
+ autocontinue: 1,
+ target_component: targetInfo.target_component,
+ target_system: targetInfo.target_system,
+ mission_type: 1, // Fence type
+ mavpackettype: "MISSION_ITEM_INT",
+ }
+
+ seqNumber++
+
+ return newFenceMissionItem
+ })
+
+ setFenceItems((prevItems) => [...prevItems, ...newFenceMissionItems])
+ }
+
return (
@@ -763,9 +834,18 @@ export default function Missions() {
className="mt-2"
>
- Mission
- Fence
- Rally
+
+ Mission
+
+
+ Fence
+
+
+ Rally
+
@@ -777,7 +857,14 @@ export default function Missions() {
updateMissionItemOrder={updateMissionItemOrder}
/>
-
+
+
+