mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 09:09:30 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0e5018179 | ||
|
|
6422589b5c | ||
|
|
407da90f8a | ||
|
|
3a0e6aa345 | ||
|
|
650dda7b61 | ||
|
|
8f1e8ffb74 | ||
|
|
93af84cbd8 | ||
|
|
117f66e9d0 | ||
|
|
bca9376edc | ||
|
|
8b076db25d | ||
|
|
807932fe3c | ||
|
|
7bb024eff5 | ||
|
|
f4a1a3a1d8 | ||
|
|
9a5efe9d48 | ||
|
|
58e0611a46 | ||
|
|
9ed496c892 | ||
|
|
31315d1ace | ||
|
|
77838e1a81 | ||
|
|
64d6484bd8 | ||
|
|
20cab8c25e | ||
|
|
b4de17ccd0 | ||
|
|
0fd90feb7a | ||
|
|
8c3b596b69 | ||
|
|
e57b9bcb20 | ||
|
|
e27750e915 | ||
|
|
14b1891efd |
@@ -179,7 +179,7 @@ I've made several changes to this Weather Star 4000 simulation compared to the o
|
|||||||
* Radar displays the timestamp of the image.
|
* Radar displays the timestamp of the image.
|
||||||
* A new hour-by-hour graph of the temperature, cloud cover and precipitation chances for the next 24 hours.
|
* A new hour-by-hour graph of the temperature, cloud cover and precipitation chances for the next 24 hours.
|
||||||
* A new hourly forecast display for the next 24 hours is available, and is shown in the style of the travel cities forecast. (off by default because it duplicates the hourly graph)
|
* A new hourly forecast display for the next 24 hours is available, and is shown in the style of the travel cities forecast. (off by default because it duplicates the hourly graph)
|
||||||
* The SPC Outlook is shown in the style of the old air quality screen. This shows the probability of severe weather over the next 3 days at your location.
|
* The SPC Outlook is shown in the style of the old air quality screen. This shows the probability of severe weather over the next 3 days at your location. SPC outlook only displays if you're within one of the highlight areas over the next 3 day. You can view the [maps](https://www.weather.gov/crh/outlooks) and pick a location within one of the risk categories to see if the screen is working for you.
|
||||||
* The "Local Forecast" and "Extended Forecast" provide several additional days of information compared to the original format in the 90s.
|
* The "Local Forecast" and "Extended Forecast" provide several additional days of information compared to the original format in the 90s.
|
||||||
* The original music has been replaced. More info in [Music](#music).
|
* The original music has been replaced. More info in [Music](#music).
|
||||||
* Marine forecast (tides) is not available as it is not reliably part of the new API.
|
* Marine forecast (tides) is not available as it is not reliably part of the new API.
|
||||||
@@ -321,6 +321,7 @@ Thanks to the WeatherStar+ community for providing these discussions to further
|
|||||||
* [ws4channels](https://github.com/rice9797/ws4channels) A Dockerized Node.js application to stream WeatherStar 4000 data into Channels DVR using Puppeteer and FFmpeg.
|
* [ws4channels](https://github.com/rice9797/ws4channels) A Dockerized Node.js application to stream WeatherStar 4000 data into Channels DVR using Puppeteer and FFmpeg.
|
||||||
* [SSL Certificates](https://github.com/netbymatt/ws4kp/issues/135) Discussion about how to host with an SSL certificate (enables geolocation).
|
* [SSL Certificates](https://github.com/netbymatt/ws4kp/issues/135) Discussion about how to host with an SSL certificate (enables geolocation).
|
||||||
* [Changing playlists](https://github.com/netbymatt/ws4kp/issues/138) Possible ways to automatically change the playlist on a schedule.
|
* [Changing playlists](https://github.com/netbymatt/ws4kp/issues/138) Possible ways to automatically change the playlist on a schedule.
|
||||||
|
* [Customize Travel Forecast Cities](https://github.com/netbymatt/ws4kp/issues/146#issuecomment-3363940202)
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
|
|||||||
@@ -84,8 +84,8 @@
|
|||||||
"Latitude": 29.7633,
|
"Latitude": 29.7633,
|
||||||
"Longitude": -95.3633,
|
"Longitude": -95.3633,
|
||||||
"point": {
|
"point": {
|
||||||
"x": 65,
|
"x": 63,
|
||||||
"y": 97,
|
"y": 95,
|
||||||
"wfo": "HGX"
|
"wfo": "HGX"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import rename from 'gulp-rename';
|
|||||||
const clean = () => deleteAsync(['./server/scripts/vendor/auto/**']);
|
const clean = () => deleteAsync(['./server/scripts/vendor/auto/**']);
|
||||||
|
|
||||||
const vendorFiles = [
|
const vendorFiles = [
|
||||||
'./node_modules/luxon/build/es6/luxon.js',
|
'./node_modules/luxon/build/es6/luxon.mjs',
|
||||||
'./node_modules/luxon/build/es6/luxon.js.map',
|
'./node_modules/luxon/build/es6/luxon.mjs.map',
|
||||||
'./node_modules/nosleep.js/dist/NoSleep.js',
|
'./node_modules/nosleep.js/dist/NoSleep.js',
|
||||||
'./node_modules/suncalc/suncalc.js',
|
'./node_modules/suncalc/suncalc.js',
|
||||||
'./node_modules/swiped-events/src/swiped-events.js',
|
'./node_modules/swiped-events/src/swiped-events.js',
|
||||||
@@ -23,7 +23,6 @@ const copy = () => src(vendorFiles)
|
|||||||
path.dirname = path.dirname.toLowerCase();
|
path.dirname = path.dirname.toLowerCase();
|
||||||
path.basename = path.basename.toLowerCase();
|
path.basename = path.basename.toLowerCase();
|
||||||
path.extname = path.extname.toLowerCase();
|
path.extname = path.extname.toLowerCase();
|
||||||
if (path.basename === 'luxon') path.extname = '.mjs';
|
|
||||||
}))
|
}))
|
||||||
.pipe(dest('./server/scripts/vendor/auto'));
|
.pipe(dest('./server/scripts/vendor/auto'));
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import playlist from './src/playlist.mjs';
|
import playlist from './src/playlist.mjs';
|
||||||
import OVERRIDES from './src/overrides.mjs';
|
import OVERRIDES from './src/overrides.mjs';
|
||||||
import cache from './proxy/cache.mjs';
|
import cache from './proxy/cache.mjs';
|
||||||
|
import devTools from './src/com.chrome.devtools.mjs';
|
||||||
|
|
||||||
const travelCities = JSON.parse(await readFile('./datagenerators/output/travelcities.json'));
|
const travelCities = JSON.parse(await readFile('./datagenerators/output/travelcities.json'));
|
||||||
const regionalCities = JSON.parse(await readFile('./datagenerators/output/regionalcities.json'));
|
const regionalCities = JSON.parse(await readFile('./datagenerators/output/regionalcities.json'));
|
||||||
@@ -168,6 +169,7 @@ if (process.env?.DIST === '1') {
|
|||||||
app.use('/geoip', geoip);
|
app.use('/geoip', geoip);
|
||||||
app.use('/resources', express.static('./server/scripts/modules'));
|
app.use('/resources', express.static('./server/scripts/modules'));
|
||||||
app.get('/', index);
|
app.get('/', index);
|
||||||
|
app.get('/.well-known/appspecific/com.chrome.devtools.json', devTools);
|
||||||
app.get('*name', express.static('./server', staticOptions));
|
app.get('*name', express.static('./server', staticOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ server {
|
|||||||
|
|
||||||
add_header X-Weatherstar true always;
|
add_header X-Weatherstar true always;
|
||||||
|
|
||||||
|
include /etc/nginx/includes/wsqs_redirect.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
index redirect.html index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4150
package-lock.json
generated
4150
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.0",
|
"version": "6.2.6",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"start": "node index.mjs",
|
"start": "node index.mjs",
|
||||||
"stop": "pkill -f 'node index.mjs' || echo 'No process found'",
|
"stop": "pkill -f 'node index.mjs' || echo 'No process found'",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build:travelcities": "node datagenerators/travelcities.mjs",
|
||||||
"build:css": "sass --style=compressed ./server/styles/scss/main.scss ./server/styles/main.css",
|
"build:css": "sass --style=compressed ./server/styles/scss/main.scss ./server/styles/main.css",
|
||||||
"build": "gulp buildDist",
|
"build": "gulp buildDist",
|
||||||
"lint": "eslint ./server/scripts/**/*.mjs ./proxy/**/*.mjs ./src/**/*.mjs *.mjs",
|
"lint": "eslint ./server/scripts/**/*.mjs ./proxy/**/*.mjs ./src/**/*.mjs *.mjs",
|
||||||
@@ -50,13 +51,12 @@
|
|||||||
"swiped-events": "^1.1.4",
|
"swiped-events": "^1.1.4",
|
||||||
"terser-webpack-plugin": "^5.3.6",
|
"terser-webpack-plugin": "^5.3.6",
|
||||||
"webpack": "^5.99.9",
|
"webpack": "^5.99.9",
|
||||||
"webpack-stream": "^7.0.0"
|
"webpack-stream": "^7.0.0",
|
||||||
|
"metar-taf-parser": "^9.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"ejs": "^3.1.5",
|
"ejs": "^3.1.5",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0"
|
||||||
"metar-taf-parser": "^9.0.0",
|
|
||||||
"npm": "^11.6.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,17 +106,34 @@ const init = async () => {
|
|||||||
|
|
||||||
// attempt to parse the url parameters
|
// attempt to parse the url parameters
|
||||||
const parsedParameters = parseQueryString();
|
const parsedParameters = parseQueryString();
|
||||||
const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon;
|
const loadFromParsed = !!parsedParameters.latLon;
|
||||||
|
|
||||||
// Auto load the parsed parameters and fall back to the previous query
|
// Auto load the parsed parameters and fall back to the previous query
|
||||||
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
||||||
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
||||||
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
||||||
|
|
||||||
if (query && latLon && !fromGPS) {
|
if (parsedParameters.latLonQuery && !parsedParameters.latLon) {
|
||||||
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
txtAddress.value = query;
|
txtAddress.value = parsedParameters.latLonQuery;
|
||||||
loadData(JSON.parse(latLon));
|
const geometry = await geocodeLatLonQuery(parsedParameters.latLonQuery);
|
||||||
|
if (geometry) {
|
||||||
|
doRedirectToGeometry(geometry);
|
||||||
|
}
|
||||||
|
} else if (latLon && !fromGPS) {
|
||||||
|
// update in-page search box if using cached data, or parsed parameter
|
||||||
|
if ((query && !loadFromParsed) || (parsedParameters.latLonQuery && loadFromParsed)) {
|
||||||
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
|
txtAddress.value = query;
|
||||||
|
}
|
||||||
|
// use lat-long lookup if that's all that was provided in the query string
|
||||||
|
if (loadFromParsed && parsedParameters.latLon && !parsedParameters.latLonQuery) {
|
||||||
|
const { lat, lon } = JSON.parse(latLon);
|
||||||
|
getForecastFromLatLon(lat, lon, true);
|
||||||
|
} else {
|
||||||
|
// otherwise use pre-stored data
|
||||||
|
loadData(JSON.parse(latLon));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (fromGPS) {
|
if (fromGPS) {
|
||||||
btnGetGpsClick();
|
btnGetGpsClick();
|
||||||
@@ -162,6 +179,26 @@ const init = async () => {
|
|||||||
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const geocodeLatLonQuery = async (query) => {
|
||||||
|
try {
|
||||||
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
|
data: {
|
||||||
|
text: query,
|
||||||
|
f: 'json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const loc = data.locations?.[0];
|
||||||
|
if (loc) {
|
||||||
|
return loc.feature.geometry;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Geocoding failed:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const autocompleteOnSelect = async (suggestion) => {
|
const autocompleteOnSelect = async (suggestion) => {
|
||||||
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
||||||
const data = await 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', {
|
||||||
|
|||||||
@@ -12,9 +12,14 @@ const secondsToTicks = (seconds) => Math.ceil((seconds * 1000) / TICK_INTERVAL_M
|
|||||||
const DEFAULT_UPDATE = secondsToTicks(4.0); // 4 second default for each current conditions
|
const DEFAULT_UPDATE = secondsToTicks(4.0); // 4 second default for each current conditions
|
||||||
|
|
||||||
// items on page
|
// items on page
|
||||||
const mainScroll = document.querySelector('#container>.scroll');
|
let mainScroll;
|
||||||
const fixedScroll = document.querySelector('#container>.scroll .fixed');
|
let fixedScroll;
|
||||||
const header = document.querySelector('#container>.scroll .scroll-header');
|
let header;
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
mainScroll = document.querySelector('#container>.scroll');
|
||||||
|
fixedScroll = document.querySelector('#container>.scroll .fixed');
|
||||||
|
header = document.querySelector('#container>.scroll .scroll-header');
|
||||||
|
});
|
||||||
|
|
||||||
// local variables
|
// local variables
|
||||||
let interval;
|
let interval;
|
||||||
@@ -293,4 +298,5 @@ export {
|
|||||||
hide,
|
hide,
|
||||||
screenCount,
|
screenCount,
|
||||||
atDefault,
|
atDefault,
|
||||||
|
hazards,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Setting from './utils/setting.mjs';
|
import Setting from './utils/setting.mjs';
|
||||||
import { reset as resetScroll, addScreen as addScroll } from './currentweatherscroll.mjs';
|
import { reset as resetScroll, addScreen as addScroll, hazards } from './currentweatherscroll.mjs';
|
||||||
import { json } from './utils/fetch.mjs';
|
import { json } from './utils/fetch.mjs';
|
||||||
|
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
@@ -42,8 +42,9 @@ const parseFeed = (textInput) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add single text scroll
|
// add single text scroll after hazards if present
|
||||||
resetScroll();
|
resetScroll();
|
||||||
|
addScroll(hazards);
|
||||||
addScroll(
|
addScroll(
|
||||||
() => (
|
() => (
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,8 @@ const getFeed = async (url) => {
|
|||||||
|
|
||||||
// reset the scroll, then add the screens
|
// reset the scroll, then add the screens
|
||||||
resetScroll();
|
resetScroll();
|
||||||
|
// add the hazards scroll first
|
||||||
|
addScroll(hazards);
|
||||||
titles.forEach((title) => {
|
titles.forEach((title) => {
|
||||||
// data is provided to the screen handler, so we return a function
|
// data is provided to the screen handler, so we return a function
|
||||||
addScroll(
|
addScroll(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import STATUS from './status.mjs';
|
import STATUS from './status.mjs';
|
||||||
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
||||||
import { safeJson } from './utils/fetch.mjs';
|
import { safeJson } from './utils/fetch.mjs';
|
||||||
import { temperature as temperatureUnit, distanceKilometers } from './utils/units.mjs';
|
import { temperature as temperatureUnit, windSpeed as windUnit } from './utils/units.mjs';
|
||||||
import { getHourlyIcon } from './icons.mjs';
|
import { getHourlyIcon } from './icons.mjs';
|
||||||
import { directionToNSEW } from './utils/calc.mjs';
|
import { directionToNSEW } from './utils/calc.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
@@ -191,7 +191,7 @@ class Hourly extends WeatherDisplay {
|
|||||||
const parseForecast = async (data) => {
|
const parseForecast = async (data) => {
|
||||||
// get unit converters
|
// get unit converters
|
||||||
const temperatureConverter = temperatureUnit();
|
const temperatureConverter = temperatureUnit();
|
||||||
const distanceConverter = distanceKilometers();
|
const windConverter = windUnit();
|
||||||
|
|
||||||
// parse data
|
// parse data
|
||||||
const temperature = expand(data.temperature.values);
|
const temperature = expand(data.temperature.values);
|
||||||
@@ -210,8 +210,8 @@ const parseForecast = async (data) => {
|
|||||||
temperature: temperatureConverter(temperature[idx]),
|
temperature: temperatureConverter(temperature[idx]),
|
||||||
temperatureUnit: temperatureConverter.units,
|
temperatureUnit: temperatureConverter.units,
|
||||||
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
||||||
windSpeed: distanceConverter(windSpeed[idx]),
|
windSpeed: windConverter(windSpeed[idx]),
|
||||||
windUnit: distanceConverter.units,
|
windUnit: windConverter.units,
|
||||||
windDirection: directionToNSEW(windDirection[idx]),
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||||
skyCover: skyCover[idx],
|
skyCover: skyCover[idx],
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class SpcOutlook extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters, refresh) {
|
async getData(weatherParameters, refresh) {
|
||||||
|
if (weatherParameters) this.weatherParameters = weatherParameters;
|
||||||
if (!super.getData(weatherParameters, refresh)) return;
|
if (!super.getData(weatherParameters, refresh)) return;
|
||||||
|
|
||||||
// SPC outlook data does not need to be reloaded on a location change, only during silent refresh
|
// SPC outlook data does not need to be reloaded on a location change, only during silent refresh
|
||||||
@@ -93,7 +94,7 @@ class SpcOutlook extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// parse the data
|
// parse the data
|
||||||
this.data = testAllPoints([weatherParameters.longitude, weatherParameters.latitude], this.rawOutlookData);
|
this.data = testAllPoints([this.weatherParameters.longitude, this.weatherParameters.latitude], this.rawOutlookData);
|
||||||
|
|
||||||
// check if there's a "risk" for any of the three days, otherwise skip the SPC Outlook screen
|
// check if there's a "risk" for any of the three days, otherwise skip the SPC Outlook screen
|
||||||
if (this.data.reduce((prev, cur) => prev || !!cur, false)) {
|
if (this.data.reduce((prev, cur) => prev || !!cur, false)) {
|
||||||
|
|||||||
@@ -5,21 +5,22 @@ import en from '../../vendor/auto/locale/en.js';
|
|||||||
|
|
||||||
// metar-taf-parser requires regex lookbehind
|
// metar-taf-parser requires regex lookbehind
|
||||||
// this does not work in iOS < 16.4
|
// this does not work in iOS < 16.4
|
||||||
// this is a detection algorithm for iOS versions
|
// this is a detection algorithm for missing lookbehind support
|
||||||
const isIos = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
|
const supportsRegexLookAheadLookBehindCheck = () => {
|
||||||
let iosVersionOk = false;
|
try {
|
||||||
if (isIos) {
|
return (
|
||||||
// regex match the version string
|
// deliberately using RegExp for broader browser support during check
|
||||||
const iosVersionRaw = /OS (\d+)_(\d+)/.exec(window.navigator.userAgent);
|
/* eslint-disable prefer-regex-literals */
|
||||||
// check for match
|
'hibyehihi'
|
||||||
if (iosVersionRaw) {
|
.replace(new RegExp('(?<=hi)hi', 'g'), 'hello')
|
||||||
// break into parts
|
.replace(new RegExp('hi(?!bye)', 'g'), 'hey') === 'hibyeheyhello'
|
||||||
const iosVersionMajor = parseInt(iosVersionRaw[1], 10);
|
/* eslint-enable prefer-regex-literals */
|
||||||
const iosVersionMinor = parseInt(iosVersionRaw[2], 10);
|
);
|
||||||
if (iosVersionMajor > 16) iosVersionOk = true;
|
} catch {
|
||||||
if (iosVersionMajor === 16 && iosVersionMinor >= 4) iosVersionOk = true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
const supportsRegexLookAheadLookBehind = supportsRegexLookAheadLookBehindCheck();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Augment observation data by parsing METAR when API fields are missing
|
* Augment observation data by parsing METAR when API fields are missing
|
||||||
@@ -27,8 +28,8 @@ if (isIos) {
|
|||||||
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
||||||
*/
|
*/
|
||||||
const augmentObservationWithMetar = (observation) => {
|
const augmentObservationWithMetar = (observation) => {
|
||||||
// check for a metar message and for unusable ios versions
|
// check for a metar message and for regex lookbehind support
|
||||||
if (!observation?.rawMessage || (isIos && !iosVersionOk)) {
|
if (!observation?.rawMessage || (!supportsRegexLookAheadLookBehind)) {
|
||||||
return observation;
|
return observation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
server/scripts/vendor/auto/luxon.js.map
vendored
1
server/scripts/vendor/auto/luxon.js.map
vendored
File diff suppressed because one or more lines are too long
4
server/scripts/vendor/auto/luxon.mjs
vendored
4
server/scripts/vendor/auto/luxon.mjs
vendored
@@ -8127,7 +8127,7 @@ function friendlyDateTime(dateTimeish) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "3.7.1";
|
const VERSION = "3.7.2";
|
||||||
|
|
||||||
export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
|
export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
|
||||||
//# sourceMappingURL=luxon.js.map
|
//# sourceMappingURL=luxon.mjs.map
|
||||||
|
|||||||
1
server/scripts/vendor/auto/luxon.mjs.map
vendored
Normal file
1
server/scripts/vendor/auto/luxon.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
25
src/com.chrome.devtools.mjs
Normal file
25
src/com.chrome.devtools.mjs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// get values for devtools json
|
||||||
|
const uuid = 'd2bd1130-560f-4c8e-b2c5-e91073784964';
|
||||||
|
const root = path.resolve('server');
|
||||||
|
|
||||||
|
const DEVTOOLS_CONFIG = {
|
||||||
|
workspace: {
|
||||||
|
uuid,
|
||||||
|
root,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const devTools = (req, res) => {
|
||||||
|
// test for localhost
|
||||||
|
if (['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.ip)) {
|
||||||
|
console.log(DEVTOOLS_CONFIG);
|
||||||
|
res.json(DEVTOOLS_CONFIG);
|
||||||
|
} else {
|
||||||
|
// not localhost
|
||||||
|
res.status(404).send('File not found');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default devTools;
|
||||||
@@ -12,6 +12,9 @@ url_encode() {
|
|||||||
|
|
||||||
# build query string from WSQS_ env vars
|
# build query string from WSQS_ env vars
|
||||||
while IFS='=' read -r key val; do
|
while IFS='=' read -r key val; do
|
||||||
|
# Skip empty lines
|
||||||
|
[ -z "$key" ] && continue
|
||||||
|
|
||||||
# Remove WSQS_ prefix and convert underscores to hyphens
|
# Remove WSQS_ prefix and convert underscores to hyphens
|
||||||
key="${key#WSQS_}"
|
key="${key#WSQS_}"
|
||||||
key="${key//_/-}"
|
key="${key//_/-}"
|
||||||
@@ -23,11 +26,16 @@ while IFS='=' read -r key val; do
|
|||||||
QS="${key}=${encoded_val}"
|
QS="${key}=${encoded_val}"
|
||||||
fi
|
fi
|
||||||
done << EOF
|
done << EOF
|
||||||
$(env | grep '^WSQS_')
|
$(env | grep '^WSQS_' || true)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/includes
|
||||||
|
|
||||||
if [ -n "$QS" ]; then
|
if [ -n "$QS" ]; then
|
||||||
|
# Escape the query string for use in JavaScript (escape backslashes and single quotes)
|
||||||
|
QS_ESCAPED=$(printf '%s' "$QS" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\'/g")
|
||||||
|
|
||||||
|
# Generate redirect.html with JavaScript logic
|
||||||
cat > "$ROOT/redirect.html" <<EOF
|
cat > "$ROOT/redirect.html" <<EOF
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -35,10 +43,36 @@ if [ -n "$QS" ]; then
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Redirecting</title>
|
<title>Redirecting</title>
|
||||||
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var wsqsParams = '$QS_ESCAPED';
|
||||||
|
var currentParams = window.location.search.substring(1);
|
||||||
|
var targetParams = currentParams || wsqsParams;
|
||||||
|
window.location.replace('/index.html?' + targetParams);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Generate nginx config for conditional redirects
|
||||||
|
cat > /etc/nginx/includes/wsqs_redirect.conf <<'EOF'
|
||||||
|
location = / {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
rewrite ^/$ /index.html?$args? redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
touch /etc/nginx/includes/wsqs_redirect.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@@ -188,14 +188,22 @@
|
|||||||
|
|
||||||
<div class='heading'>Headend Information</div>
|
<div class='heading'>Headend Information</div>
|
||||||
<div id="divInfo">
|
<div id="divInfo">
|
||||||
Location: <span id="spanCity"></span> <span id="spanState"></span><br />
|
<div class="header">Location:</div>
|
||||||
Station Id: <span id="spanStationId"></span><br />
|
<div class="header"><span id="spanCity"></span> <span id="spanState"></span></div>
|
||||||
Radar Id: <span id="spanRadarId"></span><br />
|
<div class="header">Station Id:</div>
|
||||||
Zone Id: <span id="spanZoneId"></span><br />
|
<div class="header"><span id="spanStationId"></span></div>
|
||||||
Office Id: <span id="spanOfficeId"></span><br />
|
<div class="header">Radar Id:</div>
|
||||||
Grid X,Y: <span id="spanGridPoint"></span><br />
|
<div class="header"><span id="spanRadarId"></span></div>
|
||||||
Music: <span id="musicTrack">Not playing</span><br />
|
<div class="header">Zone Id:</div>
|
||||||
Ws4kp Version: <span><%- version %></span>
|
<div class="header"><span id="spanZoneId"></span></div>
|
||||||
|
<div class="header">Office Id:</div>
|
||||||
|
<div class="header"><span id="spanOfficeId"></span></div>
|
||||||
|
<div class="header">Grid X,Y:</div>
|
||||||
|
<div class="header"><span id="spanGridPoint"></span></div>
|
||||||
|
<div class="header">Music:</div>
|
||||||
|
<div class="header"><span id="musicTrack">Not playing</span></div>
|
||||||
|
<div class="header">Ws4kp Version:</div>
|
||||||
|
<div class="header"><span><%- version %></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,10 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
},
|
||||||
|
"cSpell.words": [
|
||||||
|
"hibyehihi"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
|
|||||||
Reference in New Issue
Block a user