From a83afa71cd058f8010df790a4a32ac55c6662285 Mon Sep 17 00:00:00 2001 From: Matt Walsh Date: Thu, 29 May 2025 08:30:01 -0500 Subject: [PATCH] code cleanup --- server/scripts/index.mjs | 3 +-- server/scripts/modules/almanac.mjs | 15 +++++------ server/scripts/modules/autocomplete.mjs | 19 ------------- server/scripts/modules/currentweather.mjs | 4 +-- server/scripts/modules/extendedforecast.mjs | 23 ++++++++-------- server/scripts/modules/hazards.mjs | 4 +-- server/scripts/modules/hourly-graph.mjs | 8 +++--- server/scripts/modules/hourly.mjs | 11 +++----- server/scripts/modules/icons/icons-hourly.mjs | 6 ++--- server/scripts/modules/latestobservations.mjs | 5 +--- server/scripts/modules/progress.mjs | 4 --- server/scripts/modules/radar.mjs | 6 ++--- .../modules/regionalforecast-utils.mjs | 2 +- server/scripts/modules/regionalforecast.mjs | 23 ++++++++-------- server/scripts/modules/share.mjs | 9 +++---- server/scripts/modules/utils/image.mjs | 27 +------------------ server/scripts/modules/utils/weather.mjs | 2 +- server/scripts/modules/weatherdisplay.mjs | 4 +-- views/partials/radar.ejs | 1 - 19 files changed, 57 insertions(+), 119 deletions(-) diff --git a/server/scripts/index.mjs b/server/scripts/index.mjs index d14b6e1..0baeb15 100644 --- a/server/scripts/index.mjs +++ b/server/scripts/index.mjs @@ -61,7 +61,7 @@ const init = () => { paramName: 'text', params: { f: 'json', - countryCode: 'USA', // 'USA,PRI,VIR,GUM,ASM', + countryCode: 'USA', category, maxSuggestions: 10, }, @@ -82,7 +82,6 @@ const init = () => { // attempt to parse the url parameters const parsedParameters = parseQueryString(); - const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon; // Auto load the parsed parameters and fall back to the previous query diff --git a/server/scripts/modules/almanac.mjs b/server/scripts/modules/almanac.mjs index 651f1d2..2b75fa9 100644 --- a/server/scripts/modules/almanac.mjs +++ b/server/scripts/modules/almanac.mjs @@ -1,5 +1,5 @@ // display sun and moon data -import { loadImg, preloadImg } from './utils/image.mjs'; +import { preloadImg } from './utils/image.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs'; import STATUS from './status.mjs'; import WeatherDisplay from './weatherdisplay.mjs'; @@ -9,9 +9,6 @@ class Almanac extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, 'Almanac', true); - // pre-load background images (returns promises) - this.backgroundImage0 = loadImg('images/backgrounds/1.png'); - // preload the moon images preloadImg(imageName('Full')); preloadImg(imageName('Last')); @@ -122,10 +119,10 @@ class Almanac extends WeatherDisplay { // sun and moon data this.elem.querySelector('.day-1').innerHTML = Today.toLocaleString({ weekday: 'long' }); this.elem.querySelector('.day-2').innerHTML = Tomorrow.toLocaleString({ weekday: 'long' }); - this.elem.querySelector('.rise-1').innerHTML = DateTime.fromJSDate(info.sun[0].sunrise).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); - this.elem.querySelector('.rise-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunrise).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); - this.elem.querySelector('.set-1').innerHTML = DateTime.fromJSDate(info.sun[0].sunset).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); - this.elem.querySelector('.set-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunset).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); + this.elem.querySelector('.rise-1').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[0].sunrise)); + this.elem.querySelector('.rise-2').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[1].sunrise)); + this.elem.querySelector('.set-1').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[0].sunset)); + this.elem.querySelector('.set-2').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[1].sunset)); const days = info.moon.map((MoonPhase) => { const fill = {}; @@ -171,6 +168,8 @@ const imageName = (type) => { } }; +const timeFormat = (dt) => dt.setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); + // register display const display = new Almanac(9, 'almanac'); registerDisplay(display); diff --git a/server/scripts/modules/autocomplete.mjs b/server/scripts/modules/autocomplete.mjs index 83a701f..5642ba7 100644 --- a/server/scripts/modules/autocomplete.mjs +++ b/server/scripts/modules/autocomplete.mjs @@ -3,43 +3,24 @@ import { json } from './utils/fetch.mjs'; const KEYS = { ESC: 27, - TAB: 9, - RETURN: 13, - LEFT: 37, UP: 38, - RIGHT: 39, DOWN: 40, ENTER: 13, }; const DEFAULT_OPTIONS = { - autoSelectFirst: false, serviceUrl: null, - lookup: null, - onSelect: () => { }, - onHint: null, - width: 'auto', minChars: 3, maxHeight: 300, deferRequestBy: 0, params: {}, - delimiter: null, zIndex: 9999, type: 'GET', - noCache: false, - preserveInput: false, containerClass: 'autocomplete-suggestions', - tabDisabled: false, - dataType: 'text', - currentRequest: null, - triggerSelectOnValidInput: true, - preventBadQueries: true, paramName: 'query', transformResult: (a) => a, showNoSuggestionNotice: false, noSuggestionNotice: 'No results', - orientation: 'bottom', - forceFixPosition: false, }; const escapeRegExChars = (string) => string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); diff --git a/server/scripts/modules/currentweather.mjs b/server/scripts/modules/currentweather.mjs index c10e7be..0766f05 100644 --- a/server/scripts/modules/currentweather.mjs +++ b/server/scripts/modules/currentweather.mjs @@ -1,6 +1,6 @@ // current weather conditions display import STATUS from './status.mjs'; -import { loadImg, preloadImg } from './utils/image.mjs'; +import { preloadImg } from './utils/image.mjs'; import { json } from './utils/fetch.mjs'; import { directionToNSEW } from './utils/calc.mjs'; import { locationCleanup } from './utils/string.mjs'; @@ -17,8 +17,6 @@ const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F' class CurrentWeather extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, 'Current Conditions', true); - // pre-load background image (returns promise) - this.backgroundImage = loadImg('images/backgrounds/1.png'); } async getData(weatherParameters, refresh) { diff --git a/server/scripts/modules/extendedforecast.mjs b/server/scripts/modules/extendedforecast.mjs index 5600e6b..b641306 100644 --- a/server/scripts/modules/extendedforecast.mjs +++ b/server/scripts/modules/extendedforecast.mjs @@ -59,11 +59,10 @@ class ExtendedForecast extends WeatherDisplay { date: Day.dayName, }; - const { low } = Day; + const { low, high } = Day; if (low !== undefined) { fill['value-lo'] = Math.round(low); } - const { high } = Day; fill['value-hi'] = Math.round(high); // return the filled template @@ -121,17 +120,17 @@ const parse = (fullForecast) => { return forecast; }; +const regexList = [ + [/ and /gi, ' '], + [/slight /gi, ''], + [/chance /gi, ''], + [/very /gi, ''], + [/patchy /gi, ''], + [/areas /gi, ''], + [/dense /gi, ''], + [/Thunderstorm/g, 'T\'Storm'], +]; const shortenExtendedForecastText = (long) => { - const regexList = [ - [/ and /gi, ' '], - [/slight /gi, ''], - [/chance /gi, ''], - [/very /gi, ''], - [/patchy /gi, ''], - [/areas /gi, ''], - [/dense /gi, ''], - [/Thunderstorm/g, 'T\'Storm'], - ]; // run all regexes const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long); diff --git a/server/scripts/modules/hazards.mjs b/server/scripts/modules/hazards.mjs index ca5c8ac..bc5cd1d 100644 --- a/server/scripts/modules/hazards.mjs +++ b/server/scripts/modules/hazards.mjs @@ -50,7 +50,7 @@ class Hazards extends WeatherDisplay { // show alert indicator if (this.data.length > 0) alert.classList.add('show'); } catch (error) { - console.error('Get hourly forecast failed'); + console.error('Get hazards failed'); console.error(error.status, error.responseJSON); if (this.isEnabled) this.setStatus(STATUS.failed); // return undefined to other subscribers @@ -129,7 +129,7 @@ class Hazards extends WeatherDisplay { // don't let offset go negative if (offsetY < 0) offsetY = 0; - // copy the scrolled portion of the canvas + // move the element this.elem.querySelector('.main').scrollTo(0, offsetY); } diff --git a/server/scripts/modules/hourly-graph.mjs b/server/scripts/modules/hourly-graph.mjs index 7914e3e..e110f34 100644 --- a/server/scripts/modules/hourly-graph.mjs +++ b/server/scripts/modules/hourly-graph.mjs @@ -6,6 +6,10 @@ import WeatherDisplay from './weatherdisplay.mjs'; import { registerDisplay, timeZone } from './navigation.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs'; +// get available space +const availableWidth = 532; +const availableHeight = 285; + class HourlyGraph extends WeatherDisplay { constructor(navId, elemId, defaultActive) { super(navId, elemId, 'Hourly Graph', defaultActive); @@ -47,10 +51,6 @@ class HourlyGraph extends WeatherDisplay { drawCanvas() { if (!this.image) this.image = this.elem.querySelector('.chart img'); - // get available space - const availableWidth = 532; - const availableHeight = 285; - this.image.width = availableWidth; this.image.height = availableHeight; diff --git a/server/scripts/modules/hourly.mjs b/server/scripts/modules/hourly.mjs index 1927350..adba555 100644 --- a/server/scripts/modules/hourly.mjs +++ b/server/scripts/modules/hourly.mjs @@ -69,8 +69,7 @@ class Hourly extends WeatherDisplay { const fillValues = {}; // hour const hour = startingHour.plus({ hours: index }); - const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' }); - fillValues.hour = formattedHour; + fillValues.hour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' }); // temperatures, convert to strings with no decimal const temperature = data.temperature.toString().padStart(3); @@ -81,12 +80,11 @@ class Hourly extends WeatherDisplay { fillValues.like = feelsLike; // wind - let wind = 'Calm'; + fillValues.wind = 'Calm'; if (data.windSpeed > 0) { const windSpeed = Math.round(data.windSpeed).toString(); - wind = data.windDirection + (Array(6 - data.windDirection.length - windSpeed.length).join(' ')) + windSpeed; + fillValues.wind = data.windDirection + (Array(6 - data.windDirection.length - windSpeed.length).join(' ')) + windSpeed; } - fillValues.wind = wind; // image fillValues.icon = { type: 'img', src: data.icon }; @@ -96,8 +94,7 @@ class Hourly extends WeatherDisplay { // alter the color of the feels like column to reflect wind chill or heat index if (feelsLike < temperature) { filledRow.querySelector('.like').classList.add('wind-chill'); - } - if (feelsLike > temperature) { + } else if (feelsLike > temperature) { filledRow.querySelector('.like').classList.add('heat-index'); } diff --git a/server/scripts/modules/icons/icons-hourly.mjs b/server/scripts/modules/icons/icons-hourly.mjs index 0a230fe..028c105 100644 --- a/server/scripts/modules/icons/icons-hourly.mjs +++ b/server/scripts/modules/icons/icons-hourly.mjs @@ -1,7 +1,7 @@ -const hourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => { - // internal function to add path to returned icon - const addPath = (icon) => `images/icons/regional-maps/${icon}`; +// internal function to add path to returned icon +const addPath = (icon) => `images/icons/regional-maps/${icon}`; +const hourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => { // possible phenomenon let thunder = false; let snow = false; diff --git a/server/scripts/modules/latestobservations.mjs b/server/scripts/modules/latestobservations.mjs index a52c747..bcef22d 100644 --- a/server/scripts/modules/latestobservations.mjs +++ b/server/scripts/modules/latestobservations.mjs @@ -22,8 +22,7 @@ class LatestObservations extends WeatherDisplay { // this is intentional because up to 30 stations are available to pull data from // calculate distance to each station - const stationsByDistance = Object.keys(StationInfo).map((key) => { - const station = StationInfo[key]; + const stationsByDistance = Object.values(StationInfo).map((station) => { const distance = calcDistance(station.lat, station.lon, this.weatherParameters.latitude, this.weatherParameters.longitude); return { ...station, distance }; }); @@ -104,8 +103,6 @@ class LatestObservations extends WeatherDisplay { linesContainer.innerHTML = ''; linesContainer.append(...lines); - // update temperature unit header - this.finishDraw(); } } diff --git a/server/scripts/modules/progress.mjs b/server/scripts/modules/progress.mjs index d9a51bd..a3dbff0 100644 --- a/server/scripts/modules/progress.mjs +++ b/server/scripts/modules/progress.mjs @@ -1,5 +1,4 @@ // regional forecast and observations -import { loadImg } from './utils/image.mjs'; import STATUS, { calcStatusClass, statusClasses } from './status.mjs'; import WeatherDisplay from './weatherdisplay.mjs'; import { @@ -10,9 +9,6 @@ class Progress extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, '', false); - // pre-load background image (returns promise) - this.backgroundImage = loadImg('images/backgrounds/1.png'); - // disable any navigation timing this.timing = false; diff --git a/server/scripts/modules/radar.mjs b/server/scripts/modules/radar.mjs index cca8cdd..0a1d768 100644 --- a/server/scripts/modules/radar.mjs +++ b/server/scripts/modules/radar.mjs @@ -100,10 +100,8 @@ class Radar extends WeatherDisplay { const urls = sortedPngs.slice(-(this.dopplerRadarImageMax)); // calculate offsets and sizes - let offsetX = 120; - let offsetY = 69; - offsetX *= 2; - offsetY *= 2; + const offsetX = 120 * 2; + const offsetY = 69 * 2; const sourceXY = utils.getXYFromLatitudeLongitudeMap(this.weatherParameters, offsetX, offsetY); const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(this.weatherParameters, offsetX, offsetY); diff --git a/server/scripts/modules/regionalforecast-utils.mjs b/server/scripts/modules/regionalforecast-utils.mjs index e8cb95f..e5cf702 100644 --- a/server/scripts/modules/regionalforecast-utils.mjs +++ b/server/scripts/modules/regionalforecast-utils.mjs @@ -20,7 +20,7 @@ const buildForecast = (forecast, city, cityXY) => { const getRegionalObservation = async (point, city) => { try { // get stations - const stations = await json(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/stations`); + const stations = await json(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/stations?limit=1`); // get the first station const station = stations.features[0].id; diff --git a/server/scripts/modules/regionalforecast.mjs b/server/scripts/modules/regionalforecast.mjs index 023ab2b..f714dc5 100644 --- a/server/scripts/modules/regionalforecast.mjs +++ b/server/scripts/modules/regionalforecast.mjs @@ -13,6 +13,12 @@ import { registerDisplay } from './navigation.mjs'; import * as utils from './regionalforecast-utils.mjs'; import { getPoint } from './utils/weather.mjs'; +// map offset +const mapOffsetXY = { + x: 240, + y: 117, +}; + class RegionalForecast extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, 'Regional Forecast', true); @@ -36,23 +42,18 @@ class RegionalForecast extends WeatherDisplay { } this.elem.querySelector('.map img').src = baseMap; - // map offset - const offsetXY = { - x: 240, - y: 117, - }; // get user's location in x/y - const sourceXY = utils.getXYFromLatitudeLongitude(this.weatherParameters.latitude, this.weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state); + const sourceXY = utils.getXYFromLatitudeLongitude(this.weatherParameters.latitude, this.weatherParameters.longitude, mapOffsetXY.x, mapOffsetXY.y, weatherParameters.state); // get latitude and longitude limits - const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, this.weatherParameters.state); + const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, mapOffsetXY.x, mapOffsetXY.y, this.weatherParameters.state); // get a target distance let targetDistance = 2.5; if (this.weatherParameters.state === 'HI') targetDistance = 1; // make station info into an array - const stationInfoArray = Object.values(StationInfo).map((value) => ({ ...value, targetDistance })); + const stationInfoArray = Object.values(StationInfo).map((station) => ({ ...station, targetDistance })); // combine regional cities with station info for additional stations // stations are intentionally after cities to allow cities priority when drawing the map const combinedCities = [...RegionalCities, ...stationInfoArray]; @@ -137,7 +138,7 @@ class RegionalForecast extends WeatherDisplay { // return the weather data and offsets this.data = { regionalData, - offsetXY, + mapOffsetXY, sourceXY, }; @@ -147,7 +148,7 @@ class RegionalForecast extends WeatherDisplay { drawCanvas() { super.drawCanvas(); // break up data into useful values - const { regionalData: data, sourceXY, offsetXY } = this.data; + const { regionalData: data, sourceXY } = this.data; // draw the header graphics @@ -170,7 +171,7 @@ class RegionalForecast extends WeatherDisplay { } // draw the map - const scale = 640 / (offsetXY.x * 2); + const scale = 640 / (mapOffsetXY.x * 2); const map = this.elem.querySelector('.map'); map.style.transform = `scale(${scale}) translate(-${sourceXY.x}px, -${sourceXY.y}px)`; diff --git a/server/scripts/modules/share.mjs b/server/scripts/modules/share.mjs index 485cac1..9eaf0b8 100644 --- a/server/scripts/modules/share.mjs +++ b/server/scripts/modules/share.mjs @@ -1,3 +1,5 @@ +import { elemForEach } from './utils/elem.mjs'; + document.addEventListener('DOMContentLoaded', () => init()); // shorthand mappings for frequently used values @@ -19,21 +21,18 @@ const init = () => { const createLink = async (e) => { // cancel default event (click on hyperlink) e.preventDefault(); - // get all checkboxes on page - const checkboxes = document.querySelectorAll('input[type=checkbox]'); // list to receive checkbox statuses const queryStringElements = {}; - [...checkboxes].forEach((elem) => { + elemForEach('input[type=checkbox]', (elem) => { if (elem?.id) { queryStringElements[elem.id] = elem?.checked ?? false; } }); // get all select boxes - const selects = document.querySelectorAll('select'); - [...selects].forEach((elem) => { + elemForEach('select', (elem) => { if (elem?.id) { queryStringElements[elem.id] = elem?.value ?? 0; } diff --git a/server/scripts/modules/utils/image.mjs b/server/scripts/modules/utils/image.mjs index 5d62363..ac6c48a 100644 --- a/server/scripts/modules/utils/image.mjs +++ b/server/scripts/modules/utils/image.mjs @@ -1,21 +1,4 @@ import { blob } from './fetch.mjs'; -import { rewriteUrl } from './cors.mjs'; - -// ****************************** load images ********************************* -// load an image from a blob or url -const loadImg = (imgData, cors = false) => new Promise((resolve) => { - const img = new Image(); - img.onload = (e) => { - resolve(e.target); - }; - if (imgData instanceof Blob) { - img.src = window.URL.createObjectURL(imgData); - } else { - let url = imgData; - if (cors) url = rewriteUrl(imgData); - img.src = url; - } -}); // preload an image // the goal is to get it in the browser's cache so it is available more quickly when the browser needs it @@ -28,15 +11,7 @@ const preloadImg = (src) => { return true; }; -const loadImgElement = (url) => new Promise((resolve, reject) => { - const image = new Image(); - image.onload = () => resolve(image); - image.onerror = reject; - image.src = url; -}); - export { - loadImg, + // eslint-disable-next-line import/prefer-default-export preloadImg, - loadImgElement, }; diff --git a/server/scripts/modules/utils/weather.mjs b/server/scripts/modules/utils/weather.mjs index cc06c70..0024bc5 100644 --- a/server/scripts/modules/utils/weather.mjs +++ b/server/scripts/modules/utils/weather.mjs @@ -2,7 +2,7 @@ import { json } from './fetch.mjs'; const getPoint = async (lat, lon) => { try { - return await json(`https://api.weather.gov/points/${lat},${lon}`); + return await json(`https://api.weather.gov/points/${lat.toFixed(4)},${lon.toFixed(4)}`); } catch (error) { console.log(`Unable to get point ${lat}, ${lon}`); console.error(error); diff --git a/server/scripts/modules/weatherdisplay.mjs b/server/scripts/modules/weatherdisplay.mjs index 34a5b75..59ee223 100644 --- a/server/scripts/modules/weatherdisplay.mjs +++ b/server/scripts/modules/weatherdisplay.mjs @@ -7,6 +7,7 @@ import { } from './navigation.mjs'; import { parseQueryString } from './share.mjs'; import settings from './settings.mjs'; +import { elemForEach } from './utils/elem.mjs'; class WeatherDisplay { constructor(navId, elemId, name, defaultEnabled) { @@ -391,8 +392,7 @@ class WeatherDisplay { this.templates = {}; this.elem = document.querySelector(`#${this.elemId}-html`); if (!this.elem) return; - const templates = this.elem.querySelectorAll('.template'); - templates.forEach((template) => { + elemForEach(`#${this.elemId}-html .template`, (template) => { const className = template.classList[0]; const node = template.cloneNode(true); node.classList.remove('template'); diff --git a/views/partials/radar.ejs b/views/partials/radar.ejs index 7b2c42d..bd36cfa 100644 --- a/views/partials/radar.ejs +++ b/views/partials/radar.ejs @@ -35,7 +35,6 @@
-