weather displays complete
This commit is contained in:
parent
c28608bb39
commit
cc61d2c6d1
|
@ -12,7 +12,7 @@ module.exports = {
|
|||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
rules: {
|
||||
indent: [
|
||||
|
@ -46,6 +46,13 @@ module.exports = {
|
|||
allowSamePrecedence: true,
|
||||
},
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
{
|
||||
mjs: 'always',
|
||||
json: 'always',
|
||||
},
|
||||
],
|
||||
},
|
||||
ignorePatterns: [
|
||||
'*.min.js',
|
||||
|
|
|
@ -35,7 +35,6 @@ const jsSources = [
|
|||
'server/scripts/index.js',
|
||||
'server/scripts/vendor/auto/luxon.js',
|
||||
'server/scripts/vendor/auto/suncalc.js',
|
||||
'server/scripts/modules/draw.js',
|
||||
'server/scripts/modules/weatherdisplay.js',
|
||||
'server/scripts/modules/icons.js',
|
||||
'server/scripts/modules/utilities.js',
|
||||
|
|
|
@ -9,8 +9,8 @@ const clean = (cb) => {
|
|||
};
|
||||
|
||||
const vendorFiles = [
|
||||
'./node_modules/luxon/build/global/luxon.js',
|
||||
'./node_modules/luxon/build/global/luxon.js.map',
|
||||
'./node_modules/luxon/build/es6/luxon.js',
|
||||
'./node_modules/luxon/build/es6/luxon.js.map',
|
||||
'./node_modules/nosleep.js/dist/NoSleep.js',
|
||||
'./node_modules/jquery/dist/jquery.js',
|
||||
'./node_modules/suncalc/suncalc.js',
|
||||
|
@ -22,6 +22,7 @@ const copy = () => gulp.src(vendorFiles)
|
|||
path.dirname = path.dirname.toLowerCase();
|
||||
path.basename = path.basename.toLowerCase();
|
||||
path.extname = path.extname.toLowerCase();
|
||||
if (path.basename === 'luxon') path.extname = '.mjs';
|
||||
}))
|
||||
.pipe(gulp.dest('./server/scripts/vendor/auto'));
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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', () => {
|
||||
init();
|
||||
});
|
||||
|
@ -175,7 +176,7 @@ const autocompleteOnSelect = async (suggestion, elem) => {
|
|||
if (overrides[suggestion.value]) {
|
||||
doRedirectToGeometry(overrides[suggestion.value]);
|
||||
} 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: {
|
||||
text: suggestion.value,
|
||||
magicKey: suggestion.data,
|
||||
|
@ -492,7 +493,7 @@ const btnGetGpsClick = async () => {
|
|||
|
||||
let data;
|
||||
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: {
|
||||
location: `${longitude},${latitude}`,
|
||||
distance: 1000, // Find location up to 1 KM.
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Almanac', true);
|
||||
|
||||
// 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
|
||||
utils.image.preload('images/2/Full-Moon.gif');
|
||||
utils.image.preload('images/2/Last-Quarter.gif');
|
||||
utils.image.preload('images/2/New-Moon.gif');
|
||||
utils.image.preload('images/2/First-Quarter.gif');
|
||||
preloadImg('images/2/Full-Moon.gif');
|
||||
preloadImg('images/2/Last-Quarter.gif');
|
||||
preloadImg('images/2/New-Moon.gif');
|
||||
preloadImg('images/2/First-Quarter.gif');
|
||||
|
||||
this.timing.totalScreens = 1;
|
||||
}
|
||||
|
@ -39,8 +41,6 @@ class Almanac extends WeatherDisplay {
|
|||
}
|
||||
|
||||
calcSunMoonData(weatherParameters) {
|
||||
const { DateTime } = luxon;
|
||||
|
||||
const sun = [
|
||||
SunCalc.getTimes(new Date(), 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() {
|
||||
super.drawCanvas();
|
||||
const info = this.data;
|
||||
const { DateTime } = luxon;
|
||||
const Today = DateTime.local();
|
||||
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 {
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
UNITS,
|
||||
};
|
||||
|
||||
window.UNITS = UNITS;
|
||||
console.log('config');
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Current Conditions', true);
|
||||
// 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) {
|
||||
|
@ -25,7 +33,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||
try {
|
||||
// station observations
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
observations = await utils.fetch.json(`${station.id}/observations`, {
|
||||
observations = await json(`${station.id}/observations`, {
|
||||
cors: true,
|
||||
data: {
|
||||
limit: 2,
|
||||
|
@ -50,7 +58,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||
return;
|
||||
}
|
||||
// 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
|
||||
this.data = { ...observations, station };
|
||||
|
@ -74,14 +82,14 @@ class CurrentWeather extends WeatherDisplay {
|
|||
data.Visibility = Math.round(observations.visibility.value / 1000);
|
||||
data.VisibilityUnit = ' km.';
|
||||
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.HeatIndex = Math.round(observations.heatIndex.value);
|
||||
data.WindChill = Math.round(observations.windChill.value);
|
||||
data.WindGust = Math.round(observations.windGust.value);
|
||||
data.WindUnit = 'KPH';
|
||||
data.Humidity = Math.round(observations.relativeHumidity.value);
|
||||
data.Icon = icons.getWeatherIconFromIconLink(observations.icon);
|
||||
data.Icon = getWeatherIconFromIconLink(observations.icon);
|
||||
data.PressureDirection = '';
|
||||
data.TextConditions = observations.textDescription;
|
||||
data.station = this.data.station;
|
||||
|
@ -92,19 +100,19 @@ class CurrentWeather extends WeatherDisplay {
|
|||
if (pressureDiff < -150) data.PressureDirection = 'F';
|
||||
|
||||
if (navigation.units() === UNITS.english) {
|
||||
data.Temperature = utils.units.celsiusToFahrenheit(data.Temperature);
|
||||
data.Temperature = units.celsiusToFahrenheit(data.Temperature);
|
||||
data.TemperatureUnit = 'F';
|
||||
data.DewPoint = utils.units.celsiusToFahrenheit(data.DewPoint);
|
||||
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling) / 100) * 100;
|
||||
data.DewPoint = units.celsiusToFahrenheit(data.DewPoint);
|
||||
data.Ceiling = Math.round(units.metersToFeet(data.Ceiling) / 100) * 100;
|
||||
data.CeilingUnit = 'ft.';
|
||||
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value / 1000);
|
||||
data.Visibility = units.kilometersToMiles(observations.visibility.value / 1000);
|
||||
data.VisibilityUnit = ' mi.';
|
||||
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
|
||||
data.WindSpeed = units.kphToMph(data.WindSpeed);
|
||||
data.WindUnit = 'MPH';
|
||||
data.Pressure = utils.units.pascalToInHg(data.Pressure).toFixed(2);
|
||||
data.HeatIndex = utils.units.celsiusToFahrenheit(data.HeatIndex);
|
||||
data.WindChill = utils.units.celsiusToFahrenheit(data.WindChill);
|
||||
data.WindGust = utils.units.kphToMph(data.WindGust);
|
||||
data.Pressure = units.pascalToInHg(data.Pressure).toFixed(2);
|
||||
data.HeatIndex = units.celsiusToFahrenheit(data.HeatIndex);
|
||||
data.WindChill = units.celsiusToFahrenheit(data.WindChill);
|
||||
data.WindGust = units.kphToMph(data.WindGust);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
@ -126,7 +134,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
||||
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.dewpoint = data.DewPoint + String.fromCharCode(176);
|
||||
|
@ -181,3 +189,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||
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
|
||||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Extended Forecast', true);
|
||||
|
@ -21,7 +28,7 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
if (navigation.units() === UNITS.metric) units = 'si';
|
||||
let forecast;
|
||||
try {
|
||||
forecast = await utils.fetch.json(weatherParameters.forecast, {
|
||||
forecast = await json(weatherParameters.forecast, {
|
||||
data: {
|
||||
units,
|
||||
},
|
||||
|
@ -44,7 +51,7 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
const Days = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
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' });
|
||||
});
|
||||
|
||||
|
@ -61,12 +68,12 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
// get the object to modify/populate
|
||||
const fDay = forecast[destIndex];
|
||||
// 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.dayName = dates[destIndex];
|
||||
|
||||
// preload the icon
|
||||
utils.image.preload(fDay.icon);
|
||||
preloadImg(fDay.icon);
|
||||
|
||||
if (period.isDaytime) {
|
||||
// day time is the high temperature
|
||||
|
@ -136,11 +143,11 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
|
||||
let { low } = Day;
|
||||
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);
|
||||
}
|
||||
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.condition = Day.text;
|
||||
|
||||
|
@ -158,3 +165,7 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
this.finishDraw();
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtendedForecast;
|
||||
|
||||
window.ExtendedForecast = ExtendedForecast;
|
|
@ -1,7 +1,14 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId, defaultActive) {
|
||||
// special height and width for scrolling
|
||||
|
@ -25,7 +32,7 @@ class Hourly extends WeatherDisplay {
|
|||
let forecast;
|
||||
try {
|
||||
// get the forecast
|
||||
forecast = await utils.fetch.json(weatherParameters.forecastGridData);
|
||||
forecast = await json(weatherParameters.forecastGridData);
|
||||
} catch (e) {
|
||||
console.error('Get hourly forecast failed');
|
||||
console.error(e.status, e.responseJSON);
|
||||
|
@ -59,16 +66,16 @@ class Hourly extends WeatherDisplay {
|
|||
temperature: temperature[idx],
|
||||
apparentTemperature: apparentTemperature[idx],
|
||||
windSpeed: windSpeed[idx],
|
||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||
windDirection: directionToNSEW(windDirection[idx]),
|
||||
icon: icons[idx],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
|
||||
apparentTemperature: utils.units.celsiusToFahrenheit(apparentTemperature[idx]),
|
||||
windSpeed: utils.units.kilometersToMiles(windSpeed[idx]),
|
||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||
temperature: units.celsiusToFahrenheit(temperature[idx]),
|
||||
apparentTemperature: units.celsiusToFahrenheit(apparentTemperature[idx]),
|
||||
windSpeed: units.kilometersToMiles(windSpeed[idx]),
|
||||
windDirection: directionToNSEW(windDirection[idx]),
|
||||
icon: icons[idx],
|
||||
};
|
||||
});
|
||||
|
@ -76,24 +83,24 @@ class Hourly extends WeatherDisplay {
|
|||
|
||||
// given forecast paramaters determine a suitable icon
|
||||
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 overnight = luxon.Interval.fromDateTimes(luxon.DateTime.fromJSDate(sunTimes[0].sunset), luxon.DateTime.fromJSDate(sunTimes[1].sunrise));
|
||||
const tomorrowOvernight = luxon.DateTime.fromJSDate(sunTimes[1].sunset);
|
||||
const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise));
|
||||
const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset);
|
||||
return skyCover.map((val, idx) => {
|
||||
const hour = startOfHour.plus({ hours: idx });
|
||||
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
|
||||
static expand(data) {
|
||||
const startOfHour = luxon.DateTime.utc().startOf('hour').toMillis();
|
||||
const startOfHour = DateTime.utc().startOf('hour').toMillis();
|
||||
const result = []; // resulting expanded values
|
||||
data.forEach((item) => {
|
||||
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;
|
||||
// loop through duration at one hour intervals
|
||||
do {
|
||||
|
@ -114,7 +121,7 @@ class Hourly extends WeatherDisplay {
|
|||
const list = this.elem.querySelector('.hourly-lines');
|
||||
list.innerHTML = '';
|
||||
|
||||
const startingHour = luxon.DateTime.local();
|
||||
const startingHour = DateTime.local();
|
||||
|
||||
const lines = this.data.map((data, index) => {
|
||||
const fillValues = {};
|
||||
|
@ -177,7 +184,6 @@ class Hourly extends WeatherDisplay {
|
|||
}
|
||||
|
||||
static getTravelCitiesDayName(cities) {
|
||||
const { DateTime } = luxon;
|
||||
// effectively returns early on the first found date
|
||||
return cities.reduce((dayName, city) => {
|
||||
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
|
||||
/* 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Latest Observations', true);
|
||||
|
@ -17,7 +22,7 @@ class LatestObservations extends WeatherDisplay {
|
|||
// calculate distance to each station
|
||||
const stationsByDistance = Object.keys(StationInfo).map((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 };
|
||||
});
|
||||
|
||||
|
@ -29,7 +34,7 @@ class LatestObservations extends WeatherDisplay {
|
|||
// get data for regional stations
|
||||
const allConditions = await Promise.all(regionalStations.map(async (station) => {
|
||||
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
|
||||
if (data.properties.temperature.value === null
|
||||
|| data.properties.textDescription === ''
|
||||
|
@ -76,17 +81,17 @@ class LatestObservations extends WeatherDisplay {
|
|||
const lines = sortedConditions.map((condition) => {
|
||||
let Temperature = condition.temperature.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) {
|
||||
Temperature = utils.units.celsiusToFahrenheit(Temperature);
|
||||
WindSpeed = utils.units.kphToMph(WindSpeed);
|
||||
Temperature = units.celsiusToFahrenheit(Temperature);
|
||||
WindSpeed = units.kphToMph(WindSpeed);
|
||||
}
|
||||
WindSpeed = Math.round(WindSpeed);
|
||||
Temperature = Math.round(Temperature);
|
||||
|
||||
const fill = {};
|
||||
fill.location = utils.string.locationCleanup(condition.city).substr(0, 14);
|
||||
fill.location = locationCleanup(condition.city).substr(0, 14);
|
||||
fill.temp = Temperature;
|
||||
fill.weather = LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
||||
if (WindSpeed > 0) {
|
||||
|
@ -126,3 +131,5 @@ class LatestObservations extends WeatherDisplay {
|
|||
return condition;
|
||||
}
|
||||
}
|
||||
|
||||
window.LatestObservations = LatestObservations;
|
|
@ -1,8 +1,10 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Forecast', true);
|
||||
|
@ -62,7 +64,7 @@ class LocalForecast extends WeatherDisplay {
|
|||
let units = 'us';
|
||||
if (navigation.units() === UNITS.metric) units = 'si';
|
||||
try {
|
||||
return await utils.fetch.json(weatherParameters.forecast, {
|
||||
return await json(weatherParameters.forecast, {
|
||||
data: {
|
||||
units,
|
||||
},
|
||||
|
@ -94,3 +96,5 @@ class LocalForecast extends WeatherDisplay {
|
|||
}));
|
||||
}
|
||||
}
|
||||
|
||||
window.LocalForecast = LocalForecast;
|
|
@ -1,14 +1,14 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, '', false);
|
||||
|
||||
// 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
|
||||
this.timing = false;
|
||||
|
@ -101,3 +101,5 @@ class Progress extends WeatherDisplay {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.Progress = Progress;
|
|
@ -1,7 +1,11 @@
|
|||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Radar', true);
|
||||
|
@ -43,13 +47,10 @@ class Radar extends WeatherDisplay {
|
|||
return;
|
||||
}
|
||||
|
||||
// date and time parsing
|
||||
const { DateTime } = luxon;
|
||||
|
||||
// get the base map
|
||||
let src = 'images/4000RadarMap2.jpg';
|
||||
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 baseUrlEnd = '/GIS/uscomp/';
|
||||
|
@ -65,7 +66,7 @@ class Radar extends WeatherDisplay {
|
|||
const lists = (await Promise.all(baseUrls.map(async (url) => {
|
||||
try {
|
||||
// get a list of available radars
|
||||
const radarHtml = await utils.fetch.text(url, { cors: true });
|
||||
const radarHtml = await text(url, { cors: true });
|
||||
return radarHtml;
|
||||
} catch (e) {
|
||||
console.log('Unable to get list of radars');
|
||||
|
@ -130,7 +131,7 @@ class Radar extends WeatherDisplay {
|
|||
context.imageSmoothingEnabled = false;
|
||||
|
||||
// get the image
|
||||
const response = await fetch(utils.cors.rewriteUrl(url));
|
||||
const response = await fetch(rewriteUrl(url));
|
||||
|
||||
// test response
|
||||
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
|
||||
const imgBlob = await utils.image.load(blob);
|
||||
const imgBlob = await loadImg(blob);
|
||||
|
||||
// draw the entire image
|
||||
workingContext.clearRect(0, 0, width, 1600);
|
||||
|
@ -204,7 +205,6 @@ class Radar extends WeatherDisplay {
|
|||
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
const { DateTime } = luxon;
|
||||
const time = this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE);
|
||||
const timePadded = time.length >= 8 ? time : ` ${time}`;
|
||||
this.elem.querySelector('.header .right .time').innerHTML = timePadded;
|
||||
|
@ -396,3 +396,5 @@ class Radar extends WeatherDisplay {
|
|||
mapContext.drawImage(radarContext.canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
window.Radar = Radar;
|
|
@ -1,9 +1,16 @@
|
|||
// regional forecast and observations
|
||||
// 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 {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Regional Forecast', true);
|
||||
|
@ -55,7 +62,7 @@ class RegionalForecast extends WeatherDisplay {
|
|||
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.
|
||||
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;
|
||||
}, true);
|
||||
if (okToAddCity) regionalCities.push(city);
|
||||
|
@ -70,7 +77,7 @@ class RegionalForecast extends WeatherDisplay {
|
|||
// start off the observation task
|
||||
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
|
||||
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
|
||||
const regionalObservation = {
|
||||
daytime: !!observation.icon.match(/\/day\//),
|
||||
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
|
||||
temperature: units.celsiusToFahrenheit(observation.temperature.value),
|
||||
name: RegionalForecast.formatCity(city.city),
|
||||
icon: observation.icon,
|
||||
x: cityXY.x,
|
||||
|
@ -88,7 +95,7 @@ class RegionalForecast extends WeatherDisplay {
|
|||
};
|
||||
|
||||
// preload the icon
|
||||
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(regionalObservation.icon, !regionalObservation.daytime));
|
||||
preloadImg(getWeatherRegionalIconFromIconLink(regionalObservation.icon, !regionalObservation.daytime));
|
||||
|
||||
// return a pared-down forecast
|
||||
// 0th object is the current conditions
|
||||
|
@ -141,15 +148,15 @@ class RegionalForecast extends WeatherDisplay {
|
|||
static async getRegionalObservation(point, city) {
|
||||
try {
|
||||
// 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
|
||||
const station = stations.features[0].id;
|
||||
// get the observation data
|
||||
const observation = await utils.fetch.json(`${station}/observations/latest`);
|
||||
const observation = await json(`${station}/observations/latest`);
|
||||
// preload the image
|
||||
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 observation.properties;
|
||||
} catch (e) {
|
||||
|
@ -328,7 +335,6 @@ class RegionalForecast extends WeatherDisplay {
|
|||
// break up data into useful values
|
||||
const { regionalData: data, sourceXY, offsetXY } = this.data;
|
||||
|
||||
const { DateTime } = luxon;
|
||||
// draw the header graphics
|
||||
|
||||
// draw the appropriate title
|
||||
|
@ -362,10 +368,10 @@ class RegionalForecast extends WeatherDisplay {
|
|||
const fill = {};
|
||||
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;
|
||||
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;
|
||||
|
||||
const elem = this.fillTemplate('location', fill);
|
||||
|
@ -382,3 +388,5 @@ class RegionalForecast extends WeatherDisplay {
|
|||
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
|
||||
/* 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 {
|
||||
constructor(navId, elemId, defaultActive) {
|
||||
// special height and width for scrolling
|
||||
|
@ -30,7 +35,7 @@ class TravelForecast extends WeatherDisplay {
|
|||
try {
|
||||
// get point then forecast
|
||||
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)
|
||||
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
|
||||
// return a pared-down forecast
|
||||
|
@ -39,7 +44,7 @@ class TravelForecast extends WeatherDisplay {
|
|||
high: forecast.properties.periods[todayShift].temperature,
|
||||
low: forecast.properties.periods[todayShift + 1].temperature,
|
||||
name: city.Name,
|
||||
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
||||
icon: getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`GetTravelWeather for ${city.Name} failed`);
|
||||
|
@ -85,8 +90,8 @@ class TravelForecast extends WeatherDisplay {
|
|||
let { low, high } = city;
|
||||
|
||||
if (navigation.units() === UNITS.metric) {
|
||||
low = utils.units.fahrenheitToCelsius(low);
|
||||
high = utils.units.fahrenheitToCelsius(high);
|
||||
low = fahrenheitToCelsius(low);
|
||||
high = fahrenheitToCelsius(high);
|
||||
}
|
||||
|
||||
// convert to strings with no decimal
|
||||
|
@ -142,7 +147,6 @@ class TravelForecast extends WeatherDisplay {
|
|||
}
|
||||
|
||||
static getTravelCitiesDayName(cities) {
|
||||
const { DateTime } = luxon;
|
||||
// effectively returns early on the first found date
|
||||
return cities.reduce((dayName, city) => {
|
||||
if (city && dayName === '') {
|
||||
|
@ -160,3 +164,5 @@ class TravelForecast extends WeatherDisplay {
|
|||
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
|
||||
|
||||
/* globals navigation, utils, luxon, currentWeatherScroll */
|
||||
|
||||
const STATUS = {
|
||||
loading: Symbol('loading'),
|
||||
loaded: Symbol('loaded'),
|
||||
failed: Symbol('failed'),
|
||||
noData: Symbol('noData'),
|
||||
disabled: Symbol('disabled'),
|
||||
};
|
||||
/* globals navigation, utils, luxon, currentWeatherScroll, STATUS */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
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/
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
|
@ -9,7 +9,7 @@
|
|||
* Released under the MIT license
|
||||
* https://jquery.org/license
|
||||
*
|
||||
* Date: 2021-03-02T17:08Z
|
||||
* Date: 2022-08-26T17:52Z
|
||||
*/
|
||||
( function( global, factory ) {
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
// (such as Node.js), expose a factory as module.exports.
|
||||
// This accentuates the need for the creation of a real `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 ?
|
||||
factory( global, true ) :
|
||||
function( w ) {
|
||||
|
@ -151,7 +151,7 @@ function toType( obj ) {
|
|||
|
||||
|
||||
var
|
||||
version = "3.6.0",
|
||||
version = "3.6.1",
|
||||
|
||||
// Define a local copy of jQuery
|
||||
jQuery = function( selector, context ) {
|
||||
|
@ -3129,8 +3129,8 @@ jQuery.fn.extend( {
|
|||
var rootjQuery,
|
||||
|
||||
// A simple way to check for HTML strings
|
||||
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
||||
// Strict HTML recognition (#11290: must start with <)
|
||||
// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
|
||||
// Strict HTML recognition (trac-11290: must start with <)
|
||||
// Shortcut simple #id case for speed
|
||||
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
||||
|
||||
|
@ -4087,7 +4087,7 @@ jQuery.extend( {
|
|||
isReady: false,
|
||||
|
||||
// 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,
|
||||
|
||||
// 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
|
||||
// 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 ) {
|
||||
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
||||
}
|
||||
|
@ -4251,7 +4251,7 @@ Data.prototype = {
|
|||
value = {};
|
||||
|
||||
// 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.
|
||||
if ( acceptData( owner ) ) {
|
||||
|
||||
|
@ -4490,7 +4490,7 @@ jQuery.fn.extend( {
|
|||
while ( i-- ) {
|
||||
|
||||
// Support: IE 11 only
|
||||
// The attrs elements can be null (#14894)
|
||||
// The attrs elements can be null (trac-14894)
|
||||
if ( attrs[ i ] ) {
|
||||
name = attrs[ i ].name;
|
||||
if ( name.indexOf( "data-" ) === 0 ) {
|
||||
|
@ -4913,9 +4913,9 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
|||
input = document.createElement( "input" );
|
||||
|
||||
// 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)
|
||||
// `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( "checked", "checked" );
|
||||
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 = {
|
||||
|
||||
// XHTML parsers do not magically insert elements in the
|
||||
|
@ -4965,7 +4965,7 @@ if ( !support.option ) {
|
|||
function getAll( context, tag ) {
|
||||
|
||||
// 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;
|
||||
|
||||
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
||||
|
@ -5048,7 +5048,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
|
|||
// Remember the top-level container
|
||||
tmp = fragment.firstChild;
|
||||
|
||||
// Ensure the created nodes are orphaned (#12392)
|
||||
// Ensure the created nodes are orphaned (trac-12392)
|
||||
tmp.textContent = "";
|
||||
}
|
||||
}
|
||||
|
@ -5469,15 +5469,15 @@ jQuery.event = {
|
|||
|
||||
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
||||
|
||||
// Don't check non-elements (#13208)
|
||||
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
|
||||
// Don't check non-elements (trac-13208)
|
||||
// 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 ) ) {
|
||||
matchedHandlers = [];
|
||||
matchedSelectors = {};
|
||||
for ( i = 0; i < delegateCount; i++ ) {
|
||||
handleObj = handlers[ i ];
|
||||
|
||||
// Don't conflict with Object.prototype properties (#13203)
|
||||
// Don't conflict with Object.prototype properties (trac-13203)
|
||||
sel = handleObj.selector + " ";
|
||||
|
||||
if ( matchedSelectors[ sel ] === undefined ) {
|
||||
|
@ -5731,7 +5731,7 @@ jQuery.Event = function( src, props ) {
|
|||
|
||||
// Create target properties
|
||||
// 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 ) ?
|
||||
src.target.parentNode :
|
||||
src.target;
|
||||
|
@ -5854,10 +5854,10 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
|
|||
return true;
|
||||
},
|
||||
|
||||
// Suppress native focus or blur as it's already being fired
|
||||
// in leverageNative.
|
||||
_default: function() {
|
||||
return true;
|
||||
// Suppress native focus or blur if we're currently inside
|
||||
// a leveraged native-event stack
|
||||
_default: function( event ) {
|
||||
return dataPriv.get( event.target, type );
|
||||
},
|
||||
|
||||
delegateType: delegateType
|
||||
|
@ -5956,7 +5956,8 @@ var
|
|||
|
||||
// checked="checked" or checked
|
||||
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
|
||||
function manipulationTarget( elem, content ) {
|
||||
|
@ -6070,7 +6071,7 @@ function domManip( collection, args, callback, ignored ) {
|
|||
|
||||
// Use the original fragment for the last item
|
||||
// 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++ ) {
|
||||
node = fragment;
|
||||
|
||||
|
@ -6111,6 +6112,12 @@ function domManip( collection, args, callback, ignored ) {
|
|||
}, doc );
|
||||
}
|
||||
} 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 );
|
||||
}
|
||||
}
|
||||
|
@ -6393,9 +6400,12 @@ jQuery.each( {
|
|||
} );
|
||||
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
||||
|
||||
var rcustomProp = /^--/;
|
||||
|
||||
|
||||
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
|
||||
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
||||
var view = elem.ownerDocument.defaultView;
|
||||
|
@ -6430,6 +6440,15 @@ var swap = function( elem, options, callback ) {
|
|||
|
||||
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||
|
||||
var whitespace = "[\\x20\\t\\r\\n\\f]";
|
||||
|
||||
|
||||
var rtrimCSS = new RegExp(
|
||||
"^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
|
||||
"g"
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
( function() {
|
||||
|
@ -6495,7 +6514,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||
}
|
||||
|
||||
// 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.cloneNode( true ).style.backgroundClip = "";
|
||||
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
||||
|
@ -6575,6 +6594,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||
|
||||
function curCSS( elem, name, computed ) {
|
||||
var width, minWidth, maxWidth, ret,
|
||||
isCustomProp = rcustomProp.test( name ),
|
||||
|
||||
// Support: Firefox 51+
|
||||
// Retrieving style before computed somehow
|
||||
|
@ -6585,11 +6605,22 @@ function curCSS( elem, name, computed ) {
|
|||
computed = computed || getStyles( elem );
|
||||
|
||||
// getPropertyValue is needed for:
|
||||
// .css('filter') (IE 9 only, #12537)
|
||||
// .css('--customProperty) (#3144)
|
||||
// .css('filter') (IE 9 only, trac-12537)
|
||||
// .css('--customProperty) (gh-3144)
|
||||
if ( computed ) {
|
||||
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 ) ) {
|
||||
ret = jQuery.style( elem, name );
|
||||
}
|
||||
|
@ -6685,7 +6716,6 @@ var
|
|||
// except "table", "table-cell", or "table-caption"
|
||||
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
||||
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
||||
rcustomProp = /^--/,
|
||||
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
||||
cssNormalTransform = {
|
||||
letterSpacing: "0",
|
||||
|
@ -6921,15 +6951,15 @@ jQuery.extend( {
|
|||
if ( value !== undefined ) {
|
||||
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 ] ) {
|
||||
value = adjustCSS( elem, name, ret );
|
||||
|
||||
// Fixes bug #9237
|
||||
// Fixes bug trac-9237
|
||||
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 ) {
|
||||
return;
|
||||
}
|
||||
|
@ -7553,7 +7583,7 @@ function Animation( elem, properties, options ) {
|
|||
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
||||
|
||||
// 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,
|
||||
percent = 1 - temp,
|
||||
index = 0,
|
||||
|
@ -7943,7 +7973,6 @@ jQuery.fx.speeds = {
|
|||
|
||||
|
||||
// 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 ) {
|
||||
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
||||
type = type || "fx";
|
||||
|
@ -8168,8 +8197,7 @@ jQuery.extend( {
|
|||
// Support: IE <=9 - 11 only
|
||||
// elem.tabIndex doesn't always return the
|
||||
// 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(#12072)
|
||||
// Use proper attribute retrieval (trac-12072)
|
||||
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
||||
|
||||
if ( tabindex ) {
|
||||
|
@ -8273,8 +8301,7 @@ function classesToArray( value ) {
|
|||
|
||||
jQuery.fn.extend( {
|
||||
addClass: function( value ) {
|
||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
||||
i = 0;
|
||||
var classNames, cur, curValue, className, i, finalValue;
|
||||
|
||||
if ( isFunction( value ) ) {
|
||||
return this.each( function( j ) {
|
||||
|
@ -8282,36 +8309,35 @@ jQuery.fn.extend( {
|
|||
} );
|
||||
}
|
||||
|
||||
classes = classesToArray( value );
|
||||
classNames = classesToArray( value );
|
||||
|
||||
if ( classes.length ) {
|
||||
while ( ( elem = this[ i++ ] ) ) {
|
||||
curValue = getClass( elem );
|
||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||
if ( classNames.length ) {
|
||||
return this.each( function() {
|
||||
curValue = getClass( this );
|
||||
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||
|
||||
if ( cur ) {
|
||||
j = 0;
|
||||
while ( ( clazz = classes[ j++ ] ) ) {
|
||||
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
|
||||
cur += clazz + " ";
|
||||
for ( i = 0; i < classNames.length; i++ ) {
|
||||
className = classNames[ i ];
|
||||
if ( cur.indexOf( " " + className + " " ) < 0 ) {
|
||||
cur += className + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Only assign if different to avoid unneeded rendering.
|
||||
finalValue = stripAndCollapse( cur );
|
||||
if ( curValue !== finalValue ) {
|
||||
elem.setAttribute( "class", finalValue );
|
||||
}
|
||||
this.setAttribute( "class", finalValue );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
removeClass: function( value ) {
|
||||
var classes, elem, cur, curValue, clazz, j, finalValue,
|
||||
i = 0;
|
||||
var classNames, cur, curValue, className, i, finalValue;
|
||||
|
||||
if ( isFunction( value ) ) {
|
||||
return this.each( function( j ) {
|
||||
|
@ -8323,45 +8349,42 @@ jQuery.fn.extend( {
|
|||
return this.attr( "class", "" );
|
||||
}
|
||||
|
||||
classes = classesToArray( value );
|
||||
classNames = classesToArray( value );
|
||||
|
||||
if ( classes.length ) {
|
||||
while ( ( elem = this[ i++ ] ) ) {
|
||||
curValue = getClass( elem );
|
||||
if ( classNames.length ) {
|
||||
return this.each( function() {
|
||||
curValue = getClass( this );
|
||||
|
||||
// This expression is here for better compressibility (see addClass)
|
||||
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
||||
|
||||
if ( cur ) {
|
||||
j = 0;
|
||||
while ( ( clazz = classes[ j++ ] ) ) {
|
||||
for ( i = 0; i < classNames.length; i++ ) {
|
||||
className = classNames[ i ];
|
||||
|
||||
// Remove *all* instances
|
||||
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
|
||||
cur = cur.replace( " " + clazz + " ", " " );
|
||||
while ( cur.indexOf( " " + className + " " ) > -1 ) {
|
||||
cur = cur.replace( " " + className + " ", " " );
|
||||
}
|
||||
}
|
||||
|
||||
// Only assign if different to avoid unneeded rendering.
|
||||
finalValue = stripAndCollapse( cur );
|
||||
if ( curValue !== finalValue ) {
|
||||
elem.setAttribute( "class", finalValue );
|
||||
}
|
||||
this.setAttribute( "class", finalValue );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
toggleClass: function( value, stateVal ) {
|
||||
var type = typeof value,
|
||||
var classNames, className, i, self,
|
||||
type = typeof value,
|
||||
isValidValue = type === "string" || Array.isArray( value );
|
||||
|
||||
if ( typeof stateVal === "boolean" && isValidValue ) {
|
||||
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
||||
}
|
||||
|
||||
if ( isFunction( value ) ) {
|
||||
return this.each( function( i ) {
|
||||
jQuery( this ).toggleClass(
|
||||
|
@ -8371,17 +8394,20 @@ jQuery.fn.extend( {
|
|||
} );
|
||||
}
|
||||
|
||||
return this.each( function() {
|
||||
var className, i, self, classNames;
|
||||
if ( typeof stateVal === "boolean" && isValidValue ) {
|
||||
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
||||
}
|
||||
|
||||
classNames = classesToArray( value );
|
||||
|
||||
return this.each( function() {
|
||||
if ( isValidValue ) {
|
||||
|
||||
// Toggle individual class names
|
||||
i = 0;
|
||||
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
|
||||
if ( self.hasClass( className ) ) {
|
||||
|
@ -8515,7 +8541,7 @@ jQuery.extend( {
|
|||
val :
|
||||
|
||||
// Support: IE <=10 - 11 only
|
||||
// option.text throws exceptions (#14686, #14858)
|
||||
// option.text throws exceptions (trac-14686, trac-14858)
|
||||
// Strip and collapse whitespace
|
||||
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
||||
stripAndCollapse( jQuery.text( elem ) );
|
||||
|
@ -8542,7 +8568,7 @@ jQuery.extend( {
|
|||
option = options[ i ];
|
||||
|
||||
// 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 ) &&
|
||||
|
||||
// Don't return options that are disabled or in a disabled optgroup
|
||||
|
@ -8685,8 +8711,8 @@ jQuery.extend( jQuery.event, {
|
|||
return;
|
||||
}
|
||||
|
||||
// Determine event propagation path in advance, per W3C events spec (#9951)
|
||||
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
||||
// 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 (trac-9724)
|
||||
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
||||
|
||||
bubbleType = special.delegateType || type;
|
||||
|
@ -8738,7 +8764,7 @@ jQuery.extend( jQuery.event, {
|
|||
acceptData( elem ) ) {
|
||||
|
||||
// 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 ) ) {
|
||||
|
||||
// Don't re-trigger an onFOO event when we call its FOO() method
|
||||
|
@ -9012,7 +9038,7 @@ var
|
|||
rantiCache = /([?&])_=[^&]*/,
|
||||
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):$/,
|
||||
rnoContent = /^(?:GET|HEAD)$/,
|
||||
rprotocol = /^\/\//,
|
||||
|
@ -9035,7 +9061,7 @@ var
|
|||
*/
|
||||
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( "*" ),
|
||||
|
||||
// Anchor tag for parsing the document origin
|
||||
|
@ -9106,7 +9132,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX
|
|||
|
||||
// A special extend for ajax options
|
||||
// that takes "flat" options (not to be deep extended)
|
||||
// Fixes #9887
|
||||
// Fixes trac-9887
|
||||
function ajaxExtend( target, src ) {
|
||||
var key, deep,
|
||||
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
||||
|
@ -9517,12 +9543,12 @@ jQuery.extend( {
|
|||
deferred.promise( jqXHR );
|
||||
|
||||
// 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
|
||||
s.url = ( ( url || s.url || location.href ) + "" )
|
||||
.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;
|
||||
|
||||
// Extract dataTypes list
|
||||
|
@ -9565,7 +9591,7 @@ jQuery.extend( {
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Watch for a new set of requests
|
||||
|
@ -9594,7 +9620,7 @@ jQuery.extend( {
|
|||
if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -9867,7 +9893,7 @@ jQuery._evalUrl = function( url, options, doc ) {
|
|||
return jQuery.ajax( {
|
||||
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",
|
||||
dataType: "script",
|
||||
cache: true,
|
||||
|
@ -9976,7 +10002,7 @@ var xhrSuccessStatus = {
|
|||
0: 200,
|
||||
|
||||
// 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
|
||||
},
|
||||
xhrSupported = jQuery.ajaxSettings.xhr();
|
||||
|
@ -10048,7 +10074,7 @@ jQuery.ajaxTransport( function( options ) {
|
|||
} else {
|
||||
complete(
|
||||
|
||||
// File: protocol always yields status 0; see #8605, #14207
|
||||
// File: protocol always yields status 0; see trac-8605, trac-14207
|
||||
xhr.status,
|
||||
xhr.statusText
|
||||
);
|
||||
|
@ -10109,7 +10135,7 @@ jQuery.ajaxTransport( function( options ) {
|
|||
xhr.send( options.hasContent && options.data || null );
|
||||
} 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 ) {
|
||||
throw e;
|
||||
}
|
||||
|
@ -10753,7 +10779,9 @@ jQuery.each(
|
|||
|
||||
// Support: Android <=4.0 only
|
||||
// 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
|
||||
// arguments.
|
||||
|
@ -10820,7 +10848,7 @@ jQuery.isNumeric = function( obj ) {
|
|||
jQuery.trim = function( text ) {
|
||||
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
|
||||
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
||||
// and CommonJS for browser emulators (#13566)
|
||||
// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
||||
// and CommonJS for browser emulators (trac-13566)
|
||||
if ( typeof noGlobal === "undefined" ) {
|
||||
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-->
|
||||
<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="text/javascript" src="scripts/data/states.js"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/vendor/auto/luxon.js"></script>
|
||||
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
||||
<!-- data -->
|
||||
<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/regionalcities.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/icons.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>
|
||||
|
||||
<% } %>
|
||||
|
|
Loading…
Reference in a new issue