Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gcs/src/components/mapComponents/contextMenuItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
<div className="w-full flex justify-between gap-2">{children}</div>
</div>
)
}
54 changes: 54 additions & 0 deletions gcs/src/components/mapComponents/contextMenuSubMenuItem.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
ref={parentRef}
className="hover:bg-falcongrey-800 hover:cursor-pointer py-1 px-4 rounded"
onMouseOver={() => setIsHovered(true)}
onMouseOut={() => setIsHovered(false)}
>
<div className="w-full flex justify-between items-center gap-2">
<p>{title}</p>
<span className="text-gray-400">▶</span>
</div>
{isHovered && (
<div
ref={subMenuRef}
className="absolute top-0 left-full bg-falcongrey-700 rounded p-1"
style={{
top: `${submenuPosition.top}px`,
left: `${submenuPosition.left}px`,
position: "fixed", // Use fixed to align with the viewport
}}
>
{children}
</div>
)}
</div>
)
}
15 changes: 15 additions & 0 deletions gcs/src/components/mapComponents/drawLineCoordinates.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export default function DrawLineCoordinates({
colour,
width = 1,
lineProps = {},
fillLayer = false,
fillOpacity = 0.5,
}) {
return (
<Source
Expand All @@ -21,6 +23,7 @@ export default function DrawLineCoordinates({
coordinates,
},
}}
buffer={512} // Attempt to increase buffer to remove fill layer clipping
Comment thread
1Blademaster marked this conversation as resolved.
Comment thread
1Blademaster marked this conversation as resolved.
>
<Layer
{...{
Expand All @@ -36,6 +39,18 @@ export default function DrawLineCoordinates({
},
}}
/>
{fillLayer && (
<Layer
type="fill"
paint={{
"fill-color": colour,
"fill-opacity": fillOpacity,
}}
layout={{
"fill-sort-key": 10,
}}
/>
)}
</Source>
)
}
181 changes: 181 additions & 0 deletions gcs/src/components/mapComponents/fenceItems.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<MarkerPin
key={index}
id={item.id}
lat={intToCoord(item.x)}
lon={intToCoord(item.y)}
colour={tailwindColors.blue[400]}
draggable={editable}
dragEndCallback={dragEndCallback}
/>
)
})}

{/* 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 (
<DrawLineCoordinates
key={index}
coordinates={[
...polygon.map((item) => [
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 (
<MarkerPin
key={index}
id={item.id}
lat={intToCoord(item.x)}
lon={intToCoord(item.y)}
colour={tailwindColors.blue[400]}
draggable={editable}
dragEndCallback={dragEndCallback}
/>
)
})}

<Source
id="circle-source"
type="geojson"
data={{
type: "FeatureCollection",
features: fenceCircleItems.map((item) =>
circle([intToCoord(item.y), intToCoord(item.x)], item.param1, {
Comment thread
1Blademaster marked this conversation as resolved.
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
},
}),
),
}}
>
<Layer
id="fence-circle-fill-layer"
type="fill"
paint={{
"fill-color": ["get", "color"],
"fill-opacity": ["get", "fillOpacity"],
}}
/>
<Layer
id="fence-circle-border-layer"
type="line"
paint={{
"line-color": ["get", "color"],
"line-width": 2,
"line-dasharray": [2, 2],
}}
/>
</Source>
</>
)
}
54 changes: 54 additions & 0 deletions gcs/src/components/mapComponents/polygon.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<MarkerPin
key={index}
id={item.id}
lat={item.lat}
lon={item.lon}
colour={tailwindColors.red[400]}
draggable={editable}
dragEndCallback={dragEndCallback}
/>
)
})}

{/* Show polygon outlines */}
{polygonPoints.length > 0 && (
<DrawLineCoordinates
coordinates={[
...polygonPoints.map((item) => [item.lon, item.lat]),
[polygonPoints[0].lon, polygonPoints[0].lat],
]}
colour={tailwindColors.red[200]}
/>
)}
</>
)
}
55 changes: 55 additions & 0 deletions gcs/src/components/missions/fenceItemsTable.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Table striped withTableBorder withColumnBorders stickyHeader>
<Table.Thead>
<Table.Tr>
<Table.Th></Table.Th>
<Table.Th>Command</Table.Th>
<Table.Th>Param 1</Table.Th>
<Table.Th></Table.Th>
<Table.Th></Table.Th>
<Table.Th></Table.Th>
<Table.Th>Lat</Table.Th>
<Table.Th>Lng</Table.Th>
<Table.Th></Table.Th>
<Table.Th>Frame</Table.Th>
<Table.Th></Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{fenceItems.map((fenceItem, idx) => {
return (
<FenceItemsTableRow
key={fenceItem.id}
index={idx}
fenceItem={fenceItem}
updateMissionItem={updateMissionItem}
deleteMissionItem={deleteMissionItem}
updateMissionItemOrder={updateMissionItemOrder}
/>
)
})}
</Table.Tbody>
</Table>
)
}

function propsAreEqual(prev, next) {
return JSON.stringify(prev) === JSON.stringify(next)
}
const FenceItemsTable = React.memo(FenceItemsTableNonMemo, propsAreEqual)

export default FenceItemsTable
Loading
Loading