mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-14 15:49:31 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbc56f014a | ||
|
|
3161a03797 | ||
|
|
205fa77f51 | ||
|
|
28bb8f2e2a | ||
|
|
cf9a99a6ca | ||
|
|
a83afa71cd | ||
|
|
74f1abd6f8 |
745
package-lock.json
generated
745
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.21.1",
|
||||
"version": "5.21.3",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, '\\$&');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,9 +253,9 @@ const resize = () => {
|
||||
|
||||
const scale = Math.min(widthZoomPercent, heightZoomPercent);
|
||||
if (scale < 1.0 || document.fullscreenElement || settings.kiosk) {
|
||||
document.querySelector('#container').style.transform = `scale(${scale})`;
|
||||
document.querySelector('#container').style.zoom = scale;
|
||||
} else {
|
||||
document.querySelector('#container').style.transform = 'unset';
|
||||
document.querySelector('#container').style.zoom = 'unset';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -266,6 +266,7 @@ const resetStatuses = () => {
|
||||
|
||||
// allow displays to register themselves
|
||||
const registerDisplay = (display) => {
|
||||
if (displays[display.navId]) console.warn(`Display nav ID ${display.navId} already in use`);
|
||||
displays[display.navId] = display;
|
||||
|
||||
// generate checkboxes
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,12 +7,18 @@ import { json } from './utils/fetch.mjs';
|
||||
import { temperature as temperatureUnit } from './utils/units.mjs';
|
||||
import { getSmallIcon } from './icons.mjs';
|
||||
import { preloadImg } from './utils/image.mjs';
|
||||
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||
import { DateTime, Interval } from '../vendor/auto/luxon.mjs';
|
||||
import WeatherDisplay from './weatherdisplay.mjs';
|
||||
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];
|
||||
@@ -76,6 +77,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||
// get a unit converter
|
||||
const temperatureConverter = temperatureUnit();
|
||||
|
||||
// get now as DateTime for calculations below
|
||||
const now = DateTime.now();
|
||||
|
||||
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
||||
const regionalDataAll = await Promise.all(regionalCities.map(async (city) => {
|
||||
try {
|
||||
@@ -109,14 +113,24 @@ class RegionalForecast extends WeatherDisplay {
|
||||
preloadImg(getSmallIcon(regionalObservation.icon, !regionalObservation.daytime));
|
||||
|
||||
// return a pared-down forecast
|
||||
// 0th object is the current conditions
|
||||
// first object is the next period i.e. if it's daytime then it's the "tonight" forecast
|
||||
// second object is the following period
|
||||
// always skip the first forecast index because it's what's going on right now
|
||||
// 0th object should contain the current conditions, but when WFOs go offline or otherwise don't post
|
||||
// an updated forecast it's possible that the 0th object is in the past.
|
||||
// so we go on a search for the current time in the start/end times provided in the forecast periods
|
||||
const { periods } = forecast.properties;
|
||||
const currentPeriod = periods.reduce((prev, period, index) => {
|
||||
const start = DateTime.fromISO(period.startTime);
|
||||
const end = DateTime.fromISO(period.endTime);
|
||||
const interval = Interval.fromDateTimes(start, end);
|
||||
if (interval.contains(now)) {
|
||||
return index;
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
// group together the current observation and next two periods
|
||||
return [
|
||||
regionalObservation,
|
||||
utils.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[currentPeriod + 1], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[currentPeriod + 2], city, cityXY),
|
||||
];
|
||||
} catch (error) {
|
||||
console.log(`No regional forecast data for '${city.name ?? city.city}'`);
|
||||
@@ -137,7 +151,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
// return the weather data and offsets
|
||||
this.data = {
|
||||
regionalData,
|
||||
offsetXY,
|
||||
mapOffsetXY,
|
||||
sourceXY,
|
||||
};
|
||||
|
||||
@@ -147,7 +161,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 +184,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)`;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -189,11 +189,33 @@ body {
|
||||
#divTwcBottom>div {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
|
||||
// scale down the buttons on narrower screens
|
||||
@media (max-width: 550px) {
|
||||
zoom: 0.90;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
zoom: 0.80;
|
||||
}
|
||||
|
||||
@media (max-width: 450px) {
|
||||
zoom: 0.70;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
zoom: 0.60;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
zoom: 0.50;
|
||||
}
|
||||
}
|
||||
|
||||
#divTwcBottomLeft {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
|
||||
}
|
||||
|
||||
#divTwcBottomMiddle {
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
<div class="scroll-area">
|
||||
<div class="frame template">
|
||||
<div class="map">
|
||||
<!-- <img src="images/maps/radar.webp" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user