weather displays complete
This commit is contained in:
parent
c28608bb39
commit
cc61d2c6d1
|
@ -12,7 +12,7 @@ module.exports = {
|
||||||
SharedArrayBuffer: 'readonly',
|
SharedArrayBuffer: 'readonly',
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2021,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
indent: [
|
indent: [
|
||||||
|
@ -46,6 +46,13 @@ module.exports = {
|
||||||
allowSamePrecedence: true,
|
allowSamePrecedence: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'import/extensions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
mjs: 'always',
|
||||||
|
json: 'always',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
'*.min.js',
|
'*.min.js',
|
||||||
|
|
|
@ -35,7 +35,6 @@ const jsSources = [
|
||||||
'server/scripts/index.js',
|
'server/scripts/index.js',
|
||||||
'server/scripts/vendor/auto/luxon.js',
|
'server/scripts/vendor/auto/luxon.js',
|
||||||
'server/scripts/vendor/auto/suncalc.js',
|
'server/scripts/vendor/auto/suncalc.js',
|
||||||
'server/scripts/modules/draw.js',
|
|
||||||
'server/scripts/modules/weatherdisplay.js',
|
'server/scripts/modules/weatherdisplay.js',
|
||||||
'server/scripts/modules/icons.js',
|
'server/scripts/modules/icons.js',
|
||||||
'server/scripts/modules/utilities.js',
|
'server/scripts/modules/utilities.js',
|
||||||
|
|
|
@ -9,8 +9,8 @@ const clean = (cb) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const vendorFiles = [
|
const vendorFiles = [
|
||||||
'./node_modules/luxon/build/global/luxon.js',
|
'./node_modules/luxon/build/es6/luxon.js',
|
||||||
'./node_modules/luxon/build/global/luxon.js.map',
|
'./node_modules/luxon/build/es6/luxon.js.map',
|
||||||
'./node_modules/nosleep.js/dist/NoSleep.js',
|
'./node_modules/nosleep.js/dist/NoSleep.js',
|
||||||
'./node_modules/jquery/dist/jquery.js',
|
'./node_modules/jquery/dist/jquery.js',
|
||||||
'./node_modules/suncalc/suncalc.js',
|
'./node_modules/suncalc/suncalc.js',
|
||||||
|
@ -22,6 +22,7 @@ const copy = () => gulp.src(vendorFiles)
|
||||||
path.dirname = path.dirname.toLowerCase();
|
path.dirname = path.dirname.toLowerCase();
|
||||||
path.basename = path.basename.toLowerCase();
|
path.basename = path.basename.toLowerCase();
|
||||||
path.extname = path.extname.toLowerCase();
|
path.extname = path.extname.toLowerCase();
|
||||||
|
if (path.basename === 'luxon') path.extname = '.mjs';
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest('./server/scripts/vendor/auto'));
|
.pipe(gulp.dest('./server/scripts/vendor/auto'));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { UNITS } from './modules/config.mjs';
|
import { UNITS } from './modules/config.mjs';
|
||||||
|
import { json } from './modules/utils/fetch.mjs';
|
||||||
|
|
||||||
/* globals NoSleep, states, navigation, utils */
|
/* globals NoSleep, states, navigation */
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
@ -175,7 +176,7 @@ const autocompleteOnSelect = async (suggestion, elem) => {
|
||||||
if (overrides[suggestion.value]) {
|
if (overrides[suggestion.value]) {
|
||||||
doRedirectToGeometry(overrides[suggestion.value]);
|
doRedirectToGeometry(overrides[suggestion.value]);
|
||||||
} else {
|
} else {
|
||||||
const data = await utils.fetch.json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
data: {
|
data: {
|
||||||
text: suggestion.value,
|
text: suggestion.value,
|
||||||
magicKey: suggestion.data,
|
magicKey: suggestion.data,
|
||||||
|
@ -492,7 +493,7 @@ const btnGetGpsClick = async () => {
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await utils.fetch.json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode', {
|
data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode', {
|
||||||
data: {
|
data: {
|
||||||
location: `${longitude},${latitude}`,
|
location: `${longitude},${latitude}`,
|
||||||
distance: 1000, // Find location up to 1 KM.
|
distance: 1000, // Find location up to 1 KM.
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
// display sun and moon data
|
// display sun and moon data
|
||||||
|
import { loadImg, preloadImg } from './utils/image.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, SunCalc, luxon */
|
/* globals WeatherDisplay, SunCalc */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class Almanac extends WeatherDisplay {
|
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)
|
// pre-load background images (returns promises)
|
||||||
this.backgroundImage0 = utils.image.load('images/BackGround3_1.png');
|
this.backgroundImage0 = loadImg('images/BackGround3_1.png');
|
||||||
|
|
||||||
// preload the moon images
|
// preload the moon images
|
||||||
utils.image.preload('images/2/Full-Moon.gif');
|
preloadImg('images/2/Full-Moon.gif');
|
||||||
utils.image.preload('images/2/Last-Quarter.gif');
|
preloadImg('images/2/Last-Quarter.gif');
|
||||||
utils.image.preload('images/2/New-Moon.gif');
|
preloadImg('images/2/New-Moon.gif');
|
||||||
utils.image.preload('images/2/First-Quarter.gif');
|
preloadImg('images/2/First-Quarter.gif');
|
||||||
|
|
||||||
this.timing.totalScreens = 1;
|
this.timing.totalScreens = 1;
|
||||||
}
|
}
|
||||||
|
@ -39,8 +41,6 @@ class Almanac extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
calcSunMoonData(weatherParameters) {
|
calcSunMoonData(weatherParameters) {
|
||||||
const { DateTime } = luxon;
|
|
||||||
|
|
||||||
const sun = [
|
const sun = [
|
||||||
SunCalc.getTimes(new Date(), weatherParameters.latitude, weatherParameters.longitude),
|
SunCalc.getTimes(new Date(), weatherParameters.latitude, weatherParameters.longitude),
|
||||||
SunCalc.getTimes(DateTime.local().plus({ days: 1 }).toJSDate(), weatherParameters.latitude, weatherParameters.longitude),
|
SunCalc.getTimes(DateTime.local().plus({ days: 1 }).toJSDate(), weatherParameters.latitude, weatherParameters.longitude),
|
||||||
|
@ -115,7 +115,6 @@ class Almanac extends WeatherDisplay {
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
const info = this.data;
|
const info = this.data;
|
||||||
const { DateTime } = luxon;
|
|
||||||
const Today = DateTime.local();
|
const Today = DateTime.local();
|
||||||
const Tomorrow = Today.plus({ days: 1 });
|
const Tomorrow = Today.plus({ days: 1 });
|
||||||
|
|
||||||
|
@ -170,3 +169,7 @@ class Almanac extends WeatherDisplay {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Almanac;
|
||||||
|
|
||||||
|
window.Almanac = Almanac;
|
|
@ -4,8 +4,8 @@ const UNITS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
UNITS,
|
UNITS,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.UNITS = UNITS;
|
window.UNITS = UNITS;
|
||||||
console.log('config');
|
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, navigation */
|
import STATUS from './status.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import { loadImg, preloadImg } from './utils/image.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import { directionToNSEW } from './utils/calc.mjs';
|
||||||
|
import * as units from './utils/units.mjs';
|
||||||
|
import { locationCleanup } from './utils/string.mjs';
|
||||||
|
import { getWeatherIconFromIconLink } from './icons.mjs';
|
||||||
|
|
||||||
|
/* globals WeatherDisplay, navigation */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
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)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
this.backgroundImage = loadImg('images/BackGround1_1.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
|
@ -25,7 +33,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
try {
|
try {
|
||||||
// station observations
|
// station observations
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
observations = await utils.fetch.json(`${station.id}/observations`, {
|
observations = await json(`${station.id}/observations`, {
|
||||||
cors: true,
|
cors: true,
|
||||||
data: {
|
data: {
|
||||||
limit: 2,
|
limit: 2,
|
||||||
|
@ -50,7 +58,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// preload the icon
|
// preload the icon
|
||||||
utils.image.preload(icons.getWeatherIconFromIconLink(observations.features[0].properties.icon));
|
preloadImg(getWeatherIconFromIconLink(observations.features[0].properties.icon));
|
||||||
|
|
||||||
// we only get here if there was no error above
|
// we only get here if there was no error above
|
||||||
this.data = { ...observations, station };
|
this.data = { ...observations, station };
|
||||||
|
@ -74,14 +82,14 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
data.Visibility = Math.round(observations.visibility.value / 1000);
|
data.Visibility = Math.round(observations.visibility.value / 1000);
|
||||||
data.VisibilityUnit = ' km.';
|
data.VisibilityUnit = ' km.';
|
||||||
data.WindSpeed = Math.round(observations.windSpeed.value);
|
data.WindSpeed = Math.round(observations.windSpeed.value);
|
||||||
data.WindDirection = utils.calc.directionToNSEW(observations.windDirection.value);
|
data.WindDirection = directionToNSEW(observations.windDirection.value);
|
||||||
data.Pressure = Math.round(observations.barometricPressure.value);
|
data.Pressure = Math.round(observations.barometricPressure.value);
|
||||||
data.HeatIndex = Math.round(observations.heatIndex.value);
|
data.HeatIndex = Math.round(observations.heatIndex.value);
|
||||||
data.WindChill = Math.round(observations.windChill.value);
|
data.WindChill = Math.round(observations.windChill.value);
|
||||||
data.WindGust = Math.round(observations.windGust.value);
|
data.WindGust = Math.round(observations.windGust.value);
|
||||||
data.WindUnit = 'KPH';
|
data.WindUnit = 'KPH';
|
||||||
data.Humidity = Math.round(observations.relativeHumidity.value);
|
data.Humidity = Math.round(observations.relativeHumidity.value);
|
||||||
data.Icon = icons.getWeatherIconFromIconLink(observations.icon);
|
data.Icon = getWeatherIconFromIconLink(observations.icon);
|
||||||
data.PressureDirection = '';
|
data.PressureDirection = '';
|
||||||
data.TextConditions = observations.textDescription;
|
data.TextConditions = observations.textDescription;
|
||||||
data.station = this.data.station;
|
data.station = this.data.station;
|
||||||
|
@ -92,19 +100,19 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
if (pressureDiff < -150) data.PressureDirection = 'F';
|
if (pressureDiff < -150) data.PressureDirection = 'F';
|
||||||
|
|
||||||
if (navigation.units() === UNITS.english) {
|
if (navigation.units() === UNITS.english) {
|
||||||
data.Temperature = utils.units.celsiusToFahrenheit(data.Temperature);
|
data.Temperature = units.celsiusToFahrenheit(data.Temperature);
|
||||||
data.TemperatureUnit = 'F';
|
data.TemperatureUnit = 'F';
|
||||||
data.DewPoint = utils.units.celsiusToFahrenheit(data.DewPoint);
|
data.DewPoint = units.celsiusToFahrenheit(data.DewPoint);
|
||||||
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling) / 100) * 100;
|
data.Ceiling = Math.round(units.metersToFeet(data.Ceiling) / 100) * 100;
|
||||||
data.CeilingUnit = 'ft.';
|
data.CeilingUnit = 'ft.';
|
||||||
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value / 1000);
|
data.Visibility = units.kilometersToMiles(observations.visibility.value / 1000);
|
||||||
data.VisibilityUnit = ' mi.';
|
data.VisibilityUnit = ' mi.';
|
||||||
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
|
data.WindSpeed = units.kphToMph(data.WindSpeed);
|
||||||
data.WindUnit = 'MPH';
|
data.WindUnit = 'MPH';
|
||||||
data.Pressure = utils.units.pascalToInHg(data.Pressure).toFixed(2);
|
data.Pressure = units.pascalToInHg(data.Pressure).toFixed(2);
|
||||||
data.HeatIndex = utils.units.celsiusToFahrenheit(data.HeatIndex);
|
data.HeatIndex = units.celsiusToFahrenheit(data.HeatIndex);
|
||||||
data.WindChill = utils.units.celsiusToFahrenheit(data.WindChill);
|
data.WindChill = units.celsiusToFahrenheit(data.WindChill);
|
||||||
data.WindGust = utils.units.kphToMph(data.WindGust);
|
data.WindGust = units.kphToMph(data.WindGust);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +134,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
||||||
if (data.WindGust) fill['wind-gusts'] = `Gusts to ${data.WindGust}`;
|
if (data.WindGust) fill['wind-gusts'] = `Gusts to ${data.WindGust}`;
|
||||||
|
|
||||||
fill.location = utils.string.locationCleanup(this.data.station.properties.name).substr(0, 20);
|
fill.location = locationCleanup(this.data.station.properties.name).substr(0, 20);
|
||||||
|
|
||||||
fill.humidity = `${data.Humidity}%`;
|
fill.humidity = `${data.Humidity}%`;
|
||||||
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
|
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
|
||||||
|
@ -181,3 +189,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CurrentWeather;
|
||||||
|
|
||||||
|
window.CurrentWeather = CurrentWeather;
|
|
@ -1,101 +0,0 @@
|
||||||
/* globals navigation, utils */
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const currentWeatherScroll = (() => {
|
|
||||||
// constants
|
|
||||||
const degree = String.fromCharCode(176);
|
|
||||||
|
|
||||||
// local variables
|
|
||||||
let interval;
|
|
||||||
let screenIndex = 0;
|
|
||||||
|
|
||||||
// start drawing conditions
|
|
||||||
// reset starts from the first item in the text scroll list
|
|
||||||
const start = () => {
|
|
||||||
// store see if the context is new
|
|
||||||
|
|
||||||
// set up the interval if needed
|
|
||||||
if (!interval) {
|
|
||||||
interval = setInterval(incrementInterval, 4000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the data
|
|
||||||
drawScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
const stop = (reset) => {
|
|
||||||
if (interval) interval = clearInterval(interval);
|
|
||||||
if (reset) screenIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// increment interval, roll over
|
|
||||||
const incrementInterval = () => {
|
|
||||||
screenIndex = (screenIndex + 1) % (screens.length);
|
|
||||||
// draw new text
|
|
||||||
drawScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawScreen = async () => {
|
|
||||||
// get the conditions
|
|
||||||
const data = await navigation.getCurrentWeather();
|
|
||||||
|
|
||||||
// nothing to do if there's no data yet
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
drawCondition(screens[screenIndex](data));
|
|
||||||
};
|
|
||||||
|
|
||||||
// the "screens" are stored in an array for easy addition and removal
|
|
||||||
const screens = [
|
|
||||||
// station name
|
|
||||||
(data) => `Conditions at ${utils.string.locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
|
||||||
|
|
||||||
// temperature
|
|
||||||
(data) => {
|
|
||||||
let text = `Temp: ${data.Temperature}${degree} ${data.TemperatureUnit}`;
|
|
||||||
if (data.observations.heatIndex.value) {
|
|
||||||
text += ` Heat Index: ${data.HeatIndex}${degree} ${data.TemperatureUnit}`;
|
|
||||||
} else if (data.observations.windChill.value) {
|
|
||||||
text += ` Wind Chill: ${data.WindChill}${degree} ${data.TemperatureUnit}`;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
|
|
||||||
// humidity
|
|
||||||
(data) => `Humidity: ${data.Humidity}${degree} ${data.TemperatureUnit} Dewpoint: ${data.DewPoint}${degree} ${data.TemperatureUnit}`,
|
|
||||||
|
|
||||||
// barometric pressure
|
|
||||||
(data) => `Barometric Pressure: ${data.Pressure} ${data.PressureDirection}`,
|
|
||||||
|
|
||||||
// wind
|
|
||||||
(data) => {
|
|
||||||
let text = '';
|
|
||||||
if (data.WindSpeed > 0) {
|
|
||||||
text = `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`;
|
|
||||||
} else {
|
|
||||||
text = 'Wind: Calm';
|
|
||||||
}
|
|
||||||
if (data.WindGust > 0) {
|
|
||||||
text += ` Gusts to ${data.WindGust}`;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
|
|
||||||
// visibility
|
|
||||||
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : `${data.Ceiling} ${data.CeilingUnit}`}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
// internal draw function with preset parameters
|
|
||||||
const drawCondition = (text) => {
|
|
||||||
// update all html scroll elements
|
|
||||||
utils.elem.forEach('.weather-display .scroll .fixed', (elem) => {
|
|
||||||
elem.innerHTML = text;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// return the api
|
|
||||||
return {
|
|
||||||
start,
|
|
||||||
stop,
|
|
||||||
};
|
|
||||||
})();
|
|
105
server/scripts/modules/currentweatherscroll.mjs
Normal file
105
server/scripts/modules/currentweatherscroll.mjs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/* globals navigation */
|
||||||
|
import { locationCleanup } from './utils/string.mjs';
|
||||||
|
import { elemForEach } from './utils/elem.mjs';
|
||||||
|
|
||||||
|
// constants
|
||||||
|
const degree = String.fromCharCode(176);
|
||||||
|
|
||||||
|
// local variables
|
||||||
|
let interval;
|
||||||
|
let screenIndex = 0;
|
||||||
|
|
||||||
|
// start drawing conditions
|
||||||
|
// reset starts from the first item in the text scroll list
|
||||||
|
const start = () => {
|
||||||
|
// store see if the context is new
|
||||||
|
|
||||||
|
// set up the interval if needed
|
||||||
|
if (!interval) {
|
||||||
|
interval = setInterval(incrementInterval, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the data
|
||||||
|
drawScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = (reset) => {
|
||||||
|
if (interval) interval = clearInterval(interval);
|
||||||
|
if (reset) screenIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// increment interval, roll over
|
||||||
|
const incrementInterval = () => {
|
||||||
|
screenIndex = (screenIndex + 1) % (screens.length);
|
||||||
|
// draw new text
|
||||||
|
drawScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawScreen = async () => {
|
||||||
|
// get the conditions
|
||||||
|
const data = await navigation.getCurrentWeather();
|
||||||
|
|
||||||
|
// nothing to do if there's no data yet
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
drawCondition(screens[screenIndex](data));
|
||||||
|
};
|
||||||
|
|
||||||
|
// the "screens" are stored in an array for easy addition and removal
|
||||||
|
const screens = [
|
||||||
|
// station name
|
||||||
|
(data) => `Conditions at ${locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
||||||
|
|
||||||
|
// temperature
|
||||||
|
(data) => {
|
||||||
|
let text = `Temp: ${data.Temperature}${degree} ${data.TemperatureUnit}`;
|
||||||
|
if (data.observations.heatIndex.value) {
|
||||||
|
text += ` Heat Index: ${data.HeatIndex}${degree} ${data.TemperatureUnit}`;
|
||||||
|
} else if (data.observations.windChill.value) {
|
||||||
|
text += ` Wind Chill: ${data.WindChill}${degree} ${data.TemperatureUnit}`;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
|
||||||
|
// humidity
|
||||||
|
(data) => `Humidity: ${data.Humidity}${degree} ${data.TemperatureUnit} Dewpoint: ${data.DewPoint}${degree} ${data.TemperatureUnit}`,
|
||||||
|
|
||||||
|
// barometric pressure
|
||||||
|
(data) => `Barometric Pressure: ${data.Pressure} ${data.PressureDirection}`,
|
||||||
|
|
||||||
|
// wind
|
||||||
|
(data) => {
|
||||||
|
let text = '';
|
||||||
|
if (data.WindSpeed > 0) {
|
||||||
|
text = `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`;
|
||||||
|
} else {
|
||||||
|
text = 'Wind: Calm';
|
||||||
|
}
|
||||||
|
if (data.WindGust > 0) {
|
||||||
|
text += ` Gusts to ${data.WindGust}`;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
|
||||||
|
// visibility
|
||||||
|
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : `${data.Ceiling} ${data.CeilingUnit}`}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
// internal draw function with preset parameters
|
||||||
|
const drawCondition = (text) => {
|
||||||
|
// update all html scroll elements
|
||||||
|
elemForEach('.weather-display .scroll .fixed', (elem) => {
|
||||||
|
elem.innerHTML = text;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// return the api
|
||||||
|
export {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.currentWeatherScroll = {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
};
|
|
@ -1,100 +0,0 @@
|
||||||
// drawing functionality and constants
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const draw = (() => {
|
|
||||||
const horizontalGradient = (context, x1, y1, x2, y2, color1, color2) => {
|
|
||||||
const linearGradient = context.createLinearGradient(0, y1, 0, y2);
|
|
||||||
linearGradient.addColorStop(0, color1);
|
|
||||||
linearGradient.addColorStop(0.4, color2);
|
|
||||||
linearGradient.addColorStop(0.6, color2);
|
|
||||||
linearGradient.addColorStop(1, color1);
|
|
||||||
context.fillStyle = linearGradient;
|
|
||||||
context.fillRect(x1, y1, x2 - x1, y2 - y1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const horizontalGradientSingle = (context, x1, y1, x2, y2, color1, color2) => {
|
|
||||||
const linearGradient = context.createLinearGradient(0, y1, 0, y2);
|
|
||||||
linearGradient.addColorStop(0, color1);
|
|
||||||
linearGradient.addColorStop(1, color2);
|
|
||||||
context.fillStyle = linearGradient;
|
|
||||||
context.fillRect(x1, y1, x2 - x1, y2 - y1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const triangle = (context, color, x1, y1, x2, y2, x3, y3) => {
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(x1, y1);
|
|
||||||
context.lineTo(x2, y2);
|
|
||||||
context.lineTo(x3, y3);
|
|
||||||
context.fill();
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleText = (context, title1, title2) => {
|
|
||||||
const font = 'Star4000';
|
|
||||||
const size = '24pt';
|
|
||||||
const color = '#ffff00';
|
|
||||||
const shadow = 3;
|
|
||||||
const x = 170;
|
|
||||||
let y = 55;
|
|
||||||
|
|
||||||
if (title2) {
|
|
||||||
text(context, font, size, color, x, y, title1, shadow); y += 30;
|
|
||||||
text(context, font, size, color, x, y, title2, shadow); y += 30;
|
|
||||||
} else {
|
|
||||||
y += 15;
|
|
||||||
text(context, font, size, color, x, y, title1, shadow); y += 30;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const text = (context, font, size, color, x, y, myText, shadow = 0, align = 'start') => {
|
|
||||||
context.textAlign = align;
|
|
||||||
context.font = `${size} '${font}'`;
|
|
||||||
context.shadowColor = '#000000';
|
|
||||||
context.shadowOffsetX = shadow;
|
|
||||||
context.shadowOffsetY = shadow;
|
|
||||||
context.strokeStyle = '#000000';
|
|
||||||
context.lineWidth = 2;
|
|
||||||
context.strokeText(myText, x, y);
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.fillText(myText, x, y);
|
|
||||||
context.fillStyle = '';
|
|
||||||
context.strokeStyle = '';
|
|
||||||
context.shadowOffsetX = 0;
|
|
||||||
context.shadowOffsetY = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const box = (context, color, x, y, width, height) => {
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.fillRect(x, y, width, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
const border = (context, color, lineWith, x, y, width, height) => {
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = lineWith;
|
|
||||||
context.strokeRect(x, y, width, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
const theme = 1; // classic
|
|
||||||
const topColor1 = 'rgb(192, 91, 2)';
|
|
||||||
const topColor2 = 'rgb(72, 34, 64)';
|
|
||||||
const sideColor1 = 'rgb(46, 18, 80)';
|
|
||||||
const sideColor2 = 'rgb(192, 91, 2)';
|
|
||||||
|
|
||||||
return {
|
|
||||||
// methods
|
|
||||||
horizontalGradient,
|
|
||||||
horizontalGradientSingle,
|
|
||||||
triangle,
|
|
||||||
titleText,
|
|
||||||
text,
|
|
||||||
box,
|
|
||||||
border,
|
|
||||||
|
|
||||||
// constant-ish
|
|
||||||
theme,
|
|
||||||
topColor1,
|
|
||||||
topColor2,
|
|
||||||
sideColor1,
|
|
||||||
sideColor2,
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,9 +1,16 @@
|
||||||
// display extended forecast graphically
|
// display extended forecast graphically
|
||||||
// technically uses the same data as the local forecast, we'll let the browser do the caching of that
|
// technically uses the same data as the local forecast, we'll let the browser do the caching of that
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, icons, navigation, luxon */
|
import STATUS from './status.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
import { fahrenheitToCelsius } from './utils/units.mjs';
|
||||||
|
import { getWeatherIconFromIconLink } from './icons.mjs';
|
||||||
|
import { preloadImg } from './utils/image.mjs';
|
||||||
|
|
||||||
|
/* globals WeatherDisplay, navigation */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class ExtendedForecast extends WeatherDisplay {
|
class ExtendedForecast extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId, 'Extended Forecast', true);
|
super(navId, elemId, 'Extended Forecast', true);
|
||||||
|
@ -21,7 +28,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
if (navigation.units() === UNITS.metric) units = 'si';
|
if (navigation.units() === UNITS.metric) units = 'si';
|
||||||
let forecast;
|
let forecast;
|
||||||
try {
|
try {
|
||||||
forecast = await utils.fetch.json(weatherParameters.forecast, {
|
forecast = await json(weatherParameters.forecast, {
|
||||||
data: {
|
data: {
|
||||||
units,
|
units,
|
||||||
},
|
},
|
||||||
|
@ -44,7 +51,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
const Days = [0, 1, 2, 3, 4, 5, 6];
|
const Days = [0, 1, 2, 3, 4, 5, 6];
|
||||||
|
|
||||||
const dates = Days.map((shift) => {
|
const dates = Days.map((shift) => {
|
||||||
const date = luxon.DateTime.local().startOf('day').plus({ days: shift });
|
const date = DateTime.local().startOf('day').plus({ days: shift });
|
||||||
return date.toLocaleString({ weekday: 'short' });
|
return date.toLocaleString({ weekday: 'short' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,12 +68,12 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
// get the object to modify/populate
|
// get the object to modify/populate
|
||||||
const fDay = forecast[destIndex];
|
const fDay = forecast[destIndex];
|
||||||
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
||||||
fDay.icon = icons.getWeatherIconFromIconLink(period.icon);
|
fDay.icon = getWeatherIconFromIconLink(period.icon);
|
||||||
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
||||||
fDay.dayName = dates[destIndex];
|
fDay.dayName = dates[destIndex];
|
||||||
|
|
||||||
// preload the icon
|
// preload the icon
|
||||||
utils.image.preload(fDay.icon);
|
preloadImg(fDay.icon);
|
||||||
|
|
||||||
if (period.isDaytime) {
|
if (period.isDaytime) {
|
||||||
// day time is the high temperature
|
// day time is the high temperature
|
||||||
|
@ -136,11 +143,11 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
|
|
||||||
let { low } = Day;
|
let { low } = Day;
|
||||||
if (low !== undefined) {
|
if (low !== undefined) {
|
||||||
if (navigation.units() === UNITS.metric) low = utils.units.fahrenheitToCelsius(low);
|
if (navigation.units() === UNITS.metric) low = fahrenheitToCelsius(low);
|
||||||
fill['value-lo'] = Math.round(low);
|
fill['value-lo'] = Math.round(low);
|
||||||
}
|
}
|
||||||
let { high } = Day;
|
let { high } = Day;
|
||||||
if (navigation.units() === UNITS.metric) high = utils.units.fahrenheitToCelsius(high);
|
if (navigation.units() === UNITS.metric) high = fahrenheitToCelsius(high);
|
||||||
fill['value-hi'] = Math.round(high);
|
fill['value-hi'] = Math.round(high);
|
||||||
fill.condition = Day.text;
|
fill.condition = Day.text;
|
||||||
|
|
||||||
|
@ -158,3 +165,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ExtendedForecast;
|
||||||
|
|
||||||
|
window.ExtendedForecast = ExtendedForecast;
|
|
@ -1,7 +1,14 @@
|
||||||
// hourly forecast list
|
// hourly forecast list
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, navigation, icons, luxon */
|
/* globals WeatherDisplay, navigation */
|
||||||
|
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import * as units from './utils/units.mjs';
|
||||||
|
import { getHourlyIcon } from './icons.mjs';
|
||||||
|
import { directionToNSEW } from './utils/calc.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class Hourly extends WeatherDisplay {
|
class Hourly extends WeatherDisplay {
|
||||||
constructor(navId, elemId, defaultActive) {
|
constructor(navId, elemId, defaultActive) {
|
||||||
// special height and width for scrolling
|
// special height and width for scrolling
|
||||||
|
@ -25,7 +32,7 @@ class Hourly extends WeatherDisplay {
|
||||||
let forecast;
|
let forecast;
|
||||||
try {
|
try {
|
||||||
// get the forecast
|
// get the forecast
|
||||||
forecast = await utils.fetch.json(weatherParameters.forecastGridData);
|
forecast = await json(weatherParameters.forecastGridData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Get hourly forecast failed');
|
console.error('Get hourly forecast failed');
|
||||||
console.error(e.status, e.responseJSON);
|
console.error(e.status, e.responseJSON);
|
||||||
|
@ -59,16 +66,16 @@ class Hourly extends WeatherDisplay {
|
||||||
temperature: temperature[idx],
|
temperature: temperature[idx],
|
||||||
apparentTemperature: apparentTemperature[idx],
|
apparentTemperature: apparentTemperature[idx],
|
||||||
windSpeed: windSpeed[idx],
|
windSpeed: windSpeed[idx],
|
||||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
icon: icons[idx],
|
icon: icons[idx],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
|
temperature: units.celsiusToFahrenheit(temperature[idx]),
|
||||||
apparentTemperature: utils.units.celsiusToFahrenheit(apparentTemperature[idx]),
|
apparentTemperature: units.celsiusToFahrenheit(apparentTemperature[idx]),
|
||||||
windSpeed: utils.units.kilometersToMiles(windSpeed[idx]),
|
windSpeed: units.kilometersToMiles(windSpeed[idx]),
|
||||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
icon: icons[idx],
|
icon: icons[idx],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -76,24 +83,24 @@ class Hourly extends WeatherDisplay {
|
||||||
|
|
||||||
// given forecast paramaters determine a suitable icon
|
// given forecast paramaters determine a suitable icon
|
||||||
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
|
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
|
||||||
const startOfHour = luxon.DateTime.local().startOf('hour');
|
const startOfHour = DateTime.local().startOf('hour');
|
||||||
const sunTimes = (await navigation.getSun()).sun;
|
const sunTimes = (await navigation.getSun()).sun;
|
||||||
const overnight = luxon.Interval.fromDateTimes(luxon.DateTime.fromJSDate(sunTimes[0].sunset), luxon.DateTime.fromJSDate(sunTimes[1].sunrise));
|
const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise));
|
||||||
const tomorrowOvernight = luxon.DateTime.fromJSDate(sunTimes[1].sunset);
|
const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset);
|
||||||
return skyCover.map((val, idx) => {
|
return skyCover.map((val, idx) => {
|
||||||
const hour = startOfHour.plus({ hours: idx });
|
const hour = startOfHour.plus({ hours: idx });
|
||||||
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
||||||
return icons.getHourlyIcon(skyCover[idx], weather[idx], iceAccumulation[idx], probabilityOfPrecipitation[idx], snowfallAmount[idx], windSpeed[idx], isNight);
|
return getHourlyIcon(skyCover[idx], weather[idx], iceAccumulation[idx], probabilityOfPrecipitation[idx], snowfallAmount[idx], windSpeed[idx], isNight);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// expand a set of values with durations to an hour-by-hour array
|
// expand a set of values with durations to an hour-by-hour array
|
||||||
static expand(data) {
|
static expand(data) {
|
||||||
const startOfHour = luxon.DateTime.utc().startOf('hour').toMillis();
|
const startOfHour = DateTime.utc().startOf('hour').toMillis();
|
||||||
const result = []; // resulting expanded values
|
const result = []; // resulting expanded values
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
||||||
const duration = luxon.Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/') + 1)).shiftTo('milliseconds').values.milliseconds;
|
const duration = Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/') + 1)).shiftTo('milliseconds').values.milliseconds;
|
||||||
const endTime = startTime + duration;
|
const endTime = startTime + duration;
|
||||||
// loop through duration at one hour intervals
|
// loop through duration at one hour intervals
|
||||||
do {
|
do {
|
||||||
|
@ -114,7 +121,7 @@ class Hourly extends WeatherDisplay {
|
||||||
const list = this.elem.querySelector('.hourly-lines');
|
const list = this.elem.querySelector('.hourly-lines');
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
|
|
||||||
const startingHour = luxon.DateTime.local();
|
const startingHour = DateTime.local();
|
||||||
|
|
||||||
const lines = this.data.map((data, index) => {
|
const lines = this.data.map((data, index) => {
|
||||||
const fillValues = {};
|
const fillValues = {};
|
||||||
|
@ -177,7 +184,6 @@ class Hourly extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
const { DateTime } = luxon;
|
|
||||||
// effectively returns early on the first found date
|
// effectively returns early on the first found date
|
||||||
return cities.reduce((dayName, city) => {
|
return cities.reduce((dayName, city) => {
|
||||||
if (city && dayName === '') {
|
if (city && dayName === '') {
|
||||||
|
@ -190,3 +196,7 @@ class Hourly extends WeatherDisplay {
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Hourly;
|
||||||
|
|
||||||
|
window.Hourly = Hourly;
|
|
@ -1,335 +0,0 @@
|
||||||
/* spell-checker: disable */
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const icons = (() => {
|
|
||||||
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
|
||||||
// extract day or night if not provided
|
|
||||||
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
|
||||||
// internal function to add path to returned icon
|
|
||||||
const addPath = (icon) => `images/r/${icon}`;
|
|
||||||
|
|
||||||
// grab everything after the last slash ending at any of these: ?&,
|
|
||||||
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
|
||||||
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1];
|
|
||||||
// using probability as a crude heavy/light indication where possible
|
|
||||||
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
|
|
||||||
|
|
||||||
// if a 'DualImage' is captured, adjust to just the j parameter
|
|
||||||
if (conditionName === 'dualimage') {
|
|
||||||
const match = link.match(/&j=(.*)&/);
|
|
||||||
[, conditionName] = match;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the icon
|
|
||||||
switch (conditionName + (isNightTime ? '-n' : '')) {
|
|
||||||
case 'skc':
|
|
||||||
case 'hot':
|
|
||||||
case 'haze':
|
|
||||||
return addPath('Sunny.gif');
|
|
||||||
|
|
||||||
case 'skc-n':
|
|
||||||
case 'nskc':
|
|
||||||
case 'nskc-n':
|
|
||||||
case 'cold-n':
|
|
||||||
return addPath('Clear-1992.gif');
|
|
||||||
|
|
||||||
case 'bkn':
|
|
||||||
return addPath('Mostly-Cloudy-1994-2.gif');
|
|
||||||
|
|
||||||
case 'bkn-n':
|
|
||||||
case 'few-n':
|
|
||||||
case 'nfew-n':
|
|
||||||
case 'nfew':
|
|
||||||
return addPath('Partly-Clear-1994-2.gif');
|
|
||||||
|
|
||||||
case 'sct':
|
|
||||||
case 'few':
|
|
||||||
return addPath('Partly-Cloudy.gif');
|
|
||||||
|
|
||||||
case 'sct-n':
|
|
||||||
case 'nsct':
|
|
||||||
case 'nsct-n':
|
|
||||||
return addPath('Mostly-Clear.gif');
|
|
||||||
|
|
||||||
case 'ovc':
|
|
||||||
case 'ovc-n':
|
|
||||||
return addPath('Cloudy.gif');
|
|
||||||
|
|
||||||
case 'fog':
|
|
||||||
case 'fog-n':
|
|
||||||
return addPath('Fog.gif');
|
|
||||||
|
|
||||||
case 'rain_sleet':
|
|
||||||
return addPath('Sleet.gif');
|
|
||||||
|
|
||||||
case 'rain_showers':
|
|
||||||
case 'rain_showers_high':
|
|
||||||
return addPath('Scattered-Showers-1994-2.gif');
|
|
||||||
|
|
||||||
case 'rain_showers-n':
|
|
||||||
case 'rain_showers_high-n':
|
|
||||||
return addPath('Scattered-Showers-Night-1994-2.gif');
|
|
||||||
|
|
||||||
case 'rain':
|
|
||||||
case 'rain-n':
|
|
||||||
return addPath('Rain-1992.gif');
|
|
||||||
|
|
||||||
// case 'snow':
|
|
||||||
// return addPath('Light-Snow.gif');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case 'cc_snowshowers.gif':
|
|
||||||
// //case "heavy-snow.gif":
|
|
||||||
// return addPath('AM-Snow-1994.gif');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case 'snow':
|
|
||||||
case 'snow-n':
|
|
||||||
if (value > 50) return addPath('Heavy-Snow-1994-2.gif');
|
|
||||||
return addPath('Light-Snow.gif');
|
|
||||||
|
|
||||||
case 'rain_snow':
|
|
||||||
return addPath('Rain-Snow-1992.gif');
|
|
||||||
|
|
||||||
case 'snow_fzra':
|
|
||||||
case 'snow_fzra-n':
|
|
||||||
return addPath('Freezing-Rain-Snow-1992.gif');
|
|
||||||
|
|
||||||
case 'fzra':
|
|
||||||
case 'fzra-n':
|
|
||||||
return addPath('Freezing-Rain-1992.gif');
|
|
||||||
|
|
||||||
case 'snow_sleet':
|
|
||||||
case 'snow_sleet-n':
|
|
||||||
return addPath('Snow and Sleet.gif');
|
|
||||||
|
|
||||||
case 'sleet':
|
|
||||||
case 'sleet-n':
|
|
||||||
return addPath('Sleet.gif');
|
|
||||||
|
|
||||||
case 'tsra_sct':
|
|
||||||
case 'tsra':
|
|
||||||
return addPath('Scattered-Tstorms-1994-2.gif');
|
|
||||||
|
|
||||||
case 'tsra_sct-n':
|
|
||||||
case 'tsra-n':
|
|
||||||
return addPath('Scattered-Tstorms-Night-1994-2.gif');
|
|
||||||
|
|
||||||
case 'tsra_hi':
|
|
||||||
case 'tsra_hi-n':
|
|
||||||
case 'hurricane':
|
|
||||||
case 'tropical_storm':
|
|
||||||
return addPath('Thunderstorm.gif');
|
|
||||||
|
|
||||||
case 'wind_few':
|
|
||||||
case 'wind_sct':
|
|
||||||
case 'wind_bkn':
|
|
||||||
case 'wind_ovc':
|
|
||||||
return addPath('Wind.gif');
|
|
||||||
|
|
||||||
case 'wind_skc':
|
|
||||||
return addPath('Sunny-Wind-1994.gif');
|
|
||||||
|
|
||||||
case 'wind_skc-n':
|
|
||||||
case 'wind_sct-n':
|
|
||||||
return addPath('Clear-Wind-1994.gif');
|
|
||||||
|
|
||||||
case 'blizzard':
|
|
||||||
return addPath('Blowing Snow.gif');
|
|
||||||
|
|
||||||
case 'cold':
|
|
||||||
return addPath('cold.gif');
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
|
||||||
if (!link) return false;
|
|
||||||
|
|
||||||
// internal function to add path to returned icon
|
|
||||||
const addPath = (icon) => `images/${icon}`;
|
|
||||||
// extract day or night if not provided
|
|
||||||
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
|
||||||
|
|
||||||
// grab everything after the last slash ending at any of these: ?&,
|
|
||||||
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
|
||||||
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1];
|
|
||||||
// using probability as a crude heavy/light indication where possible
|
|
||||||
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
|
|
||||||
|
|
||||||
// if a 'DualImage' is captured, adjust to just the j parameter
|
|
||||||
if (conditionName === 'dualimage') {
|
|
||||||
const match = link.match(/&j=(.*)&/);
|
|
||||||
[, conditionName] = match;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the icon
|
|
||||||
switch (conditionName + (isNightTime ? '-n' : '')) {
|
|
||||||
case 'skc':
|
|
||||||
case 'hot':
|
|
||||||
case 'haze':
|
|
||||||
case 'cold':
|
|
||||||
return addPath('CC_Clear1.gif');
|
|
||||||
|
|
||||||
case 'skc-n':
|
|
||||||
case 'nskc':
|
|
||||||
case 'nskc-n':
|
|
||||||
case 'cold-n':
|
|
||||||
return addPath('CC_Clear0.gif');
|
|
||||||
|
|
||||||
case 'sct':
|
|
||||||
case 'few':
|
|
||||||
case 'bkn':
|
|
||||||
return addPath('CC_PartlyCloudy1.gif');
|
|
||||||
|
|
||||||
case 'bkn-n':
|
|
||||||
case 'few-n':
|
|
||||||
case 'nfew-n':
|
|
||||||
case 'nfew':
|
|
||||||
case 'sct-n':
|
|
||||||
case 'nsct':
|
|
||||||
case 'nsct-n':
|
|
||||||
return addPath('CC_PartlyCloudy0.gif');
|
|
||||||
|
|
||||||
case 'ovc':
|
|
||||||
case 'novc':
|
|
||||||
case 'ovc-n':
|
|
||||||
return addPath('CC_Cloudy.gif');
|
|
||||||
|
|
||||||
case 'fog':
|
|
||||||
case 'fog-n':
|
|
||||||
return addPath('CC_Fog.gif');
|
|
||||||
|
|
||||||
case 'rain_sleet':
|
|
||||||
return addPath('Sleet.gif');
|
|
||||||
|
|
||||||
case 'rain_showers':
|
|
||||||
case 'rain_showers_high':
|
|
||||||
return addPath('CC_Showers.gif');
|
|
||||||
|
|
||||||
case 'rain_showers-n':
|
|
||||||
case 'rain_showers_high-n':
|
|
||||||
return addPath('CC_Showers.gif');
|
|
||||||
|
|
||||||
case 'rain':
|
|
||||||
case 'rain-n':
|
|
||||||
return addPath('CC_Rain.gif');
|
|
||||||
|
|
||||||
// case 'snow':
|
|
||||||
// return addPath('Light-Snow.gif');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case 'cc_snowshowers.gif':
|
|
||||||
// //case "heavy-snow.gif":
|
|
||||||
// return addPath('AM-Snow-1994.gif');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case 'snow':
|
|
||||||
case 'snow-n':
|
|
||||||
if (value > 50) return addPath('CC_Snow.gif');
|
|
||||||
return addPath('CC_SnowShowers.gif');
|
|
||||||
|
|
||||||
case 'rain_snow':
|
|
||||||
return addPath('CC_RainSnow.gif');
|
|
||||||
|
|
||||||
case 'snow_fzra':
|
|
||||||
case 'snow_fzra-n':
|
|
||||||
case 'fzra':
|
|
||||||
case 'fzra-n':
|
|
||||||
return addPath('CC_FreezingRain.gif');
|
|
||||||
|
|
||||||
case 'snow_sleet':
|
|
||||||
return addPath('Snow-Sleet.gif');
|
|
||||||
|
|
||||||
case 'tsra_sct':
|
|
||||||
case 'tsra':
|
|
||||||
return addPath('EF_ScatTstorms.gif');
|
|
||||||
|
|
||||||
case 'tsra_sct-n':
|
|
||||||
case 'tsra-n':
|
|
||||||
return addPath('CC_TStorm.gif');
|
|
||||||
|
|
||||||
case 'tsra_hi':
|
|
||||||
case 'tsra_hi-n':
|
|
||||||
case 'hurricane':
|
|
||||||
case 'tropical_storm':
|
|
||||||
return addPath('CC_TStorm.gif');
|
|
||||||
|
|
||||||
case 'wind_few':
|
|
||||||
case 'wind_sct':
|
|
||||||
case 'wind_bkn':
|
|
||||||
case 'wind_ovc':
|
|
||||||
return addPath('CC_Windy.gif');
|
|
||||||
|
|
||||||
case 'wind_skc':
|
|
||||||
case 'wind_skc-n':
|
|
||||||
case 'wind_sct-n':
|
|
||||||
return addPath('CC_Windy.gif');
|
|
||||||
|
|
||||||
case 'blizzard':
|
|
||||||
return addPath('Blowing-Snow.gif');
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log(`Unable to locate icon for ${conditionName} ${link} ${isNightTime}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getHourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => {
|
|
||||||
// internal function to add path to returned icon
|
|
||||||
const addPath = (icon) => `images/r/${icon}`;
|
|
||||||
|
|
||||||
// possible phenomenon
|
|
||||||
let thunder = false;
|
|
||||||
let snow = false;
|
|
||||||
let ice = false;
|
|
||||||
let fog = false;
|
|
||||||
let wind = false;
|
|
||||||
|
|
||||||
// test the phenomenon for various value if it is provided.
|
|
||||||
weather.forEach((phenomenon) => {
|
|
||||||
if (!phenomenon.weather) return;
|
|
||||||
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
|
|
||||||
if (phenomenon.weather.toLowerCase().includes('snow')) snow = true;
|
|
||||||
if (phenomenon.weather.toLowerCase().includes('ice')) ice = true;
|
|
||||||
if (phenomenon.weather.toLowerCase().includes('fog')) fog = true;
|
|
||||||
if (phenomenon.weather.toLowerCase().includes('wind')) wind = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// first item in list is highest priority, units are metric where applicable
|
|
||||||
if (iceAccumulation > 0 || ice) return addPath('Freezing-Rain-1992.gif');
|
|
||||||
if (snowfallAmount > 10) {
|
|
||||||
if (windSpeed > 30 || wind) return addPath('Blowing Snow.gif');
|
|
||||||
return addPath('Heavy-Snow-1994.gif');
|
|
||||||
}
|
|
||||||
if ((snowfallAmount > 0 || snow) && thunder) return addPath('ThunderSnow.gif');
|
|
||||||
if (snowfallAmount > 0 || snow) return addPath('Light-Snow.gif');
|
|
||||||
if (thunder) return (addPath('Thunderstorm.gif'));
|
|
||||||
if (probabilityOfPrecipitation > 70) return addPath('Rain-1992.gif');
|
|
||||||
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
|
|
||||||
if (probabilityOfPrecipitation > 30) {
|
|
||||||
if (!isNight) return addPath('Scattered-Showers-1994.gif');
|
|
||||||
return addPath('Scattered-Showers-Night.gif');
|
|
||||||
}
|
|
||||||
if (fog) return addPath('Fog.gif');
|
|
||||||
if (skyCover > 70) return addPath('Cloudy.gif');
|
|
||||||
if (skyCover > 50) {
|
|
||||||
if (!isNight) return addPath('Mostly-Cloudy-1994.gif');
|
|
||||||
return addPath('Partly-Clear-1994.gif');
|
|
||||||
}
|
|
||||||
if (skyCover > 30) {
|
|
||||||
if (!isNight) return addPath('Partly-Cloudy.gif');
|
|
||||||
return addPath('Mostly-Clear.gif');
|
|
||||||
}
|
|
||||||
if (isNight) return addPath('Clear-1992.gif');
|
|
||||||
return addPath('Sunny.gif');
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
getWeatherIconFromIconLink,
|
|
||||||
getWeatherRegionalIconFromIconLink,
|
|
||||||
getHourlyIcon,
|
|
||||||
};
|
|
||||||
})();
|
|
339
server/scripts/modules/icons.mjs
Normal file
339
server/scripts/modules/icons.mjs
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
/* spell-checker: disable */
|
||||||
|
|
||||||
|
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
||||||
|
// extract day or night if not provided
|
||||||
|
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
||||||
|
// internal function to add path to returned icon
|
||||||
|
const addPath = (icon) => `images/r/${icon}`;
|
||||||
|
|
||||||
|
// grab everything after the last slash ending at any of these: ?&,
|
||||||
|
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
||||||
|
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1];
|
||||||
|
// using probability as a crude heavy/light indication where possible
|
||||||
|
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
|
||||||
|
|
||||||
|
// if a 'DualImage' is captured, adjust to just the j parameter
|
||||||
|
if (conditionName === 'dualimage') {
|
||||||
|
const match = link.match(/&j=(.*)&/);
|
||||||
|
[, conditionName] = match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the icon
|
||||||
|
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||||
|
case 'skc':
|
||||||
|
case 'hot':
|
||||||
|
case 'haze':
|
||||||
|
return addPath('Sunny.gif');
|
||||||
|
|
||||||
|
case 'skc-n':
|
||||||
|
case 'nskc':
|
||||||
|
case 'nskc-n':
|
||||||
|
case 'cold-n':
|
||||||
|
return addPath('Clear-1992.gif');
|
||||||
|
|
||||||
|
case 'bkn':
|
||||||
|
return addPath('Mostly-Cloudy-1994-2.gif');
|
||||||
|
|
||||||
|
case 'bkn-n':
|
||||||
|
case 'few-n':
|
||||||
|
case 'nfew-n':
|
||||||
|
case 'nfew':
|
||||||
|
return addPath('Partly-Clear-1994-2.gif');
|
||||||
|
|
||||||
|
case 'sct':
|
||||||
|
case 'few':
|
||||||
|
return addPath('Partly-Cloudy.gif');
|
||||||
|
|
||||||
|
case 'sct-n':
|
||||||
|
case 'nsct':
|
||||||
|
case 'nsct-n':
|
||||||
|
return addPath('Mostly-Clear.gif');
|
||||||
|
|
||||||
|
case 'ovc':
|
||||||
|
case 'ovc-n':
|
||||||
|
return addPath('Cloudy.gif');
|
||||||
|
|
||||||
|
case 'fog':
|
||||||
|
case 'fog-n':
|
||||||
|
return addPath('Fog.gif');
|
||||||
|
|
||||||
|
case 'rain_sleet':
|
||||||
|
return addPath('Sleet.gif');
|
||||||
|
|
||||||
|
case 'rain_showers':
|
||||||
|
case 'rain_showers_high':
|
||||||
|
return addPath('Scattered-Showers-1994-2.gif');
|
||||||
|
|
||||||
|
case 'rain_showers-n':
|
||||||
|
case 'rain_showers_high-n':
|
||||||
|
return addPath('Scattered-Showers-Night-1994-2.gif');
|
||||||
|
|
||||||
|
case 'rain':
|
||||||
|
case 'rain-n':
|
||||||
|
return addPath('Rain-1992.gif');
|
||||||
|
|
||||||
|
// case 'snow':
|
||||||
|
// return addPath('Light-Snow.gif');
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'cc_snowshowers.gif':
|
||||||
|
// //case "heavy-snow.gif":
|
||||||
|
// return addPath('AM-Snow-1994.gif');
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 'snow':
|
||||||
|
case 'snow-n':
|
||||||
|
if (value > 50) return addPath('Heavy-Snow-1994-2.gif');
|
||||||
|
return addPath('Light-Snow.gif');
|
||||||
|
|
||||||
|
case 'rain_snow':
|
||||||
|
return addPath('Rain-Snow-1992.gif');
|
||||||
|
|
||||||
|
case 'snow_fzra':
|
||||||
|
case 'snow_fzra-n':
|
||||||
|
return addPath('Freezing-Rain-Snow-1992.gif');
|
||||||
|
|
||||||
|
case 'fzra':
|
||||||
|
case 'fzra-n':
|
||||||
|
return addPath('Freezing-Rain-1992.gif');
|
||||||
|
|
||||||
|
case 'snow_sleet':
|
||||||
|
case 'snow_sleet-n':
|
||||||
|
return addPath('Snow and Sleet.gif');
|
||||||
|
|
||||||
|
case 'sleet':
|
||||||
|
case 'sleet-n':
|
||||||
|
return addPath('Sleet.gif');
|
||||||
|
|
||||||
|
case 'tsra_sct':
|
||||||
|
case 'tsra':
|
||||||
|
return addPath('Scattered-Tstorms-1994-2.gif');
|
||||||
|
|
||||||
|
case 'tsra_sct-n':
|
||||||
|
case 'tsra-n':
|
||||||
|
return addPath('Scattered-Tstorms-Night-1994-2.gif');
|
||||||
|
|
||||||
|
case 'tsra_hi':
|
||||||
|
case 'tsra_hi-n':
|
||||||
|
case 'hurricane':
|
||||||
|
case 'tropical_storm':
|
||||||
|
return addPath('Thunderstorm.gif');
|
||||||
|
|
||||||
|
case 'wind_few':
|
||||||
|
case 'wind_sct':
|
||||||
|
case 'wind_bkn':
|
||||||
|
case 'wind_ovc':
|
||||||
|
return addPath('Wind.gif');
|
||||||
|
|
||||||
|
case 'wind_skc':
|
||||||
|
return addPath('Sunny-Wind-1994.gif');
|
||||||
|
|
||||||
|
case 'wind_skc-n':
|
||||||
|
case 'wind_sct-n':
|
||||||
|
return addPath('Clear-Wind-1994.gif');
|
||||||
|
|
||||||
|
case 'blizzard':
|
||||||
|
return addPath('Blowing Snow.gif');
|
||||||
|
|
||||||
|
case 'cold':
|
||||||
|
return addPath('cold.gif');
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
||||||
|
if (!link) return false;
|
||||||
|
|
||||||
|
// internal function to add path to returned icon
|
||||||
|
const addPath = (icon) => `images/${icon}`;
|
||||||
|
// extract day or night if not provided
|
||||||
|
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
||||||
|
|
||||||
|
// grab everything after the last slash ending at any of these: ?&,
|
||||||
|
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
||||||
|
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1];
|
||||||
|
// using probability as a crude heavy/light indication where possible
|
||||||
|
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
|
||||||
|
|
||||||
|
// if a 'DualImage' is captured, adjust to just the j parameter
|
||||||
|
if (conditionName === 'dualimage') {
|
||||||
|
const match = link.match(/&j=(.*)&/);
|
||||||
|
[, conditionName] = match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the icon
|
||||||
|
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||||
|
case 'skc':
|
||||||
|
case 'hot':
|
||||||
|
case 'haze':
|
||||||
|
case 'cold':
|
||||||
|
return addPath('CC_Clear1.gif');
|
||||||
|
|
||||||
|
case 'skc-n':
|
||||||
|
case 'nskc':
|
||||||
|
case 'nskc-n':
|
||||||
|
case 'cold-n':
|
||||||
|
return addPath('CC_Clear0.gif');
|
||||||
|
|
||||||
|
case 'sct':
|
||||||
|
case 'few':
|
||||||
|
case 'bkn':
|
||||||
|
return addPath('CC_PartlyCloudy1.gif');
|
||||||
|
|
||||||
|
case 'bkn-n':
|
||||||
|
case 'few-n':
|
||||||
|
case 'nfew-n':
|
||||||
|
case 'nfew':
|
||||||
|
case 'sct-n':
|
||||||
|
case 'nsct':
|
||||||
|
case 'nsct-n':
|
||||||
|
return addPath('CC_PartlyCloudy0.gif');
|
||||||
|
|
||||||
|
case 'ovc':
|
||||||
|
case 'novc':
|
||||||
|
case 'ovc-n':
|
||||||
|
return addPath('CC_Cloudy.gif');
|
||||||
|
|
||||||
|
case 'fog':
|
||||||
|
case 'fog-n':
|
||||||
|
return addPath('CC_Fog.gif');
|
||||||
|
|
||||||
|
case 'rain_sleet':
|
||||||
|
return addPath('Sleet.gif');
|
||||||
|
|
||||||
|
case 'rain_showers':
|
||||||
|
case 'rain_showers_high':
|
||||||
|
return addPath('CC_Showers.gif');
|
||||||
|
|
||||||
|
case 'rain_showers-n':
|
||||||
|
case 'rain_showers_high-n':
|
||||||
|
return addPath('CC_Showers.gif');
|
||||||
|
|
||||||
|
case 'rain':
|
||||||
|
case 'rain-n':
|
||||||
|
return addPath('CC_Rain.gif');
|
||||||
|
|
||||||
|
// case 'snow':
|
||||||
|
// return addPath('Light-Snow.gif');
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'cc_snowshowers.gif':
|
||||||
|
// //case "heavy-snow.gif":
|
||||||
|
// return addPath('AM-Snow-1994.gif');
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 'snow':
|
||||||
|
case 'snow-n':
|
||||||
|
if (value > 50) return addPath('CC_Snow.gif');
|
||||||
|
return addPath('CC_SnowShowers.gif');
|
||||||
|
|
||||||
|
case 'rain_snow':
|
||||||
|
return addPath('CC_RainSnow.gif');
|
||||||
|
|
||||||
|
case 'snow_fzra':
|
||||||
|
case 'snow_fzra-n':
|
||||||
|
case 'fzra':
|
||||||
|
case 'fzra-n':
|
||||||
|
return addPath('CC_FreezingRain.gif');
|
||||||
|
|
||||||
|
case 'snow_sleet':
|
||||||
|
return addPath('Snow-Sleet.gif');
|
||||||
|
|
||||||
|
case 'tsra_sct':
|
||||||
|
case 'tsra':
|
||||||
|
return addPath('EF_ScatTstorms.gif');
|
||||||
|
|
||||||
|
case 'tsra_sct-n':
|
||||||
|
case 'tsra-n':
|
||||||
|
return addPath('CC_TStorm.gif');
|
||||||
|
|
||||||
|
case 'tsra_hi':
|
||||||
|
case 'tsra_hi-n':
|
||||||
|
case 'hurricane':
|
||||||
|
case 'tropical_storm':
|
||||||
|
return addPath('CC_TStorm.gif');
|
||||||
|
|
||||||
|
case 'wind_few':
|
||||||
|
case 'wind_sct':
|
||||||
|
case 'wind_bkn':
|
||||||
|
case 'wind_ovc':
|
||||||
|
return addPath('CC_Windy.gif');
|
||||||
|
|
||||||
|
case 'wind_skc':
|
||||||
|
case 'wind_skc-n':
|
||||||
|
case 'wind_sct-n':
|
||||||
|
return addPath('CC_Windy.gif');
|
||||||
|
|
||||||
|
case 'blizzard':
|
||||||
|
return addPath('Blowing-Snow.gif');
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`Unable to locate icon for ${conditionName} ${link} ${isNightTime}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHourlyIcon = (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed, isNight = false) => {
|
||||||
|
// internal function to add path to returned icon
|
||||||
|
const addPath = (icon) => `images/r/${icon}`;
|
||||||
|
|
||||||
|
// possible phenomenon
|
||||||
|
let thunder = false;
|
||||||
|
let snow = false;
|
||||||
|
let ice = false;
|
||||||
|
let fog = false;
|
||||||
|
let wind = false;
|
||||||
|
|
||||||
|
// test the phenomenon for various value if it is provided.
|
||||||
|
weather.forEach((phenomenon) => {
|
||||||
|
if (!phenomenon.weather) return;
|
||||||
|
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
|
||||||
|
if (phenomenon.weather.toLowerCase().includes('snow')) snow = true;
|
||||||
|
if (phenomenon.weather.toLowerCase().includes('ice')) ice = true;
|
||||||
|
if (phenomenon.weather.toLowerCase().includes('fog')) fog = true;
|
||||||
|
if (phenomenon.weather.toLowerCase().includes('wind')) wind = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// first item in list is highest priority, units are metric where applicable
|
||||||
|
if (iceAccumulation > 0 || ice) return addPath('Freezing-Rain-1992.gif');
|
||||||
|
if (snowfallAmount > 10) {
|
||||||
|
if (windSpeed > 30 || wind) return addPath('Blowing Snow.gif');
|
||||||
|
return addPath('Heavy-Snow-1994.gif');
|
||||||
|
}
|
||||||
|
if ((snowfallAmount > 0 || snow) && thunder) return addPath('ThunderSnow.gif');
|
||||||
|
if (snowfallAmount > 0 || snow) return addPath('Light-Snow.gif');
|
||||||
|
if (thunder) return (addPath('Thunderstorm.gif'));
|
||||||
|
if (probabilityOfPrecipitation > 70) return addPath('Rain-1992.gif');
|
||||||
|
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
|
||||||
|
if (probabilityOfPrecipitation > 30) {
|
||||||
|
if (!isNight) return addPath('Scattered-Showers-1994.gif');
|
||||||
|
return addPath('Scattered-Showers-Night.gif');
|
||||||
|
}
|
||||||
|
if (fog) return addPath('Fog.gif');
|
||||||
|
if (skyCover > 70) return addPath('Cloudy.gif');
|
||||||
|
if (skyCover > 50) {
|
||||||
|
if (!isNight) return addPath('Mostly-Cloudy-1994.gif');
|
||||||
|
return addPath('Partly-Clear-1994.gif');
|
||||||
|
}
|
||||||
|
if (skyCover > 30) {
|
||||||
|
if (!isNight) return addPath('Partly-Cloudy.gif');
|
||||||
|
return addPath('Mostly-Clear.gif');
|
||||||
|
}
|
||||||
|
if (isNight) return addPath('Clear-1992.gif');
|
||||||
|
return addPath('Sunny.gif');
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getWeatherIconFromIconLink,
|
||||||
|
getWeatherRegionalIconFromIconLink,
|
||||||
|
getHourlyIcon,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.icons = {
|
||||||
|
getWeatherIconFromIconLink,
|
||||||
|
getWeatherRegionalIconFromIconLink,
|
||||||
|
getHourlyIcon,
|
||||||
|
};
|
|
@ -1,7 +1,12 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, navigation, StationInfo */
|
/* globals WeatherDisplay, navigation, StationInfo */
|
||||||
|
import { distance as calcDistance, directionToNSEW } from './utils/calc.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { locationCleanup } from './utils/string.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import * as units from './utils/units.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class LatestObservations extends WeatherDisplay {
|
class LatestObservations extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId, 'Latest Observations', true);
|
super(navId, elemId, 'Latest Observations', true);
|
||||||
|
@ -17,7 +22,7 @@ class LatestObservations extends WeatherDisplay {
|
||||||
// calculate distance to each station
|
// calculate distance to each station
|
||||||
const stationsByDistance = Object.keys(StationInfo).map((key) => {
|
const stationsByDistance = Object.keys(StationInfo).map((key) => {
|
||||||
const station = StationInfo[key];
|
const station = StationInfo[key];
|
||||||
const distance = utils.calc.distance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
|
const distance = calcDistance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
|
||||||
return { ...station, distance };
|
return { ...station, distance };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,7 +34,7 @@ class LatestObservations extends WeatherDisplay {
|
||||||
// get data for regional stations
|
// get data for regional stations
|
||||||
const allConditions = await Promise.all(regionalStations.map(async (station) => {
|
const allConditions = await Promise.all(regionalStations.map(async (station) => {
|
||||||
try {
|
try {
|
||||||
const data = await utils.fetch.json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
|
const data = await json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
|
||||||
// test for temperature, weather and wind values present
|
// test for temperature, weather and wind values present
|
||||||
if (data.properties.temperature.value === null
|
if (data.properties.temperature.value === null
|
||||||
|| data.properties.textDescription === ''
|
|| data.properties.textDescription === ''
|
||||||
|
@ -76,17 +81,17 @@ class LatestObservations extends WeatherDisplay {
|
||||||
const lines = sortedConditions.map((condition) => {
|
const lines = sortedConditions.map((condition) => {
|
||||||
let Temperature = condition.temperature.value;
|
let Temperature = condition.temperature.value;
|
||||||
let WindSpeed = condition.windSpeed.value;
|
let WindSpeed = condition.windSpeed.value;
|
||||||
const windDirection = utils.calc.directionToNSEW(condition.windDirection.value);
|
const windDirection = directionToNSEW(condition.windDirection.value);
|
||||||
|
|
||||||
if (navigation.units() === UNITS.english) {
|
if (navigation.units() === UNITS.english) {
|
||||||
Temperature = utils.units.celsiusToFahrenheit(Temperature);
|
Temperature = units.celsiusToFahrenheit(Temperature);
|
||||||
WindSpeed = utils.units.kphToMph(WindSpeed);
|
WindSpeed = units.kphToMph(WindSpeed);
|
||||||
}
|
}
|
||||||
WindSpeed = Math.round(WindSpeed);
|
WindSpeed = Math.round(WindSpeed);
|
||||||
Temperature = Math.round(Temperature);
|
Temperature = Math.round(Temperature);
|
||||||
|
|
||||||
const fill = {};
|
const fill = {};
|
||||||
fill.location = utils.string.locationCleanup(condition.city).substr(0, 14);
|
fill.location = locationCleanup(condition.city).substr(0, 14);
|
||||||
fill.temp = Temperature;
|
fill.temp = Temperature;
|
||||||
fill.weather = LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
fill.weather = LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
||||||
if (WindSpeed > 0) {
|
if (WindSpeed > 0) {
|
||||||
|
@ -126,3 +131,5 @@ class LatestObservations extends WeatherDisplay {
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.LatestObservations = LatestObservations;
|
|
@ -1,8 +1,10 @@
|
||||||
// display text based local forecast
|
// display text based local forecast
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, navigation */
|
/* globals WeatherDisplay, navigation */
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class LocalForecast extends WeatherDisplay {
|
class LocalForecast extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId, 'Local Forecast', true);
|
super(navId, elemId, 'Local Forecast', true);
|
||||||
|
@ -62,7 +64,7 @@ class LocalForecast extends WeatherDisplay {
|
||||||
let units = 'us';
|
let units = 'us';
|
||||||
if (navigation.units() === UNITS.metric) units = 'si';
|
if (navigation.units() === UNITS.metric) units = 'si';
|
||||||
try {
|
try {
|
||||||
return await utils.fetch.json(weatherParameters.forecast, {
|
return await json(weatherParameters.forecast, {
|
||||||
data: {
|
data: {
|
||||||
units,
|
units,
|
||||||
},
|
},
|
||||||
|
@ -94,3 +96,5 @@ class LocalForecast extends WeatherDisplay {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.LocalForecast = LocalForecast;
|
|
@ -1,14 +1,14 @@
|
||||||
// regional forecast and observations
|
// regional forecast and observations
|
||||||
|
/* globals WeatherDisplay, navigation */
|
||||||
|
import { loadImg } from './utils/image.mjs';
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, navigation */
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class Progress extends WeatherDisplay {
|
class Progress extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId, '', false);
|
super(navId, elemId, '', false);
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
this.backgroundImage = loadImg('images/BackGround1_1.png');
|
||||||
|
|
||||||
// disable any navigation timing
|
// disable any navigation timing
|
||||||
this.timing = false;
|
this.timing = false;
|
||||||
|
@ -101,3 +101,5 @@ class Progress extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.Progress = Progress;
|
|
@ -1,7 +1,11 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, luxon */
|
/* globals WeatherDisplay */
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
import { loadImg } from './utils/image.mjs';
|
||||||
|
import { text } from './utils/fetch.mjs';
|
||||||
|
import { rewriteUrl } from './utils/cors.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class Radar extends WeatherDisplay {
|
class Radar extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId, 'Local Radar', true);
|
super(navId, elemId, 'Local Radar', true);
|
||||||
|
@ -43,13 +47,10 @@ class Radar extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// date and time parsing
|
|
||||||
const { DateTime } = luxon;
|
|
||||||
|
|
||||||
// get the base map
|
// get the base map
|
||||||
let src = 'images/4000RadarMap2.jpg';
|
let src = 'images/4000RadarMap2.jpg';
|
||||||
if (weatherParameters.State === 'HI') src = 'images/HawaiiRadarMap2.png';
|
if (weatherParameters.State === 'HI') src = 'images/HawaiiRadarMap2.png';
|
||||||
this.baseMap = await utils.image.load(src);
|
this.baseMap = await loadImg(src);
|
||||||
|
|
||||||
const baseUrl = 'https://mesonet.agron.iastate.edu/archive/data/';
|
const baseUrl = 'https://mesonet.agron.iastate.edu/archive/data/';
|
||||||
const baseUrlEnd = '/GIS/uscomp/';
|
const baseUrlEnd = '/GIS/uscomp/';
|
||||||
|
@ -65,7 +66,7 @@ class Radar extends WeatherDisplay {
|
||||||
const lists = (await Promise.all(baseUrls.map(async (url) => {
|
const lists = (await Promise.all(baseUrls.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
// get a list of available radars
|
// get a list of available radars
|
||||||
const radarHtml = await utils.fetch.text(url, { cors: true });
|
const radarHtml = await text(url, { cors: true });
|
||||||
return radarHtml;
|
return radarHtml;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Unable to get list of radars');
|
console.log('Unable to get list of radars');
|
||||||
|
@ -130,7 +131,7 @@ class Radar extends WeatherDisplay {
|
||||||
context.imageSmoothingEnabled = false;
|
context.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
// get the image
|
// get the image
|
||||||
const response = await fetch(utils.cors.rewriteUrl(url));
|
const response = await fetch(rewriteUrl(url));
|
||||||
|
|
||||||
// test response
|
// test response
|
||||||
if (!response.ok) throw new Error(`Unable to fetch radar error ${response.status} ${response.statusText} from ${response.url}`);
|
if (!response.ok) throw new Error(`Unable to fetch radar error ${response.status} ${response.statusText} from ${response.url}`);
|
||||||
|
@ -157,7 +158,7 @@ class Radar extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign to an html image element
|
// assign to an html image element
|
||||||
const imgBlob = await utils.image.load(blob);
|
const imgBlob = await loadImg(blob);
|
||||||
|
|
||||||
// draw the entire image
|
// draw the entire image
|
||||||
workingContext.clearRect(0, 0, width, 1600);
|
workingContext.clearRect(0, 0, width, 1600);
|
||||||
|
@ -204,7 +205,6 @@ class Radar extends WeatherDisplay {
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
const { DateTime } = luxon;
|
|
||||||
const time = this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE);
|
const time = this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE);
|
||||||
const timePadded = time.length >= 8 ? time : ` ${time}`;
|
const timePadded = time.length >= 8 ? time : ` ${time}`;
|
||||||
this.elem.querySelector('.header .right .time').innerHTML = timePadded;
|
this.elem.querySelector('.header .right .time').innerHTML = timePadded;
|
||||||
|
@ -396,3 +396,5 @@ class Radar extends WeatherDisplay {
|
||||||
mapContext.drawImage(radarContext.canvas, 0, 0);
|
mapContext.drawImage(radarContext.canvas, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.Radar = Radar;
|
|
@ -1,9 +1,16 @@
|
||||||
// regional forecast and observations
|
// regional forecast and observations
|
||||||
// type 0 = observations, 1 = first forecast, 2 = second forecast
|
// type 0 = observations, 1 = first forecast, 2 = second forecast
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, navigation, luxon, StationInfo, RegionalCities */
|
/* globals WeatherDisplay, navigation, StationInfo, RegionalCities */
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import { distance as calcDistance } from './utils/calc.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import * as units from './utils/units.mjs';
|
||||||
|
import { getWeatherRegionalIconFromIconLink } from './icons.mjs';
|
||||||
|
import { preloadImg } from './utils/image.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
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);
|
||||||
|
@ -55,7 +62,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
const targetDist = city.targetDistance || 1;
|
const targetDist = city.targetDistance || 1;
|
||||||
// Only add the city as long as it isn't within set distance degree of any other city already in the array.
|
// Only add the city as long as it isn't within set distance degree of any other city already in the array.
|
||||||
const okToAddCity = regionalCities.reduce((acc, testCity) => {
|
const okToAddCity = regionalCities.reduce((acc, testCity) => {
|
||||||
const distance = utils.calc.distance(city.lon, city.lat, testCity.lon, testCity.lat);
|
const distance = calcDistance(city.lon, city.lat, testCity.lon, testCity.lat);
|
||||||
return acc && distance >= targetDist;
|
return acc && distance >= targetDist;
|
||||||
}, true);
|
}, true);
|
||||||
if (okToAddCity) regionalCities.push(city);
|
if (okToAddCity) regionalCities.push(city);
|
||||||
|
@ -70,7 +77,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// start off the observation task
|
// start off the observation task
|
||||||
const observationPromise = RegionalForecast.getRegionalObservation(city.point, city);
|
const observationPromise = RegionalForecast.getRegionalObservation(city.point, city);
|
||||||
|
|
||||||
const forecast = await utils.fetch.json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
||||||
|
|
||||||
// get XY on map for city
|
// get XY on map for city
|
||||||
const cityXY = RegionalForecast.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
const cityXY = RegionalForecast.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
||||||
|
@ -80,7 +87,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// format the observation the same as the forecast
|
// format the observation the same as the forecast
|
||||||
const regionalObservation = {
|
const regionalObservation = {
|
||||||
daytime: !!observation.icon.match(/\/day\//),
|
daytime: !!observation.icon.match(/\/day\//),
|
||||||
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
|
temperature: units.celsiusToFahrenheit(observation.temperature.value),
|
||||||
name: RegionalForecast.formatCity(city.city),
|
name: RegionalForecast.formatCity(city.city),
|
||||||
icon: observation.icon,
|
icon: observation.icon,
|
||||||
x: cityXY.x,
|
x: cityXY.x,
|
||||||
|
@ -88,7 +95,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
};
|
};
|
||||||
|
|
||||||
// preload the icon
|
// preload the icon
|
||||||
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(regionalObservation.icon, !regionalObservation.daytime));
|
preloadImg(getWeatherRegionalIconFromIconLink(regionalObservation.icon, !regionalObservation.daytime));
|
||||||
|
|
||||||
// return a pared-down forecast
|
// return a pared-down forecast
|
||||||
// 0th object is the current conditions
|
// 0th object is the current conditions
|
||||||
|
@ -141,15 +148,15 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
static async getRegionalObservation(point, city) {
|
static async getRegionalObservation(point, city) {
|
||||||
try {
|
try {
|
||||||
// get stations
|
// get stations
|
||||||
const stations = await utils.fetch.json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/stations`);
|
const stations = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/stations`);
|
||||||
|
|
||||||
// get the first station
|
// get the first station
|
||||||
const station = stations.features[0].id;
|
const station = stations.features[0].id;
|
||||||
// get the observation data
|
// get the observation data
|
||||||
const observation = await utils.fetch.json(`${station}/observations/latest`);
|
const observation = await json(`${station}/observations/latest`);
|
||||||
// preload the image
|
// preload the image
|
||||||
if (!observation.properties.icon) return false;
|
if (!observation.properties.icon) return false;
|
||||||
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
preloadImg(getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
||||||
// return the observation
|
// return the observation
|
||||||
return observation.properties;
|
return observation.properties;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -328,7 +335,6 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// break up data into useful values
|
// break up data into useful values
|
||||||
const { regionalData: data, sourceXY, offsetXY } = this.data;
|
const { regionalData: data, sourceXY, offsetXY } = this.data;
|
||||||
|
|
||||||
const { DateTime } = luxon;
|
|
||||||
// draw the header graphics
|
// draw the header graphics
|
||||||
|
|
||||||
// draw the appropriate title
|
// draw the appropriate title
|
||||||
|
@ -362,10 +368,10 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
const fill = {};
|
const fill = {};
|
||||||
const period = city[this.screenIndex];
|
const period = city[this.screenIndex];
|
||||||
|
|
||||||
fill.icon = { type: 'img', src: icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) };
|
fill.icon = { type: 'img', src: getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) };
|
||||||
fill.city = period.name;
|
fill.city = period.name;
|
||||||
let { temperature } = period;
|
let { temperature } = period;
|
||||||
if (navigation.units() === UNITS.metric) temperature = Math.round(utils.units.fahrenheitToCelsius(temperature));
|
if (navigation.units() === UNITS.metric) temperature = Math.round(units.fahrenheitToCelsius(temperature));
|
||||||
fill.temp = temperature;
|
fill.temp = temperature;
|
||||||
|
|
||||||
const elem = this.fillTemplate('location', fill);
|
const elem = this.fillTemplate('location', fill);
|
||||||
|
@ -382,3 +388,5 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.RegionalForecast = RegionalForecast;
|
10
server/scripts/modules/status.mjs
Normal file
10
server/scripts/modules/status.mjs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const STATUS = {
|
||||||
|
loading: Symbol('loading'),
|
||||||
|
loaded: Symbol('loaded'),
|
||||||
|
failed: Symbol('failed'),
|
||||||
|
noData: Symbol('noData'),
|
||||||
|
disabled: Symbol('disabled'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default STATUS;
|
||||||
|
window.STATUS = STATUS;
|
|
@ -1,7 +1,12 @@
|
||||||
// travel forecast display
|
// travel forecast display
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, navigation, icons, luxon, TravelCities */
|
/* globals WeatherDisplay, navigation, TravelCities */
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { UNITS } from './config.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import { getWeatherRegionalIconFromIconLink } from './icons.mjs';
|
||||||
|
import { fahrenheitToCelsius } from './utils/units.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
class TravelForecast extends WeatherDisplay {
|
class TravelForecast extends WeatherDisplay {
|
||||||
constructor(navId, elemId, defaultActive) {
|
constructor(navId, elemId, defaultActive) {
|
||||||
// special height and width for scrolling
|
// special height and width for scrolling
|
||||||
|
@ -30,7 +35,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
try {
|
try {
|
||||||
// get point then forecast
|
// get point then forecast
|
||||||
if (!city.point) throw new Error('No pre-loaded point');
|
if (!city.point) throw new Error('No pre-loaded point');
|
||||||
const forecast = await utils.fetch.json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
||||||
// determine today or tomorrow (shift periods by 1 if tomorrow)
|
// determine today or tomorrow (shift periods by 1 if tomorrow)
|
||||||
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
|
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
|
||||||
// return a pared-down forecast
|
// return a pared-down forecast
|
||||||
|
@ -39,7 +44,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
high: forecast.properties.periods[todayShift].temperature,
|
high: forecast.properties.periods[todayShift].temperature,
|
||||||
low: forecast.properties.periods[todayShift + 1].temperature,
|
low: forecast.properties.periods[todayShift + 1].temperature,
|
||||||
name: city.Name,
|
name: city.Name,
|
||||||
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
icon: getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`GetTravelWeather for ${city.Name} failed`);
|
console.error(`GetTravelWeather for ${city.Name} failed`);
|
||||||
|
@ -85,8 +90,8 @@ class TravelForecast extends WeatherDisplay {
|
||||||
let { low, high } = city;
|
let { low, high } = city;
|
||||||
|
|
||||||
if (navigation.units() === UNITS.metric) {
|
if (navigation.units() === UNITS.metric) {
|
||||||
low = utils.units.fahrenheitToCelsius(low);
|
low = fahrenheitToCelsius(low);
|
||||||
high = utils.units.fahrenheitToCelsius(high);
|
high = fahrenheitToCelsius(high);
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to strings with no decimal
|
// convert to strings with no decimal
|
||||||
|
@ -142,7 +147,6 @@ class TravelForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
const { DateTime } = luxon;
|
|
||||||
// effectively returns early on the first found date
|
// effectively returns early on the first found date
|
||||||
return cities.reduce((dayName, city) => {
|
return cities.reduce((dayName, city) => {
|
||||||
if (city && dayName === '') {
|
if (city && dayName === '') {
|
||||||
|
@ -160,3 +164,5 @@ class TravelForecast extends WeatherDisplay {
|
||||||
return this.longCanvas;
|
return this.longCanvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.TravelForecast = TravelForecast;
|
62
server/scripts/modules/utils/calc.mjs
Normal file
62
server/scripts/modules/utils/calc.mjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const relativeHumidity = (Temperature, DewPoint) => {
|
||||||
|
const T = Temperature;
|
||||||
|
const TD = DewPoint;
|
||||||
|
return Math.round(100 * (Math.exp((17.625 * TD) / (243.04 + TD)) / Math.exp((17.625 * T) / (243.04 + T))));
|
||||||
|
};
|
||||||
|
|
||||||
|
const heatIndex = (Temperature, RelativeHumidity) => {
|
||||||
|
const T = Temperature;
|
||||||
|
const RH = RelativeHumidity;
|
||||||
|
let HI = 0.5 * (T + 61.0 + ((T - 68.0) * 1.2) + (RH * 0.094));
|
||||||
|
let ADJUSTMENT;
|
||||||
|
|
||||||
|
if (T >= 80) {
|
||||||
|
HI = -42.379 + 2.04901523 * T + 10.14333127 * RH - 0.22475541 * T * RH - 0.00683783 * T * T - 0.05481717 * RH * RH + 0.00122874 * T * T * RH + 0.00085282 * T * RH * RH - 0.00000199 * T * T * RH * RH;
|
||||||
|
|
||||||
|
if (RH < 13 && (T > 80 && T < 112)) {
|
||||||
|
ADJUSTMENT = ((13 - RH) / 4) * Math.sqrt((17 - Math.abs(T - 95)) / 17);
|
||||||
|
HI -= ADJUSTMENT;
|
||||||
|
} else if (RH > 85 && (T > 80 && T < 87)) {
|
||||||
|
ADJUSTMENT = ((RH - 85) / 10) * ((87 - T) / 5);
|
||||||
|
HI += ADJUSTMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HI < Temperature) {
|
||||||
|
HI = Temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(HI);
|
||||||
|
};
|
||||||
|
|
||||||
|
const windChill = (Temperature, WindSpeed) => {
|
||||||
|
if (WindSpeed === '0' || WindSpeed === 'Calm' || WindSpeed === 'NA') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const T = Temperature;
|
||||||
|
const V = WindSpeed;
|
||||||
|
|
||||||
|
return Math.round(35.74 + (0.6215 * T) - (35.75 * (V ** 0.16)) + (0.4275 * T * (V ** 0.16)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// wind direction
|
||||||
|
const directionToNSEW = (Direction) => {
|
||||||
|
const val = Math.floor((Direction / 22.5) + 0.5);
|
||||||
|
const arr = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
|
||||||
|
return arr[(val % 16)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const distance = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
||||||
|
|
||||||
|
// wrap a number to 0-m
|
||||||
|
const wrap = (x, m) => ((x % m) + m) % m;
|
||||||
|
|
||||||
|
export {
|
||||||
|
relativeHumidity,
|
||||||
|
heatIndex,
|
||||||
|
windChill,
|
||||||
|
directionToNSEW,
|
||||||
|
distance,
|
||||||
|
wrap,
|
||||||
|
};
|
12
server/scripts/modules/utils/cors.mjs
Normal file
12
server/scripts/modules/utils/cors.mjs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// rewrite some urls for local server
|
||||||
|
const rewriteUrl = (_url) => {
|
||||||
|
let url = _url;
|
||||||
|
url = url.replace('https://api.weather.gov/', window.location.href);
|
||||||
|
url = url.replace('https://www.cpc.ncep.noaa.gov/', window.location.href);
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
rewriteUrl,
|
||||||
|
};
|
8
server/scripts/modules/utils/elem.mjs
Normal file
8
server/scripts/modules/utils/elem.mjs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const elemForEach = (selector, callback) => {
|
||||||
|
[...document.querySelectorAll(selector)].forEach(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
elemForEach,
|
||||||
|
};
|
55
server/scripts/modules/utils/fetch.mjs
Normal file
55
server/scripts/modules/utils/fetch.mjs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { rewriteUrl } from './cors.mjs';
|
||||||
|
|
||||||
|
const json = (url, params) => fetchAsync(url, 'json', params);
|
||||||
|
const text = (url, params) => fetchAsync(url, 'text', params);
|
||||||
|
const raw = (url, params) => fetchAsync(url, '', params);
|
||||||
|
const blob = (url, params) => fetchAsync(url, 'blob', params);
|
||||||
|
|
||||||
|
const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||||
|
// combine default and provided parameters
|
||||||
|
const params = {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors',
|
||||||
|
type: 'GET',
|
||||||
|
..._params,
|
||||||
|
};
|
||||||
|
// build a url, including the rewrite for cors if necessary
|
||||||
|
let corsUrl = _url;
|
||||||
|
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
||||||
|
const url = new URL(corsUrl, `${window.location.origin}/`);
|
||||||
|
// match the security protocol when not on localhost
|
||||||
|
url.protocol = window.location.hostname !== 'localhost' ? window.location.protocol : url.protocol;
|
||||||
|
// add parameters if necessary
|
||||||
|
if (params.data) {
|
||||||
|
Object.keys(params.data).forEach((key) => {
|
||||||
|
// get the value
|
||||||
|
const value = params.data[key];
|
||||||
|
// add to the url
|
||||||
|
url.searchParams.append(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// make the request
|
||||||
|
const response = await fetch(url, params);
|
||||||
|
|
||||||
|
// check for ok response
|
||||||
|
if (!response.ok) throw new Error(`Fetch error ${response.status} ${response.statusText} while fetching ${response.url}`);
|
||||||
|
// return the requested response
|
||||||
|
switch (responseType) {
|
||||||
|
case 'json':
|
||||||
|
return response.json();
|
||||||
|
case 'text':
|
||||||
|
return response.text();
|
||||||
|
case 'blob':
|
||||||
|
return response.blob();
|
||||||
|
default:
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
json,
|
||||||
|
text,
|
||||||
|
raw,
|
||||||
|
blob,
|
||||||
|
};
|
34
server/scripts/modules/utils/image.mjs
Normal file
34
server/scripts/modules/utils/image.mjs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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
|
||||||
|
// a list of cached icons is used to avoid hitting the cache multiple times
|
||||||
|
const cachedImages = [];
|
||||||
|
const preloadImg = (src) => {
|
||||||
|
if (cachedImages.includes(src)) return false;
|
||||||
|
blob(src);
|
||||||
|
// cachedImages.push(src);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
loadImg,
|
||||||
|
preloadImg,
|
||||||
|
};
|
19
server/scripts/modules/utils/string.mjs
Normal file
19
server/scripts/modules/utils/string.mjs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const locationCleanup = (input) => {
|
||||||
|
// regexes to run
|
||||||
|
const regexes = [
|
||||||
|
// "Chicago / West Chicago", removes before slash
|
||||||
|
/^[A-Za-z ]+ \/ /,
|
||||||
|
// "Chicago/Waukegan" removes before slash
|
||||||
|
/^[A-Za-z ]+\//,
|
||||||
|
// "Chicago, Chicago O'hare" removes before comma
|
||||||
|
/^[A-Za-z ]+, /,
|
||||||
|
];
|
||||||
|
|
||||||
|
// run all regexes
|
||||||
|
return regexes.reduce((value, regex) => value.replace(regex, ''), input);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
locationCleanup,
|
||||||
|
};
|
25
server/scripts/modules/utils/units.mjs
Normal file
25
server/scripts/modules/utils/units.mjs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
|
||||||
|
|
||||||
|
const mphToKph = (Mph) => Math.round(Mph * 1.60934);
|
||||||
|
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
|
||||||
|
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
|
||||||
|
const fahrenheitToCelsius = (Fahrenheit) => round2((((Fahrenheit) - 32) * 5) / 9, 1);
|
||||||
|
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
|
||||||
|
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
|
||||||
|
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
|
||||||
|
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
|
||||||
|
const inchesToCentimeters = (Inches) => round2(Inches * 2.54, 2);
|
||||||
|
const pascalToInHg = (Pascal) => round2(Pascal * 0.0002953, 2);
|
||||||
|
|
||||||
|
export {
|
||||||
|
mphToKph,
|
||||||
|
kphToMph,
|
||||||
|
celsiusToFahrenheit,
|
||||||
|
fahrenheitToCelsius,
|
||||||
|
milesToKilometers,
|
||||||
|
kilometersToMiles,
|
||||||
|
feetToMeters,
|
||||||
|
metersToFeet,
|
||||||
|
inchesToCentimeters,
|
||||||
|
pascalToInHg,
|
||||||
|
};
|
|
@ -1,14 +1,6 @@
|
||||||
// base weather display class
|
// base weather display class
|
||||||
|
|
||||||
/* globals navigation, utils, luxon, currentWeatherScroll */
|
/* globals navigation, utils, luxon, currentWeatherScroll, STATUS */
|
||||||
|
|
||||||
const STATUS = {
|
|
||||||
loading: Symbol('loading'),
|
|
||||||
loaded: Symbol('loaded'),
|
|
||||||
failed: Symbol('failed'),
|
|
||||||
noData: Symbol('noData'),
|
|
||||||
disabled: Symbol('disabled'),
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class WeatherDisplay {
|
class WeatherDisplay {
|
||||||
|
|
214
server/scripts/vendor/auto/jquery.js
vendored
214
server/scripts/vendor/auto/jquery.js
vendored
|
@ -1,5 +1,5 @@
|
||||||
/*!
|
/*!
|
||||||
* jQuery JavaScript Library v3.6.0
|
* jQuery JavaScript Library v3.6.1
|
||||||
* https://jquery.com/
|
* https://jquery.com/
|
||||||
*
|
*
|
||||||
* Includes Sizzle.js
|
* Includes Sizzle.js
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://jquery.org/license
|
* https://jquery.org/license
|
||||||
*
|
*
|
||||||
* Date: 2021-03-02T17:08Z
|
* Date: 2022-08-26T17:52Z
|
||||||
*/
|
*/
|
||||||
( function( global, factory ) {
|
( function( global, factory ) {
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
// (such as Node.js), expose a factory as module.exports.
|
// (such as Node.js), expose a factory as module.exports.
|
||||||
// This accentuates the need for the creation of a real `window`.
|
// This accentuates the need for the creation of a real `window`.
|
||||||
// e.g. var jQuery = require("jquery")(window);
|
// e.g. var jQuery = require("jquery")(window);
|
||||||
// See ticket #14549 for more info.
|
// See ticket trac-14549 for more info.
|
||||||
module.exports = global.document ?
|
module.exports = global.document ?
|
||||||
factory( global, true ) :
|
factory( global, true ) :
|
||||||
function( w ) {
|
function( w ) {
|
||||||
|
@ -151,7 +151,7 @@ function toType( obj ) {
|
||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
version = "3.6.0",
|
version = "3.6.1",
|
||||||
|
|
||||||
// Define a local copy of jQuery
|
// Define a local copy of jQuery
|
||||||
jQuery = function( selector, context ) {
|
jQuery = function( selector, context ) {
|
||||||
|
@ -3129,8 +3129,8 @@ jQuery.fn.extend( {
|
||||||
var rootjQuery,
|
var rootjQuery,
|
||||||
|
|
||||||
// A simple way to check for HTML strings
|
// A simple way to check for HTML strings
|
||||||
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
|
||||||
// Strict HTML recognition (#11290: must start with <)
|
// Strict HTML recognition (trac-11290: must start with <)
|
||||||
// Shortcut simple #id case for speed
|
// Shortcut simple #id case for speed
|
||||||
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
||||||
|
|
||||||
|
@ -4087,7 +4087,7 @@ jQuery.extend( {
|
||||||
isReady: false,
|
isReady: false,
|
||||||
|
|
||||||
// A counter to track how many items to wait for before
|
// A counter to track how many items to wait for before
|
||||||
// the ready event fires. See #6781
|
// the ready event fires. See trac-6781
|
||||||
readyWait: 1,
|
readyWait: 1,
|
||||||
|
|
||||||
// Handle when the DOM is ready
|
// Handle when the DOM is ready
|
||||||
|
@ -4215,7 +4215,7 @@ function fcamelCase( _all, letter ) {
|
||||||
|
|
||||||
// Convert dashed to camelCase; used by the css and data modules
|
// Convert dashed to camelCase; used by the css and data modules
|
||||||
// Support: IE <=9 - 11, Edge 12 - 15
|
// Support: IE <=9 - 11, Edge 12 - 15
|
||||||
// Microsoft forgot to hump their vendor prefix (#9572)
|
// Microsoft forgot to hump their vendor prefix (trac-9572)
|
||||||
function camelCase( string ) {
|
function camelCase( string ) {
|
||||||
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
||||||
}
|
}
|
||||||
|
@ -4251,7 +4251,7 @@ Data.prototype = {
|
||||||
value = {};
|
value = {};
|
||||||
|
|
||||||
// We can accept data for non-element nodes in modern browsers,
|
// We can accept data for non-element nodes in modern browsers,
|
||||||
// but we should not, see #8335.
|
// but we should not, see trac-8335.
|
||||||
// Always return an empty object.
|
// Always return an empty object.
|
||||||
if ( acceptData( owner ) ) {
|
if ( acceptData( owner ) ) {
|
||||||
|
|
||||||
|
@ -4490,7 +4490,7 @@ jQuery.fn.extend( {
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
|
|
||||||
// Support: IE 11 only
|
// Support: IE 11 only
|
||||||
// The attrs elements can be null (#14894)
|
// The attrs elements can be null (trac-14894)
|
||||||
if ( attrs[ i ] ) {
|
if ( attrs[ i ] ) {
|
||||||
name = attrs[ i ].name;
|
name = attrs[ i ].name;
|
||||||
if ( name.indexOf( "data-" ) === 0 ) {
|
if ( name.indexOf( "data-" ) === 0 ) {
|
||||||
|
@ -4913,9 +4913,9 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
||||||
input = document.createElement( "input" );
|
input = document.createElement( "input" );
|
||||||
|
|
||||||
// Support: Android 4.0 - 4.3 only
|
// Support: Android 4.0 - 4.3 only
|
||||||
// Check state lost if the name is set (#11217)
|
// Check state lost if the name is set (trac-11217)
|
||||||
// Support: Windows Web Apps (WWA)
|
// Support: Windows Web Apps (WWA)
|
||||||
// `name` and `type` must use .setAttribute for WWA (#14901)
|
// `name` and `type` must use .setAttribute for WWA (trac-14901)
|
||||||
input.setAttribute( "type", "radio" );
|
input.setAttribute( "type", "radio" );
|
||||||
input.setAttribute( "checked", "checked" );
|
input.setAttribute( "checked", "checked" );
|
||||||
input.setAttribute( "name", "t" );
|
input.setAttribute( "name", "t" );
|
||||||
|
@ -4939,7 +4939,7 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
||||||
// We have to close these tags to support XHTML (#13200)
|
// We have to close these tags to support XHTML (trac-13200)
|
||||||
var wrapMap = {
|
var wrapMap = {
|
||||||
|
|
||||||
// XHTML parsers do not magically insert elements in the
|
// XHTML parsers do not magically insert elements in the
|
||||||
|
@ -4965,7 +4965,7 @@ if ( !support.option ) {
|
||||||
function getAll( context, tag ) {
|
function getAll( context, tag ) {
|
||||||
|
|
||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
|
// Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
|
||||||
var ret;
|
var ret;
|
||||||
|
|
||||||
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
||||||
|
@ -5048,7 +5048,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
|
||||||
// Remember the top-level container
|
// Remember the top-level container
|
||||||
tmp = fragment.firstChild;
|
tmp = fragment.firstChild;
|
||||||
|
|
||||||
// Ensure the created nodes are orphaned (#12392)
|
// Ensure the created nodes are orphaned (trac-12392)
|
||||||
tmp.textContent = "";
|
tmp.textContent = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5469,15 +5469,15 @@ jQuery.event = {
|
||||||
|
|
||||||
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
||||||
|
|
||||||
// Don't check non-elements (#13208)
|
// Don't check non-elements (trac-13208)
|
||||||
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
|
// Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
|
||||||
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
||||||
matchedHandlers = [];
|
matchedHandlers = [];
|
||||||
matchedSelectors = {};
|
matchedSelectors = {};
|
||||||
for ( i = 0; i < delegateCount; i++ ) {
|
for ( i = 0; i < delegateCount; i++ ) {
|
||||||
handleObj = handlers[ i ];
|
handleObj = handlers[ i ];
|
||||||
|
|
||||||
// Don't conflict with Object.prototype properties (#13203)
|
// Don't conflict with Object.prototype properties (trac-13203)
|
||||||
sel = handleObj.selector + " ";
|
sel = handleObj.selector + " ";
|
||||||
|
|
||||||
if ( matchedSelectors[ sel ] === undefined ) {
|
if ( matchedSelectors[ sel ] === undefined ) {
|
||||||
|
@ -5731,7 +5731,7 @@ jQuery.Event = function( src, props ) {
|
||||||
|
|
||||||
// Create target properties
|
// Create target properties
|
||||||
// Support: Safari <=6 - 7 only
|
// Support: Safari <=6 - 7 only
|
||||||
// Target should not be a text node (#504, #13143)
|
// Target should not be a text node (trac-504, trac-13143)
|
||||||
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
||||||
src.target.parentNode :
|
src.target.parentNode :
|
||||||
src.target;
|
src.target;
|
||||||
|
@ -5854,10 +5854,10 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Suppress native focus or blur as it's already being fired
|
// Suppress native focus or blur if we're currently inside
|
||||||
// in leverageNative.
|
// a leveraged native-event stack
|
||||||
_default: function() {
|
_default: function( event ) {
|
||||||
return true;
|
return dataPriv.get( event.target, type );
|
||||||
},
|
},
|
||||||
|
|
||||||
delegateType: delegateType
|
delegateType: delegateType
|
||||||
|
@ -5956,7 +5956,8 @@ var
|
||||||
|
|
||||||
// checked="checked" or checked
|
// checked="checked" or checked
|
||||||
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
||||||
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
|
|
||||||
|
rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;
|
||||||
|
|
||||||
// Prefer a tbody over its parent table for containing new rows
|
// Prefer a tbody over its parent table for containing new rows
|
||||||
function manipulationTarget( elem, content ) {
|
function manipulationTarget( elem, content ) {
|
||||||
|
@ -6070,7 +6071,7 @@ function domManip( collection, args, callback, ignored ) {
|
||||||
|
|
||||||
// Use the original fragment for the last item
|
// Use the original fragment for the last item
|
||||||
// instead of the first because it can end up
|
// instead of the first because it can end up
|
||||||
// being emptied incorrectly in certain situations (#8070).
|
// being emptied incorrectly in certain situations (trac-8070).
|
||||||
for ( ; i < l; i++ ) {
|
for ( ; i < l; i++ ) {
|
||||||
node = fragment;
|
node = fragment;
|
||||||
|
|
||||||
|
@ -6111,6 +6112,12 @@ function domManip( collection, args, callback, ignored ) {
|
||||||
}, doc );
|
}, doc );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Unwrap a CDATA section containing script contents. This shouldn't be
|
||||||
|
// needed as in XML documents they're already not visible when
|
||||||
|
// inspecting element contents and in HTML documents they have no
|
||||||
|
// meaning but we're preserving that logic for backwards compatibility.
|
||||||
|
// This will be removed completely in 4.0. See gh-4904.
|
||||||
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
|
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6393,9 +6400,12 @@ jQuery.each( {
|
||||||
} );
|
} );
|
||||||
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
||||||
|
|
||||||
|
var rcustomProp = /^--/;
|
||||||
|
|
||||||
|
|
||||||
var getStyles = function( elem ) {
|
var getStyles = function( elem ) {
|
||||||
|
|
||||||
// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
|
// Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
|
||||||
// IE throws on elements created in popups
|
// IE throws on elements created in popups
|
||||||
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
||||||
var view = elem.ownerDocument.defaultView;
|
var view = elem.ownerDocument.defaultView;
|
||||||
|
@ -6430,6 +6440,15 @@ var swap = function( elem, options, callback ) {
|
||||||
|
|
||||||
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
|
|
||||||
|
var whitespace = "[\\x20\\t\\r\\n\\f]";
|
||||||
|
|
||||||
|
|
||||||
|
var rtrimCSS = new RegExp(
|
||||||
|
"^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
|
||||||
|
"g"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
( function() {
|
( function() {
|
||||||
|
@ -6495,7 +6514,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// Style of cloned element affects source element cloned (#8908)
|
// Style of cloned element affects source element cloned (trac-8908)
|
||||||
div.style.backgroundClip = "content-box";
|
div.style.backgroundClip = "content-box";
|
||||||
div.cloneNode( true ).style.backgroundClip = "";
|
div.cloneNode( true ).style.backgroundClip = "";
|
||||||
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
||||||
|
@ -6575,6 +6594,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
|
|
||||||
function curCSS( elem, name, computed ) {
|
function curCSS( elem, name, computed ) {
|
||||||
var width, minWidth, maxWidth, ret,
|
var width, minWidth, maxWidth, ret,
|
||||||
|
isCustomProp = rcustomProp.test( name ),
|
||||||
|
|
||||||
// Support: Firefox 51+
|
// Support: Firefox 51+
|
||||||
// Retrieving style before computed somehow
|
// Retrieving style before computed somehow
|
||||||
|
@ -6585,11 +6605,22 @@ function curCSS( elem, name, computed ) {
|
||||||
computed = computed || getStyles( elem );
|
computed = computed || getStyles( elem );
|
||||||
|
|
||||||
// getPropertyValue is needed for:
|
// getPropertyValue is needed for:
|
||||||
// .css('filter') (IE 9 only, #12537)
|
// .css('filter') (IE 9 only, trac-12537)
|
||||||
// .css('--customProperty) (#3144)
|
// .css('--customProperty) (gh-3144)
|
||||||
if ( computed ) {
|
if ( computed ) {
|
||||||
ret = computed.getPropertyValue( name ) || computed[ name ];
|
ret = computed.getPropertyValue( name ) || computed[ name ];
|
||||||
|
|
||||||
|
// trim whitespace for custom property (issue gh-4926)
|
||||||
|
if ( isCustomProp ) {
|
||||||
|
|
||||||
|
// rtrim treats U+000D CARRIAGE RETURN and U+000C FORM FEED
|
||||||
|
// as whitespace while CSS does not, but this is not a problem
|
||||||
|
// because CSS preprocessing replaces them with U+000A LINE FEED
|
||||||
|
// (which *is* CSS whitespace)
|
||||||
|
// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
|
||||||
|
ret = ret.replace( rtrimCSS, "$1" );
|
||||||
|
}
|
||||||
|
|
||||||
if ( ret === "" && !isAttached( elem ) ) {
|
if ( ret === "" && !isAttached( elem ) ) {
|
||||||
ret = jQuery.style( elem, name );
|
ret = jQuery.style( elem, name );
|
||||||
}
|
}
|
||||||
|
@ -6685,7 +6716,6 @@ var
|
||||||
// except "table", "table-cell", or "table-caption"
|
// except "table", "table-cell", or "table-caption"
|
||||||
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
||||||
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
||||||
rcustomProp = /^--/,
|
|
||||||
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
||||||
cssNormalTransform = {
|
cssNormalTransform = {
|
||||||
letterSpacing: "0",
|
letterSpacing: "0",
|
||||||
|
@ -6921,15 +6951,15 @@ jQuery.extend( {
|
||||||
if ( value !== undefined ) {
|
if ( value !== undefined ) {
|
||||||
type = typeof value;
|
type = typeof value;
|
||||||
|
|
||||||
// Convert "+=" or "-=" to relative numbers (#7345)
|
// Convert "+=" or "-=" to relative numbers (trac-7345)
|
||||||
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
||||||
value = adjustCSS( elem, name, ret );
|
value = adjustCSS( elem, name, ret );
|
||||||
|
|
||||||
// Fixes bug #9237
|
// Fixes bug trac-9237
|
||||||
type = "number";
|
type = "number";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that null and NaN values aren't set (#7116)
|
// Make sure that null and NaN values aren't set (trac-7116)
|
||||||
if ( value == null || value !== value ) {
|
if ( value == null || value !== value ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7553,7 +7583,7 @@ function Animation( elem, properties, options ) {
|
||||||
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
||||||
|
|
||||||
// Support: Android 2.3 only
|
// Support: Android 2.3 only
|
||||||
// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
|
// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497)
|
||||||
temp = remaining / animation.duration || 0,
|
temp = remaining / animation.duration || 0,
|
||||||
percent = 1 - temp,
|
percent = 1 - temp,
|
||||||
index = 0,
|
index = 0,
|
||||||
|
@ -7943,7 +7973,6 @@ jQuery.fx.speeds = {
|
||||||
|
|
||||||
|
|
||||||
// Based off of the plugin by Clint Helfers, with permission.
|
// Based off of the plugin by Clint Helfers, with permission.
|
||||||
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
|
|
||||||
jQuery.fn.delay = function( time, type ) {
|
jQuery.fn.delay = function( time, type ) {
|
||||||
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
||||||
type = type || "fx";
|
type = type || "fx";
|
||||||
|
@ -8168,8 +8197,7 @@ jQuery.extend( {
|
||||||
// Support: IE <=9 - 11 only
|
// Support: IE <=9 - 11 only
|
||||||
// elem.tabIndex doesn't always return the
|
// elem.tabIndex doesn't always return the
|
||||||
// correct value when it hasn't been explicitly set
|
// correct value when it hasn't been explicitly set
|
||||||
// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
|
// Use proper attribute retrieval (trac-12072)
|
||||||
// Use proper attribute retrieval(#12072)
|
|
||||||
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
||||||
|
|
||||||
if ( tabindex ) {
|
if ( tabindex ) {
|
||||||
|
@ -8273,8 +8301,7 @@ function classesToArray( value ) {
|
||||||
|
|
||||||
jQuery.fn.extend( {
|
jQuery.fn.extend( {
|
||||||
addClass: function( value ) {
|
addClass: function( value ) {
|
||||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
var classNames, cur, curValue, className, i, finalValue;
|
||||||
i = 0;
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( j ) {
|
return this.each( function( j ) {
|
||||||
|
@ -8282,36 +8309,35 @@ jQuery.fn.extend( {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = classesToArray( value );
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
if ( classes.length ) {
|
if ( classNames.length ) {
|
||||||
while ( ( elem = this[ i++ ] ) ) {
|
return this.each( function() {
|
||||||
curValue = getClass( elem );
|
curValue = getClass( this );
|
||||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||||
|
|
||||||
if ( cur ) {
|
if ( cur ) {
|
||||||
j = 0;
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
while ( ( clazz = classes[ j++ ] ) ) {
|
className = classNames[ i ];
|
||||||
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
|
if ( cur.indexOf( " " + className + " " ) < 0 ) {
|
||||||
cur += clazz + " ";
|
cur += className + " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only assign if different to avoid unneeded rendering.
|
// Only assign if different to avoid unneeded rendering.
|
||||||
finalValue = stripAndCollapse( cur );
|
finalValue = stripAndCollapse( cur );
|
||||||
if ( curValue !== finalValue ) {
|
if ( curValue !== finalValue ) {
|
||||||
elem.setAttribute( "class", finalValue );
|
this.setAttribute( "class", finalValue );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeClass: function( value ) {
|
removeClass: function( value ) {
|
||||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
var classNames, cur, curValue, className, i, finalValue;
|
||||||
i = 0;
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( j ) {
|
return this.each( function( j ) {
|
||||||
|
@ -8323,45 +8349,42 @@ jQuery.fn.extend( {
|
||||||
return this.attr( "class", "" );
|
return this.attr( "class", "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = classesToArray( value );
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
if ( classes.length ) {
|
if ( classNames.length ) {
|
||||||
while ( ( elem = this[ i++ ] ) ) {
|
return this.each( function() {
|
||||||
curValue = getClass( elem );
|
curValue = getClass( this );
|
||||||
|
|
||||||
// This expression is here for better compressibility (see addClass)
|
// This expression is here for better compressibility (see addClass)
|
||||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||||
|
|
||||||
if ( cur ) {
|
if ( cur ) {
|
||||||
j = 0;
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
while ( ( clazz = classes[ j++ ] ) ) {
|
className = classNames[ i ];
|
||||||
|
|
||||||
// Remove *all* instances
|
// Remove *all* instances
|
||||||
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
|
while ( cur.indexOf( " " + className + " " ) > -1 ) {
|
||||||
cur = cur.replace( " " + clazz + " ", " " );
|
cur = cur.replace( " " + className + " ", " " );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only assign if different to avoid unneeded rendering.
|
// Only assign if different to avoid unneeded rendering.
|
||||||
finalValue = stripAndCollapse( cur );
|
finalValue = stripAndCollapse( cur );
|
||||||
if ( curValue !== finalValue ) {
|
if ( curValue !== finalValue ) {
|
||||||
elem.setAttribute( "class", finalValue );
|
this.setAttribute( "class", finalValue );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleClass: function( value, stateVal ) {
|
toggleClass: function( value, stateVal ) {
|
||||||
var type = typeof value,
|
var classNames, className, i, self,
|
||||||
|
type = typeof value,
|
||||||
isValidValue = type === "string" || Array.isArray( value );
|
isValidValue = type === "string" || Array.isArray( value );
|
||||||
|
|
||||||
if ( typeof stateVal === "boolean" && isValidValue ) {
|
|
||||||
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isFunction( value ) ) {
|
if ( isFunction( value ) ) {
|
||||||
return this.each( function( i ) {
|
return this.each( function( i ) {
|
||||||
jQuery( this ).toggleClass(
|
jQuery( this ).toggleClass(
|
||||||
|
@ -8371,17 +8394,20 @@ jQuery.fn.extend( {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.each( function() {
|
if ( typeof stateVal === "boolean" && isValidValue ) {
|
||||||
var className, i, self, classNames;
|
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
classNames = classesToArray( value );
|
||||||
|
|
||||||
|
return this.each( function() {
|
||||||
if ( isValidValue ) {
|
if ( isValidValue ) {
|
||||||
|
|
||||||
// Toggle individual class names
|
// Toggle individual class names
|
||||||
i = 0;
|
|
||||||
self = jQuery( this );
|
self = jQuery( this );
|
||||||
classNames = classesToArray( value );
|
|
||||||
|
|
||||||
while ( ( className = classNames[ i++ ] ) ) {
|
for ( i = 0; i < classNames.length; i++ ) {
|
||||||
|
className = classNames[ i ];
|
||||||
|
|
||||||
// Check each className given, space separated list
|
// Check each className given, space separated list
|
||||||
if ( self.hasClass( className ) ) {
|
if ( self.hasClass( className ) ) {
|
||||||
|
@ -8515,7 +8541,7 @@ jQuery.extend( {
|
||||||
val :
|
val :
|
||||||
|
|
||||||
// Support: IE <=10 - 11 only
|
// Support: IE <=10 - 11 only
|
||||||
// option.text throws exceptions (#14686, #14858)
|
// option.text throws exceptions (trac-14686, trac-14858)
|
||||||
// Strip and collapse whitespace
|
// Strip and collapse whitespace
|
||||||
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
||||||
stripAndCollapse( jQuery.text( elem ) );
|
stripAndCollapse( jQuery.text( elem ) );
|
||||||
|
@ -8542,7 +8568,7 @@ jQuery.extend( {
|
||||||
option = options[ i ];
|
option = options[ i ];
|
||||||
|
|
||||||
// Support: IE <=9 only
|
// Support: IE <=9 only
|
||||||
// IE8-9 doesn't update selected after form reset (#2551)
|
// IE8-9 doesn't update selected after form reset (trac-2551)
|
||||||
if ( ( option.selected || i === index ) &&
|
if ( ( option.selected || i === index ) &&
|
||||||
|
|
||||||
// Don't return options that are disabled or in a disabled optgroup
|
// Don't return options that are disabled or in a disabled optgroup
|
||||||
|
@ -8685,8 +8711,8 @@ jQuery.extend( jQuery.event, {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine event propagation path in advance, per W3C events spec (#9951)
|
// Determine event propagation path in advance, per W3C events spec (trac-9951)
|
||||||
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
// Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
|
||||||
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
||||||
|
|
||||||
bubbleType = special.delegateType || type;
|
bubbleType = special.delegateType || type;
|
||||||
|
@ -8738,7 +8764,7 @@ jQuery.extend( jQuery.event, {
|
||||||
acceptData( elem ) ) {
|
acceptData( elem ) ) {
|
||||||
|
|
||||||
// Call a native DOM method on the target with the same name as the event.
|
// Call a native DOM method on the target with the same name as the event.
|
||||||
// Don't do default actions on window, that's where global variables be (#6170)
|
// Don't do default actions on window, that's where global variables be (trac-6170)
|
||||||
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
||||||
|
|
||||||
// Don't re-trigger an onFOO event when we call its FOO() method
|
// Don't re-trigger an onFOO event when we call its FOO() method
|
||||||
|
@ -9012,7 +9038,7 @@ var
|
||||||
rantiCache = /([?&])_=[^&]*/,
|
rantiCache = /([?&])_=[^&]*/,
|
||||||
rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
|
rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
|
||||||
|
|
||||||
// #7653, #8125, #8152: local protocol detection
|
// trac-7653, trac-8125, trac-8152: local protocol detection
|
||||||
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
|
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
|
||||||
rnoContent = /^(?:GET|HEAD)$/,
|
rnoContent = /^(?:GET|HEAD)$/,
|
||||||
rprotocol = /^\/\//,
|
rprotocol = /^\/\//,
|
||||||
|
@ -9035,7 +9061,7 @@ var
|
||||||
*/
|
*/
|
||||||
transports = {},
|
transports = {},
|
||||||
|
|
||||||
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
|
// Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression
|
||||||
allTypes = "*/".concat( "*" ),
|
allTypes = "*/".concat( "*" ),
|
||||||
|
|
||||||
// Anchor tag for parsing the document origin
|
// Anchor tag for parsing the document origin
|
||||||
|
@ -9106,7 +9132,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX
|
||||||
|
|
||||||
// A special extend for ajax options
|
// A special extend for ajax options
|
||||||
// that takes "flat" options (not to be deep extended)
|
// that takes "flat" options (not to be deep extended)
|
||||||
// Fixes #9887
|
// Fixes trac-9887
|
||||||
function ajaxExtend( target, src ) {
|
function ajaxExtend( target, src ) {
|
||||||
var key, deep,
|
var key, deep,
|
||||||
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
||||||
|
@ -9517,12 +9543,12 @@ jQuery.extend( {
|
||||||
deferred.promise( jqXHR );
|
deferred.promise( jqXHR );
|
||||||
|
|
||||||
// Add protocol if not provided (prefilters might expect it)
|
// Add protocol if not provided (prefilters might expect it)
|
||||||
// Handle falsy url in the settings object (#10093: consistency with old signature)
|
// Handle falsy url in the settings object (trac-10093: consistency with old signature)
|
||||||
// We also use the url parameter if available
|
// We also use the url parameter if available
|
||||||
s.url = ( ( url || s.url || location.href ) + "" )
|
s.url = ( ( url || s.url || location.href ) + "" )
|
||||||
.replace( rprotocol, location.protocol + "//" );
|
.replace( rprotocol, location.protocol + "//" );
|
||||||
|
|
||||||
// Alias method option to type as per ticket #12004
|
// Alias method option to type as per ticket trac-12004
|
||||||
s.type = options.method || options.type || s.method || s.type;
|
s.type = options.method || options.type || s.method || s.type;
|
||||||
|
|
||||||
// Extract dataTypes list
|
// Extract dataTypes list
|
||||||
|
@ -9565,7 +9591,7 @@ jQuery.extend( {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can fire global events as of now if asked to
|
// We can fire global events as of now if asked to
|
||||||
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
|
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118)
|
||||||
fireGlobals = jQuery.event && s.global;
|
fireGlobals = jQuery.event && s.global;
|
||||||
|
|
||||||
// Watch for a new set of requests
|
// Watch for a new set of requests
|
||||||
|
@ -9594,7 +9620,7 @@ jQuery.extend( {
|
||||||
if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
|
if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
|
||||||
cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
|
cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
|
||||||
|
|
||||||
// #9682: remove data so that it's not used in an eventual retry
|
// trac-9682: remove data so that it's not used in an eventual retry
|
||||||
delete s.data;
|
delete s.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9867,7 +9893,7 @@ jQuery._evalUrl = function( url, options, doc ) {
|
||||||
return jQuery.ajax( {
|
return jQuery.ajax( {
|
||||||
url: url,
|
url: url,
|
||||||
|
|
||||||
// Make this explicit, since user can override this through ajaxSetup (#11264)
|
// Make this explicit, since user can override this through ajaxSetup (trac-11264)
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "script",
|
dataType: "script",
|
||||||
cache: true,
|
cache: true,
|
||||||
|
@ -9976,7 +10002,7 @@ var xhrSuccessStatus = {
|
||||||
0: 200,
|
0: 200,
|
||||||
|
|
||||||
// Support: IE <=9 only
|
// Support: IE <=9 only
|
||||||
// #1450: sometimes IE returns 1223 when it should be 204
|
// trac-1450: sometimes IE returns 1223 when it should be 204
|
||||||
1223: 204
|
1223: 204
|
||||||
},
|
},
|
||||||
xhrSupported = jQuery.ajaxSettings.xhr();
|
xhrSupported = jQuery.ajaxSettings.xhr();
|
||||||
|
@ -10048,7 +10074,7 @@ jQuery.ajaxTransport( function( options ) {
|
||||||
} else {
|
} else {
|
||||||
complete(
|
complete(
|
||||||
|
|
||||||
// File: protocol always yields status 0; see #8605, #14207
|
// File: protocol always yields status 0; see trac-8605, trac-14207
|
||||||
xhr.status,
|
xhr.status,
|
||||||
xhr.statusText
|
xhr.statusText
|
||||||
);
|
);
|
||||||
|
@ -10109,7 +10135,7 @@ jQuery.ajaxTransport( function( options ) {
|
||||||
xhr.send( options.hasContent && options.data || null );
|
xhr.send( options.hasContent && options.data || null );
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
|
|
||||||
// #14683: Only rethrow if this hasn't been notified as an error yet
|
// trac-14683: Only rethrow if this hasn't been notified as an error yet
|
||||||
if ( callback ) {
|
if ( callback ) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -10753,7 +10779,9 @@ jQuery.each(
|
||||||
|
|
||||||
// Support: Android <=4.0 only
|
// Support: Android <=4.0 only
|
||||||
// Make sure we trim BOM and NBSP
|
// Make sure we trim BOM and NBSP
|
||||||
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
// Require that the "whitespace run" starts from a non-whitespace
|
||||||
|
// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
|
||||||
|
var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;
|
||||||
|
|
||||||
// Bind a function to a context, optionally partially applying any
|
// Bind a function to a context, optionally partially applying any
|
||||||
// arguments.
|
// arguments.
|
||||||
|
@ -10820,7 +10848,7 @@ jQuery.isNumeric = function( obj ) {
|
||||||
jQuery.trim = function( text ) {
|
jQuery.trim = function( text ) {
|
||||||
return text == null ?
|
return text == null ?
|
||||||
"" :
|
"" :
|
||||||
( text + "" ).replace( rtrim, "" );
|
( text + "" ).replace( rtrim, "$1" );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -10868,8 +10896,8 @@ jQuery.noConflict = function( deep ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expose jQuery and $ identifiers, even in AMD
|
// Expose jQuery and $ identifiers, even in AMD
|
||||||
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
||||||
// and CommonJS for browser emulators (#13566)
|
// and CommonJS for browser emulators (trac-13566)
|
||||||
if ( typeof noGlobal === "undefined" ) {
|
if ( typeof noGlobal === "undefined" ) {
|
||||||
window.jQuery = window.$ = jQuery;
|
window.jQuery = window.$ = jQuery;
|
||||||
}
|
}
|
||||||
|
|
8493
server/scripts/vendor/auto/luxon.js
vendored
8493
server/scripts/vendor/auto/luxon.js
vendored
File diff suppressed because it is too large
Load diff
2
server/scripts/vendor/auto/luxon.js.map
vendored
2
server/scripts/vendor/auto/luxon.js.map
vendored
File diff suppressed because one or more lines are too long
7120
server/scripts/vendor/auto/luxon.mjs
vendored
Normal file
7120
server/scripts/vendor/auto/luxon.mjs
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -29,30 +29,32 @@
|
||||||
|
|
||||||
<!-- to be removed-->
|
<!-- to be removed-->
|
||||||
<script type="module" src="scripts/modules/config.mjs"></script>
|
<script type="module" src="scripts/modules/config.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/status.mjs"></script>
|
||||||
|
<script type="module" src="scripts/vendor/auto/luxon.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/currentweather.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/almanac.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/icons.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/currentweatherscroll.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/extendedforecast.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/hourly.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/progress.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/latestobservations.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/localforecast.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/radar.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/regionalforecast.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/travelforecast.mjs"></script>
|
||||||
|
|
||||||
<script type="module" src="scripts/index.mjs"></script>
|
<script type="module" src="scripts/index.mjs"></script>
|
||||||
<script type="text/javascript" src="scripts/data/states.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/luxon.js"></script>
|
<!-- data -->
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
<script type="text/javascript" src="scripts/data/states.js"></script>
|
||||||
<script type="text/javascript" src="scripts/data/travelcities.js"></script>
|
<script type="text/javascript" src="scripts/data/travelcities.js"></script>
|
||||||
<script type="text/javascript" src="scripts/data/regionalcities.js"></script>
|
<script type="text/javascript" src="scripts/data/regionalcities.js"></script>
|
||||||
<script type="text/javascript" src="scripts/data/stations.js"></script>
|
<script type="text/javascript" src="scripts/data/stations.js"></script>
|
||||||
<script type="text/javascript" src="scripts/modules/draw.js"></script>
|
|
||||||
|
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
||||||
<script type="text/javascript" src="scripts/modules/weatherdisplay.js"></script>
|
<script type="text/javascript" src="scripts/modules/weatherdisplay.js"></script>
|
||||||
<script type="text/javascript" src="scripts/modules/icons.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/utilities.js"></script>
|
<script type="text/javascript" src="scripts/modules/utilities.js"></script>
|
||||||
<script type="text/javascript" src="scripts/modules/currentweather.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/currentweatherscroll.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/latestobservations.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/travelforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/regionalforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/localforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/extendedforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/almanac.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/radar.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/hourly.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/progress.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/navigation.js"></script>
|
<script type="text/javascript" src="scripts/modules/navigation.js"></script>
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
Loading…
Reference in a new issue