314 lines
10 KiB
TypeScript
314 lines
10 KiB
TypeScript
import {
|
|
Box,
|
|
Grid2 as Grid,
|
|
LinearProgress,
|
|
Typography,
|
|
} from "@mui/material";
|
|
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 { getThemeType } from "theme";
|
|
import { useMobile, useThemeType } from "hooks";
|
|
import WindSpeedDark from "assets/components/windSpeedDark.png";
|
|
import WindSpeedLight from "assets/components/windSpeedLight.png";
|
|
import { makeStyles } from "@mui/styles";
|
|
//import { WiHumidity } from "weather-icons-react";
|
|
|
|
const openWeather = new OpenWeatherMap({
|
|
//apiKey: process.env.REACT_APP_OPEN_WEATHERMAP ? process.env.REACT_APP_OPEN_WEATHERMAP : ""
|
|
apiKey: import.meta.env.VITE_OPEN_WEATHERMAP ? import.meta.env.VITE_OPEN_WEATHERMAP : ""
|
|
});
|
|
|
|
interface Props {
|
|
longitude?: number;
|
|
latitude?: number;
|
|
}
|
|
|
|
const useStyles = makeStyles(() => ({
|
|
currentBox: {
|
|
boxShadow: "0px 0px 10px black",
|
|
height: "110px",
|
|
borderRadius: 10,
|
|
margin: 7
|
|
},
|
|
upperSquare: {
|
|
height: "75%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center"
|
|
},
|
|
lowerSquare: {
|
|
height: "25%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center"
|
|
},
|
|
dark: {
|
|
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
|
|
padding: 5,
|
|
opacity: 1
|
|
},
|
|
light: {
|
|
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
|
|
padding: 5,
|
|
opacity: 1
|
|
}
|
|
}))
|
|
|
|
export default function Weather(props: Props) {
|
|
const [currentConditions, setCurrentConditions] = useState<CurrentResponse>();
|
|
const [forecast, setForecast] = useState<ThreeHourResponse>();
|
|
const classes = useStyles();
|
|
const isMobile = useMobile();
|
|
const themeType = useThemeType();
|
|
|
|
useEffect(() => {
|
|
if (props.longitude && props.latitude) {
|
|
openWeather
|
|
.getThreeHourForecastByGeoCoordinates(props.latitude, props.longitude)
|
|
.then(forecast => {
|
|
setForecast(forecast);
|
|
});
|
|
openWeather
|
|
.getCurrentWeatherByGeoCoordinates(props.latitude, props.longitude)
|
|
.then(weather => {
|
|
setCurrentConditions(weather);
|
|
});
|
|
}
|
|
}, [props.latitude, props.longitude]);
|
|
|
|
const celsiusConverter = (temp: number) => {
|
|
return (temp - 32) * (5 / 9);
|
|
};
|
|
|
|
/**
|
|
* gets the high and low of the first 8 readings in the 5 day forecast, is technically getting the high and low for tomorrow
|
|
* but is usually close to the same temperatures, for actual current high/low must use a paid plan for open weather
|
|
* @returns
|
|
*/
|
|
const getHighLow = () => {
|
|
if (!forecast) return;
|
|
let loopCounter = 0;
|
|
let currentHigh = -1000;
|
|
let currentLow = 1000;
|
|
while (loopCounter <= 7) {
|
|
let temp = forecast.list[loopCounter].main.temp;
|
|
if (temp < currentLow) {
|
|
currentLow = temp;
|
|
}
|
|
if (temp > currentHigh) {
|
|
currentHigh = temp;
|
|
}
|
|
loopCounter++;
|
|
}
|
|
return (
|
|
<Typography variant="h5" style={{ fontSize: 25 }}>
|
|
{Math.round(celsiusConverter(currentHigh))}° /{" "}
|
|
{Math.round(celsiusConverter(currentLow))}°
|
|
</Typography>
|
|
);
|
|
};
|
|
|
|
const getDirection = (current: CurrentResponse) => {
|
|
if (!current) return;
|
|
let windDegrees = current.wind.deg;
|
|
let direction = "";
|
|
|
|
if (windDegrees > 338 || windDegrees <= 23) {
|
|
direction = "N";
|
|
}
|
|
if (windDegrees > 23 && windDegrees <= 67) {
|
|
direction = "NE";
|
|
}
|
|
if (windDegrees > 67 && windDegrees <= 112) {
|
|
direction = "E";
|
|
}
|
|
if (windDegrees > 112 && windDegrees <= 158) {
|
|
direction = "SE";
|
|
}
|
|
if (windDegrees > 158 && windDegrees <= 203) {
|
|
direction = "S";
|
|
}
|
|
if (windDegrees > 203 && windDegrees <= 248) {
|
|
direction = "SW";
|
|
}
|
|
if (windDegrees > 248 && windDegrees <= 293) {
|
|
direction = "W";
|
|
}
|
|
if (windDegrees > 293 && windDegrees <= 338) {
|
|
direction = "NW";
|
|
}
|
|
return direction;
|
|
};
|
|
|
|
const dayOfWeek = (day: number) => {
|
|
switch (day) {
|
|
case 1:
|
|
return "Monday";
|
|
case 2:
|
|
return "Tuesday";
|
|
case 3:
|
|
return "Wednesday";
|
|
case 4:
|
|
return "Thursday";
|
|
case 5:
|
|
return "Friday";
|
|
case 6:
|
|
return "Saturday";
|
|
default:
|
|
return "Sunday";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Three hour response contains a list of 40 items for the weather, each 8 is a 24 hour period
|
|
* loop through 8 items to get the forecast for that 24 hour period
|
|
* @param forecast the three hour response that was retrieved
|
|
* @param day the day we are getting the forecast for as a number
|
|
*/
|
|
const getForeCast = (day: number) => {
|
|
if (!forecast) return;
|
|
let startingPoint = day * 8;
|
|
let loopCount = 0;
|
|
let high = -1000;
|
|
let low = 1000;
|
|
let d = forecast.list[startingPoint + 4].dt;
|
|
let date = new Date(0);
|
|
date.setUTCSeconds(d);
|
|
|
|
while (loopCount < 8) {
|
|
let temp = forecast.list[startingPoint].main.temp;
|
|
if (temp > high) {
|
|
high = temp;
|
|
}
|
|
if (temp < low) {
|
|
low = temp;
|
|
}
|
|
startingPoint++;
|
|
loopCount++;
|
|
}
|
|
|
|
return (
|
|
<Box className={day % 2 === 0 ? classes.dark : classes.light}>
|
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
|
<Typography variant="subtitle1" style={{ fontWeight: 600 }}>
|
|
{dayOfWeek(date.getDay())}
|
|
</Typography>
|
|
<Typography variant="subtitle1" style={{ fontWeight: 600 }}>
|
|
{Math.round(celsiusConverter(high))}° / {Math.round(celsiusConverter(low))}°
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box padding="25px">
|
|
{currentConditions && forecast ? (
|
|
<Box>
|
|
<Box component="span">
|
|
<Typography style={{ fontSize: 20, fontWeight: 750 }}>
|
|
Current Weather - {dayOfWeek(new Date().getDay())}
|
|
</Typography>
|
|
</Box>
|
|
<Grid container direction="row">
|
|
<Grid size={6}>
|
|
<Box className={classes.currentBox}>
|
|
<Box
|
|
padding="5%"
|
|
className={classes.upperSquare}
|
|
style={{ flexDirection: isMobile ? "column" : "row" }}>
|
|
<WeatherIcon
|
|
openweatherIconId={currentConditions.weather[0].id}
|
|
size={isMobile ? 35 : 50}
|
|
/>
|
|
<Typography variant="body1" style={{ fontSize: 25 }}>
|
|
{Math.round(celsiusConverter(currentConditions.main.temp))}℃
|
|
</Typography>
|
|
</Box>
|
|
<Box className={classes.lowerSquare}>
|
|
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
|
|
Feels like {Math.round(celsiusConverter(currentConditions.main.feels_like))}
|
|
℃
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Grid>
|
|
<Grid size={6}>
|
|
<Box className={classes.currentBox}>
|
|
<Box
|
|
className={classes.upperSquare}
|
|
style={{ flexDirection: isMobile ? "column" : "row" }}>
|
|
{/* <WiHumidity size={isMobile ? 35 : 50} /> */}
|
|
<Typography variant="body1" style={{ fontSize: 25, display: "inline" }}>
|
|
{currentConditions.main.humidity}%
|
|
</Typography>
|
|
</Box>
|
|
<Box className={classes.lowerSquare}>
|
|
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
|
|
Humidity
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Grid>
|
|
<Grid size={6}>
|
|
<Box className={classes.currentBox}>
|
|
<Box
|
|
className={classes.upperSquare}
|
|
style={{ flexDirection: isMobile ? "column" : "row" }}>
|
|
<img
|
|
src={themeType === "light" ? WindSpeedDark : WindSpeedLight}
|
|
alt="Wind Speed"
|
|
height={isMobile ? 35 : 50}
|
|
width={isMobile ? 35 : 50}
|
|
/>
|
|
<Box>
|
|
<Typography variant="body1" style={{ fontSize: 25, display: "inline" }}>
|
|
{Math.round(currentConditions.wind.speed * 3.6)}/
|
|
</Typography>
|
|
<Typography variant="subtitle1" style={{ fontSize: 15, display: "inline" }}>
|
|
km/h
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
<Box className={classes.lowerSquare}>
|
|
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
|
|
Direction {getDirection(currentConditions)}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Grid>
|
|
<Grid size={6}>
|
|
<Box className={classes.currentBox}>
|
|
<Box className={classes.upperSquare} style={{ paddingTop: isMobile ? 35 : 0 }}>
|
|
{getHighLow()}
|
|
</Box>
|
|
<Box className={classes.lowerSquare}>
|
|
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
|
|
Today's High/Low
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Grid>
|
|
</Grid>
|
|
<Box component="span">
|
|
<Typography style={{ fontSize: 20, fontWeight: 750 }}>5 - Day Forecast</Typography>
|
|
</Box>
|
|
<Box>
|
|
{getForeCast(0)}
|
|
{getForeCast(1)}
|
|
{getForeCast(2)}
|
|
{getForeCast(3)}
|
|
{getForeCast(4)}
|
|
</Box>
|
|
</Box>
|
|
) : (
|
|
<Box paddingTop="20px">
|
|
<LinearProgress color="primary" />
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|