code cleanup

This commit is contained in:
Matt Walsh
2025-05-29 08:30:01 -05:00
parent 74f1abd6f8
commit a83afa71cd
19 changed files with 57 additions and 119 deletions

View File

@@ -61,7 +61,7 @@ const init = () => {
paramName: 'text', paramName: 'text',
params: { params: {
f: 'json', f: 'json',
countryCode: 'USA', // 'USA,PRI,VIR,GUM,ASM', countryCode: 'USA',
category, category,
maxSuggestions: 10, maxSuggestions: 10,
}, },
@@ -82,7 +82,6 @@ const init = () => {
// attempt to parse the url parameters // attempt to parse the url parameters
const parsedParameters = parseQueryString(); const parsedParameters = parseQueryString();
const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon; const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon;
// Auto load the parsed parameters and fall back to the previous query // Auto load the parsed parameters and fall back to the previous query

View File

@@ -1,5 +1,5 @@
// display sun and moon data // 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 { DateTime } from '../vendor/auto/luxon.mjs';
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
@@ -9,9 +9,6 @@ class Almanac extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
super(navId, elemId, 'Almanac', true); super(navId, elemId, 'Almanac', true);
// pre-load background images (returns promises)
this.backgroundImage0 = loadImg('images/backgrounds/1.png');
// preload the moon images // preload the moon images
preloadImg(imageName('Full')); preloadImg(imageName('Full'));
preloadImg(imageName('Last')); preloadImg(imageName('Last'));
@@ -122,10 +119,10 @@ class Almanac extends WeatherDisplay {
// sun and moon data // sun and moon data
this.elem.querySelector('.day-1').innerHTML = Today.toLocaleString({ weekday: 'long' }); this.elem.querySelector('.day-1').innerHTML = Today.toLocaleString({ weekday: 'long' });
this.elem.querySelector('.day-2').innerHTML = Tomorrow.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-1').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[0].sunrise));
this.elem.querySelector('.rise-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunrise).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); this.elem.querySelector('.rise-2').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[1].sunrise));
this.elem.querySelector('.set-1').innerHTML = DateTime.fromJSDate(info.sun[0].sunset).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); this.elem.querySelector('.set-1').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[0].sunset));
this.elem.querySelector('.set-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunset).setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(); this.elem.querySelector('.set-2').innerHTML = timeFormat(DateTime.fromJSDate(info.sun[1].sunset));
const days = info.moon.map((MoonPhase) => { const days = info.moon.map((MoonPhase) => {
const fill = {}; const fill = {};
@@ -171,6 +168,8 @@ const imageName = (type) => {
} }
}; };
const timeFormat = (dt) => dt.setZone(timeZone()).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase();
// register display // register display
const display = new Almanac(9, 'almanac'); const display = new Almanac(9, 'almanac');
registerDisplay(display); registerDisplay(display);

View File

@@ -3,43 +3,24 @@ import { json } from './utils/fetch.mjs';
const KEYS = { const KEYS = {
ESC: 27, ESC: 27,
TAB: 9,
RETURN: 13,
LEFT: 37,
UP: 38, UP: 38,
RIGHT: 39,
DOWN: 40, DOWN: 40,
ENTER: 13, ENTER: 13,
}; };
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
autoSelectFirst: false,
serviceUrl: null, serviceUrl: null,
lookup: null,
onSelect: () => { },
onHint: null,
width: 'auto',
minChars: 3, minChars: 3,
maxHeight: 300, maxHeight: 300,
deferRequestBy: 0, deferRequestBy: 0,
params: {}, params: {},
delimiter: null,
zIndex: 9999, zIndex: 9999,
type: 'GET', type: 'GET',
noCache: false,
preserveInput: false,
containerClass: 'autocomplete-suggestions', containerClass: 'autocomplete-suggestions',
tabDisabled: false,
dataType: 'text',
currentRequest: null,
triggerSelectOnValidInput: true,
preventBadQueries: true,
paramName: 'query', paramName: 'query',
transformResult: (a) => a, transformResult: (a) => a,
showNoSuggestionNotice: false, showNoSuggestionNotice: false,
noSuggestionNotice: 'No results', noSuggestionNotice: 'No results',
orientation: 'bottom',
forceFixPosition: false,
}; };
const escapeRegExChars = (string) => string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); const escapeRegExChars = (string) => string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');

View File

@@ -1,6 +1,6 @@
// current weather conditions display // current weather conditions display
import STATUS from './status.mjs'; 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 { json } from './utils/fetch.mjs';
import { directionToNSEW } from './utils/calc.mjs'; import { directionToNSEW } from './utils/calc.mjs';
import { locationCleanup } from './utils/string.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 { class CurrentWeather extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
super(navId, elemId, 'Current Conditions', true); super(navId, elemId, 'Current Conditions', true);
// pre-load background image (returns promise)
this.backgroundImage = loadImg('images/backgrounds/1.png');
} }
async getData(weatherParameters, refresh) { async getData(weatherParameters, refresh) {

View File

@@ -59,11 +59,10 @@ class ExtendedForecast extends WeatherDisplay {
date: Day.dayName, date: Day.dayName,
}; };
const { low } = Day; const { low, high } = Day;
if (low !== undefined) { if (low !== undefined) {
fill['value-lo'] = Math.round(low); fill['value-lo'] = Math.round(low);
} }
const { high } = Day;
fill['value-hi'] = Math.round(high); fill['value-hi'] = Math.round(high);
// return the filled template // return the filled template
@@ -121,7 +120,6 @@ const parse = (fullForecast) => {
return forecast; return forecast;
}; };
const shortenExtendedForecastText = (long) => {
const regexList = [ const regexList = [
[/ and /gi, ' '], [/ and /gi, ' '],
[/slight /gi, ''], [/slight /gi, ''],
@@ -132,6 +130,7 @@ const shortenExtendedForecastText = (long) => {
[/dense /gi, ''], [/dense /gi, ''],
[/Thunderstorm/g, 'T\'Storm'], [/Thunderstorm/g, 'T\'Storm'],
]; ];
const shortenExtendedForecastText = (long) => {
// run all regexes // run all regexes
const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long); const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long);

View File

@@ -50,7 +50,7 @@ class Hazards extends WeatherDisplay {
// show alert indicator // show alert indicator
if (this.data.length > 0) alert.classList.add('show'); if (this.data.length > 0) alert.classList.add('show');
} catch (error) { } catch (error) {
console.error('Get hourly forecast failed'); console.error('Get hazards failed');
console.error(error.status, error.responseJSON); console.error(error.status, error.responseJSON);
if (this.isEnabled) this.setStatus(STATUS.failed); if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers // return undefined to other subscribers
@@ -129,7 +129,7 @@ class Hazards extends WeatherDisplay {
// don't let offset go negative // don't let offset go negative
if (offsetY < 0) offsetY = 0; if (offsetY < 0) offsetY = 0;
// copy the scrolled portion of the canvas // move the element
this.elem.querySelector('.main').scrollTo(0, offsetY); this.elem.querySelector('.main').scrollTo(0, offsetY);
} }

View File

@@ -6,6 +6,10 @@ import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay, timeZone } from './navigation.mjs'; import { registerDisplay, timeZone } from './navigation.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
// get available space
const availableWidth = 532;
const availableHeight = 285;
class HourlyGraph extends WeatherDisplay { class HourlyGraph extends WeatherDisplay {
constructor(navId, elemId, defaultActive) { constructor(navId, elemId, defaultActive) {
super(navId, elemId, 'Hourly Graph', defaultActive); super(navId, elemId, 'Hourly Graph', defaultActive);
@@ -47,10 +51,6 @@ class HourlyGraph extends WeatherDisplay {
drawCanvas() { drawCanvas() {
if (!this.image) this.image = this.elem.querySelector('.chart img'); 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.width = availableWidth;
this.image.height = availableHeight; this.image.height = availableHeight;

View File

@@ -69,8 +69,7 @@ class Hourly extends WeatherDisplay {
const fillValues = {}; const fillValues = {};
// hour // hour
const hour = startingHour.plus({ hours: index }); const hour = startingHour.plus({ hours: index });
const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' }); fillValues.hour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' });
fillValues.hour = formattedHour;
// temperatures, convert to strings with no decimal // temperatures, convert to strings with no decimal
const temperature = data.temperature.toString().padStart(3); const temperature = data.temperature.toString().padStart(3);
@@ -81,12 +80,11 @@ class Hourly extends WeatherDisplay {
fillValues.like = feelsLike; fillValues.like = feelsLike;
// wind // wind
let wind = 'Calm'; fillValues.wind = 'Calm';
if (data.windSpeed > 0) { if (data.windSpeed > 0) {
const windSpeed = Math.round(data.windSpeed).toString(); 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 // image
fillValues.icon = { type: 'img', src: data.icon }; 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 // alter the color of the feels like column to reflect wind chill or heat index
if (feelsLike < temperature) { if (feelsLike < temperature) {
filledRow.querySelector('.like').classList.add('wind-chill'); filledRow.querySelector('.like').classList.add('wind-chill');
} } else if (feelsLike > temperature) {
if (feelsLike > temperature) {
filledRow.querySelector('.like').classList.add('heat-index'); filledRow.querySelector('.like').classList.add('heat-index');
} }

View File

@@ -1,7 +1,7 @@
const hourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => {
// internal function to add path to returned icon // internal function to add path to returned icon
const addPath = (icon) => `images/icons/regional-maps/${icon}`; const addPath = (icon) => `images/icons/regional-maps/${icon}`;
const hourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => {
// possible phenomenon // possible phenomenon
let thunder = false; let thunder = false;
let snow = false; let snow = false;

View File

@@ -22,8 +22,7 @@ class LatestObservations extends WeatherDisplay {
// this is intentional because up to 30 stations are available to pull data from // this is intentional because up to 30 stations are available to pull data from
// calculate distance to each station // calculate distance to each station
const stationsByDistance = Object.keys(StationInfo).map((key) => { const stationsByDistance = Object.values(StationInfo).map((station) => {
const station = StationInfo[key];
const distance = calcDistance(station.lat, station.lon, this.weatherParameters.latitude, this.weatherParameters.longitude); const distance = calcDistance(station.lat, station.lon, this.weatherParameters.latitude, this.weatherParameters.longitude);
return { ...station, distance }; return { ...station, distance };
}); });
@@ -104,8 +103,6 @@ class LatestObservations extends WeatherDisplay {
linesContainer.innerHTML = ''; linesContainer.innerHTML = '';
linesContainer.append(...lines); linesContainer.append(...lines);
// update temperature unit header
this.finishDraw(); this.finishDraw();
} }
} }

View File

@@ -1,5 +1,4 @@
// regional forecast and observations // regional forecast and observations
import { loadImg } from './utils/image.mjs';
import STATUS, { calcStatusClass, statusClasses } from './status.mjs'; import STATUS, { calcStatusClass, statusClasses } from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { import {
@@ -10,9 +9,6 @@ class Progress extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
super(navId, elemId, '', false); super(navId, elemId, '', false);
// pre-load background image (returns promise)
this.backgroundImage = loadImg('images/backgrounds/1.png');
// disable any navigation timing // disable any navigation timing
this.timing = false; this.timing = false;

View File

@@ -100,10 +100,8 @@ class Radar extends WeatherDisplay {
const urls = sortedPngs.slice(-(this.dopplerRadarImageMax)); const urls = sortedPngs.slice(-(this.dopplerRadarImageMax));
// calculate offsets and sizes // calculate offsets and sizes
let offsetX = 120; const offsetX = 120 * 2;
let offsetY = 69; const offsetY = 69 * 2;
offsetX *= 2;
offsetY *= 2;
const sourceXY = utils.getXYFromLatitudeLongitudeMap(this.weatherParameters, offsetX, offsetY); const sourceXY = utils.getXYFromLatitudeLongitudeMap(this.weatherParameters, offsetX, offsetY);
const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(this.weatherParameters, offsetX, offsetY); const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(this.weatherParameters, offsetX, offsetY);

View File

@@ -20,7 +20,7 @@ const buildForecast = (forecast, city, cityXY) => {
const getRegionalObservation = async (point, city) => { const getRegionalObservation = async (point, city) => {
try { try {
// get stations // 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 // get the first station
const station = stations.features[0].id; const station = stations.features[0].id;

View File

@@ -13,6 +13,12 @@ import { registerDisplay } from './navigation.mjs';
import * as utils from './regionalforecast-utils.mjs'; import * as utils from './regionalforecast-utils.mjs';
import { getPoint } from './utils/weather.mjs'; import { getPoint } from './utils/weather.mjs';
// map offset
const mapOffsetXY = {
x: 240,
y: 117,
};
class RegionalForecast extends WeatherDisplay { class RegionalForecast extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
super(navId, elemId, 'Regional Forecast', true); super(navId, elemId, 'Regional Forecast', true);
@@ -36,23 +42,18 @@ class RegionalForecast extends WeatherDisplay {
} }
this.elem.querySelector('.map img').src = baseMap; this.elem.querySelector('.map img').src = baseMap;
// map offset
const offsetXY = {
x: 240,
y: 117,
};
// get user's location in x/y // 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 // 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 // get a target distance
let targetDistance = 2.5; let targetDistance = 2.5;
if (this.weatherParameters.state === 'HI') targetDistance = 1; if (this.weatherParameters.state === 'HI') targetDistance = 1;
// make station info into an array // 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 // combine regional cities with station info for additional stations
// stations are intentionally after cities to allow cities priority when drawing the map // stations are intentionally after cities to allow cities priority when drawing the map
const combinedCities = [...RegionalCities, ...stationInfoArray]; const combinedCities = [...RegionalCities, ...stationInfoArray];
@@ -137,7 +138,7 @@ class RegionalForecast extends WeatherDisplay {
// return the weather data and offsets // return the weather data and offsets
this.data = { this.data = {
regionalData, regionalData,
offsetXY, mapOffsetXY,
sourceXY, sourceXY,
}; };
@@ -147,7 +148,7 @@ class RegionalForecast extends WeatherDisplay {
drawCanvas() { drawCanvas() {
super.drawCanvas(); super.drawCanvas();
// break up data into useful values // break up data into useful values
const { regionalData: data, sourceXY, offsetXY } = this.data; const { regionalData: data, sourceXY } = this.data;
// draw the header graphics // draw the header graphics
@@ -170,7 +171,7 @@ class RegionalForecast extends WeatherDisplay {
} }
// draw the map // draw the map
const scale = 640 / (offsetXY.x * 2); const scale = 640 / (mapOffsetXY.x * 2);
const map = this.elem.querySelector('.map'); const map = this.elem.querySelector('.map');
map.style.transform = `scale(${scale}) translate(-${sourceXY.x}px, -${sourceXY.y}px)`; map.style.transform = `scale(${scale}) translate(-${sourceXY.x}px, -${sourceXY.y}px)`;

View File

@@ -1,3 +1,5 @@
import { elemForEach } from './utils/elem.mjs';
document.addEventListener('DOMContentLoaded', () => init()); document.addEventListener('DOMContentLoaded', () => init());
// shorthand mappings for frequently used values // shorthand mappings for frequently used values
@@ -19,21 +21,18 @@ const init = () => {
const createLink = async (e) => { const createLink = async (e) => {
// cancel default event (click on hyperlink) // cancel default event (click on hyperlink)
e.preventDefault(); e.preventDefault();
// get all checkboxes on page
const checkboxes = document.querySelectorAll('input[type=checkbox]');
// list to receive checkbox statuses // list to receive checkbox statuses
const queryStringElements = {}; const queryStringElements = {};
[...checkboxes].forEach((elem) => { elemForEach('input[type=checkbox]', (elem) => {
if (elem?.id) { if (elem?.id) {
queryStringElements[elem.id] = elem?.checked ?? false; queryStringElements[elem.id] = elem?.checked ?? false;
} }
}); });
// get all select boxes // get all select boxes
const selects = document.querySelectorAll('select'); elemForEach('select', (elem) => {
[...selects].forEach((elem) => {
if (elem?.id) { if (elem?.id) {
queryStringElements[elem.id] = elem?.value ?? 0; queryStringElements[elem.id] = elem?.value ?? 0;
} }

View File

@@ -1,21 +1,4 @@
import { blob } from './fetch.mjs'; 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 // 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 // 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; return true;
}; };
const loadImgElement = (url) => new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = url;
});
export { export {
loadImg, // eslint-disable-next-line import/prefer-default-export
preloadImg, preloadImg,
loadImgElement,
}; };

View File

@@ -2,7 +2,7 @@ import { json } from './fetch.mjs';
const getPoint = async (lat, lon) => { const getPoint = async (lat, lon) => {
try { 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) { } catch (error) {
console.log(`Unable to get point ${lat}, ${lon}`); console.log(`Unable to get point ${lat}, ${lon}`);
console.error(error); console.error(error);

View File

@@ -7,6 +7,7 @@ import {
} from './navigation.mjs'; } from './navigation.mjs';
import { parseQueryString } from './share.mjs'; import { parseQueryString } from './share.mjs';
import settings from './settings.mjs'; import settings from './settings.mjs';
import { elemForEach } from './utils/elem.mjs';
class WeatherDisplay { class WeatherDisplay {
constructor(navId, elemId, name, defaultEnabled) { constructor(navId, elemId, name, defaultEnabled) {
@@ -391,8 +392,7 @@ class WeatherDisplay {
this.templates = {}; this.templates = {};
this.elem = document.querySelector(`#${this.elemId}-html`); this.elem = document.querySelector(`#${this.elemId}-html`);
if (!this.elem) return; if (!this.elem) return;
const templates = this.elem.querySelectorAll('.template'); elemForEach(`#${this.elemId}-html .template`, (template) => {
templates.forEach((template) => {
const className = template.classList[0]; const className = template.classList[0];
const node = template.cloneNode(true); const node = template.cloneNode(true);
node.classList.remove('template'); node.classList.remove('template');

View File

@@ -35,7 +35,6 @@
<div class="scroll-area"> <div class="scroll-area">
<div class="frame template"> <div class="frame template">
<div class="map"> <div class="map">
<!-- <img src="images/maps/radar.webp" /> -->
</div> </div>
</div> </div>
</div> </div>