Compare commits

...

15 Commits

Author SHA1 Message Date
Matt Walsh
62fbe1787f switch local forecast to mixed case for better readability and to match the latest captures from the late '00s 2026-04-14 08:52:36 -05:00
Matt Walsh
994c9240b8 shorten permalinks close #206 2026-04-13 16:19:26 -05:00
Matt Walsh
27d75ba62d current observations don't show like when the same close #207 2026-04-13 14:54:18 -05:00
Matt Walsh
63e089451d restructure wide/enhanced settings close #205 2026-04-13 14:36:15 -05:00
Matt Walsh
42f1f66117 squeeze preview format into available space 2026-04-08 23:36:33 -05:00
Matt Walsh
d4f648f244 better 'version' numbers for staging site 2026-04-08 23:34:10 -05:00
Matt Walsh
71d52c0b72 fixe wide-enhanced radar and almanac backgrounds 2026-04-08 23:25:05 -05:00
Matt Walsh
d2bf8f3f99 fix radar tiles 2026-04-08 23:01:42 -05:00
Matt Walsh
f4289e6329 Merge branch 'main' into screen-enahnce/magic-number-code 2026-04-08 22:56:20 -05:00
Matt Walsh
11c54391b2 6.5.7 2026-04-08 22:42:07 -05:00
Matt Walsh
0b47cf79c1 don't overwrite timestamps when enhancing with mapclick 2026-04-08 22:41:42 -05:00
Matt Walsh
ba36904477 6.5.6 2026-04-08 11:39:36 -05:00
Matt Walsh
dae5b20bc6 fix radar round/floor mismatch in calculations close #200 2026-04-08 11:39:25 -05:00
Matt Walsh
ccc936d81a 6.5.5 2026-04-08 09:57:24 -05:00
Matt Walsh
5dc214c6a5 filter station list upon receipt 2026-04-08 09:57:18 -05:00
30 changed files with 181 additions and 88 deletions

View File

@@ -139,8 +139,8 @@ services:
# Following the "Sharing a Permalink" example below, here are a few environment variables defined. Visit that section for a
# more complete list of configuration options.
- WSQS_latLonQuery=Orlando International Airport Orlando FL USA
- WSQS_hazards_checkbox=false
- WSQS_current_weather_checkbox=true
- WSQS_hazards=false
- WSQS_current_weather=true
ports:
- 8080:8080 # change the first 8080 to meet your local network needs
restart: unless-stopped
@@ -192,13 +192,13 @@ Selected displays, the forecast city and widescreen setting are sticky from one
Your permalink will be very long. Here is an example for the Orlando International Airport:
```
https://weatherstar.netbymatt.com/?hazards-checkbox=false&current-weather-checkbox=true&latest-observations-checkbox=true&hourly-checkbox=false&hourly-graph-checkbox=true&travel-checkbox=false&regional-forecast-checkbox=true&local-forecast-checkbox=true&extended-forecast-checkbox=true&almanac-checkbox=false&spc-outlook-checkbox=true&radar-checkbox=true&settings-wide-checkbox=false&settings-kiosk-checkbox=false&settings-scanLines-checkbox=false&settings-speed-select=1.00&settings-units-select=us&latLonQuery=Orlando+International+Airport%2C+Orlando%2C+FL%2C+USA&latLon=%7B%22lat%22%3A28.431%2C%22lon%22%3A-81.3076%7D
https://weatherstar.netbymatt.com/?hazards=false&current-weather=true&latest-observations=true&hourly=false&hourly-graph=true&travel=false&regional-forecast=true&local-forecast=true&extended-forecast=true&almanac=false&spc-outlook=true&radar=true&wide=false&kiosk=false&scanLines=false&speed-select=1.00&units-select=us&latLonQuery=Orlando+International+Airport%2C+Orlando%2C+FL%2C+USA&latLon=%7B%22lat%22%3A28.431%2C%22lon%22%3A-81.3076%7D
```
You can also build your own permalink. Any omitted settings will be filled with defaults. Here are a few examples:
```
https://weatherstar.netbymatt.com/?latLonQuery=Orlando+International+Airport
https://weatherstar.netbymatt.com/?kiosk=true
https://weatherstar.netbymatt.com/?settings-units-select=metric
https://weatherstar.netbymatt.com/?units-select=metric
```
### Kiosk mode
@@ -213,7 +213,7 @@ When serving this via the built-in Express server, it's possible to define envir
Environment variables can be added to the command line as usual, or via a .env file which is parsed with [dotenv](https://github.com/motdotla/dotenv). Both methods have the same effect.
Environment variables that are to be added to the default query string are prefixed with `WSQS_` and then use the same key/value pairs generated by the [Permalink](#sharing-a-permalink-bookmarking) above, with the `- (dash)` character replaced by an `_ (underscore)`. For example, if you wanted to turn the travel forecast on, you would find `travel-checkbox=true` in the permalink, its matching environment variable becomes `WSQS_travel_checkbox=true`.
Environment variables that are to be added to the default query string are prefixed with `WSQS_` and then use the same key/value pairs generated by the [Permalink](#sharing-a-permalink-bookmarking) above, with the `- (dash)` character replaced by an `_ (underscore)`. For example, if you wanted to turn the travel forecast on, you would find `travel-checkbox=true` in the permalink, its matching environment variable becomes `WSQS_travel=true`.
When using the Docker container, these environment variables are read on container start-up to generate the static redirect HTML.
@@ -221,7 +221,13 @@ When using the Docker container, these environment variables are read on contain
**Speed:** Controls the playback speed multiplier of the displays, from "Very Fast" (1.5x) to "Very Slow" (0.5x) with "Normal" being 1x
**Widescreen:** Stretches the background to 16:9 to avoid "pillarboxing" on modern displays
**Display Mode:**
- Standard: Classic 4:3 display with the classic (not enhanced, below) screen layouts.
- Widescreen: Stretches the background to 16:9 to avoid "pillarboxing" on modern displays
- Widescreen Enhanced: Stretches as above, and makes use of the additional space to provide wider maps, more weather data and/or additional days in the forecast
- Portrait Enhanced: (in progress) Rotates the screen to a 16:9 portrait orientation and enhances the original displays by adjusting them to fit the new orientation.
**Kiosk:** Immediately activates kiosk mode, which hides all settings. Exit by refreshing the page or using `Ctrl-K`. (Kiosk mode is similar to clicking the "Fullscreen" icon, but scales to the current browser viewport instead of activating the browser's actual "Fullscreen" mode.)

View File

@@ -8,13 +8,11 @@ import states from './stations-states.mjs';
import chunk from './chunk.mjs';
import overrides from './stations-overrides.mjs';
import postProcessor from './stations-postprocessor.mjs';
import { stationFilter } from '../server/scripts/modules/utils/string.mjs';
// check for cached flag
const USE_CACHE = process.argv.includes('--use-cache');
// skip stations starting with these letters
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
// chunk the list of states
const chunkStates = chunk(states, 3);
@@ -41,10 +39,8 @@ if (!USE_CACHE) {
// eslint-disable-next-line no-await-in-loop
const stationsRaw = await https(next);
stations = JSON.parse(stationsRaw);
// filter stations for 4 letter identifiers
const stationsFiltered4 = stations.features.filter((station) => station.properties.stationIdentifier.match(/^[A-Z]{4}$/));
// filter against starting letter
const stationsFiltered = stationsFiltered4.filter((station) => !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
const stationsFiltered = stations.filter(stationFilter);
// add each resulting station to the output
stationsFiltered.forEach((station) => {
const id = station.properties.stationIdentifier;

View File

@@ -19,6 +19,7 @@ import * as dartSass from 'sass';
import gulpSass from 'gulp-sass';
import sourceMaps from 'gulp-sourcemaps';
import OVERRIDES from '../src/overrides.mjs';
import { DateTime } from 'luxon';
// get cloudfront
import reader from '../src/playlist-reader.mjs';
@@ -110,10 +111,9 @@ const htmlSources = [
const packageJson = await readFile('package.json');
let { version } = JSON.parse(packageJson);
const previewVersion = async () => {
// generate a relatively unique timestamp for cache invalidation of the preview site
const now = new Date();
const msNow = now.getTime() % 1_000_000;
version = msNow.toString();
// generate a unique timestamp for cache invalidation of the preview site
const now = DateTime.utc();
version = now.toFormat('yyyyLLddHHmm').substring(3);
};
const compressHtml = async () => src(htmlSources)

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ws4kp",
"version": "6.5.4",
"version": "6.5.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ws4kp",
"version": "6.5.4",
"version": "6.5.7",
"license": "MIT",
"dependencies": {
"dotenv": "^17.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "ws4kp",
"version": "6.5.4",
"version": "6.5.7",
"description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.mjs",
"type": "module",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

View File

@@ -141,7 +141,7 @@ const init = async () => {
}
// Handle kiosk mode initialization
const urlKioskCheckbox = parsedParameters['settings-kiosk-checkbox'];
const urlKioskCheckbox = parsedParameters?.kiosk ?? parsedParameters['settings-kiosk-checkbox'];
// If kiosk=false is specified, disable kiosk mode and clear any stored value
if (urlKioskCheckbox === 'false') {

View File

@@ -16,9 +16,6 @@ import { isDataStale, enhanceObservationWithMapClick } from './utils/mapclick.mj
import { DateTime } from '../vendor/auto/luxon.mjs';
import settings from './settings.mjs';
// some stations prefixed do not provide all the necessary data
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
class CurrentWeather extends WeatherDisplay {
constructor(navId, elemId) {
super(navId, elemId, 'Current Conditions', true);
@@ -30,8 +27,8 @@ class CurrentWeather extends WeatherDisplay {
// note: current weather does not use old data on a silent refresh
// this is deliberate because it can pull data from more than one station in sequence
// filter for 4-letter observation stations, only those contain sky conditions and thus an icon
const filteredStations = this.weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
// get the available stations
const { stations } = this.weatherParameters;
// Load the observations
let observations;
@@ -39,9 +36,9 @@ class CurrentWeather extends WeatherDisplay {
// station number counter
let stationNum = 0;
while (!observations && stationNum < filteredStations.length) {
while (!observations && stationNum < stations.length) {
// get the station
station = filteredStations[stationNum];
station = stations[stationNum];
const stationId = station.properties.stationIdentifier;
stationNum += 1;
@@ -105,7 +102,11 @@ class CurrentWeather extends WeatherDisplay {
debugContext: 'currentweather',
});
// copy enhanced data and restore the timestamp if it was overwritten by older data from mapclick
const { timestamp } = candidateObservation.features[0].properties;
candidateObservation.features[0].properties = enhancedResult.data;
candidateObservation.features[0].properties.timestamp = timestamp;
const { missingFields } = enhancedResult;
const missingRequired = missingFields.filter((fieldName) => {
const field = requiredFields.find((f) => f.name === fieldName && f.required);
@@ -194,7 +195,7 @@ class CurrentWeather extends WeatherDisplay {
// get location (city name) from StationInfo if available (allows for overrides)
// longer name allowed if in wide-enhanced
const locationLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 25 : 20;
const locationLimit = (settings.wide?.value && settings.enhanced?.value) ? 25 : 20;
const location = (StationInfo[this.data.station.properties.stationIdentifier]?.city ?? locationCleanup(this.data.station.properties.name)).substr(0, locationLimit);
const fill = {

View File

@@ -18,7 +18,7 @@ const scaling = () => {
xTicks: 4,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
available.width = available.width + 107 + 107;
available.height = 285;
dataLength.hours = 48;

View File

@@ -162,8 +162,8 @@ class LatestObservations extends WeatherDisplay {
const Like = likeTemperature(condition.heatIndex?.value, condition.windChill?.value, Temperature, temperatureConverter);
const WindSpeed = windConverter(condition.windSpeed.value);
const locationLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 20 : 14;
const weatherLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 10 : 9;
const locationLimit = (settings.wide?.value && settings.enhanced?.value) ? 20 : 14;
const weatherLimit = (settings.wide?.value && settings.enhanced?.value) ? 10 : 9;
const fill = {
location: locationCleanup(condition.city).substr(0, locationLimit),
@@ -183,7 +183,7 @@ class LatestObservations extends WeatherDisplay {
const filledRow = this.fillTemplate('observation-row', fill);
// add the feels like class
filledRow.querySelector('.like').classList.add(Like.cssClass);
if (Like.cssClass) filledRow.querySelector('.like').classList.add(Like.cssClass);
return filledRow;
});

View File

@@ -37,9 +37,9 @@ class LocalForecast extends WeatherDisplay {
// read each text
this.screenTexts = conditions.map((condition) => {
// process the text
let text = `${condition.DayName.toUpperCase()}...`;
let text = `${condition.DayName}...`;
const conditionText = condition.Text;
text += conditionText.toUpperCase().replace('...', ' ');
text += conditionText.replace('...', ' ');
return text;
});
@@ -257,7 +257,7 @@ const parse = (forecast, forecastUrl) => {
return activePeriods.slice(0, 6).map((text) => ({
// format day and text
DayName: text.name.toUpperCase(),
DayName: text.name,
Text: text.detailedForecast,
}));
};

View File

@@ -1,6 +1,5 @@
import { text } from './utils/fetch.mjs';
import Setting from './utils/setting.mjs';
import { registerHiddenSetting } from './share.mjs';
let playlist;
let currentTrack = 0;
@@ -33,9 +32,6 @@ document.addEventListener('DOMContentLoaded', () => {
// get the playlist
getMedia();
// register the volume setting
registerHiddenSetting(mediaVolume.elemId, mediaVolume);
});
const scanMusicDirectory = async () => {
@@ -246,6 +242,7 @@ const mediaVolume = new Setting('mediaVolume', {
[0.25, '25%'],
],
changeAction: setVolume,
visible: false,
});
const initializePlayer = () => {

View File

@@ -6,6 +6,7 @@ import { safeJson } from './utils/fetch.mjs';
import { getPoint } from './utils/weather.mjs';
import { debugFlag } from './utils/debug.mjs';
import settings from './settings.mjs';
import { stationFilter } from './utils/string.mjs';
document.addEventListener('DOMContentLoaded', () => {
init();
@@ -90,7 +91,15 @@ const getWeather = async (latLon, haveDataCallback) => {
return;
}
const StationId = stations.features[0].properties.stationIdentifier;
// filter stations for proper format
const stationsFiltered = stations.features.filter(stationFilter);
// check for stations available after filtering
if (stationsFiltered.length === 0) {
console.warn('No observation stations left for location after filtering');
return;
}
const StationId = stationsFiltered[0].properties.stationIdentifier;
let { city } = point.properties.relativeLocation.properties;
const { state } = point.properties.relativeLocation.properties;
@@ -113,7 +122,7 @@ const getWeather = async (latLon, haveDataCallback) => {
weatherParameters.timeZone = point.properties.timeZone;
weatherParameters.forecast = point.properties.forecast;
weatherParameters.forecastGridData = point.properties.forecastGridData;
weatherParameters.stations = stations.features;
weatherParameters.stations = stationsFiltered;
weatherParameters.relativeLocation = point.properties.relativeLocation.properties;
// update the main process for display purposes

View File

@@ -4,7 +4,7 @@ const radarFinalSize = () => {
const size = {
width: 640, height: 367,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
size.width = 854;
}
return size;
@@ -15,7 +15,7 @@ const radarSourceSize = () => {
width: 240,
height: 163,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
size.width = 240 / 640 * 854; // original size of 640 scaled up to wide at 854
}
return size;
@@ -26,7 +26,7 @@ const radarOffset = () => {
x: 240,
y: 138,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
// 107 is the margins shift, 640/854 is the scaling factor normal => wide, /2 is because of the fixed 2:1 scaling between source radar and map tiles
offset.x = 240 + (107 * 640 / 854 / 2); // original size of 640 scaled up to wide at 854;
}
@@ -40,7 +40,7 @@ const radarShift = () => {
x: 0,
y: 0,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
shift.x = 107;
}
return shift;

View File

@@ -9,10 +9,12 @@ const pixelToFile = (xPixel, yPixel) => {
return `${yTile}-${xTile}`;
};
// convert a pixel location in the overall map to a pixel location on the tile
// convert a pixel location in the overall map to a pixel location on the tile set
const modTile = (xPixel, yPixel) => {
const x = Math.round(xPixel) % TILE_SIZE.x;
const y = Math.round(yPixel) % TILE_SIZE.y;
// adjust for additional 1 tile when odd
const x = (Math.floor(xPixel) % (TILE_SIZE.x));
const y = (Math.floor(yPixel) % (TILE_SIZE.y));
return { x, y };
};
@@ -46,11 +48,11 @@ const setTiles = (data) => {
const tileShift = modTile(sourceXY.x, sourceXY.y);
// determine which tiles are used
const secondRow = TILE_SIZE.y - tileShift.y < RADAR_FINAL_SIZE().width;
const secondRow = tileShift.y + TILE_SIZE.y > RADAR_FINAL_SIZE().height;
const usedTiles = [
true,
TILE_SIZE.x - tileShift.x < RADAR_FINAL_SIZE().width,
TILE_SIZE.x - (tileShift.x * 2) < RADAR_FINAL_SIZE().width,
tileShift.x + TILE_SIZE.x > RADAR_FINAL_SIZE().width,
tileShift.x + (TILE_SIZE.x * 2) > RADAR_FINAL_SIZE().width,
secondRow,
];
// second row is a copy of the first row when in use

View File

@@ -29,7 +29,7 @@ const scaling = () => {
y: 117,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
if (settings.wide?.value && settings.enhanced?.value) {
mapOffsetXY.x = 320;
available.x = 854;
}

View File

@@ -10,6 +10,11 @@ const deferredDomSettings = new Set();
// don't show checkboxes for these settings
const hiddenSettings = [
'scanLines',
// wide, portrait and enhanced are handled by a dropdown which sets these individual settings accordingly
'wide',
'portrait',
'enhanced',
];
// Declare change functions first, before they're referenced in init() to avoid the Temporal Dead Zone (TDZ)
@@ -32,12 +37,31 @@ const wideScreenChange = (value) => {
window.dispatchEvent(new Event('resize'));
};
const enhancedScreenChange = (value) => {
const portraitChange = (value) => {
const container = document.querySelector('#divTwc');
if (!container) {
// DOM not ready; defer enabling if set
if (value) {
deferredDomSettings.add('enhancedScreens');
deferredDomSettings.add('portrait');
}
return;
}
if (value) {
container.classList.add('portrait');
} else {
container.classList.remove('portrait');
}
// Trigger resize to recalculate scaling for new width
window.dispatchEvent(new Event('resize'));
};
const enhancedChange = (value) => {
const container = document.querySelector('#divTwc');
if (!container) {
// DOM not ready; defer enabling if set
if (value) {
deferredDomSettings.add('enhanced');
}
return;
}
@@ -51,6 +75,31 @@ const enhancedScreenChange = (value) => {
window.dispatchEvent(new Event('redraw'));
};
const viewModeChange = (value) => {
// set the appropriate mode bits which triggers change actions above
switch (value) {
case 'wide':
settings.wide.value = true;
settings.enhanced.value = false;
settings.portrait.value = false;
break;
case 'wide-enhanced':
settings.wide.value = true;
settings.enhanced.value = true;
settings.portrait.value = false;
break;
case 'portrait-enhanced':
settings.wide.value = false;
settings.enhanced.value = true;
settings.portrait.value = true;
break;
default:
settings.wide.value = false;
settings.enhanced.value = false;
settings.portrait.value = false;
}
};
const kioskChange = (value) => {
const body = document.querySelector('body');
if (!body) {
@@ -151,15 +200,37 @@ const init = () => {
});
settings.portrait = new Setting('portrait', {
name: 'Allow Portrait',
changeAction: portraitChange,
defaultValue: false,
sticky: true,
});
settings.enhancedScreens = new Setting('enhancedScreens', {
settings.enhanced = new Setting('enhanced', {
name: 'Enhanced Screens',
defaultValue: false,
changeAction: enhancedScreenChange,
changeAction: enhancedChange,
sticky: true,
});
// widescreen, portrait and enhanced are handled by a dropdown
// the dropdown change action sets the above bits accordingly
// first, figure out the default value based on other settings
// this also enforces rules on how these can be combined
let viewModeDefault = 'standard';
if (settings.wide.value && !settings.enhanced.value) viewModeDefault = 'wide';
if (settings.wide.value && settings.enhanced.value) viewModeDefault = 'wide-enhanced';
if (settings.portrait.value) viewModeDefault = 'portrait-enhanced';
settings.viewMode = new Setting('viewMode', {
name: 'Display mode',
type: 'select',
defaultValue: viewModeDefault,
changeAction: viewModeChange,
sticky: false, // not sticky because the above 3 settings are sticky and define this item's starting state
values: [
['standard', 'Standard'],
['wide', 'Widescreen'],
['wide-enhanced', 'Widescreen enhanced'],
['portrait-enhanced', 'Portrait enhanced'],
],
});
settings.kiosk = new Setting('kiosk', {
name: 'Kiosk',
defaultValue: false,
@@ -202,6 +273,7 @@ const init = () => {
['medium', 'Medium (2x)'],
['thick', 'Thick (3x)'],
],
visible: false,
});
settings.units = new Setting('units', {
name: 'Units',
@@ -251,7 +323,7 @@ document.addEventListener('DOMContentLoaded', () => {
const settingHtml = Object.values(settings).map((setting) => {
if (hiddenSettings.includes(setting.shortName)) {
// setting is hidden, register it
registerHiddenSetting(setting.elemId, setting);
registerHiddenSetting(setting.shortName, setting);
return false;
}
// generate HTML for setting
@@ -269,7 +341,6 @@ document.addEventListener('DOMContentLoaded', () => {
} else if (modeSelect) {
modeSelect.style.display = 'none';
}
registerHiddenSetting('settings-scanLineMode-select', settings.scanLineMode);
});
export default settings;

View File

@@ -25,22 +25,28 @@ const createLink = async (e) => {
const queryStringElements = {};
elemForEach('input[type=checkbox]', (elem) => {
if (elem?.id) {
queryStringElements[elem.id] = elem?.checked ?? false;
// use name, and fallback to id (older prefix/suffix permalinks)
const key = elem?.name ?? elem?.id;
if (key) {
queryStringElements[key] = elem?.checked ?? false;
}
});
// get all select boxes
elemForEach('select', (elem) => {
if (elem?.id) {
queryStringElements[elem.id] = encodeURIComponent(elem?.value ?? '');
// use name, and fallback to id (older prefix/suffix permalinks)
const key = elem?.name ?? elem?.id;
if (key) {
queryStringElements[key] = encodeURIComponent(elem?.value ?? '');
}
});
// get all text boxes
elemForEach('input[type=text]', ((elem) => {
if (elem?.id) {
queryStringElements[elem.id] = elem?.value ?? 0;
// use name, and fallback to id (older prefix/suffix permalinks)
const key = elem?.name ?? elem?.id;
if (key && key !== '') {
queryStringElements[key] = elem?.value ?? 0;
}
}));

View File

@@ -41,7 +41,9 @@ class Setting {
this.elemId = `settings-${shortName}-${this.type}`;
// get value from url
const urlValue = parseQueryString()?.[this.elemId];
// includes a fallback to the older prefix/suffix version
const queryString = parseQueryString();
const urlValue = queryString?.[shortName] ?? queryString?.[this.elemId];
let urlState;
if (this.type === 'checkbox' && urlValue !== undefined) {
urlState = urlValue === 'true';
@@ -92,7 +94,7 @@ class Setting {
const select = document.createElement('select');
select.id = `settings-${this.shortName}-select`;
select.name = `settings-${this.shortName}-select`;
select.name = this.shortName;
select.addEventListener('change', (e) => this.selectChange(e));
this.values.forEach(([value, text]) => {
@@ -125,7 +127,7 @@ class Setting {
checkbox.type = 'checkbox';
checkbox.value = true;
checkbox.id = `settings-${this.shortName}-checkbox`;
checkbox.name = `settings-${this.shortName}-checkbox`;
checkbox.name = this.shortName;
checkbox.checked = this.myValue;
checkbox.addEventListener('change', (e) => this.checkboxChange(e));
const span = document.createElement('span');
@@ -148,14 +150,14 @@ class Setting {
textInput.type = 'text';
textInput.value = this.myValue;
textInput.id = `settings-${this.shortName}-string`;
textInput.name = `settings-${this.shortName}-string`;
textInput.name = this.shortName;
textInput.placeholder = this.placeholder;
// set button
const setButton = document.createElement('input');
setButton.type = 'button';
setButton.value = 'Set';
setButton.id = `settings-${this.shortName}-button`;
setButton.name = `settings-${this.shortName}-button`;
setButton.name = this.shortName;
setButton.addEventListener('click', () => {
this.stringChange({ target: { value: textInput.value } });
});

View File

@@ -13,7 +13,12 @@ const locationCleanup = (input) => {
return regexes.reduce((value, regex) => value.replace(regex, ''), input);
};
// stations must be 4 alpha characters and not start with the provided list
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
const stationFilter = (station) => station.properties.stationIdentifier.match(/^[A-Z]{4}$/) && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1));
export {
// eslint-disable-next-line import/prefer-default-export
locationCleanup,
stationFilter,
};

View File

@@ -55,8 +55,9 @@ class WeatherDisplay {
// no checkbox if progress
if (this.elemId === 'progress') return false;
// get url provided state
const urlValue = parseQueryString()?.[`${this.elemId}-checkbox`];
// get url provided state, and fall back to the older suffix naming convention
const queryString = parseQueryString();
const urlValue = queryString?.[this.elemId] ?? queryString?.[`${this.elemId}-checkbox`];
let urlState;
if (urlValue !== undefined) {
urlState = urlValue === 'true';
@@ -78,7 +79,7 @@ class WeatherDisplay {
checkbox.type = 'checkbox';
checkbox.value = true;
checkbox.id = `${this.elemId}-checkbox`;
checkbox.name = `${this.elemId}-checkbox`;
checkbox.name = this.elemId;
checkbox.checked = this.isEnabled;
checkbox.addEventListener('change', (e) => this.checkboxChange(e));
const span = document.createElement('span');

View File

@@ -6,7 +6,7 @@
// repeat the background if wide-enhanced
.wide.enhanced & {
background-repeat: repeat-x;
background-image: url('../images/backgrounds/3-wide-enhanced.png');
}
}

View File

@@ -58,13 +58,15 @@
.like {
left: 380px;
display: block;
display: hidden;
&.heat-index {
color: c.$heat-index;
display: block;
}
&.wind-chill {
display: block;
color: c.$wind-chill;
}
}

View File

@@ -25,7 +25,6 @@
.forecast {
font-family: 'Star4000';
font-size: 24pt;
text-transform: uppercase;
@include u.text-shadow();
min-height: 280px;
line-height: 40px;

View File

@@ -5,6 +5,10 @@
#radar-html.weather-display {
background-image: url('../images/backgrounds/4.png');
.wide & {
background: url(../images/backgrounds/4-wide.png);
}
.header {
height: 83px;
@@ -122,8 +126,4 @@
position: relative;
}
}
}
.wide.radar #container {
background: url(../images/backgrounds/4-wide.png);
}

View File

@@ -1,10 +1,6 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
#regional-forecast-html.weather-display {
background-image: url('../images/backgrounds/5.png');
}
.weather-display .main.regional-forecast {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -68,10 +68,10 @@
</head>
<body <% if (query && query['settings-kiosk-checkbox'] === 'true' ) { %>class="kiosk" <% }%>>
<body <% if (query && (query['kiosk'] === true || query['settings-kiosk-checkbox'] === 'true' )) { %>class="kiosk" <% }%>>
<div id="divQuery">
<input id="txtLocation" type="text" value="" placeholder="ZIP Code or City, State" data-1p-ignore />
<input id="txtLocation" name="txtLocation" type="text" value="" placeholder="ZIP Code or City, State" data-1p-ignore />
<div class="buttons">
<button id="btnGetGps" type="button" title="Get GPS Location"><img src="images/nav/ic_gps_fixed_black_18dp_1x.png" class="light" />
<img src="images/nav/ic_gps_fixed_white_18dp_1x.png" class="dark" />