247 lines
8.1 KiB
TypeScript
247 lines
8.1 KiB
TypeScript
import { Box,Grid2 as Grid, Theme, Tooltip, Typography } from "@mui/material";
|
|
import { makeStyles } from "@mui/styles";
|
|
import MiniPie from "charts/MiniPie";
|
|
import { useMobile } from "hooks";
|
|
import { clone } from "lodash";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
import { Marker } from "react-map-gl/mapbox-legacy";
|
|
|
|
//interface for markers
|
|
export interface MarkerData {
|
|
title: string; //displayed in the tooltip
|
|
longitude: number;
|
|
latitude: number;
|
|
colour: string;
|
|
centered?: boolean;
|
|
details?: string[];
|
|
markerIcon?: JSX.Element;
|
|
subtype?: number;
|
|
graphPercent?: number; //if defined show graph behind markericon
|
|
customImage?: JSX.Element; //if defined use image rather than basic marker with icon
|
|
customSize?: number; //if the size is different from the normal or mini markers
|
|
mini?: boolean; //whether the marker is the small scouting marker or the normal size object marker
|
|
visibleLevels?: { min?: number; max?: number }; //zoom levels that determine when the marker is visible
|
|
clickFunc?: (event: React.PointerEvent<HTMLElement>, index: number, isMobile: boolean) => void; //function the marker will run when clicked
|
|
updateFunc?: (location: pond.Location) => void; //function the marker will run when something changes
|
|
}
|
|
|
|
interface Props {
|
|
markerData: MarkerData[];
|
|
mapZoomLevel: number;
|
|
dragOn?: boolean;
|
|
groupSelect?: boolean;
|
|
showMarkerTitle?: boolean;
|
|
displayDetails?: boolean;
|
|
}
|
|
|
|
const useStyles = makeStyles((theme: Theme) => ({
|
|
pin: {
|
|
borderRadius: "50rem",
|
|
display: "inline-block",
|
|
borderBottomRightRadius: "0",
|
|
transform: "rotate(45deg)",
|
|
cursor: "pointer",
|
|
boxShadow: "4px 4px 10px black"
|
|
},
|
|
details: {
|
|
transform: "rotate(45deg)",
|
|
background: theme.palette.background.default,
|
|
opacity: 0.75,
|
|
padding: 5,
|
|
borderRadius: 10,
|
|
paddingLeft: 40,
|
|
clipPath: "polygon(25% 0, 100% 0%, 100% 100%, 25% 100%, 0% 50%)"
|
|
},
|
|
titleDisplay: {
|
|
textAlign: "center",
|
|
width: 100,
|
|
marginLeft: -25,
|
|
marginTop: 10,
|
|
backgroundColor: theme.palette.background.default,
|
|
borderRadius: 30,
|
|
boxShadow: "4px 4px 10px black"
|
|
}
|
|
}));
|
|
|
|
export default function Markers(props: Props) {
|
|
const { markerData, dragOn, mapZoomLevel, displayDetails, showMarkerTitle, groupSelect } = props;
|
|
const classes = useStyles();
|
|
const isMobile = useMobile();
|
|
const [groupedMarkers, setGroupedMarkers] = useState<Map<number, MarkerData>>(new Map());
|
|
|
|
const moveGroup = (oldLong: number, oldLat: number, newLong: number, newLat: number) => {
|
|
//find the diff of the oldLong (the starting longitude of the marker that was actually dragged) and the newLong
|
|
const diffLong = newLong - oldLong;
|
|
//find the diff of the oldLat and new Lat
|
|
const diffLat = newLat - oldLat;
|
|
|
|
let gm = clone(groupedMarkers);
|
|
|
|
gm.forEach(marker => {
|
|
if (marker.updateFunc) {
|
|
marker.longitude = marker.longitude + diffLong;
|
|
marker.latitude = marker.latitude + diffLat;
|
|
marker.updateFunc(
|
|
pond.Location.create({ longitude: marker.longitude, latitude: marker.latitude })
|
|
);
|
|
}
|
|
});
|
|
setGroupedMarkers(gm);
|
|
};
|
|
|
|
//when group select is turned off, clear the grouped markers
|
|
useEffect(() => {
|
|
if (!groupSelect) {
|
|
setGroupedMarkers(new Map());
|
|
}
|
|
}, [groupSelect]);
|
|
|
|
const renderMarkers = useCallback(() => {
|
|
let markerList: JSX.Element[] = [];
|
|
markerData.forEach((marker, index) => {
|
|
let markerSize = marker.mini ? 20 : 50;
|
|
if (marker.customSize) {
|
|
markerSize = marker.customSize;
|
|
}
|
|
|
|
let yOff = 0;
|
|
if (!marker.centered) {
|
|
yOff = -markerSize * 0.7;
|
|
}
|
|
|
|
let vis = "block";
|
|
if (marker.visibleLevels) {
|
|
let max = marker.visibleLevels.max;
|
|
let min = marker.visibleLevels.min;
|
|
if ((max && mapZoomLevel > max) || (min && mapZoomLevel < min)) {
|
|
vis = "none";
|
|
}
|
|
}
|
|
|
|
let gm = groupedMarkers.get(index);
|
|
if (gm) {
|
|
marker.longitude = gm.longitude;
|
|
marker.latitude = gm.latitude;
|
|
}
|
|
|
|
let border = marker.customImage ? "" : "1px solid white";
|
|
if (gm) {
|
|
border = "2px solid black";
|
|
}
|
|
|
|
let blah: JSX.Element = (
|
|
<Marker
|
|
//style={{border: gm ? "2px solid black" : ""}}
|
|
key={"marker-" + index}
|
|
longitude={marker.longitude}
|
|
latitude={marker.latitude}
|
|
//offsetTop={yOff}
|
|
offset={[0, yOff]}
|
|
draggable={dragOn}
|
|
onDragEnd={e => {
|
|
let newLong = e.lngLat.lng;
|
|
let newLat = e.lngLat.lat;
|
|
if (groupSelect && groupedMarkers.size > 0) {
|
|
moveGroup(marker.longitude, marker.latitude, newLong, newLat);
|
|
} else {
|
|
if (marker.updateFunc) {
|
|
marker.longitude = newLong;
|
|
marker.latitude = newLat;
|
|
marker.updateFunc(
|
|
pond.Location.create({ latitude: e.lngLat.lat, longitude: e.lngLat.lng })
|
|
);
|
|
}
|
|
}
|
|
}}>
|
|
<Box id={"marker-" + index} />
|
|
<Tooltip title={marker.title} placement={"right"} arrow>
|
|
<Box
|
|
className={!marker.customImage ? classes.pin : undefined}
|
|
style={{
|
|
display: vis,
|
|
border: border,
|
|
background: marker.customImage ? "none" : marker.colour,
|
|
width: markerSize,
|
|
height: markerSize
|
|
}}
|
|
onPointerUp={e => {
|
|
if (!dragOn && marker.clickFunc) {
|
|
marker.clickFunc(e, index, isMobile);
|
|
} else if (groupSelect) {
|
|
let group = clone(groupedMarkers);
|
|
group.set(index, marker);
|
|
setGroupedMarkers(group);
|
|
}
|
|
}}>
|
|
{marker.graphPercent !== undefined && (
|
|
<Box
|
|
width={"100%"}
|
|
height={"100%"}
|
|
style={{ pointerEvents: "none", position: "absolute" }}>
|
|
<MiniPie
|
|
max={100}
|
|
current={marker.graphPercent}
|
|
colour={marker.colour}
|
|
size={markerSize}
|
|
/>
|
|
</Box>
|
|
)}
|
|
{marker.customImage ? (
|
|
marker.customImage
|
|
) : (
|
|
<Box
|
|
style={{
|
|
transform: "rotate(-45deg)",
|
|
width: markerSize * 0.6,
|
|
height: markerSize * 0.6,
|
|
paddingTop: marker.mini ? "10%" : "30%",
|
|
pointerEvents: "none"
|
|
}}>
|
|
{marker.markerIcon}
|
|
</Box>
|
|
)}
|
|
{marker.details && displayDetails && (
|
|
<Box
|
|
className={classes.details}
|
|
style={{
|
|
position: "absolute",
|
|
left: markerSize / 2,
|
|
top: markerSize / 2,
|
|
width: 200,
|
|
marginTop: 80
|
|
}}>
|
|
<Grid container direction="column" wrap="nowrap">
|
|
{marker.details.map((detail, i) => (
|
|
<Grid key={i}>
|
|
<Typography noWrap style={{ fontSize: 10, fontWeight: 750, height: 15 }}>
|
|
{detail}
|
|
</Typography>
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</Tooltip>
|
|
{showMarkerTitle && <Box className={classes.titleDisplay}>{marker.title}</Box>}
|
|
</Marker>
|
|
);
|
|
markerList.push(<React.Fragment key={index}>{blah}</React.Fragment>);
|
|
});
|
|
return markerList;
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
markerData,
|
|
classes,
|
|
displayDetails,
|
|
dragOn,
|
|
groupSelect,
|
|
groupedMarkers,
|
|
isMobile,
|
|
showMarkerTitle,
|
|
mapZoomLevel
|
|
]);
|
|
|
|
return <React.Fragment>{renderMarkers()}</React.Fragment>;
|
|
}
|