diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ea588d3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{js,ts,tsx}] +indent_size = 2 +indent_style = space diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 6ee4503..b029331 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -22,7 +22,7 @@ jobs: run: node scripts/preview.mjs "${{ github.sha }}" env: NEXT_PUBLIC_IP_API_KEY: ${{ secrets.NEXT_PUBLIC_IP_API_KEY }} - NEXT_PUBLIC_WEATHER_API_KEY: ${{ secrets.NEXT_PUBLIC_WEATHER_API_KEY }} + NEXT_PUBLIC_OPEN_WEATHER_API_KEY: ${{ secrets.NEXT_PUBLIC_OPEN_WEATHER_API_KEY }} - name: Upload Preview if: ${{ startsWith(github.event.head_commit.message, '[pre]') }} uses: elementemerald/r2-upload-action@v1.0.5 @@ -32,4 +32,4 @@ jobs: r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }} r2-bucket: ${{ secrets.R2_BUCKET }} source-dir: scripts/dist/ - destination-dir: projects/weatherscan-rewritten/autopreviews/ \ No newline at end of file + destination-dir: projects/weatherscan-rewritten/autopreviews/ diff --git a/components/CCIcon.tsx b/components/CCIcon.tsx index 25396a6..94c647c 100644 --- a/components/CCIcon.tsx +++ b/components/CCIcon.tsx @@ -2,27 +2,31 @@ import * as React from "react"; import { Icons2010, getIcon } from "../hooks/useIconMap"; interface CCIconProps { - iconCode: number - windData: number + iconCode: number; + windData: number; } const CCIcon = ({ iconCode, windData }: CCIconProps) => { - const [icon, setIcon] = React.useState(Icons2010.UNK); + const [icon, setIcon] = React.useState(Icons2010.UNK); - React.useEffect(() => { - if (typeof iconCode === "number" && typeof windData === "number") { - const mapped = getIcon(iconCode, windData); - setIcon(mapped); - } - }, [iconCode, windData]); + React.useEffect(() => { + if (typeof iconCode === "number" && typeof windData === "number") { + const mapped = getIcon(iconCode, windData); + setIcon(mapped); + } + }, [iconCode, windData]); - return ( -
- ); + return ( +
+ ); }; export default CCIcon; diff --git a/components/Current.tsx b/components/Current.tsx index acfae32..77d8a86 100644 --- a/components/Current.tsx +++ b/components/Current.tsx @@ -2,79 +2,97 @@ import * as React from "react"; import type { CurrentCond } from "../hooks/useWeather"; interface CurrentProps { - temp: number - info: Partial + temp: number; + info: Partial; } const Current = ({ temp, info }: CurrentProps) => { - const [cycle, setCycle] = React.useState([]); - const [idx, setIdx] = React.useState(0); - const [infoMsg, setInfoMsg] = React.useState(""); + const [cycle, setCycle] = React.useState([]); + const [idx, setIdx] = React.useState(0); + const [infoMsg, setInfoMsg] = React.useState(""); - React.useEffect(() => { - let intervalTimer: NodeJS.Timeout; - if (info) { - if (info.phrase !== "") { - const tempCycle = [ - `visibility ${info.visib} ${info.visib != 1 ? "miles" : "mile"}`, - `UV index ${info.uvIndex}`, - info.phrase, - `wind ${info.wind}`, - `humidity ${info.humidity}%`, - `dew point ${info.dewpt}°`, - `pressure ${info.pres}` - ]; + React.useEffect(() => { + let intervalTimer: NodeJS.Timeout; + if (info) { + if (info.phrase !== "") { + const tempCycle = [ + `Visibility: ${Math.round(info.visib / 1000)}km`, + `Humidity: ${info.humidity}%`, + `Wind: ${info.windSpeed}km/h ${info.windDirection}`, + `Pressure: ${info.pres}hPa`, + `Dew Point: ${info.dewpt}°C`, + ]; - setCycle(tempCycle); - - intervalTimer = setInterval(() => { - setIdx((idx) => { - if (idx >= (tempCycle.length - 1)) { - return 0; - } else { - return idx + 1; - } - }); - }, 6000); + setCycle(tempCycle); + + intervalTimer = setInterval(() => { + setIdx((idx) => { + if (idx >= tempCycle.length - 1) { + return 0; + } else { + return idx + 1; } - } + }); + }, 6000); + } + } - return () => clearInterval(intervalTimer); - }, [info]); + return () => clearInterval(intervalTimer); + }, [info]); - React.useEffect(() => { - if (cycle) { - if (cycle.length > 0) { - const msg = cycle[idx]; - setInfoMsg(msg); - } - } - }, [idx]); + React.useEffect(() => { + if (cycle) { + if (cycle.length > 0) { + const msg = cycle[idx]; + setInfoMsg(msg); + } + } + }, [idx]); - return ( -
- {(temp !== NaN) ? <> -
now
-
now
-
{temp.toString()}
-
{infoMsg}
- : <> -
no report
- } -
- ); + return ( +
+ {!Number.isNaN(temp) ? ( + <> +
+ now +
+
+ now +
+
+ {temp.toString() + " °C"} +
+
+ {infoMsg} +
+ + ) : ( + <> +
+ no report +
+ + )} +
+ ); }; export default Current; diff --git a/components/DateTime.tsx b/components/DateTime.tsx index 3d1e1bc..dc7a8ed 100644 --- a/components/DateTime.tsx +++ b/components/DateTime.tsx @@ -1,46 +1,62 @@ import * as React from "react"; interface DateTimeProps { - tz: string + tz: string; } const DateTime = ({ tz }: DateTimeProps) => { - const [date, setDate] = React.useState("Jan 01"); - const [time, setTime] = React.useState("12:00:00pm"); - - const updateTime = () => { - const date = new Date(); - - const formatDate = date.toString().slice(4, 10).trimEnd(); - const formatTime = date.toLocaleTimeString("en-US", { - hour: "numeric", - hour12: true, - minute: "numeric", - second: "numeric", - timeZone: tz - }).replace(/ /g, "").toLowerCase(); - - setDate(formatDate); - setTime(formatTime); - }; - - React.useEffect(() => { - let interval: NodeJS.Timeout; - - if (tz !== "") { - updateTime(); - interval = setInterval(updateTime, 500); - } - - return () => clearInterval(interval); - }, [tz]); - - return ( -
-
{date}
-
{time}
-
- ); + const [date, setDate] = React.useState("Jan 01"); + const [time, setTime] = React.useState("12:00:00pm"); + + const updateTime = () => { + const date = new Date(); + + const formatDate = date.toString().slice(4, 10).trimEnd(); + const formatTime = date + .toLocaleTimeString("en-US", { + hour: "numeric", + hour12: true, + minute: "numeric", + second: "numeric", + timeZone: tz, + }) + .replace(/ /g, "") + .toLowerCase(); + + setDate(formatDate); + setTime(formatTime); + }; + + React.useEffect(() => { + let interval: NodeJS.Timeout; + + if (tz !== "") { + updateTime(); + interval = setInterval(updateTime, 500); + } + + return () => clearInterval(interval); + }, [tz]); + + return ( +
+
+ {date} +
+
+ {time} +
+
+ ); }; export default DateTime; diff --git a/components/Display.tsx b/components/Display.tsx index 9bc3f3e..06e4877 100644 --- a/components/Display.tsx +++ b/components/Display.tsx @@ -1,13 +1,13 @@ import * as React from "react"; import type { Location, CurrentCond, Alert } from "../hooks/useWeather"; import { - defaults, - currentDefaults, - getMainLocation, - getClosestLocation, - getCurrentCond, - getAlerts, - getAlertText + defaults, + currentDefaults, + getMainLocation, + getClosestLocation, + getCurrentCond, + getAlerts, + getAlertText, } from "../hooks/useWeather"; import SlideBg from "./Slides/SlideBg"; @@ -20,161 +20,189 @@ import InfoMarquee from "./Marquee"; import MarqueeSevere from "./MarqueeSevere"; const resizeWindow = ( - mainRef: React.MutableRefObject, - winWidth: number, - winHeight: number + mainRef: React.MutableRefObject, + winWidth: number, + winHeight: number ) => { - const mainAspect = 4/3; - const windowAspect = winWidth / winHeight; - let scale: number; + const mainAspect = 4 / 3; + const windowAspect = winWidth / winHeight; + let scale: number; - const refWidth = mainRef.current.clientWidth; - const refHeight = mainRef.current.clientHeight; + const refWidth = mainRef.current.clientWidth; + const refHeight = mainRef.current.clientHeight; - if (windowAspect >= mainAspect) { - scale = winHeight / refHeight; - } else { - scale = winWidth / refWidth; - } + if (windowAspect >= mainAspect) { + scale = winHeight / refHeight; + } else { + scale = winWidth / refWidth; + } - mainRef.current.style.transform = `translate(-50%, -50%) scale(${scale})`; + mainRef.current.style.transform = `translate(-50%, -50%) scale(${scale})`; }; interface DisplayProps { - isReady: boolean - winSize: number[] - location: string - language: string - setMainVol: React.Dispatch> + isReady: boolean; + winSize: number[]; + location: string; + language: string; + setMainVol: React.Dispatch>; } -const Display = ({ isReady, winSize, location, language, setMainVol }: DisplayProps) => { - const [innerWidth, innerHeight] = winSize; - const mainRef = React.useRef(); - - const resize = () => { - resizeWindow(mainRef, innerWidth, innerHeight); - }; - - // Resize handler - React.useEffect(() => { - if (typeof window === undefined) { - return; - } - - if (mainRef && mainRef.current) { - resize(); - } - }, [mainRef, innerWidth, innerHeight]); - - const [locInfo, setLocInfo] = React.useState>(defaults); - const [currentInfo, setCurrentInfo] = React.useState>(currentDefaults); - - const [alerts, setAlerts] = React.useState([]); - const [focusedAlert, setFocusedAlert] = React.useState(null); - const [focusedAlertText, setFocusedAlertText] = React.useState(null); - - // Location handler - React.useEffect(() => { - if (isReady) { - if (location !== "") { - getMainLocation(location, language).then(data => { - setLocInfo(data); - }).catch(err => { - console.error(err); - }); - } else { - getClosestLocation().then(data => { - setLocInfo(data); - }).catch(err => { - console.error(err); - }); - } - } - }, [isReady, location]); - - const fetchCurrent = (lat: number, lon: number) => { - getCurrentCond(lat, lon, language).then(data => { - setCurrentInfo(data); - }).catch(err => { - console.error(err); - }); - }; +const Display = ({ + isReady, + winSize, + location, + language, + setMainVol, +}: DisplayProps) => { + const [innerWidth, innerHeight] = winSize; + const mainRef = React.useRef(); + + const resize = () => { + resizeWindow(mainRef, innerWidth, innerHeight); + }; + + // Resize handler + React.useEffect(() => { + if (typeof window === undefined) { + return; + } - const fetchAlerts = (lat: number, lon: number) => { - getAlerts(lat, lon, language).then(data => { - setAlerts(data); - }).catch(err => { + if (mainRef && mainRef.current) { + resize(); + } + }, [mainRef, innerWidth, innerHeight]); + + const [locInfo, setLocInfo] = React.useState>(defaults); + const [currentInfo, setCurrentInfo] = + React.useState>(currentDefaults); + + const [alerts, setAlerts] = React.useState([]); + const [focusedAlert, setFocusedAlert] = React.useState(null); + const [focusedAlertText, setFocusedAlertText] = React.useState(null); + + // Location handler + React.useEffect(() => { + if (isReady) { + if (location !== "") { + getMainLocation(location, language) + .then((data) => { + setLocInfo(data); + }) + .catch((err) => { console.error(err); + }); + } else { + getClosestLocation() + .then((data) => { + setLocInfo(data); + }) + .catch((err) => { + console.error(err); + }); + } + } + }, [isReady, location]); + + const fetchCurrent = (lat: number, lon: number) => { + getCurrentCond(lat, lon, language) + .then((data) => { + setCurrentInfo(data); + }) + .catch((err) => { + console.error(err); + }); + }; + + const fetchAlerts = (lat: number, lon: number) => { + getAlerts(lat, lon, language) + .then((data) => { + setAlerts(data); + }) + .catch((err) => { + console.error(err); + }); + }; + + // Current conditions and alerts handler + React.useEffect(() => { + if (isReady) { + const lat = locInfo.latitude; + const lon = locInfo.longitude; + + let intervalTimer: NodeJS.Timeout; + if (lat && lon) { + fetchCurrent(lat, lon); + fetchAlerts(lat, lon); + intervalTimer = setInterval(() => { + fetchCurrent(lat, lon); + fetchAlerts(lat, lon); + }, 300000); + } + + return () => clearInterval(intervalTimer); + } + }, [isReady, locInfo.latitude, locInfo.longitude]); + + React.useEffect(() => { + if (alerts.length > 0) { + setFocusedAlert(alerts[0]); + getAlertText(alerts[0].id, language) + .then((texts) => { + if (texts.length > 0) { + setFocusedAlertText(texts[0].description); + } + }) + .catch((err) => { + console.error(err); }); - }; - - // Current conditions and alerts handler - React.useEffect(() => { - if (isReady) { - const lat = locInfo.latitude; - const lon = locInfo.longitude; - - let intervalTimer: NodeJS.Timeout; - if (lat && lon) { - fetchCurrent(lat, lon); - fetchAlerts(lat, lon); - intervalTimer = setInterval(() => { - fetchCurrent(lat, lon); - fetchAlerts(lat, lon); - }, 300000); - } - - return () => clearInterval(intervalTimer); - } - }, [isReady, locInfo.latitude, locInfo.longitude]); - - React.useEffect(() => { - if (alerts.length > 0) { - setFocusedAlert(alerts[0]); - getAlertText(alerts[0].detailKey, language).then(texts => { - if (texts.length > 0) { - setFocusedAlertText(texts[0].description); - } - }).catch(err => { - console.error(err); - }) - } - }, [alerts.length]); - - /* + } + }, [alerts.length]); + + /* */ - return ( -
- background - - {isReady && } - {locInfo.timezone !== "" && } - {locInfo.city !== "" &&
{locInfo.city}
} - - - - - {focusedAlert && } + return ( +
+ background + + {isReady && } + {locInfo.timezone !== "" && } + {locInfo.city !== "" && ( +
+ {locInfo.city}
- ); + )} + + + + + {focusedAlert && ( + + )} +
+ ); }; export default Display; diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx index f79cd7f..5eae8e7 100644 --- a/components/ErrorBoundary.tsx +++ b/components/ErrorBoundary.tsx @@ -1,34 +1,32 @@ import * as React from "react"; interface Props { - key: string - children: React.ReactNode + key: string; + children: React.ReactNode; } interface State { - error: string + error: string; } class ErrorBoundary extends React.Component { - constructor(props: Props) { - super(props); - this.state = { error: "" }; - } + constructor(props: Props) { + super(props); + this.state = { error: "" }; + } - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - this.setState({ error: `${error.name}: ${error.message}` }); - } + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + this.setState({ error: `${error.name}: ${error.message}` }); + } - render() { - const { error } = this.state; - if (error) { - return ( -
{error}
- ) - } else { - return <>{this.props.children} - } + render() { + const { error } = this.state; + if (error) { + return
{error}
; + } else { + return <>{this.props.children}; } + } } export default ErrorBoundary; diff --git a/components/Intro.tsx b/components/Intro.tsx index e6f3292..70b00a4 100644 --- a/components/Intro.tsx +++ b/components/Intro.tsx @@ -3,142 +3,181 @@ import * as React from "react"; import perlin from "../hooks/usePerlin"; const resizeWindow = ( - mainRef: React.MutableRefObject, - winWidth: number, - winHeight: number + mainRef: React.MutableRefObject, + winWidth: number, + winHeight: number ) => { - const mainAspect = 4/3; - const windowAspect = winWidth / winHeight; - let scale: number; - - const main = document.getElementById("main"); - - if (main instanceof HTMLElement) { - const mainWidth = main.clientWidth; - const mainHeight = main.clientHeight; - - if (windowAspect >= mainAspect) { - scale = winHeight / mainHeight; - } else { - scale = winWidth / mainWidth; - } - - mainRef.current.style.transform = `translate(-50%, -50%) scale(${scale})`; + const mainAspect = 4 / 3; + const windowAspect = winWidth / winHeight; + let scale: number; + + const main = document.getElementById("main"); + + if (main instanceof HTMLElement) { + const mainWidth = main.clientWidth; + const mainHeight = main.clientHeight; + + if (windowAspect >= mainAspect) { + scale = winHeight / mainHeight; + } else { + scale = winWidth / mainWidth; } + + mainRef.current.style.transform = `translate(-50%, -50%) scale(${scale})`; + } }; interface IntroProps { - winSize: number[] - callback: () => void + winSize: number[]; + callback: () => void; } const Intro = ({ winSize, callback }: IntroProps) => { - const [innerWidth, innerHeight] = winSize; + const [innerWidth, innerHeight] = winSize; + + const mainRef = React.useRef(); + const intellistarRef = React.useRef(); - const mainRef = React.useRef(); - const intellistarRef = React.useRef(); + function resize() { + resizeWindow(mainRef, innerWidth, innerHeight); + } - function resize() { - resizeWindow(mainRef, innerWidth, innerHeight); + React.useEffect(() => { + if (typeof window === undefined) { + return; } - React.useEffect(() => { - if (typeof window === undefined) { - return; - } - - if (mainRef && mainRef.current) { - resize(); - } - }, [mainRef, innerWidth, innerHeight]); - - React.useEffect(() => { - if (intellistarRef && intellistarRef.current) { - // Intro spinner - const startxx = Math.random()*1000; - const startxy = Math.random()*1000; - const startyx = Math.random()*1000; - const startyy = Math.random()*1000; - const startzx = Math.random()*1000; - const startzy = Math.random()*1000; - - let rotatex = perlin.get(startxx, startxy)*2; - let rotatey = perlin.get(startyx, startyy)*2; - let rotatez = perlin.get(startzx, startzy)*2; - - intellistarRef.current.style.transition = "transform 2s linear"; - intellistarRef.current.style.transform = `rotatex(${rotatex}turn) rotatey(${rotatey}turn) rotatez(${rotatez}turn)`; - - let rotInterval: NodeJS.Timeout; - let time = 0; - - setTimeout(() => { - rotInterval = setInterval(() => { - time += .005; - - rotatex = perlin.get(startxx + time, startxy + time)*2; - rotatey = perlin.get(startyx + time, startyy + time)*2; - rotatez = perlin.get(startzx + time, startzy + time)*2; - - intellistarRef.current.style.transition = "transform 1s linear"; - intellistarRef.current.style.transform = `rotatex(${rotatex}turn) rotatey(${rotatey}turn) rotatez(${rotatez}turn)`; - }, 100); - }, 1000); - - setTimeout(() => { - clearInterval(rotInterval); - mainRef.current.classList.add("hidden"); - callback(); - }, 5000); - } - }, [intellistarRef]); - - return ( -
-
-
-
headend id:
-
serial number:
-
location name:
-
affiliate name:
-
-
-
appearance settings
-
location settings
-
weather settings
-
other settings
-
Save
-
- weatherscan -
-
- - - -
-
-
Location Settings
-
- intellistar + if (mainRef && mainRef.current) { + resize(); + } + }, [mainRef, innerWidth, innerHeight]); + + React.useEffect(() => { + if (intellistarRef && intellistarRef.current) { + // Intro spinner + const startxx = Math.random() * 1000; + const startxy = Math.random() * 1000; + const startyx = Math.random() * 1000; + const startyy = Math.random() * 1000; + const startzx = Math.random() * 1000; + const startzy = Math.random() * 1000; + + let rotatex = perlin.get(startxx, startxy) * 2; + let rotatey = perlin.get(startyx, startyy) * 2; + let rotatez = perlin.get(startzx, startzy) * 2; + + intellistarRef.current.style.transition = "transform 2s linear"; + intellistarRef.current.style.transform = `rotatex(${rotatex}turn) rotatey(${rotatey}turn) rotatez(${rotatez}turn)`; + + let rotInterval: NodeJS.Timeout; + let time = 0; + + setTimeout(() => { + rotInterval = setInterval(() => { + time += 0.005; + + rotatex = perlin.get(startxx + time, startxy + time) * 2; + rotatey = perlin.get(startyx + time, startyy + time) * 2; + rotatez = perlin.get(startzx + time, startzy + time) * 2; + + intellistarRef.current.style.transition = "transform 1s linear"; + intellistarRef.current.style.transform = `rotatex(${rotatex}turn) rotatey(${rotatey}turn) rotatez(${rotatez}turn)`; + }, 100); + }, 1000); + + setTimeout(() => { + clearInterval(rotInterval); + mainRef.current.classList.add("hidden"); + callback(); + }, 5000); + } + }, [intellistarRef]); + + return ( +
+
+
+
+ headend id: +
+
+ serial number: +
+
+ location name: +
+
+ affiliate name: +
+
+
+
+ appearance settings +
+
+ location settings +
+
+ weather settings +
+
+ other settings +
+
+ Save +
+
+ weatherscan +
+
+ + + +
+
+
+ Location Settings
- ); +
+ intellistar +
+ ); }; export default Intro; diff --git a/components/LogoArea.tsx b/components/LogoArea.tsx index d780192..8975afc 100644 --- a/components/LogoArea.tsx +++ b/components/LogoArea.tsx @@ -1,13 +1,16 @@ import * as React from "react"; const LogoArea = () => ( -
- network -
+
+ network +
); export default LogoArea; diff --git a/components/Marquee.tsx b/components/Marquee.tsx index beaa66b..236575e 100644 --- a/components/Marquee.tsx +++ b/components/Marquee.tsx @@ -33,19 +33,21 @@ import Marquee from "./CustomMarquee"; */ interface InfoMarqueeProps { - top: string - bottom: string + top: string; + bottom: string; } const InfoMarquee = ({ top, bottom }: InfoMarqueeProps) => ( -
- - {top} - - - {bottom} - -
+
+ + {top} + + + + {bottom} + + +
); export default InfoMarquee; diff --git a/components/MarqueeSevere.tsx b/components/MarqueeSevere.tsx index f9c6c43..954e895 100644 --- a/components/MarqueeSevere.tsx +++ b/components/MarqueeSevere.tsx @@ -2,23 +2,29 @@ import * as React from "react"; import Marquee from "./CustomMarquee"; interface MarqueeSevereProps { - top: string - bottom: string + top: string; + bottom: string; } const MarqueeSevere = ({ top, bottom }: MarqueeSevereProps) => ( - <> -
- {top} -
-
- - - {bottom} - - -
- + <> +
+ {top} +
+
+ + + {bottom} + + +
+ ); export default MarqueeSevere; diff --git a/components/MusicAudio.tsx b/components/MusicAudio.tsx index fe10815..211692f 100644 --- a/components/MusicAudio.tsx +++ b/components/MusicAudio.tsx @@ -100,4 +100,4 @@ const MusicAudio = ({ vol }: MusicAudioProps) => { ); }; -export default MusicAudio; +export default MusicAudio; \ No newline at end of file diff --git a/components/VocalAudio.tsx b/components/VocalAudio.tsx index 7273ca6..d3bf116 100644 --- a/components/VocalAudio.tsx +++ b/components/VocalAudio.tsx @@ -4,32 +4,32 @@ import { useAudioPlayer } from "react-use-audio-player"; export const enum VocalMale {} export const enum VocalFemale { - RADAR = "/vocals/female/The_local_Doppler_radar.mp3", - CURRENT_COND = "/vocals/female/Your_current_conditions.mp3", - LOCAL_FORECAST_1 = "/vocals/female/Your_local_forecast_1.mp3", - LOCAL_FORECAST_2 = "/vocals/female/Your_local_forecast_2.mp3" + RADAR = "/vocals/female/The_local_Doppler_radar.mp3", + CURRENT_COND = "/vocals/female/Your_current_conditions.mp3", + LOCAL_FORECAST_1 = "/vocals/female/Your_local_forecast_1.mp3", + LOCAL_FORECAST_2 = "/vocals/female/Your_local_forecast_2.mp3", } interface VocalProps { - vocal?: VocalMale | VocalFemale - setMainVol: React.Dispatch> + vocal?: VocalMale | VocalFemale; + setMainVol: React.Dispatch>; } const VocalAudio = ({ vocal, setMainVol }: VocalProps) => { - const { load } = useAudioPlayer(); + const { load } = useAudioPlayer(); - React.useEffect(() => { - if (vocal) { - load({ - src: vocal, - autoplay: true, - onplay: () => setMainVol(0.25), - onend: () => setMainVol(1) - }); - } - }, [vocal]); + React.useEffect(() => { + if (vocal) { + load({ + src: vocal, + autoplay: true, + onplay: () => setMainVol(0.25), + onend: () => setMainVol(1), + }); + } + }, [vocal]); - return null; + return null; }; export default VocalAudio; diff --git a/hooks/useCalc.ts b/hooks/useCalc.ts index 0d70f2b..3ea39db 100644 --- a/hooks/useCalc.ts +++ b/hooks/useCalc.ts @@ -1,14 +1,26 @@ type WidthType = "inner" | "outer" | "width" | "full"; export const getWidth = (el: HTMLElement, type: WidthType) => { - if (type === 'inner') // .innerWidth() - return el.clientWidth; - else if (type === 'outer') // .outerWidth() - return el.offsetWidth; - let s = window.getComputedStyle(el, null); - if (type === 'width') // .width() - return el.clientWidth - parseInt(s.getPropertyValue('padding-left')) - parseInt(s.getPropertyValue('padding-right')); - else if (type === 'full') // .outerWidth(includeMargins = true) - return el.offsetWidth + parseInt(s.getPropertyValue('margin-left')) + parseInt(s.getPropertyValue('margin-right')); - return null; -} \ No newline at end of file + if (type === "inner") + // .innerWidth() + return el.clientWidth; + else if (type === "outer") + // .outerWidth() + return el.offsetWidth; + let s = window.getComputedStyle(el, null); + if (type === "width") + // .width() + return ( + el.clientWidth - + parseInt(s.getPropertyValue("padding-left")) - + parseInt(s.getPropertyValue("padding-right")) + ); + else if (type === "full") + // .outerWidth(includeMargins = true) + return ( + el.offsetWidth + + parseInt(s.getPropertyValue("margin-left")) + + parseInt(s.getPropertyValue("margin-right")) + ); + return null; +}; diff --git a/hooks/useIconMap.ts b/hooks/useIconMap.ts index 26527fa..8d775d1 100644 --- a/hooks/useIconMap.ts +++ b/hooks/useIconMap.ts @@ -1,115 +1,123 @@ export enum Icons2010 { - UNUSED, - THUNDER, - MIXED_RAIN_SNOW, - MIXED_FREEZE_RAIN_SNOW, - FREEZING_DRIZZLE, - DRIZZLE, - FREEZING_RAIN, - LIGHT_RAIN_ALT, - RAIN, - SNOW, - SNOW_ALT, - BLOWING_SNOW, - HEAVY_SNOW, - HAIL, - SMOKE, - FOG, - SMOKE_ALT, - WIND, - HEAVY_WIND, - UNK, - CLOUDS, - MOSTLY_CLOUDS_NIGHT, - MOSTLY_CLOUDS, - PARTLY_CLOUDS_NIGHT, - PARTLY_CLOUDS, - CLEAR_NIGHT, - CLEAR, - LIGHT_CLOUDS_NIGHT, - LIGHT_CLOUDS, - THUNDER_SUN, - RAIN_SUN, - HEAVY_RAIN, - HEAVY_SNOW_SUN, - HEAVY_SNOW_ALT, - HEAVY_SNOW_ALT2, - RAIN_NIGHT, - SNOW_NIGHT, - THUNDER_NIGHT, - MIXED_SNOW_SLEET, - SNOW_WIND, - SNOW_STORM, - HOT, - COLD, - HEAVY_RAIN_SNOW_ICY, - HEAVY_RAIN_ALT, - LIGHT_THUNDER, - STRONG_STORMS, - HAIL_ALT, - FOG_ALT, - HEAVY_WIND_ALT -}; + UNUSED, + THUNDER, + MIXED_RAIN_SNOW, + MIXED_FREEZE_RAIN_SNOW, + FREEZING_DRIZZLE, + DRIZZLE, + FREEZING_RAIN, + LIGHT_RAIN_ALT, + RAIN, + SNOW, + SNOW_ALT, + BLOWING_SNOW, + HEAVY_SNOW, + HAIL, + SMOKE, + FOG, + SMOKE_ALT, + WIND, + HEAVY_WIND, + UNK, + CLOUDS, + MOSTLY_CLOUDS_NIGHT, + MOSTLY_CLOUDS, + PARTLY_CLOUDS_NIGHT, + PARTLY_CLOUDS, + CLEAR_NIGHT, + CLEAR, + LIGHT_CLOUDS_NIGHT, + LIGHT_CLOUDS, + THUNDER_SUN, + RAIN_SUN, + HEAVY_RAIN, + HEAVY_SNOW_SUN, + HEAVY_SNOW_ALT, + HEAVY_SNOW_ALT2, + RAIN_NIGHT, + SNOW_NIGHT, + THUNDER_NIGHT, + MIXED_SNOW_SLEET, + SNOW_WIND, + SNOW_STORM, + HOT, + COLD, + HEAVY_RAIN_SNOW_ICY, + HEAVY_RAIN_ALT, + LIGHT_THUNDER, + STRONG_STORMS, + HAIL_ALT, + FOG_ALT, + HEAVY_WIND_ALT, +} const Icons2010CodeMap = Object.freeze([ - Icons2010.UNUSED, // 0 - Icons2010.UNUSED, // 1 - Icons2010.UNUSED, // 2 - Icons2010.STRONG_STORMS, // 3 - Icons2010.THUNDER, // 4 - Icons2010.MIXED_RAIN_SNOW, // 5 - Icons2010.MIXED_SNOW_SLEET, // 6 - Icons2010.MIXED_FREEZE_RAIN_SNOW, // 7 - Icons2010.FREEZING_DRIZZLE, // 8 - Icons2010.DRIZZLE, // 9 - Icons2010.FREEZING_RAIN, // 10 - Icons2010.RAIN, // 11 - Icons2010.RAIN, // 12 - Icons2010.SNOW, // 13 - Icons2010.HEAVY_SNOW, // 14 - Icons2010.BLOWING_SNOW, // 15 - Icons2010.HEAVY_SNOW, // 16 - Icons2010.HAIL_ALT, // 17 - Icons2010.HAIL_ALT, // 18 - Icons2010.SMOKE_ALT, // 19 - Icons2010.FOG_ALT, // 20 - Icons2010.SMOKE_ALT, // 21 - Icons2010.SMOKE, // 22 - Icons2010.UNUSED, // 23 - Icons2010.HEAVY_WIND_ALT, // 24 - Icons2010.HEAVY_SNOW_ALT2, // 25 - Icons2010.CLOUDS, // 26 - Icons2010.MOSTLY_CLOUDS_NIGHT, // 27 - Icons2010.MOSTLY_CLOUDS, // 28 - Icons2010.PARTLY_CLOUDS_NIGHT, // 29 - Icons2010.PARTLY_CLOUDS, // 30 - Icons2010.CLEAR_NIGHT, // 31 - Icons2010.CLEAR, // 32 - Icons2010.LIGHT_CLOUDS_NIGHT, // 33 - Icons2010.LIGHT_CLOUDS, // 34 - Icons2010.HAIL, // 35 - Icons2010.HOT, // 36 - Icons2010.THUNDER_SUN, // 37 - Icons2010.THUNDER_SUN, // 38 - Icons2010.RAIN_SUN, // 39 - Icons2010.HEAVY_RAIN, // 40 - Icons2010.HEAVY_SNOW_SUN, // 41 - Icons2010.HEAVY_SNOW_ALT, // 42 - Icons2010.HEAVY_SNOW_ALT2, // 43 - Icons2010.UNK, // 44 - Icons2010.RAIN_NIGHT, // 45 - Icons2010.SNOW_NIGHT, // 46 - Icons2010.THUNDER_NIGHT // 47 + Icons2010.UNUSED, // 0 + Icons2010.UNUSED, // 1 + Icons2010.UNUSED, // 2 + Icons2010.STRONG_STORMS, // 3 + Icons2010.THUNDER, // 4 + Icons2010.MIXED_RAIN_SNOW, // 5 + Icons2010.MIXED_SNOW_SLEET, // 6 + Icons2010.MIXED_FREEZE_RAIN_SNOW, // 7 + Icons2010.FREEZING_DRIZZLE, // 8 + Icons2010.DRIZZLE, // 9 + Icons2010.FREEZING_RAIN, // 10 + Icons2010.RAIN, // 11 + Icons2010.RAIN, // 12 + Icons2010.SNOW, // 13 + Icons2010.HEAVY_SNOW, // 14 + Icons2010.BLOWING_SNOW, // 15 + Icons2010.HEAVY_SNOW, // 16 + Icons2010.HAIL_ALT, // 17 + Icons2010.HAIL_ALT, // 18 + Icons2010.SMOKE_ALT, // 19 + Icons2010.FOG_ALT, // 20 + Icons2010.SMOKE_ALT, // 21 + Icons2010.SMOKE, // 22 + Icons2010.UNUSED, // 23 + Icons2010.HEAVY_WIND_ALT, // 24 + Icons2010.HEAVY_SNOW_ALT2, // 25 + Icons2010.CLOUDS, // 26 + Icons2010.MOSTLY_CLOUDS_NIGHT, // 27 + Icons2010.MOSTLY_CLOUDS, // 28 + Icons2010.PARTLY_CLOUDS_NIGHT, // 29 + Icons2010.PARTLY_CLOUDS, // 30 + Icons2010.CLEAR_NIGHT, // 31 + Icons2010.CLEAR, // 32 + Icons2010.LIGHT_CLOUDS_NIGHT, // 33 + Icons2010.LIGHT_CLOUDS, // 34 + Icons2010.HAIL, // 35 + Icons2010.HOT, // 36 + Icons2010.THUNDER_SUN, // 37 + Icons2010.THUNDER_SUN, // 38 + Icons2010.RAIN_SUN, // 39 + Icons2010.HEAVY_RAIN, // 40 + Icons2010.HEAVY_SNOW_SUN, // 41 + Icons2010.HEAVY_SNOW_ALT, // 42 + Icons2010.HEAVY_SNOW_ALT2, // 43 + Icons2010.UNK, // 44 + Icons2010.RAIN_NIGHT, // 45 + Icons2010.SNOW_NIGHT, // 46 + Icons2010.THUNDER_NIGHT, // 47 ]); -export const getIcon = (iconCode: number, windData: number) : number => { - let icon = Icons2010CodeMap[iconCode]; - if (windData >= 20) { - if (icon === Icons2010.HEAVY_SNOW || icon === Icons2010.SNOW || icon === Icons2010.SNOW_ALT) { - icon = Icons2010.HEAVY_SNOW_ALT2; - } else if (icon === Icons2010.HEAVY_RAIN || icon === Icons2010.LIGHT_RAIN_ALT || icon === Icons2010.RAIN) { - icon = Icons2010.HEAVY_RAIN_ALT; - } +export const getIcon = (iconCode: number, windData: number): number => { + let icon = Icons2010CodeMap[iconCode]; + if (windData >= 20) { + if ( + icon === Icons2010.HEAVY_SNOW || + icon === Icons2010.SNOW || + icon === Icons2010.SNOW_ALT + ) { + icon = Icons2010.HEAVY_SNOW_ALT2; + } else if ( + icon === Icons2010.HEAVY_RAIN || + icon === Icons2010.LIGHT_RAIN_ALT || + icon === Icons2010.RAIN + ) { + icon = Icons2010.HEAVY_RAIN_ALT; } - return icon; -}; \ No newline at end of file + } + return icon; +}; diff --git a/hooks/usePerlin.ts b/hooks/usePerlin.ts index b559a30..59d253c 100644 --- a/hooks/usePerlin.ts +++ b/hooks/usePerlin.ts @@ -1,59 +1,58 @@ /* https://github.com/joeiddon/perlin */ type Memory = { - [index: string]: number -} + [index: string]: number; +}; const gradients: Memory = {}; const memory: Memory = {}; const perlin = { - rand_vect: function() { - let theta = Math.random() * 2 * Math.PI; - return {x: Math.cos(theta), y: Math.sin(theta)}; - }, - dot_prod_grid: function(x: number, y: number, vx: number, vy: number) { - let g_vect; - let d_vect = {x: x - vx, y: y - vy}; - if (gradients[`${vx},${vy}`]){ - g_vect = gradients[`${vx},${vy}`]; - } else { - g_vect = this.rand_vect(); - gradients[`${vx},${vy}`] = g_vect; - } - return d_vect.x * g_vect.x + d_vect.y * g_vect.y; - }, - smootherstep: function(x: number) { - return 6*x**5 - 15*x**4 + 10*x**3; - }, - interp: function(x: number, a: number, b: number) { - return a + this.smootherstep(x) * (b-a); - }, - get: function(x: number, y: number) : number { - if (memory.hasOwnProperty(`${x},${y}`)) - return memory[`${x},${y}`]; - let xf = Math.floor(x); - let yf = Math.floor(y); - //interpolate - let tl = this.dot_prod_grid(x, y, xf, yf); - let tr = this.dot_prod_grid(x, y, xf+1, yf); - let bl = this.dot_prod_grid(x, y, xf, yf+1); - let br = this.dot_prod_grid(x, y, xf+1, yf+1); - let xt = this.interp(x-xf, tl, tr); - let xb = this.interp(x-xf, bl, br); - let v = this.interp(y-yf, xt, xb); - memory[`${x},${y}`] = v; - return v; - }, - purge: function() { - for (const key in gradients) { - delete gradients[key]; - } + rand_vect: function () { + let theta = Math.random() * 2 * Math.PI; + return { x: Math.cos(theta), y: Math.sin(theta) }; + }, + dot_prod_grid: function (x: number, y: number, vx: number, vy: number) { + let g_vect; + let d_vect = { x: x - vx, y: y - vy }; + if (gradients[`${vx},${vy}`]) { + g_vect = gradients[`${vx},${vy}`]; + } else { + g_vect = this.rand_vect(); + gradients[`${vx},${vy}`] = g_vect; + } + return d_vect.x * g_vect.x + d_vect.y * g_vect.y; + }, + smootherstep: function (x: number) { + return 6 * x ** 5 - 15 * x ** 4 + 10 * x ** 3; + }, + interp: function (x: number, a: number, b: number) { + return a + this.smootherstep(x) * (b - a); + }, + get: function (x: number, y: number): number { + if (memory.hasOwnProperty(`${x},${y}`)) return memory[`${x},${y}`]; + let xf = Math.floor(x); + let yf = Math.floor(y); + //interpolate + let tl = this.dot_prod_grid(x, y, xf, yf); + let tr = this.dot_prod_grid(x, y, xf + 1, yf); + let bl = this.dot_prod_grid(x, y, xf, yf + 1); + let br = this.dot_prod_grid(x, y, xf + 1, yf + 1); + let xt = this.interp(x - xf, tl, tr); + let xb = this.interp(x - xf, bl, br); + let v = this.interp(y - yf, xt, xb); + memory[`${x},${y}`] = v; + return v; + }, + purge: function () { + for (const key in gradients) { + delete gradients[key]; + } - for (const key in memory) { - delete memory[key]; - } + for (const key in memory) { + delete memory[key]; } -} + }, +}; export default perlin; diff --git a/hooks/useSlides.ts b/hooks/useSlides.ts index 916132a..67d574a 100644 --- a/hooks/useSlides.ts +++ b/hooks/useSlides.ts @@ -1,37 +1,37 @@ export const enum Slides { - INTRO, - INFO + INTRO, + INFO, } export interface SlideProps { - next: () => void + next: () => void; } export const enum ActionType { - INCREASE = "INCREASE", - DECREASE = "DECREASE", - SET = "SET" + INCREASE = "INCREASE", + DECREASE = "DECREASE", + SET = "SET", } interface Action { - type: ActionType - payload: number + type: ActionType; + payload: number; } interface State { - index: number + index: number; } export const SlideshowReducer = (state: State, action: Action) => { - const { type, payload } = action; - switch (type) { - case ActionType.INCREASE: - return { index: state.index + payload }; - case ActionType.DECREASE: - return { index: state.index - payload }; - case ActionType.SET: - return { index: payload }; - default: - return state; - } -}; \ No newline at end of file + const { type, payload } = action; + switch (type) { + case ActionType.INCREASE: + return { index: state.index + payload }; + case ActionType.DECREASE: + return { index: state.index - payload }; + case ActionType.SET: + return { index: payload }; + default: + return state; + } +}; diff --git a/hooks/useWeather.ts b/hooks/useWeather.ts index 8df9607..8d4be12 100644 --- a/hooks/useWeather.ts +++ b/hooks/useWeather.ts @@ -3,382 +3,464 @@ import { Icons2010 } from "./useIconMap"; // Mapped location export interface Location { - city: string - state: string - country: string - countryCode: string - latitude: number - longitude: number - timezone: string + city: string; + state: string; + country: string; + countryCode: string; + latitude: number; + longitude: number; + timezone: string; } -// api.weather.com interface WeatherAPILocation { - city: string[] - adminDistrict: string[] - country: string[] - countryCode: string[] - latitude: number[] - longitude: number[] - ianaTimeZone: string[] + city: string[]; + adminDistrict: string[]; + country: string[]; + countryCode: string[]; + latitude: number[]; + longitude: number[]; + ianaTimeZone: string[]; } interface WeatherAPILocResponse { - location: Partial + location: Partial; } // ip-api.com enum IPAPIStatus { - SUCCESS = "success", - FAIL = "fail" + SUCCESS = "success", + FAIL = "fail", } interface IPAPILocResponse { - status: IPAPIStatus - city: string - region: string - regionName: string - country: string - countryCode: string - lat: number - lon: number - timezone: string + status: IPAPIStatus; + city: string; + region: string; + regionName: string; + country: string; + countryCode: string; + lat: number; + lon: number; + timezone: string; } export const defaults: Location = Object.freeze({ - status: "", - city: "", - state: "", - country: "", - countryCode: "", - latitude: null, - longitude: null, - timezone: "" + status: "", + city: "", + state: "", + country: "", + countryCode: "", + latitude: null, + longitude: null, + timezone: "", + nearbyCities: [], }); export interface CurrentCond { - temp: number - icon: number - wind: string - windSpeed: number - visib: number - uvIndex: string - phrase: string - humidity: number - dewpt: number - pres: number + temp: number; + icon: number; + wind: string; + windSpeed: number; + windDirection: number; + visib: number; + uvIndex: string; + phrase: string; + humidity: number; + dewpt: number; + pres: number; } export const currentDefaults: CurrentCond = Object.freeze({ - temp: NaN, - icon: 44, - wind: "", - windSpeed: 0, - visib: 0, - uvIndex: "", - phrase: "", - humidity: 0, - dewpt: 0, - pres: 0 + temp: NaN, + icon: 44, + wind: "", + windSpeed: 0, + windDirection: 0, + visib: 0, + uvIndex: "", + phrase: "", + humidity: 0, + dewpt: 0, + pres: 0, }); const memoAsync = (cb) => { - const cache = new Map(); - return async (...args) => { - const key = JSON.stringify(args); - if (cache.has(key)) return cache.get(key); - const value = await cb(...args); - cache.set(key, value); - return value; - }; + const cache = new Map(); + return async (...args) => { + const key = JSON.stringify(args); + if (cache.has(key)) return cache.get(key); + const value = await cb(...args); + cache.set(key, value); + return value; + }; }; const memoizedFetch = memoAsync(fetch); export const getMainLocation = async (location: string, language?: string) => { - const apiLanguage = language || "en-US"; - - return memoizedFetch(`https://api.weather.com/v3/location/search?query=${encodeURIComponent(location)}&language=${apiLanguage}&format=json&apiKey=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}`) - .then(async (res: Response) => { - return res.json().then((data: WeatherAPILocResponse) => { - const dataLocs = data.location; - - const loc = Object.assign({}, defaults); - loc.city = dataLocs.city[0]; - loc.state = dataLocs.adminDistrict[0]; - loc.country = dataLocs.country[0]; - loc.countryCode = dataLocs.countryCode[0]; - loc.latitude = dataLocs.latitude[0]; - loc.longitude = dataLocs.longitude[0]; - loc.timezone = dataLocs.ianaTimeZone[0]; - - return loc; - }).catch(err => { - throw new Error(err); - }); - }).catch(err => { - throw new Error(err); + const apiLanguage = language || "en"; + return memoizedFetch( + `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent( + location + )}&appid=${ + process.env.NEXT_PUBLIC_OPEN_WEATHER_API_KEY + }&units=metric&lang=${apiLanguage}` + ) + .then(async (res: Response) => { + return res + .json() + .then((data: any) => { + const loc = Object.assign({}, defaults); + loc.city = data.name; + loc.state = data.sys.country; + loc.country = data.sys.country; + loc.countryCode = data.sys.country; + loc.latitude = data.coord.lat; + loc.longitude = data.coord.lon; + loc.timezone = data.timezone; + + return loc; + }) + .catch((err) => { + throw new Error(err); }); + }) + .catch((err) => { + throw new Error(err); + }); }; export const getClosestLocation = async () => { - return memoizedFetch(`https://pro.ip-api.com/json/?key=${process.env.NEXT_PUBLIC_IP_API_KEY}&exposeDate=true`) - .then(async (res: Response) => { - return res.json().then((data: IPAPILocResponse) => { - if (data.status === IPAPIStatus.SUCCESS) { - const closest = Object.assign({}, defaults); - closest.city = data.city; - closest.state = data.region; - closest.country = data.country; - closest.countryCode = data.countryCode; - closest.latitude = data.lat; - closest.longitude = data.lon; - closest.timezone = data.timezone; - - return closest; - } - - return defaults; - }).catch(err => { - throw new Error(err); - }); - }).catch(err => { - throw new Error(err); + return memoizedFetch( + `https://pro.ip-api.com/json/?key=${process.env.NEXT_PUBLIC_IP_API_KEY}&exposeDate=true` + ) + .then(async (res: Response) => { + return res + .json() + .then((data: IPAPILocResponse) => { + if (data.status === IPAPIStatus.SUCCESS) { + const closest = Object.assign({}, defaults); + closest.city = data.city; + closest.state = data.region; + closest.country = data.country; + closest.countryCode = data.countryCode; + closest.latitude = data.lat; + closest.longitude = data.lon; + closest.timezone = data.timezone; + + return closest; + } + + return defaults; + }) + .catch((err) => { + throw new Error(err); }); + }) + .catch((err) => { + throw new Error(err); + }); }; export const getExtraLocations = async (lat: number, lon: number) => { - return memoizedFetch(`https://api.weather.com/v3/location/near?geocode=${lat},${lon}&product=observation&format=json&apiKey=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}`) - .then(res => { - + // Get extra locations with OpenWeather API + return memoizedFetch( + `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&exclude=current,minutely,hourly,alerts&appid=${process.env.NEXT_PUBLIC_OPEN_WEATHER_API_KEY}&units=metric` + ) + .then(async (res: Response) => { + return res + .json() + .then((data: any) => { + const locations = data.daily.map((day: any) => { + const loc = Object.assign({}, defaults); + loc.city = data.timezone; + loc.state = data.timezone; + loc.country = data.timezone; + loc.countryCode = data.timezone; + loc.latitude = data.lat; + loc.longitude = data.lon; + loc.timezone = data.timezone; + + return loc; + }); + + return locations; + }) + .catch((err) => { + throw new Error(err); }); + }) + .catch((err) => { + throw new Error(err); + }); }; -export const getCurrentCond = async (lat: number, lon: number, language?: string) => { - const apiLanguage = language || "en-US"; - return memoizedFetch(`https://api.weather.com/v3/aggcommon/v3-wx-observations-current?geocodes=${lat},${lon};&language=${apiLanguage}&units=s&format=json&apiKey=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}`) - .then(async (res: Response) => { - return res.json().then(data => { - const dataLoc = data[0]["v3-wx-observations-current"]; - - const current = Object.assign({}, currentDefaults); - current.temp = dataLoc.temperature; - current.icon = dataLoc.iconCode; - current.wind = `${(dataLoc.windDirectionCardinal === "CALM" || dataLoc.windSpeed === 0 ? "Calm" : dataLoc.windDirectionCardinal)} ${dataLoc.windSpeed === 0 ? "" : dataLoc.windSpeed}`; - current.windSpeed = dataLoc.windSpeed; - current.visib = dataLoc.visibility; - current.uvIndex = dataLoc.uvDescription; - current.phrase = dataLoc.wxPhraseLong.toLowerCase(); - current.humidity = dataLoc.relativeHumidity; - current.dewpt = dataLoc.temperatureDewPoint; - current.pres = dataLoc.pressureAltimeter.toFixed(2); - - return current; - }).catch(err => { - throw new Error(err); - }); - }).catch(err => { - throw new Error(err); +// Get region condition from OpenWeather API +export const getCurrentCond = async ( + lat: number, + lon: number, + language?: string +) => { + const apiLanguage = language || "en"; + return memoizedFetch( + `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&exclude=minutely,hourly,daily,alerts&appid=${process.env.NEXT_PUBLIC_OPEN_WEATHER_API_KEY}&units=metric&lang=${apiLanguage}` + ) + .then(async (res: Response) => { + return res + .json() + .then((data: any) => { + const current = Object.assign({}, currentDefaults); + current.temp = Math.round(data.main.temp); + current.icon = data.weather[0].icon; + current.wind = data.wind.deg; + current.windSpeed = data.wind.speed; + current.windDirection = data.wind.deg; + current.visib = data.visibility; + current.uvIndex = data.uvi || ""; + current.phrase = data.weather[0].description; + current.humidity = data.main.humidity; + current.dewpt = data.main.feels_like; + current.pres = data.main.pressure; + + return current; + }) + .catch((err) => { + throw new Error(err); }); + }) + .catch((err) => { + throw new Error(err); + }); }; enum MessageType { - New = 1, - Update, - Cancel + New = 1, + Update, + Cancel, } enum AreaType { - County = "C", - Zone = "Z", - CanadaLocation = "CLC" + County = "C", + Zone = "Z", + CanadaLocation = "CLC", } enum Certainty { - Observed = 1, - Likely, - Possible, - Unlikely, - Unknown + Observed = 1, + Likely, + Possible, + Unlikely, + Unknown, } enum Severity { - Extreme = 1, - Severe, - Moderate, - Minor, - Unknown + Extreme = 1, + Severe, + Moderate, + Minor, + Unknown, } enum Urgency { - Immediate = 1, - Expected, - Future, - Past, - Unknown + Immediate = 1, + Expected, + Future, + Past, + Unknown, } // Category object enum CategoryCode { - Geo = 1, - Met, - Safety, - Security, - Rescue, - Fire, - Health, - Env, - Transport, - Infra, - CBRNE, - Other + Geo = 1, + Met, + Safety, + Security, + Rescue, + Fire, + Health, + Env, + Transport, + Infra, + CBRNE, + Other, } interface Category { - category: string - categoryCode: CategoryCode + category: string; + categoryCode: CategoryCode; } // Flood object interface Flood { - floodCrestTimeLocal: string - floodCrestTimeLocalTimeZone: string - floodEndTimeLocal: string - floodEndTimeLocalTimeZone: string - floodImmediateCause: string - floodImmediateCauseCode: string - floodLocationId: string - floodLocationName: string - floodRecordStatus: string - floodRecordStatusCode: string - floodSeverity: string - floodSeverityCode: string - floodStartTimeLocal: string - floodStartTimeLocalTimeZone: string + areaDesc: string; + geocode: { + valueName: string; + value: string; + }; + polygon: string; + severity: Severity; + certainty: Certainty; + urgency: Urgency; + instruction: string; + headline: string; + description: string; + effective: string; + onset: string; + expires: string; + senderName: string; + contact: string; + msgType: MessageType; + source: string; + event: string; + response: string; + parameter: { + valueName: string; + value: string; + }; } // Response type object enum ResponseTypeCode { - Shelter = 1, - Evacuate, - Prepare, - Execute, - Avoid, - Monitor, - Assess, - AllClear, - None + Shelter = 1, + Evacuate, + Prepare, + Execute, + Avoid, + Monitor, + Assess, + AllClear, + None, } interface ResponseType { - responseType: string - responseTypeCode: ResponseTypeCode + responseType: string; + responseTypeCode: ResponseTypeCode; } interface Text { - languageCode: string - description: string - instruction: string - overview: string + languageCode: string; + description: string; + instruction: string; + overview: string; } +// Alert interface for OpenWeather API export interface Alert { - adminDistrict: string - adminDistrictCode: string - areaId: string - areaName: string - areaTypeCode: AreaType - certainty: string - certaintyCode: Certainty - countryCode: string - countryName: string - detailKey: string - disclaimer: string - effectiveTimeLocal: string - effectiveTimeLocalTimeZone: string - eventDescription: string - eventTrackingNumber: string - expireTimeLocal: string - expireTimeLocalTimeZone: string - expireTimeUTC: number - headlineText: string - ianaTimeZone: string - identifier: string - issueTimeLocal: string - issueTimeLocalTimeZone: string - latitude: number - longitude: number - messageType: string - messageTypeCode: MessageType - officeAdminDistrict: string - officeAdminDistrictCode: string - officeCode: string - officeCountryCode: string - officeName: string - onsetTimeLocal: string - onsetTimeLocalTimeZone: string - phenomena: string - processTimeUTC: number - productIdentifier: string - severity: string - severityCode: Severity - significance: string - source: string - urgency: string - urgencyCode: Urgency - endTimeLocal: string - endTimeLocalTimeZone: string - endTimeUTC: number - categories: Category[] - flood: Flood - responseTypes: ResponseType[] - texts: Text[] + id: number; + senderName: string; + sent: string; + status: string; + msgType: MessageType; + source: string; + scope: string; + restriction: string; + addresses: string; + code: string; + note: string; + references: string; + incidents: string; + info: { + language: string; + category: Category[]; + event: string; + responseType: ResponseType[]; + urgency: Urgency; + severity: Severity; + certainty: Certainty; + audience: string; + eventCode: string; + effective: string; + onset: string; + expires: string; + senderName: string; + headline: string; + description: string; + instruction: string; + web: string; + contact: string; + parameter: any; + resource: any; + area: { + areaDesc: string; + polygon: string; + geocode: { + valueName: string; + value: string; + }[]; + circle: string; + altitude: number; + ceiling: number; + }[]; + flood: Flood; + text: Text; + }[]; } interface AlertsMetadata { - next: any + next: any; } interface AlertsResponse { - metadata?: AlertsMetadata - alerts: Alert[] + alerts: Alert[]; + metadata?: AlertsMetadata; } const alertsFallback: AlertsResponse = Object.freeze({ - metadata: { - next: null - }, - alerts: [] + metadata: { + next: null, + }, + alerts: [], }); -export const getAlerts = async (lat: number, lon: number, language?: string) => { - const apiLanguage = language || "en-US"; - return memoizedFetch(`https://api.weather.com/v3/alerts/headlines?geocode=${lat},${lon}&language=${apiLanguage}&format=json&apiKey=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}`) - .then(async (res: Response) => { - return res.json().then((data: AlertsResponse) => { - return data.alerts; - }).catch(() => { - return alertsFallback.alerts; - }); - }).catch(err => { - throw new Error(err); +export const getAlerts = async ( + lat: number, + lon: number, + language?: string +) => { + const apiLanguage = language || "en-US"; + return memoizedFetch( + `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&exclude=minutely,hourly,daily&appid=${process.env.NEXT_PUBLIC_OPEN_WEATHER_API_KEY}&lang=${apiLanguage}` + ) + .then(async (res: Response) => { + return res + .json() + .then((data: any) => { + const alerts = data.alerts || []; + return alerts; + }) + .catch((err) => { + throw new Error(err); }); + }) + .catch((err) => { + throw new Error(err); + }); }; interface AlertDetailResponse { - alertDetail: Alert + alert: Alert; } -export const getAlertText = async (alertId: string, language?: string) : Promise => { - const apiLanguage = language || "en-US"; - return memoizedFetch(`https://api.weather.com/v3/alerts/detail?alertId=${alertId}&language=${apiLanguage}&format=json&apiKey=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}`) - .then(async (res: Response) => { - return res.json().then((data: AlertDetailResponse) => { - return data.alertDetail.texts; - }).catch(() => { - return null; - }); - }).catch(err => { - throw new Error(err); +export const getAlertText = async ( + alertId: string | number, + language?: string +): Promise => { + const apiLanguage = language || "en-US"; + return memoizedFetch( + `https://api.openweathermap.org/data/2.5/alerts?appid=${process.env.NEXT_PUBLIC_OPEN_WEATHER_API_KEY}&id=${alertId}&lang=${apiLanguage}` + ) + .then(async (res: Response) => { + return res + .json() + .then((data) => { + const alert = data.alerts[0]; + return alert.info.text; }) -}; \ No newline at end of file + .catch((err) => { + throw new Error(err); + }); + }) + .catch((err) => { + throw new Error(err); + }); +}; diff --git a/hooks/useWinSize.ts b/hooks/useWinSize.ts index f0883b2..3fb64b2 100644 --- a/hooks/useWinSize.ts +++ b/hooks/useWinSize.ts @@ -1,42 +1,42 @@ import * as React from "react"; export const useWinSizeInner = () => { - const [size, setSize] = React.useState([0, 0]); - React.useEffect(() => { - let resizeTimer: NodeJS.Timeout; - const updateSize = () => { - setSize([window.innerWidth, window.innerHeight]); - }; - - window.addEventListener("resize", updateSize); - updateSize(); - return () => { - window.removeEventListener("resize", updateSize); - clearTimeout(resizeTimer); - }; - }, []); - - return size; + const [size, setSize] = React.useState([0, 0]); + React.useEffect(() => { + let resizeTimer: NodeJS.Timeout; + const updateSize = () => { + setSize([window.innerWidth, window.innerHeight]); + }; + + window.addEventListener("resize", updateSize); + updateSize(); + return () => { + window.removeEventListener("resize", updateSize); + clearTimeout(resizeTimer); + }; + }, []); + + return size; }; export const useWinSizeOuter = () => { - const [size, setSize] = React.useState([0, 0]); - React.useEffect(() => { - let resizeTimer; - const updateSize = () => { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(() => { - setSize([window.outerWidth, window.outerHeight]); - }, 100); - }; - - window.addEventListener("resize", updateSize); - updateSize(); - return () => { - window.removeEventListener("resize", updateSize); - clearTimeout(resizeTimer); - }; - }, []); - - return size; -}; \ No newline at end of file + const [size, setSize] = React.useState([0, 0]); + React.useEffect(() => { + let resizeTimer; + const updateSize = () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + setSize([window.outerWidth, window.outerHeight]); + }, 100); + }; + + window.addEventListener("resize", updateSize); + updateSize(); + return () => { + window.removeEventListener("resize", updateSize); + clearTimeout(resizeTimer); + }; + }, []); + + return size; +}; diff --git a/scripts/preview.mjs b/scripts/preview.mjs index 72727de..35c09e9 100644 --- a/scripts/preview.mjs +++ b/scripts/preview.mjs @@ -29,7 +29,7 @@ const __dirname = path.dirname(__filename); res.end("Internal server error"); } }); - + server.listen(3000, err => { if (err) throw err; console.log("Preview server is up. Attempting to grab a screenshot once loaded.");