added the construction and aviation map stuff as well as the site api and model

This commit is contained in:
csawatzky 2025-03-31 16:24:44 -06:00
parent 02cee8623a
commit bcacf761c8
20 changed files with 2549 additions and 9 deletions

View file

@ -0,0 +1,119 @@
import DisplayDrawer from "common/DisplayDrawer";
import { Gate as IGate } from "models/Gate";
import Gate from "pages/Gate";
import { useGateAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
interface Props {
open: boolean;
onClose: () => void;
gates: Map<string, IGate>;
selectedGate: string;
moveMap: (longitude: number, latitude: number) => void;
removeMarker: (key: string) => void;
}
export default function GateDrawer(props: Props) {
const { open, onClose, gates, selectedGate, moveMap, removeMarker } = props;
const [gate, setGate] = useState(IGate.create());
const gateAPI = useGateAPI();
const { openSnack } = useSnackbar();
useEffect(() => {
let g = gates.get(selectedGate);
if (g) {
setGate(g);
}
}, [selectedGate, gates]);
const closeDrawer = () => {
onClose();
};
const displayNext = () => {
let gateArr = Array.from(gates.values());
let index = gateArr.indexOf(gate);
let found = false;
do {
if (index === gateArr.length - 1) {
index = 0;
} else {
index++;
}
let next = gateArr[index];
if (
next.longitude() !== 0 &&
next.longitude() !== 0 &&
next.latitude() !== undefined &&
next.longitude() !== undefined
) {
setGate(next);
moveMap(next.longitude(), next.latitude());
found = true;
}
} while (!found);
};
const displayPrev = () => {
let gateArr = Array.from(gates.values());
let index = gateArr.indexOf(gate);
let found = false;
do {
if (index === 0) {
index = gateArr.length - 1;
} else {
index--;
}
let next = gateArr[index];
if (
next.longitude() !== 0 &&
next.latitude() !== 0 &&
next.latitude() !== undefined &&
next.longitude() !== undefined
) {
setGate(next);
moveMap(next.longitude(), next.latitude());
found = true;
}
} while (!found);
};
const drawerBody = () => {
return <Gate gateKey={gate.key} useMobile />;
};
/**
* function to remove the marker and coordinates from the object
*/
const remove = () => {
//set the long/lat of the yard to 0 and call an update
let settings = gate.settings;
settings.longitude = 0;
settings.latitude = 0;
gateAPI
.updateGate(gate.key, gate.name, settings)
.then(resp => {
openSnack("Marker Removed");
//then use the removeMarker prop function to update the markers in the parent map
removeMarker(gate.key);
})
.catch(err => {
openSnack("Failed to remove marker");
});
};
return (
<React.Fragment>
<DisplayDrawer
displayNext={displayNext}
displayPrev={displayPrev}
drawerBody={drawerBody()}
removeElement={remove}
onClose={closeDrawer}
open={open}
title={gate.name}
width={"40vw"}
/>
</React.Fragment>
);
}

View file

@ -0,0 +1,144 @@
import { Box } from "@mui/material";
import DisplayDrawer from "common/DisplayDrawer";
//import DeviceCardList from "device/DeviceCardList";
import { useGroupAPI, useSnackbar } from "hooks";
import { Group } from "models";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState, useCallback } from "react";
import { or } from "utils";
interface Props {
open: boolean;
groups: Map<string, Group>;
selectedGroup: string;
onClose: () => void;
removeMarker: (key: string) => void;
moveMap: (long: number, lat: number) => void;
}
export default function GroupDrawer(props: Props) {
const { open, groups, selectedGroup, onClose, moveMap, removeMarker } = props;
const [group, setGroup] = useState<Group>(Group.create());
const groupAPI = useGroupAPI();
const { openSnack } = useSnackbar();
const [grpDevs, setGrpDevs] = useState<pond.ComprehensiveDevice[]>([]);
const loadDevs = useCallback(() => {
groupAPI
.listGroupDevices(group.id(), 100, 0, "asc", undefined, true)
.then(resp => {
const groupDevices: pond.ComprehensiveDevice[] = or(
resp.data.comprehensiveDevices,
[]
).map(d => pond.ComprehensiveDevice.fromObject(d));
setGrpDevs(groupDevices);
})
.catch(err => {
openSnack("Failed to get devices for group");
});
}, [group, groupAPI, openSnack]);
useEffect(() => {
loadDevs();
}, [loadDevs]);
useEffect(() => {
let g = groups.get(selectedGroup);
if (g) {
setGroup(g);
}
}, [selectedGroup, groups]);
const closeDrawer = () => {
onClose();
};
const displayNext = () => {
let groupArr = Array.from(groups.values());
let index = groupArr.indexOf(group);
let found = false;
do {
if (index === groupArr.length - 1) {
index = 0;
} else {
index++;
}
let next = groupArr[index];
if (
next.settings.longitude !== 0 &&
next.settings.longitude !== 0 &&
next.settings.latitude !== undefined &&
next.settings.longitude !== undefined
) {
setGroup(next);
moveMap(next.settings.longitude, next.settings.latitude);
found = true;
}
} while (!found);
};
const displayPrev = () => {
let groupArr = Array.from(groups.values());
let index = groupArr.indexOf(group);
let found = false;
do {
if (index === 0) {
index = groupArr.length - 1;
} else {
index--;
}
let next = groupArr[index];
if (
next.settings.longitude !== 0 &&
next.settings.longitude !== 0 &&
next.settings.latitude !== undefined &&
next.settings.longitude !== undefined
) {
setGroup(next);
moveMap(next.settings.longitude, next.settings.latitude);
found = true;
}
} while (!found);
};
/**
* function to remove the marker and coordinates from the object
*/
const remove = () => {
//set the long/lat of the yard to 0 and call an update
let settings = group.settings;
settings.longitude = 0;
settings.latitude = 0;
groupAPI
.updateGroup(group.id(), settings)
.then(resp => {
openSnack("Marker Removed");
//then use the removeMarker prop function to update the markers in the parent map
removeMarker(group.id().toString());
})
.catch(err => {
openSnack("Failed to remove marker");
});
};
const drawerBody = () => {
return (<Box>Use the responsive table here</Box>)
//return <DeviceCardList devices={grpDevs} refreshCallback={loadDevs} showMobile={true} />;
};
return (
<React.Fragment>
<DisplayDrawer
open={open}
onClose={closeDrawer}
displayNext={displayNext}
displayPrev={displayPrev}
title={group.name()}
width={"40vw"}
drawerBody={drawerBody()}
//updateElement={update}
removeElement={remove}
/>
</React.Fragment>
);
}

View file

@ -0,0 +1,221 @@
import { Box, Typography } from "@mui/material";
import DeviceLinkDrawer from "common/DeviceLinkDrawer";
import DisplayDrawer from "common/DisplayDrawer";
import MapMarkerSettings from "maps/MapMarkerSettings";
import { Device, Site } from "models";
//import { ObjectHeater } from "models/ObjectHeater";
//import ObjectHeaterCard from "objectHeater/ObjectHeaterCard";
import { pond } from "protobuf-ts/pond";
import { /*useObjectHeaterAPI,*/ useSiteAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
interface Props {
open: boolean;
onClose: () => void;
selectedSite: string;
sites: Map<string, Site>;
heaters: Map<string, pond.ObjectHeaterData[]>;
removeMarker: (key: string) => void;
updateMarker: (key: string, newSettings: pond.SiteSettings) => void;
moveMap: (long: number, lat: number) => void;
reLoadSites: () => void;
}
export default function SiteDrawer(props: Props) {
const {
open,
onClose,
selectedSite,
sites,
removeMarker,
moveMap,
heaters,
reLoadSites,
updateMarker
} = props;
const [site, setSite] = useState<Site>(Site.create());
const siteAPI = useSiteAPI();
//const objectHeaterAPI = useObjectHeaterAPI();
const { openSnack } = useSnackbar();
const [linkDrawer, setLinkDrawer] = useState(false);
const [linkedDevices, setLinkedDevices] = useState<Map<string, pond.ComprehensiveDevice>>(
new Map<string, pond.ComprehensiveDevice>()
);
//const [selectedHeater, setSelectedHeater] = useState<ObjectHeater>();
const [openMarkerSettings, setOpenMarkerSettings] = useState(false);
useEffect(() => {
let s = sites.get(selectedSite);
if (s) {
setSite(s);
}
}, [selectedSite, sites]);
const closeDrawer = () => {
onClose();
};
const displayNext = () => {
let devArr = Array.from(sites.values());
let index = devArr.indexOf(site);
let found = false;
do {
if (index === devArr.length - 1) {
index = 0;
} else {
index++;
}
let nextSite = devArr[index];
if (
nextSite.location().longitude !== 0 &&
nextSite.location().longitude !== 0 &&
nextSite.location().latitude !== undefined &&
nextSite.location().longitude !== undefined
) {
setSite(nextSite);
moveMap(nextSite.location().longitude, nextSite.location().latitude);
found = true;
}
} while (!found);
};
const displayPrev = () => {
let devArr = Array.from(sites.values());
let index = devArr.indexOf(site);
let found = false;
do {
if (index === 0) {
index = devArr.length - 1;
} else {
index--;
}
let nextSite = devArr[index];
if (
nextSite.location().longitude !== 0 &&
nextSite.location().longitude !== 0 &&
nextSite.location().latitude !== undefined &&
nextSite.location().longitude !== undefined
) {
setSite(nextSite);
moveMap(nextSite.location().longitude, nextSite.location().latitude);
found = true;
}
} while (!found);
};
// const deviceLinkDrawer = () => {
// return (
// <DeviceLinkDrawer
// open={linkDrawer}
// close={() => {
// setLinkDrawer(false);
// }}
// linkedDevices={linkedDevices}
// updateLinkedDevices={(dev, linked) => {
// let deviceID = dev.device?.settings?.deviceId;
// if (deviceID) {
// if (linked && selectedHeater) {
// objectHeaterAPI
// .updateLink(selectedHeater.key, "objectHeater", deviceID.toString(), "device", [
// "read",
// "write",
// "grant",
// "revoke"
// ])
// .then(resp => {
// setLinkDrawer(false);
// reLoadSites();
// });
// }
// }
// }}
// />
// );
// };
const drawerBody = () => {
//let siteHeaters = heaters.get(site.key());
return (
<Box padding={2}>
{/* {siteHeaters ? (
siteHeaters.map(h => (
<ObjectHeaterCard
key={h.heater?.key}
heaterData={h}
linkDevice={(heater, linkedDevices) => {
setSelectedHeater(heater);
setLinkDrawer(true);
let d = new Map<string, pond.ComprehensiveDevice>();
linkedDevices.forEach(compDev => {
let device = Device.any(compDev.device);
d.set(device.id().toString(), compDev);
});
setLinkedDevices(d);
}}
/>
))
) : ( */}
<Typography>No Heaters Found</Typography>
{/* )} */}
</Box>
);
};
/**
* function to remove the marker and coordinates from the object
*/
const remove = () => {
siteAPI
.removeSite(site.key())
.then(resp => {
openSnack("Site Deleted");
//then use the removeMarker prop function to update the markers in the parent map
removeMarker(site.key());
})
.catch(err => {
openSnack("Failed to remove marker");
});
};
const update = () => {
setOpenMarkerSettings(true);
};
return (
<React.Fragment>
{/* {deviceLinkDrawer()} */}
<DisplayDrawer
open={open}
onClose={closeDrawer}
displayNext={displayNext}
displayPrev={displayPrev}
title={site.siteName()}
width={"40vw"}
drawerBody={drawerBody()}
updateElement={update}
removeElement={remove}
/>
<MapMarkerSettings
close={() => {
setOpenMarkerSettings(false);
}}
open={openMarkerSettings}
colourControl
theme={site.settings.theme ?? pond.ObjectTheme.create()}
updateObject={newTheme => {
let settings = site.settings;
settings.theme = newTheme;
siteAPI
.updateSite(site.key(), settings)
.then(resp => {
openSnack("marker settings updated");
updateMarker(site.key(), settings);
})
.catch(() => {
openSnack("failed to update marker settings");
});
}}
/>
</React.Fragment>
);
}

View file

@ -0,0 +1,119 @@
import DisplayDrawer from "common/DisplayDrawer";
import { Terminal as ITerminal } from "models/Terminal";
import Terminal from "pages/Terminals";
import { useSnackbar, useTerminalAPI } from "providers";
import React, { useState, useEffect } from "react";
interface Props {
open: boolean;
onClose: () => void;
terminals: Map<string, ITerminal>;
selectedKey: string;
moveMap: (longitude: number, latitude: number) => void;
removeMarker: (key: string) => void;
}
export default function TerminalDrawer(props: Props) {
const { terminals, selectedKey, open, onClose, moveMap, removeMarker } = props;
const [terminal, setTerminal] = useState<ITerminal>(ITerminal.create());
const terminalAPI = useTerminalAPI();
const { openSnack } = useSnackbar();
useEffect(() => {
let t = terminals.get(selectedKey);
if (t) {
setTerminal(t);
}
}, [selectedKey, terminals]);
const closeDrawer = () => {
onClose();
};
const displayNext = () => {
let termArr = Array.from(terminals.values());
let index = termArr.indexOf(terminal);
let found = false;
do {
if (index === termArr.length - 1) {
index = 0;
} else {
index++;
}
let next = termArr[index];
if (
next.longitude() !== 0 &&
next.longitude() !== 0 &&
next.latitude() !== undefined &&
next.longitude() !== undefined
) {
setTerminal(next);
moveMap(next.longitude(), next.latitude());
found = true;
}
} while (!found);
};
const displayPrev = () => {
let termArr = Array.from(terminals.values());
let index = termArr.indexOf(terminal);
let found = false;
do {
if (index === 0) {
index = termArr.length - 1;
} else {
index--;
}
let next = termArr[index];
if (
next.longitude() !== 0 &&
next.latitude() !== 0 &&
next.latitude() !== undefined &&
next.longitude() !== undefined
) {
setTerminal(next);
moveMap(next.longitude(), next.latitude());
found = true;
}
} while (!found);
};
const drawerBody = () => {
return <Terminal terminal={terminal} />;
};
/**
* function to remove the marker and coordinates from the object
*/
const remove = () => {
//set the long/lat of the yard to 0 and call an update
let settings = terminal.settings;
settings.longitude = 0;
settings.latitude = 0;
terminalAPI
.updateTerminal(terminal.key, terminal.name, settings)
.then(resp => {
openSnack("Marker Removed");
//then use the removeMarker prop function to update the markers in the parent map
removeMarker(terminal.key);
})
.catch(err => {
openSnack("Failed to remove marker");
});
};
return (
<React.Fragment>
<DisplayDrawer
open={open}
onClose={closeDrawer}
title={terminal.name}
width="40vw"
displayNext={displayNext}
displayPrev={displayPrev}
drawerBody={drawerBody()}
removeElement={remove}
/>
</React.Fragment>
);
}

View file

@ -0,0 +1,468 @@
import {
Box,
Button,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Autocomplete
} from "@mui/material";
//import { Autocomplete } from "@material-ui/lab";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useMobile } from "hooks";
import { clone } from "lodash";
import MapBase, { ViewData } from "maps/MapBase";
//import { GeocoderObject } from "maps/mapControllers/Geocoder";
import { Result } from "@mapbox/mapbox-gl-geocoder";
import GateDrawer from "maps/mapDrawers/GateDrawer";
import TerminalDrawer from "maps/mapDrawers/TerminalDrawer";
import { MarkerData } from "maps/mapMarkers/Markers";
import AviationMapTools from "maps/mapObjectTools/aviationMapTools";
import { Gate } from "models/Gate";
import { Terminal } from "models/Terminal";
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import { pond } from "protobuf-ts/pond";
import { useGateAPI, useGlobalState, useSnackbar, useTerminalAPI } from "providers";
import React, { useCallback, useEffect, useState } from "react";
interface Props {
startingView?: ViewData;
}
export default function AviationMapController(props: Props) {
const [{ user }] = useGlobalState();
const transDuration = 3000;
const [currentView, setCurrentView] = useState(
props.startingView
? props.startingView
: {
latitude: 52.1579,
longitude: -106.6702,
zoom: user.settings.mapZoom
}
);
const terminalAPI = useTerminalAPI();
const gateAPI = useGateAPI();
const [terminals, setTerminals] = useState<Map<string, Terminal>>(new Map());
const [gates, setGates] = useState<Map<string, Gate>>(new Map());
const [terminalOptions, setTerminalOptions] = useState<Terminal[]>([]);
const [gateOptions, setGateOptions] = useState<Gate[]>([]);
const [terminalMarkers, setTerminalMarkers] = useState<Map<string, MarkerData>>(new Map());
const [gateMarkers, setGateMarkers] = useState<Map<string, MarkerData>>(new Map());
const [markers, setMarkers] = useState<MarkerData[]>([]);
const { openSnack } = useSnackbar();
const [gateDrawerOpen, setGateDrawerOpen] = useState(false);
const [terminalDrawerOpen, setTerminalDrawerOpen] = useState(false);
const [objectKey, setObjectKey] = useState("");
const [endTime, setEndTime] = useState(0);
const isMobile = useMobile();
const [markerType, setMarkerType] = useState(pond.ObjectType.OBJECT_TYPE_UNKNOWN);
const [markerDisplay, setMarkerDisplay] = useState(false);
const [openMarkerDialog, setOpenMarkerDialog] = useState(false);
const [selectKey, setSelectKey] = useState("");
const [newLong, setNewLong] = useState(0);
const [newLat, setNewLat] = useState(0);
const zoomLevels = {
far: 14,
close: 16
};
const [customSearchEntries, setCustomSearchEntries] = useState<Result[]>([]);
const [terminalSearchEntries, setTerminalSearchEntries] = useState<Result[]>([]);
const [gateSearchEntries, setGateSearchEntries] = useState<Result[]>([]);
const closeDrawers = () => {
setTerminalDrawerOpen(false);
setGateDrawerOpen(false);
};
const markerClick = (key: string, long: number, lat: number, isMobile: boolean) => {
closeDrawers();
clickDelay();
setObjectKey(key);
moveMap(lat, long, zoomLevels.close, isMobile);
};
//this click delay is implemented to fix an issue with touch screens that caused the drawer to close immediately after opening
const clickDelay = () => {
let endTime = new Date().valueOf() + 1000;
setEndTime(endTime);
};
const updateTerminalMarkers = (
terminal: Terminal,
longitude: number,
latitude: number,
newMarker?: boolean
) => {
let settings = terminal.settings;
settings.longitude = longitude;
settings.latitude = latitude;
terminalAPI
.updateTerminal(terminal.key, terminal.name, settings)
.then(resp => {
openSnack("Terminal Location Updated");
if (newMarker) {
//create new marker
let termMarker = terminal.getMarkerData(
(e, i, isMobile) => {
markerClick(terminal.key, terminal.longitude(), terminal.latitude(), isMobile);
setTerminalDrawerOpen(true);
},
location => {
updateTerminalMarkers(terminal, location.longitude, location.latitude);
}
);
termMarker.markerIcon = <PlaneIcon width={50 * 0.6} height={50 * 0.6} />;
//clone data to update the state with and put the marker into the clone
let cloneData = clone(terminalMarkers);
cloneData.set(terminal.key, termMarker);
setTerminalMarkers(cloneData);
//remove from options
if (terminalOptions.includes(terminal)) {
terminalOptions.splice(terminalOptions.indexOf(terminal), 1);
}
}
})
.catch(err => {
openSnack("Failed to update location");
});
};
const updateGateMarkers = (
gate: Gate,
longitude: number,
latitude: number,
newMarker?: boolean
) => {
let settings = gate.settings;
settings.longitude = longitude;
settings.latitude = latitude;
gateAPI
.updateGate(gate.key, gate.name, settings)
.then(resp => {
openSnack("Gate Location Updated");
if (newMarker) {
let marker = gate.getMarkerData(
(e, i, isMobile) => {
markerClick(gate.key, gate.longitude(), gate.latitude(), isMobile);
setGateDrawerOpen(true);
},
location => {
updateGateMarkers(gate, location.longitude, location.latitude);
}
);
marker.markerIcon = <OmniAirDeviceIcon size={50 * 0.6} />;
let cloneData = clone(gateMarkers);
cloneData.set(gate.key, marker);
setGateMarkers(cloneData);
//remove from options
if (gateOptions.includes(gate)) {
gateOptions.splice(gateOptions.indexOf(gate), 1);
}
} else {
//change the click function in the markers data to use the new long and lat
}
})
.catch(err => {
openSnack("Failed to update location");
});
};
const loadTerminals = useCallback(() => {
terminalAPI.listTerminals(0, 0, undefined, undefined, undefined).then(resp => {
let terminalMap: Map<string, Terminal> = new Map();
let terminalMarkers: Map<string, MarkerData> = new Map();
let terminalOps: Terminal[] = [];
let searchEntries: Result[] = [];
resp.data.terminals.forEach(term => {
let terminal = Terminal.create(term);
terminalMap.set(terminal.key, terminal);
if (
terminal.longitude() &&
terminal.longitude() !== 0 &&
terminal.latitude() &&
terminal.latitude() !== 0
) {
//make marker
let termMarker = terminal.getMarkerData(
(e, i, isMobile) => {
markerClick(terminal.key, terminal.longitude(), terminal.latitude(), isMobile);
setTerminalDrawerOpen(true);
},
location => {
updateTerminalMarkers(terminal, location.longitude, location.latitude);
}
);
termMarker.markerIcon = <PlaneIcon width={50 * 0.6} height={50 * 0.6} />;
terminalMarkers.set(terminal.key, termMarker);
searchEntries.push({
id: terminal.key,
center: [terminal.longitude(), terminal.latitude()],
place_name: terminal.name,
place_type: ["terminal"]
} as Result);
} else {
//no location was set so add to the options
terminalOps.push(terminal);
}
});
setTerminals(terminalMap);
setTerminalMarkers(terminalMarkers);
setTerminalSearchEntries(searchEntries);
setTerminalOptions(terminalOps);
});
}, [terminalAPI]); // eslint-disable-line react-hooks/exhaustive-deps
const loadGates = useCallback(() => {
gateAPI.listGates(0, 0, undefined, undefined, undefined, true).then(resp => {
let gates: Map<string, Gate> = new Map();
let gateOptions: Gate[] = [];
let gateMarkers: Map<string, MarkerData> = new Map();
let searchEntries: Result[] = [];
resp.data.gates.forEach(g => {
let gate = Gate.create(g);
gates.set(gate.key, gate);
if (
gate.longitude() &&
gate.longitude() !== 0 &&
gate.latitude() &&
gate.latitude() !== 0
) {
let marker = gate.getMarkerData(
(e, i, isMobile) => {
markerClick(gate.key, gate.longitude(), gate.latitude(), isMobile);
setGateDrawerOpen(true);
},
location => {
updateGateMarkers(gate, location.longitude, location.latitude);
}
);
marker.markerIcon = <OmniAirDeviceIcon size={50 * 0.6} />;
gateMarkers.set(gate.key, marker);
searchEntries.push({
id: gate.key,
center: [gate.longitude(), gate.latitude()],
place_name: gate.name,
place_type: ["gate"]
} as Result);
} else {
gateOptions.push(gate);
}
});
setGates(gates);
setGateOptions(gateOptions);
setGateMarkers(gateMarkers);
setGateSearchEntries(searchEntries);
});
}, [gateAPI]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
loadTerminals();
loadGates();
}, [loadTerminals, loadGates]);
useEffect(() => {
let tArr = Array.from(terminalMarkers.values());
let gArr = Array.from(gateMarkers.values());
setMarkers(tArr.concat(gArr));
}, [terminalMarkers, gateMarkers]);
useEffect(() => {
setCustomSearchEntries(terminalSearchEntries.concat(gateSearchEntries));
}, [terminalSearchEntries, gateSearchEntries]);
const moveMap = (
lat: number,
long: number,
zoom: number,
mobileOffset?: boolean,
center?: boolean
) => {
let xOff = !mobileOffset ? -250 : undefined;
let yOff = mobileOffset ? -150 : undefined;
setCurrentView({
latitude: lat,
longitude: long,
zoom: zoom,
transitionDuration: transDuration,
xOffset: center ? 0 : xOff,
yOffset: center ? 0 : yOff
});
};
const closeNewMarkerDialog = () => {
if (new Date().valueOf() > endTime) {
setOpenMarkerDialog(false);
setSelectKey("");
}
};
const setMarker = () => {
if (markerType === pond.ObjectType.OBJECT_TYPE_TERMINAL) {
let terminal = terminals.get(selectKey);
if (terminal) {
updateTerminalMarkers(terminal, newLong, newLat, true);
}
}
if (markerType === pond.ObjectType.OBJECT_TYPE_GATE) {
let gate = gates.get(selectKey);
if (gate) {
updateGateMarkers(gate, newLong, newLat, true);
}
}
setOpenMarkerDialog(false);
};
const markerDialog = () => {
return (
<ResponsiveDialog
open={openMarkerDialog}
onClose={() => {
setOpenMarkerDialog(false);
}}>
<DialogTitle>Place Marker</DialogTitle>
<DialogContent>
<Box width={300}>
{markerType === pond.ObjectType.OBJECT_TYPE_TERMINAL && (
<Autocomplete
id="autoTerminalList"
options={terminalOptions}
getOptionLabel={(option: Terminal) => option.name}
fullWidth
onChange={(e, val) => {
val && setSelectKey(val.key);
}}
renderInput={params => <TextField {...params} label="Terminal" />}
/>
)}
{markerType === pond.ObjectType.OBJECT_TYPE_GATE && (
<Autocomplete
id="autoGateList"
options={gateOptions}
getOptionLabel={(option: Gate) => option.name}
fullWidth
onChange={(e, val) => {
val && setSelectKey(val.key);
}}
renderInput={params => <TextField {...params} label="Gate" />}
/>
)}
</Box>
</DialogContent>
<DialogActions>
<Box>
<Button onClick={closeNewMarkerDialog}>Cancel</Button>
<Button onClick={setMarker}>Plot New Marker</Button>
</Box>
</DialogActions>
</ResponsiveDialog>
);
};
return (
<React.Fragment>
{markerDialog()}
<MapBase
displayMarkerTitles={markerDisplay}
customSearchEntries={customSearchEntries}
geocoderResultFunction={result => {
//open drawer
setObjectKey(result.id?.toString() ?? "");
if (result.place_type.includes("terminal")) {
setTerminalDrawerOpen(true);
} else if (result.place_type.includes("gate")) {
setGateDrawerOpen(true);
}
}}
geocoderTransitionFunction={result => {
//override the geocoders transition with our own
let long = result.center[0];
let lat = result.center[1];
if (long && lat) {
let centered = true;
let zoom = zoomLevels.far;
if (result.place_type.includes("terminal")) {
centered = false;
} else if (result.place_type.includes("gate")) {
zoom = zoomLevels.close;
centered = false;
}
moveMap(lat, long, zoom, isMobile, centered);
}
}}
markerData={markers}
placingMarker={markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN}
mapTools={
<AviationMapTools
toggleMarkerType={setMarkerType}
toggleMarkerDisplay={setMarkerDisplay}
/>
}
mapClick={e => {
setNewLong(e.lngLat.lng);
setNewLat(e.lngLat.lat);
if (markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN) {
setOpenMarkerDialog(true);
}
}}
currentView={currentView}
/>
<GateDrawer
gates={gates}
moveMap={(long, lat) => {
moveMap(lat, long, zoomLevels.close, isMobile);
}}
open={gateDrawerOpen}
onClose={() => {
if (new Date().valueOf() > endTime) {
setObjectKey("");
setGateDrawerOpen(false);
}
}}
selectedGate={objectKey}
removeMarker={key => {
if (new Date().valueOf() > endTime) {
//use the key to remove it from the map of yard markers which should cause the markerData array to update
let newMarkers = clone(gateMarkers);
newMarkers.delete(key);
setGateMarkers(newMarkers);
//add it back to the options for terminals
let gate = gates.get(key);
if (gate) {
gateOptions.push(gate);
}
}
}}
/>
<TerminalDrawer
terminals={terminals}
moveMap={(long, lat) => {
moveMap(lat, long, zoomLevels.close, isMobile);
}}
open={terminalDrawerOpen}
onClose={() => {
if (new Date().valueOf() > endTime) {
setObjectKey("");
setTerminalDrawerOpen(false);
}
}}
selectedKey={objectKey}
removeMarker={key => {
if (new Date().valueOf() > endTime) {
//use the key to remove it from the map of yard markers which should cause the markerData array to update
let newMarkers = clone(terminalMarkers);
newMarkers.delete(key);
setTerminalMarkers(newMarkers);
//add it back to the options for terminals
let terminal = terminals.get(key);
if (terminal) {
terminalOptions.push(terminal);
}
}
}}
/>
</React.Fragment>
);
}

View file

@ -0,0 +1,757 @@
import {
TextField,
DialogContent,
DialogActions,
Button,
Box,
DialogTitle,
Autocomplete
} from "@mui/material";
import React, { useCallback, useState } from "react";
import "mapbox-gl/dist/mapbox-gl.css";
import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGroupAPI, useMobile, useSnackbar } from "hooks";
import { useEffect } from "react";
import { useGlobalState, useSiteAPI } from "providers";
import ResponsiveDialog from "common/ResponsiveDialog";
import { Device, Group, Site } from "models";
// import GroupSettingsIcon from "@material-ui/icons/Settings";
import MapBase, { ViewData } from "maps/MapBase";
import ConstructionMapTools from "maps/mapObjectTools/constructionMapTools";
import { MarkerData } from "maps/mapMarkers/Markers";
import { clone } from "lodash";
import NexusSTIcon from "products/Construction/NexusSTIcon";
import DeviceDrawer from "maps/mapDrawers/DeviceDrawer";
//import { Autocomplete } from "@material-ui/lab";
import JobsiteIcon from "products/Construction/JobSiteIcon";
import SiteDrawer from "maps/mapDrawers/SiteDrawer";
import DeviceGroupIcon from "products/Construction/DeviceGroupIcon";
import GroupDrawer from "maps/mapDrawers/GroupDrawer";
import { Result } from "@mapbox/mapbox-gl-geocoder";
//import { GeocoderObject } from "maps/mapControllers/Geocoder";
interface Props {
setSiteCallback?: React.Dispatch<React.SetStateAction<pond.SiteSettings | undefined>>;
disableDrawer?: boolean;
startingView?: ViewData;
}
export default function ConstructionMapController(props: Props) {
const transDuration = 3000;
const [{ user, as }] = useGlobalState();
const [currentView, setCurrentView] = useState<ViewData>(
props.startingView
? props.startingView
: {
latitude: 52.1579,
longitude: -106.6702,
zoom: user.settings.mapZoom
}
);
const [newLong, setNewLong] = useState(0);
const [newLat, setNewLat] = useState(0);
const [siteName, setSiteName] = useState("");
const siteAPI = useSiteAPI();
const [openSiteDrawer, setOpenSiteDrawer] = useState(false);
const [SiteDialog, setSiteDialog] = useState(false);
const [sites, setSites] = useState<Map<string, Site>>(new Map());
const { openSnack } = useSnackbar();
const deviceAPI = useDeviceAPI();
const [loadingSites, setLoadingSites] = useState(false);
const [loadingDevices, setLoadingDevices] = useState(false);
//map that is the site id as the key with and array of heater data objects (heaters for that site)
const [heaterMap, setHeaterMap] = useState<Map<string, pond.ObjectHeaterData[]>>(
new Map<string, pond.ObjectHeaterData[]>()
);
const groupAPI = useGroupAPI();
const [groups, setGroups] = useState<Map<string, Group>>(new Map());
const [openMarkerDialog, setOpenMarkerDialog] = useState(false);
const [selectKey, setSelectKey] = useState("");
const [endTime, setEndTime] = useState(0);
const [devOps, setDevOps] = useState<Device[]>([]);
const [groupOps, setGroupOps] = useState<Group[]>([]);
const [devices, setDevices] = useState<Map<string, Device>>(new Map<string, Device>());
const [openDevDrawer, setOpenDevDrawer] = useState(false);
const [openGroupDrawer, setOpenGroupDrawer] = useState(false);
const [markerType, setMarkerType] = useState<pond.ObjectType>(0);
const [devMarkerData, setDevMarkerData] = useState<Map<string, MarkerData>>(new Map());
const [siteMarkerData, setSiteMarkerData] = useState<Map<string, MarkerData>>(new Map());
const [groupMarkerData, setGroupMarkerData] = useState<Map<string, MarkerData>>(new Map());
const [objectKey, setObjectKey] = useState<string>("");
const isMobile = useMobile();
const [markers, setMarkers] = useState<MarkerData[]>([]);
const [displayMarkerTitle, setDisplayMarkerTitle] = useState(false);
const zoomLevels = {
far: 14,
close: 18
};
const [customGeoSearchEntries, setCustomGeoSearchEntries] = useState<Result[]>([]);
const [siteSearchEntries, setSiteSearchEntries] = useState<Result[]>([]);
const [deviceSearchEntries, setDeviceSearchEntries] = useState<Result[]>([]);
const [groupSearchEntries, setGroupSearchEntries] = useState<Result[]>([]);
//this click delay is implemented to fix an issue with touch screens that caused the drawer to close immediately after opening
const clickDelay = () => {
let endTime = new Date().valueOf() + 1000;
setEndTime(endTime);
};
const closeDrawers = () => {
setOpenDevDrawer(false);
setOpenSiteDrawer(false);
};
//makes sure all of the Fields are filled out on the first form of the dialog otherwise the next button is disabled
const formComplete = () => {
if (siteName.length < 1) {
return false;
} else {
return true;
}
};
//save the new site to the database
const saveSite = () => {
let newSite = Site.create();
newSite.settings.userId = user.id();
newSite.settings.siteName = siteName;
newSite.settings.objectKey = user.id();
newSite.settings.longitude = newLong;
newSite.settings.latitude = newLat;
siteAPI
.addSite(newSite.settings)
.then(resp => {
openSnack("Added new Site Location");
loadSites();
})
.catch(err => {
openSnack("Failed to add site");
});
setSiteDialog(false);
};
//load the sites from the database and set the markers for each of them
const loadSites = useCallback(() => {
if (loadingSites) return;
setLoadingSites(true);
let s: Map<string, Site> = new Map();
let sm: Map<string, MarkerData> = new Map();
let hMap: Map<string, pond.ObjectHeaterData[]> = new Map<string, pond.ObjectHeaterData[]>();
let searchEntries: Result[] = [];
siteAPI
.listSitesPageData(50, 0, "asc", "key", undefined, as)
.then(resp => {
resp.data.sites.forEach(site => {
if (!site) return;
let newSite: Site = Site.any(site.site);
s.set(newSite.key(), newSite);
//if there is no long/lat on the site give it a default of saskatoon
if (!newSite.longitude() || !newSite.latitude()) {
newSite.settings.latitude = 52.1579;
newSite.settings.longitude = -106.6702;
}
let siteMarker: MarkerData = {
colour: newSite.settings.theme?.color ?? "blue",
longitude: newSite.longitude(),
latitude: newSite.latitude(),
title: newSite.siteName(),
markerIcon: <JobsiteIcon type="light" size={50 * 0.6} />,
details: [newSite.siteName()],
clickFunc: (e, i, isMobile) => {
closeDrawers();
setObjectKey(newSite.key());
setOpenSiteDrawer(true);
moveMap(newSite.latitude(), newSite.longitude(), zoomLevels.far, isMobile);
},
updateFunc: location => {
if (newSite.settings) {
updateSiteMarkers(newSite, location.longitude, location.latitude);
}
}
};
sm.set(newSite.key(), siteMarker);
hMap.set(newSite.key(), site.heaterData);
searchEntries.push({
id: newSite.key(),
center: [newSite.longitude(), newSite.latitude()],
place_name: newSite.siteName(),
place_type: ["site"]
} as Result);
});
setSiteMarkerData(sm);
setSites(s);
setLoadingSites(false);
setHeaterMap(hMap);
setSiteSearchEntries(searchEntries);
})
.catch(err => {
openSnack("Failed to load sites");
});
}, [siteAPI, openSnack, as]); // eslint-disable-line react-hooks/exhaustive-deps
const loadDevices = useCallback(() => {
if (loadingDevices) return;
setLoadingDevices(true);
deviceAPI
.list(500, 0, "asc", "name", undefined, undefined, undefined, undefined, undefined, true)
.then(resp => {
let devOps: Device[] = []; //only devices that are not mapped
let deviceMap: Map<string, Device> = new Map<string, Device>(); // all devices
let m = clone(devMarkerData);
let searchEntries: Result[] = [];
resp.data.comprehensiveDevices.forEach(cd => {
if (cd.device?.settings?.deviceId) {
let dev = Device.any(cd.device);
deviceMap.set(dev.id().toString(), dev);
let devLoc = dev.location();
if (
devLoc.longitude &&
devLoc.longitude !== 0 &&
devLoc.latitude &&
devLoc.latitude !== 0
) {
let devMarker: MarkerData = {
colour: dev.settings.theme?.color || "blue",
title: dev.name(),
longitude: devLoc.longitude,
latitude: devLoc.latitude,
markerIcon: <NexusSTIcon size={50 * 0.6} type="light" />,
details: [dev.name()],
clickFunc: (e, i, isMobile) => {
closeDrawers();
clickDelay();
setObjectKey(dev.id().toString());
setOpenDevDrawer(true);
moveMap(devLoc.latitude, devLoc.longitude, zoomLevels.far, isMobile);
},
updateFunc: location => {
if (dev.settings) {
updateDeviceMarkers(dev, location.longitude, location.latitude);
}
}
};
m.set(dev.id().toString(), devMarker);
searchEntries.push({
id: dev.id().toString(),
center: [devLoc.longitude, devLoc.latitude],
place_name: dev.name(),
place_type: ["device"]
} as Result);
} else {
devOps.push(dev);
}
}
});
setDevices(deviceMap);
setDevOps(devOps);
setLoadingDevices(false);
setDevMarkerData(m);
setDeviceSearchEntries(searchEntries);
})
.catch(err => {
openSnack("Failed to Load Devices");
});
}, [deviceAPI, openSnack]); // eslint-disable-line react-hooks/exhaustive-deps
const loadGroups = useCallback(() => {
groupAPI
.listGroups(50, 0)
.then(resp => {
let gMap: Map<string, Group> = new Map();
let groupMarkers: Map<string, MarkerData> = new Map();
let groupOps: Group[] = [];
let searchEntries: Result[] = [];
resp.data.groups.forEach((g: pond.Group) => {
let group = Group.any(g);
gMap.set(group.id().toString(), group);
if (
group.settings.longitude &&
group.settings.longitude !== 0 &&
group.settings.latitude &&
group.settings.latitude !== 0
) {
let marker: MarkerData = {
colour: "blue",
longitude: group.settings.longitude,
latitude: group.settings.latitude,
title: group.name(),
markerIcon: <DeviceGroupIcon type="light" size={50 * 0.6} />,
details: [group.name()],
clickFunc: (e, i, isMobile) => {
closeDrawers();
clickDelay();
setObjectKey(group.id().toString());
setOpenGroupDrawer(true);
moveMap(
group.settings.latitude,
group.settings.longitude,
zoomLevels.far,
isMobile
);
},
updateFunc: location => {
if (group) {
if (group.settings) {
let dataClone = clone(groupMarkerData);
updateGroupMarkers(group, location.longitude, location.latitude);
marker.longitude = location.longitude;
marker.latitude = location.latitude;
dataClone.set(group.id().toString(), marker);
setGroupMarkerData(dataClone);
}
}
}
};
groupMarkers.set(group.id().toString(), marker);
searchEntries.push({
id: group.id().toString(),
center: [group.settings.longitude, group.settings.latitude],
place_name: group.name(),
place_type: ["group"]
} as Result);
} else {
groupOps.push(group);
}
});
setGroups(gMap);
setGroupMarkerData(groupMarkers);
setGroupOps(groupOps);
setGroupSearchEntries(searchEntries);
})
.catch(err => {
//openSnack("Failed to load Groups"); //backend function errors when there are no groups
});
}, [groupAPI]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
loadSites();
loadDevices();
loadGroups();
}, [loadSites, loadDevices, loadGroups, as]);
//assemble the different objects markerdata into a single array to pass to the map
useEffect(() => {
let dArray = Array.from(devMarkerData.values());
let sArray = Array.from(siteMarkerData.values());
let gArray = Array.from(groupMarkerData.values());
setMarkers(dArray.concat(sArray, gArray));
}, [devMarkerData, siteMarkerData, groupMarkerData]);
//assemble the different object search entries into one array to pass to the map
useEffect(() => {
setCustomGeoSearchEntries(siteSearchEntries.concat(deviceSearchEntries, groupSearchEntries));
}, [deviceSearchEntries, siteSearchEntries, groupSearchEntries]);
//the entry fields for the first part of the stepper
const siteDetailsForm = () => {
return (
<Box>
<TextField
value={siteName}
color="primary"
autoFocus
margin="dense"
id="name"
label="Site Name"
type="text"
fullWidth
onChange={e => setSiteName(e.target.value)}></TextField>
</Box>
);
};
//dialog the renders the forms for creating a new site
const dialogForSite = () => {
return (
<ResponsiveDialog
fullWidth
fullScreen={false}
open={SiteDialog}
onClose={() => {
setSiteDialog(false);
}}>
<DialogTitle>Enter Name of Site</DialogTitle>
<DialogContent>{siteDetailsForm()}</DialogContent>
<DialogActions>
<Button
onClick={() => {
setSiteDialog(false);
}}
color="primary">
Cancel
</Button>
<Button onClick={saveSite} color="primary" disabled={!formComplete()}>
Add Site
</Button>
</DialogActions>
</ResponsiveDialog>
);
};
const markerDialog = () => {
return (
<ResponsiveDialog
open={openMarkerDialog}
onClose={() => {
setOpenMarkerDialog(false);
}}>
<DialogTitle>Place Marker</DialogTitle>
<DialogContent>
<Box width={300}>
{markerType === pond.ObjectType.OBJECT_TYPE_DEVICE && (
<Autocomplete
id="autoYardList"
options={devOps}
getOptionLabel={(option: Device) => option.name()}
fullWidth
onChange={(e, val) => {
val && setSelectKey(val.id().toString());
}}
renderInput={params => <TextField {...params} label="Device" />}
/>
)}
{markerType === pond.ObjectType.OBJECT_TYPE_GROUP && (
<Autocomplete
id="autoYardList"
options={groupOps}
getOptionLabel={(option: Group) => option.name()}
fullWidth
onChange={(e, val) => {
val && setSelectKey(val.id().toString());
}}
renderInput={params => <TextField {...params} label="Group" />}
/>
)}
</Box>
</DialogContent>
<DialogActions>
<Box>
<Button onClick={closeNewMarkerDialog}>Cancel</Button>
<Button onClick={setMarker}>Plot New Marker</Button>
</Box>
</DialogActions>
</ResponsiveDialog>
);
};
const setMarker = () => {
if (markerType === pond.ObjectType.OBJECT_TYPE_DEVICE) {
let dev = devices.get(selectKey);
if (dev) {
let key = dev.id().toString();
let location = dev.location();
let newDevMarker: MarkerData = {
colour: dev.settings.theme?.color || "blue",
title: dev.name(),
longitude: newLong,
latitude: newLat,
markerIcon: <NexusSTIcon size={50 * 0.6} type="light" />,
details: [dev.name()],
clickFunc: (e, i, isMobile) => {
closeDrawers();
clickDelay();
setObjectKey(key);
setOpenDevDrawer(true);
moveMap(location.latitude, location.longitude, zoomLevels.far, isMobile);
},
updateFunc: location => {
if (dev) {
if (dev.settings) {
let dataClone = clone(devMarkerData);
updateDeviceMarkers(dev, location.longitude, location.latitude);
newDevMarker.longitude = location.longitude;
newDevMarker.latitude = location.latitude;
dataClone.set(key, newDevMarker);
setDevMarkerData(dataClone);
}
}
}
};
let devMarkers = clone(devMarkerData);
devMarkers.set(key, newDevMarker);
setDevMarkerData(devMarkers);
updateDeviceMarkers(dev, newLong, newLat);
}
}
if (markerType === pond.ObjectType.OBJECT_TYPE_GROUP) {
let group = groups.get(selectKey);
if (group) {
let key = group.id().toString();
let long = group.settings.longitude;
let lat = group.settings.latitude;
let newMarker: MarkerData = {
colour: "blue",
title: group.name(),
longitude: newLong,
latitude: newLat,
markerIcon: <DeviceGroupIcon type="light" size={50 * 0.6} />,
details: [group.name()],
clickFunc: (e, i, isMobile) => {
closeDrawers();
clickDelay();
setObjectKey(key);
setOpenGroupDrawer(true);
moveMap(lat, long, zoomLevels.far, isMobile);
},
updateFunc: location => {
if (group) {
if (group.settings) {
let dataClone = clone(groupMarkerData);
updateGroupMarkers(group, location.longitude, location.latitude);
newMarker.longitude = location.longitude;
newMarker.latitude = location.latitude;
dataClone.set(key, newMarker);
setGroupMarkerData(dataClone);
}
}
}
};
let groupMarkers = clone(groupMarkerData);
groupMarkers.set(key, newMarker);
setGroupMarkerData(groupMarkers);
updateGroupMarkers(group, newLong, newLat);
}
}
closeNewMarkerDialog();
};
const closeNewMarkerDialog = () => {
if (new Date().valueOf() > endTime) {
setOpenMarkerDialog(false);
setSelectKey("");
}
};
const updateDeviceMarkers = (device: Device, long: number, lat: number) => {
device.settings.longitude = long;
device.settings.latitude = lat;
deviceAPI
.update(device.id(), device.settings)
.then(resp => {
openSnack("Device location Updated");
if (devOps.includes(device)) {
devOps.splice(devOps.indexOf(device), 1);
}
})
.catch(err => {
openSnack("Could not update Device Location");
});
};
const updateGroupMarkers = (group: Group, long: number, lat: number) => {
group.settings.longitude = long;
group.settings.latitude = lat;
groupAPI
.updateGroup(group.id(), group.settings)
.then(resp => {
openSnack("Group location Updated");
if (groupOps.includes(group)) {
groupOps.splice(groupOps.indexOf(group), 1);
}
})
.catch(err => {
openSnack("Could not update Group Location");
});
};
const updateSiteMarkers = (site: Site, long: number, lat: number) => {
site.settings.longitude = long;
site.settings.latitude = lat;
siteAPI
.updateSite(site.key(), site.settings)
.then(resp => {
openSnack("Site location updated");
})
.catch(err => {
openSnack("Could not update site location");
});
};
const moveMap = (
lat: number,
long: number,
zoom: number,
mobileOffset?: boolean,
center?: boolean
) => {
let xOff = !mobileOffset ? -250 : undefined;
let yOff = mobileOffset ? -150 : undefined;
setCurrentView({
latitude: lat,
longitude: long,
zoom: zoom,
transitionDuration: transDuration,
xOffset: center ? 0 : xOff,
yOffset: center ? 0 : yOff
});
};
return (
<Box style={{ height: "100%" }}>
<MapBase
placingMarker={markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN}
displayMarkerTitles={displayMarkerTitle}
customSearchEntries={customGeoSearchEntries}
geocoderResultFunction={result => {
//open drawer
setObjectKey(result.id?.toString() ?? "");
if (result.place_type.includes("site")) {
setOpenSiteDrawer(true);
} else if (result.place_type.includes("device")) {
setOpenDevDrawer(true);
} else if (result.place_type.includes("group")) {
setOpenGroupDrawer(true);
}
}}
geocoderTransitionFunction={result => {
//override the geocoders transition with our own
let long = result.center[0];
let lat = result.center[1];
if (long && lat) {
let centered = true;
let zoom = zoomLevels.far;
if (
result.place_type.includes("site") ||
result.place_type.includes("device") ||
result.place_type.includes("group")
) {
centered = false;
}
moveMap(lat, long, zoom, isMobile, centered);
}
}}
mapTools={
<ConstructionMapTools
toggleMarkerType={setMarkerType}
toggleMarkerDisplay={setDisplayMarkerTitle}
/>
}
mapClick={e => {
if (markerType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN) {
setNewLong(e.lngLat.lng);
setNewLat(e.lngLat.lat);
if (markerType === pond.ObjectType.OBJECT_TYPE_SITE) {
setSiteDialog(true);
} else {
setOpenMarkerDialog(true);
}
}
}}
currentView={currentView}
markerData={markers}
/>
{dialogForSite()}
{markerDialog()}
{objectKey !== "" && (
<React.Fragment>
<DeviceDrawer
open={openDevDrawer}
onClose={() => {
if (new Date().valueOf() > endTime) {
setOpenDevDrawer(false);
setObjectKey("");
}
}}
selectedDevice={objectKey}
devices={devices}
removeMarker={key => {
if (new Date().valueOf() > endTime) {
//use the key to remove it from the map of yard markers which should cause the markerData array to update
let newMarkers = clone(devMarkerData);
newMarkers.delete(key);
setDevMarkerData(newMarkers);
//adds it back to the options
let dev = devices.get(key);
if (dev) {
devOps.push(dev);
}
}
}}
updateMarker={(key, objectSettings) => {
if (new Date().valueOf() > endTime) {
let newMarkers = clone(devMarkerData);
let toupdate = newMarkers.get(key);
if (toupdate) {
if (objectSettings.theme) {
toupdate.colour = objectSettings.theme.color;
}
}
setDevMarkerData(newMarkers);
}
}}
moveMap={(long, lat) => {
moveMap(lat, long, zoomLevels.far, isMobile);
}}
/>
<SiteDrawer
open={openSiteDrawer}
sites={sites}
heaters={heaterMap}
selectedSite={objectKey}
onClose={() => {
if (new Date().valueOf() > endTime) {
setOpenSiteDrawer(false);
setObjectKey("");
}
}}
moveMap={(long, lat) => {
moveMap(lat, long, zoomLevels.far, isMobile);
}}
removeMarker={key => {
if (new Date().valueOf() > endTime) {
//use the key to remove it from the map of yard markers which should cause the markerData array to update
let newMarkers = clone(siteMarkerData);
newMarkers.delete(key);
setSiteMarkerData(newMarkers);
}
}}
updateMarker={(key, objectSettings) => {
if (new Date().valueOf() > endTime) {
let newMarkers = clone(siteMarkerData);
let toupdate = newMarkers.get(key);
if (toupdate) {
if (objectSettings.theme) {
toupdate.colour = objectSettings.theme.color;
}
}
setSiteMarkerData(newMarkers);
}
}}
reLoadSites={() => {
loadSites();
}}
/>
<GroupDrawer
open={openGroupDrawer}
groups={groups}
moveMap={(long, lat) => {
moveMap(lat, long, zoomLevels.far, isMobile);
}}
onClose={() => {
if (new Date().valueOf() > endTime) {
setOpenGroupDrawer(false);
setObjectKey("");
}
}}
removeMarker={key => {
if (new Date().valueOf() > endTime) {
//use the key to remove it from the map of yard markers which should cause the markerData array to update
let newMarkers = clone(groupMarkerData);
newMarkers.delete(key);
setGroupMarkerData(newMarkers);
//adds it back to the options
let group = groups.get(key);
if (group) {
groupOps.push(group);
}
}
}}
selectedGroup={objectKey}
/>
</React.Fragment>
)}
</Box>
);
}

View file

@ -0,0 +1,136 @@
import {
Box,
Grid2 as Grid,
IconButton,
List,
ListItem,
ListItemIcon,
Theme,
Typography,
} from "@mui/material";
import FieldNamesIcon from "products/AgIcons/FieldNames";
import AddPlaneIcon from "products/AviationIcons/AddPlaneIcon";
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import { pond } from "protobuf-ts/pond";
import React, { useState } from "react";
import { makeStyles } from "@mui/styles";
const useStyles = makeStyles((theme: Theme) => ({
button: {
background: theme.palette.background.default,
margin: 5,
boxShadow: "4px 4px 10px black",
width: 50,
height: 50
},
list: {
background: theme.palette.background.default,
opacity: 0.75,
borderRadius: 15,
marginTop: 10,
boxShadow: "4px 4px 10px black"
},
listItems: {
borderRadius: "3rem"
},
liFont: {
fontWeight: 700,
marginLeft: -15
}
}));
interface Props {
toggleMarkerType: (type: pond.ObjectType) => void;
toggleMarkerDisplay: (display: boolean) => void;
}
export default function AviationMapTools(props: Props) {
const classes = useStyles();
const { toggleMarkerType, toggleMarkerDisplay } = props;
const [airportMenu, setAirportMenu] = useState<"block" | "none">("none");
const [siteButtonHovered, setSiteButtonHovered] = useState<boolean>(false);
const [placeTerminal, setPlaceTerminal] = useState(false);
const [placeGate, setPlaceGate] = useState(false);
const [markerDisplay, setMarkerDisplay] = useState(false);
const toggleAirportMenu = () => {
if (airportMenu === "none") {
setAirportMenu("block");
} else {
setAirportMenu("none");
setPlaceTerminal(false);
setPlaceGate(false);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_UNKNOWN);
}
};
return (
<Box style={{ position: "absolute", top: 5, left: 5, zIndex: 300 }}>
<Grid container direction="column">
<Grid>
<IconButton
className={classes.button}
title={"Terminal"}
onClick={toggleAirportMenu}
onMouseOver={() => setSiteButtonHovered(true)}
onMouseOut={() => setSiteButtonHovered(false)}
style={
airportMenu === "block"
? { background: "#004f9b" }
: siteButtonHovered
? { background: "grey" }
: {}
}>
<PlaneIcon width={35} height={35} />
</IconButton>
</Grid>
<Grid style={{ display: airportMenu }}>
<List className={classes.list}>
<ListItem
id="addSite"
onClick={() => {
setPlaceTerminal(true);
setPlaceGate(false);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_TERMINAL);
}}
className={classes.listItems}
style={placeTerminal ? { background: "#004f9b" } : {}}>
<ListItemIcon>
<AddPlaneIcon height={35} width={35} />
</ListItemIcon>
<Typography className={classes.liFont}>Add Terminal</Typography>
</ListItem>
<ListItem
id="addGate"
onClick={() => {
setPlaceGate(true);
setPlaceTerminal(false);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_GATE);
}}
className={classes.listItems}
style={placeGate ? { background: "#004f9b" } : {}}>
<ListItemIcon>
<OmniAirDeviceIcon size={35} />
</ListItemIcon>
<Typography className={classes.liFont}>Add Gate</Typography>
</ListItem>
<ListItem
id="showSiteNames"
onClick={() => {
setMarkerDisplay(!markerDisplay);
toggleMarkerDisplay(!markerDisplay);
}}
className={classes.listItems}>
<ListItemIcon>
{markerDisplay ? <VisibilityOffIcon /> : <FieldNamesIcon />}
</ListItemIcon>
<Typography className={classes.liFont}>Show Marker Names</Typography>
</ListItem>
</List>
</Grid>
</Grid>
</Box>
);
}

View file

@ -0,0 +1,238 @@
import {
//Box,
Grid2 as Grid,
IconButton,
List,
ListItem,
ListItemIcon,
Theme,
Typography
} from "@mui/material";
import { Device } from "models";
import FieldNamesIcon from "products/AgIcons/FieldNames";
import DeviceGroupIcon from "products/Construction/DeviceGroupIcon";
import JobSiteIcon from "products/Construction/JobSiteIcon";
import NexusSTIcon from "products/Construction/NexusSTIcon";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
//import { useDeviceAPI, useGlobalState } from "providers";
// import { downloadJSON } from "utils";
import { useState } from "react";
import { pond } from "protobuf-ts/pond";
import { makeStyles } from "@mui/styles";
const useStyles = makeStyles((theme: Theme) => ({
button: {
background: theme.palette.background.default,
margin: 5,
boxShadow: "4px 4px 10px black",
width: 50,
height: 50
},
list: {
background: theme.palette.background.default,
opacity: 0.75,
borderRadius: 15,
marginTop: 10,
boxShadow: "4px 4px 10px black"
},
listItems: {
borderRadius: "3rem"
},
liFont: {
fontWeight: 700,
marginLeft: -15
}
}));
interface Props {
toggleMarkerType: (type: pond.ObjectType) => void;
devices?: Device[];
toggleMarkerDisplay: (display: boolean) => void;
}
export default function ConstructionMapTools(props: Props) {
const { toggleMarkerType, toggleMarkerDisplay } = props;
//const [{ user }] = useGlobalState();
const classes = useStyles();
const [siteControls, setSiteControls] = useState<"block" | "none">("none");
const [deviceControls, setDeviceControls] = useState<"block" | "none">("none");
const [placeDevice, setPlaceDevice] = useState<boolean>(false);
const [placeGroup, setPlaceGroup] = useState<boolean>(false);
const [placeSite, setPlaceSite] = useState<boolean>(false);
const [siteButtonHovered, setSiteButtonHovered] = useState<boolean>(false);
const [devButtonHovered, setDevButtonHovered] = useState<boolean>(false);
const [markerDisplay, setMarkerDisplay] = useState(false);
//const deviceAPI = useDeviceAPI();
const toggleSiteControls = () => {
if (siteControls === "none") {
setSiteControls("block");
setDeviceControls("none");
setPlaceDevice(false);
setPlaceGroup(false);
} else {
setSiteControls("none");
setPlaceSite(false);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_UNKNOWN);
}
};
const toggleDeviceControls = () => {
if (deviceControls === "none") {
setDeviceControls("block");
setSiteControls("none");
setPlaceSite(false);
} else {
setDeviceControls("none");
setPlaceDevice(false);
setPlaceGroup(false);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_UNKNOWN);
}
};
// const getDeviceGeoJson = () => {
// let ids: number[] = [];
// if (props.devices) {
// props.devices.forEach(dev => ids.push(dev.id()));
// deviceAPI.getMultiGeoJson(ids).then(resp => {
// downloadJSON(resp.data, "allDevicesGeoJSON.json");
// });
// }
// };
return (
<Grid container direction="column">
<Grid>
<Grid container direction="row">
{" "}
{/* row that has the buttons for controlling the display of the controls */}
<Grid>
<IconButton
className={classes.button}
title={"Sites"}
onClick={toggleSiteControls}
onMouseOver={() => setSiteButtonHovered(true)}
onMouseOut={() => setSiteButtonHovered(false)}
style={
siteControls === "block"
? { background: "blue" }
: siteButtonHovered
? { background: "grey" }
: {}
}>
{siteControls === "block" ? <JobSiteIcon type="light" /> : <JobSiteIcon />}
</IconButton>
</Grid>
<Grid>
<IconButton
className={classes.button}
title={"Devices"}
onClick={toggleDeviceControls}
onMouseOver={() => setDevButtonHovered(true)}
onMouseOut={() => setDevButtonHovered(false)}
style={
deviceControls === "block"
? { background: "blue" }
: devButtonHovered
? { background: "grey" }
: {}
}>
{deviceControls === "block" ? <NexusSTIcon type="light" /> : <NexusSTIcon />}
</IconButton>
</Grid>
</Grid>
</Grid>
<Grid>
<Grid container direction="row">
{" "}
{/* row that contains the controls */}
<Grid style={{ display: siteControls }}>
<List className={classes.list}>
<ListItem
id="addSite"
onClick={() => {
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_SITE);
setPlaceSite(true);
}}
className={classes.listItems}
style={placeSite ? { background: "blue" } : {}}>
<ListItemIcon>
<JobSiteIcon />
</ListItemIcon>
<Typography className={classes.liFont}>Add Site</Typography>
</ListItem>
<ListItem
id="showSiteNames"
onClick={() => {
setMarkerDisplay(!markerDisplay);
toggleMarkerDisplay(!markerDisplay);
}}
className={classes.listItems}>
<ListItemIcon>
{markerDisplay ? <VisibilityOffIcon /> : <FieldNamesIcon />}
</ListItemIcon>
<Typography className={classes.liFont}>Show Marker Names</Typography>
</ListItem>
</List>
</Grid>
<Grid style={{ display: deviceControls }}>
<List className={classes.list} style={{ marginLeft: 60 }}>
<ListItem
id="addDevice"
onClick={() => {
setPlaceDevice(true);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_DEVICE);
setPlaceGroup(false);
}}
className={classes.listItems}
style={placeDevice ? { background: "blue" } : {}}>
<ListItemIcon>
<NexusSTIcon />
</ListItemIcon>
<Typography className={classes.liFont}>Add Device</Typography>
</ListItem>
<ListItem
id="addGroup"
onClick={() => {
setPlaceGroup(true);
toggleMarkerType(pond.ObjectType.OBJECT_TYPE_GROUP);
setPlaceDevice(false);
}}
className={classes.listItems}
style={placeGroup ? { background: "blue" } : {}}>
<ListItemIcon>
<DeviceGroupIcon />
</ListItemIcon>
<Typography className={classes.liFont}>Add Group</Typography>
</ListItem>
<ListItem
id="showGroupNames"
onClick={() => {
setMarkerDisplay(!markerDisplay);
toggleMarkerDisplay(!markerDisplay);
}}
className={classes.listItems}>
<ListItemIcon>
{markerDisplay ? <VisibilityOffIcon /> : <FieldNamesIcon />}
</ListItemIcon>
<Typography className={classes.liFont}>Show Marker Names</Typography>
</ListItem>
{/* TODO: Create flag for the GeoJson data for device from map */}
{/* {user.hasAdmin() && (
<ListItem
id="exportGeoJSON"
button
onClick={() => getDeviceGeoJson()}
className={classes.listItems}>
<ListItemIcon></ListItemIcon>
<Typography className={classes.liFont}>Export GeoJSON for devices</Typography>
</ListItem>
)} */}
</List>
</Grid>
</Grid>
</Grid>
</Grid>
);
}