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

@ -172,7 +172,7 @@ export default function Device(props: Props) {
}, [load]);
const getOrderedComponents = () => {
if (preferences.childDisplayOrder.length === Array.from(components.values()).length) {
if (preferences.childDisplayOrder && preferences.childDisplayOrder.length === Array.from(components.values()).length) {
return Array.from(components.values()).sort((a, b: Component) =>
sortComponents(a, b, preferences.childDisplayOrder)
);

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>
);
}

80
src/models/Site.ts Normal file
View file

@ -0,0 +1,80 @@
import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
export class Site {
public settings: pond.SiteSettings = pond.SiteSettings.create();
public status: pond.SiteStatus = pond.SiteStatus.create();
public static create(ps?: pond.Site): Site {
let my = new Site();
if (ps) {
my.settings = pond.SiteSettings.fromObject(cloneDeep(or(ps.settings, {})));
my.status = pond.SiteStatus.fromObject(cloneDeep(or(ps.status, {})));
}
return my;
}
public static clone(other?: Site): Site {
if (other) {
return Site.create(
pond.Site.fromObject({
settings: cloneDeep(other.settings),
status: cloneDeep(other.status)
})
);
}
return Site.create();
}
public static any(data: any): Site {
return Site.create(pond.Site.fromObject(cloneDeep(data)));
}
public key(): string {
return this.settings.key;
}
public longitude(): number {
return this.settings.longitude;
}
public latitude(): number {
return this.settings.latitude;
}
public siteName(): string {
return this.settings.siteName;
}
public siteAddress(): string {
return this.settings.siteAddress;
}
public siteDescription(): string {
return this.settings.siteDescription;
}
public clientName(): string {
return this.settings.clientName;
}
public clientPhone(): string {
return this.settings.clientPhone;
}
public jobType(): string {
return this.settings.jobType;
}
public jobDetails(): string {
return this.settings.jobDetails;
}
public location(): pond.Location {
let loc = pond.Location.create();
loc.longitude = this.settings.longitude;
loc.latitude = this.settings.latitude;
return loc;
}
}

View file

@ -8,7 +8,7 @@ export * from "./Group";
export * from "./Interaction";
export * from "./Scope";
export * from "./ShareableLink";
// export * from "./Site";
export * from "./Site";
export * from "./Tag";
// export * from "./Task";
// export * from "./Upgrade";

View file

@ -11,6 +11,8 @@ import FieldMap from "pages/FieldMap";
import GrainBag from "pages/grainBag";
import Terminals from "pages/Terminals";
import Gate from "pages/Gate";
import AviationMap from "pages/AviationMap";
import ConstructionSiteMap from "pages/ConstructionSiteMap";
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
const DevicePage = lazy(() => import("pages/Device"));
@ -246,7 +248,12 @@ export default function Router(props: Props) {
{/* Page routes */}
{/* <Route index element={<Typography>Hello!</Typography>} /> */}
<Route path="users" element={<Users/>} />
{/* Map pages */}
<Route path="visualFarm" element={<FieldMap />} />
<Route path="aviationMap" element={<AviationMap />} />
<Route path="constructionMap" element={<ConstructionSiteMap />} />
<Route path="/logout" element={<Logout />} />
<Route path="grainbags/:bagID" element={<GrainBag />} />
{/*

View file

@ -37,6 +37,7 @@ import MiningIcon from "products/ventilation/MiningIcon";
import { useAuth0 } from "@auth0/auth0-react";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import PlaneIcon from "products/AviationIcons/PlaneIcon";
import AirportMapIcon from "products/AviationIcons/AirportMapIcon";
const drawerWidth = 230;
@ -237,6 +238,20 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(IsOmniAir || user.hasFeature("admin")) && (
<Tooltip title="Aviation Map" placement="right">
<ListItemButton
id="tour-aviation-map"
onClick={() => goTo("/aviationMap")}
classes={getClasses("/aviationMap")}
>
<ListItemIcon>
<AirportMapIcon />
</ListItemIcon>
{open && <ListItemText primary="Visual Farm" />}
</ListItemButton>
</Tooltip>
)}
</List>
)
}

26
src/pages/AviationMap.tsx Normal file
View file

@ -0,0 +1,26 @@
import AviationMapController from "maps/mapObjectControllers/AviationMapController";
import { useLocation } from "react-router";
import PageContainer from "./PageContainer";
export default function AviationMap() {
const location = useLocation();
return (
<PageContainer>
{location.state !== undefined &&
location.state !== null &&
location.state.long !== undefined &&
location.state.lat !== undefined ? (
<AviationMapController
startingView={{
longitude: location.state.long,
latitude: location.state.lat,
zoom: 18
}}
/>
) : (
<AviationMapController />
)}
</PageContainer>
);
}

View file

@ -0,0 +1,27 @@
import PageContainer from "./PageContainer";
import ConstructionMapController from "maps/mapObjectControllers/ConstructionMapController";
import { useLocation } from "react-router";
export default function ConstructionSiteMap() {
const location = useLocation();
return (
<PageContainer>
{location.state !== undefined &&
location.state !== null &&
location.state.long !== undefined &&
location.state.lat !== undefined ? (
<ConstructionMapController
startingView={{
longitude: location.state.long,
latitude: location.state.lat,
zoom: 18,
transitionDuration: 0
}}
/>
) : (
<ConstructionMapController />
)}
</PageContainer>
);
}

View file

@ -25,7 +25,7 @@ export {
// useMetricAPI,
usePermissionAPI,
// usePreferenceAPI,
// useSiteAPI,
useSiteAPI, //TODO: update api with resolve, reject
useTagAPI,
useTeamAPI,
useImagekitAPI,

View file

@ -25,6 +25,7 @@ import FieldMarkerProvider, {useFieldMarkerAPI} from "./fieldMarkerAPI";
import HomeMarkerProvider, {useHomeMarkerAPI} from "./homeMarkerAPI";
import HarvestPlanProvider, {useHarvestPlanAPI} from "./harvestPlanAPI";
import TerminalProvider, {useTerminalAPI} from "./terminalAPI"
import SiteProvider, {useSiteAPI} from "./siteAPI";
// import NoteProvider from "providers/noteAPI";
export const pondURL = (partial: string, demo: boolean = false): string => {
@ -66,7 +67,9 @@ export default function PondProvider(props: PropsWithChildren<any>) {
<HomeMarkerProvider>
<HarvestPlanProvider>
<TerminalProvider>
{children}
<SiteProvider>
{children}
</SiteProvider>
</TerminalProvider>
</HarvestPlanProvider>
</HomeMarkerProvider>
@ -119,5 +122,6 @@ export {
useFieldMarkerAPI,
useHomeMarkerAPI,
useHarvestPlanAPI,
useTerminalAPI
useTerminalAPI,
useSiteAPI
};

View file

@ -0,0 +1,179 @@
import { AxiosResponse } from "axios";
import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers/StateContainer";
import React, { createContext, PropsWithChildren, useContext } from "react";
import { or } from "utils";
import { pondURL } from "./pond";
export interface ISiteAPIContext {
addSite: (site: pond.SiteSettings) => Promise<any>;
getSite: (siteID: string) => Promise<any>;
getSitePage: (siteID: string) => Promise<any>;
listSites: (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
as?: string,
asRoot?: boolean
) => Promise<AxiosResponse<pond.ListSitesResponse>>;
listSitesPageData: (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
as?: string,
asRoot?: boolean
) => Promise<AxiosResponse<pond.ListSitesPageDataResponse>>;
removeSite: (siteID: string) => Promise<AxiosResponse<pond.RemoveSiteResponse>>;
updateSite: (
key: string,
site: pond.SiteSettings,
asRoot?: true
) => Promise<AxiosResponse<pond.UpdateSiteResponse>>;
updateLink: (
parentKey: string,
parentType: string,
objectID: string,
objectType: string,
permissions: string[]
) => Promise<any>;
}
export const SiteAPIContext = createContext<ISiteAPIContext>({} as ISiteAPIContext);
interface Props {}
export default function SiteProvider(props: PropsWithChildren<Props>) {
const { children } = props;
const { get, del, post, put } = useHTTP();
const [{ as }] = useGlobalState();
const getSitePage = (siteID: string) => {
if (as) return get(pondURL("/sitePage/" + siteID + "?as=" + as));
return get(pondURL("/sitePage/" + siteID));
};
const addSite = (site: pond.SiteSettings) => {
if (as) return post(pondURL("/sites?as=" + as), site);
return post(pondURL("/sites"), site);
};
const getSite = (siteID: string) => {
if (as) return get(pondURL("/site/" + siteID + "?as=" + as));
return get(pondURL("/site/" + siteID));
};
const removeSite = (key: string) => {
return del<pond.RemoveSiteResponse>(pondURL("/sites/" + key));
};
const listSites = (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
as?: string,
asRoot?: boolean
) => {
return get<pond.ListSitesResponse>(
pondURL(
"/sites" +
"?limit=" +
limit +
"&offset=" +
offset +
("&order=" + or(order, "asc")) +
("&by=" + or(orderBy, "key")) +
(as ? "&as=" + as : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
(search ? "&search=" + search : "")
)
);
};
const listSitesPageData = (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
as?: string,
asRoot?: boolean
) => {
return get<pond.ListSitesPageDataResponse>(
pondURL(
"/sitesWithData" +
"?limit=" +
limit +
"&offset=" +
offset +
("&order=" + or(order, "asc")) +
("&by=" + or(orderBy, "key")) +
(as ? "&as=" + as : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
(search ? "&search=" + search : "")
)
);
};
const updateSite = (key: string, site: pond.SiteSettings, asRoot?: boolean) => {
if (as)
return put<pond.UpdateSiteResponse>(
pondURL(
"/sites/" + key + (as ? "?as=" + as : "") + (asRoot ? "&asRoot=" + asRoot.toString() : "")
),
site
);
return put<pond.UpdateSiteResponse>(
pondURL("/sites/" + key + (asRoot ? "?asRoot=" + asRoot.toString() : "")),
site
);
};
const updateLink = (
parentID: string,
parentType: string,
objectID: string,
objectType: string,
permissions: string[]
) => {
if (as)
return post(pondURL(`/sites/` + parentID + `/link?as=${as}`), {
Key: objectID,
Type: objectType,
Parent: parentID,
ParentType: parentType,
Permissions: permissions
});
return post(pondURL(`/sites/` + parentID + `/link`), {
Key: objectID,
Type: objectType,
Parent: parentID,
ParentType: parentType,
Permissions: permissions
});
};
return (
<SiteAPIContext.Provider
value={{
addSite,
getSite,
listSites,
listSitesPageData,
removeSite,
updateSite,
updateLink,
getSitePage
}}>
{children}
</SiteAPIContext.Provider>
);
}
export const useSiteAPI = () => useContext(SiteAPIContext);

View file

@ -8,7 +8,7 @@ import { useState } from "react";
import OpenWeatherMap from "openweathermap-ts";
import { CurrentResponse, ThreeHourResponse } from "openweathermap-ts/dist/types";
import { useEffect } from "react";
//import WeatherIcon from "./weatherIcon";
import WeatherIcon from "./weatherIcon";
import { getThemeType } from "theme";
import { useMobile, useThemeType } from "hooks";
import WindSpeedDark from "assets/components/windSpeedDark.png";
@ -220,10 +220,10 @@ export default function Weather(props: Props) {
padding="5%"
className={classes.upperSquare}
style={{ flexDirection: isMobile ? "column" : "row" }}>
{/* <WeatherIcon
<WeatherIcon
openweatherIconId={currentConditions.weather[0].id}
size={isMobile ? 35 : 50}
/> */}
/>
<Typography variant="body1" style={{ fontSize: 25 }}>
{Math.round(celsiusConverter(currentConditions.main.temp))}&#8451;
</Typography>

View file

@ -28,7 +28,7 @@ interface Props {
//need to get Dustin/Sandaru to make some custom weather icons
export default function WeatherIcon(props: Props) {
const size = props.size ? props.size : 25;
//const size = props.size ? props.size : 25;
const iconId = props.openweatherIconId;
switch (iconId) {