mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 17:19:30 -07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc77ba835c | ||
|
|
a440990696 | ||
|
|
fc4cbc1415 | ||
|
|
20ba3ddaac | ||
|
|
4205155f96 | ||
|
|
f4101f06cc | ||
|
|
4c3fcfc358 | ||
|
|
366f527aee | ||
|
|
797b4d32fa | ||
|
|
5092076050 | ||
|
|
5d891fb38f | ||
|
|
97e0fda709 | ||
|
|
7cf9dd6466 | ||
|
|
a44bd866ed | ||
|
|
21ef7f476a | ||
|
|
c5b715d631 | ||
|
|
dfd9facc79 | ||
|
|
5b926a358e | ||
|
|
ba1fbd7088 | ||
|
|
f82980ed09 | ||
|
|
af17b3c690 | ||
|
|
2c394c2e4a | ||
|
|
97f8eda236 | ||
|
|
deb107e4ec | ||
|
|
111f077e20 | ||
|
|
806ef91000 | ||
|
|
2a577aaea7 | ||
|
|
49296e53f0 |
2
dist/index.html
vendored
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/data.min.js
vendored
2
dist/resources/data.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.css
vendored
2
dist/resources/ws.min.css
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.js
vendored
2
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -71,6 +71,7 @@ gulp.task('compress_js_vendor', () => gulp.src(jsVendorSources)
|
|||||||
|
|
||||||
const mjsSources = [
|
const mjsSources = [
|
||||||
'server/scripts/modules/currentweatherscroll.mjs',
|
'server/scripts/modules/currentweatherscroll.mjs',
|
||||||
|
'server/scripts/modules/hazards.mjs',
|
||||||
'server/scripts/modules/currentweather.mjs',
|
'server/scripts/modules/currentweather.mjs',
|
||||||
'server/scripts/modules/almanac.mjs',
|
'server/scripts/modules/almanac.mjs',
|
||||||
'server/scripts/modules/icons.mjs',
|
'server/scripts/modules/icons.mjs',
|
||||||
@@ -164,4 +165,6 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
|||||||
},
|
},
|
||||||
}).promise());
|
}).promise());
|
||||||
|
|
||||||
module.exports = gulp.series(clean, gulp.parallel('build_js', 'compress_js_data', 'compress_js_vendor', 'copy_css', 'compress_html', 'copy_other_files'), gulp.parallel('upload', 'upload_images'), 'invalidate');
|
// upload_images could be in parallel with upload, but _images logs a lot and has little changes
|
||||||
|
// by running upload last the majority of the changes will be at the bottom of the log for easy viewing
|
||||||
|
module.exports = gulp.series(clean, gulp.parallel('build_js', 'compress_js_data', 'compress_js_vendor', 'copy_css', 'compress_html', 'copy_other_files'), 'upload_images', 'upload', 'invalidate');
|
||||||
|
|||||||
498
package-lock.json
generated
498
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "5.8.0",
|
"version": "5.9.4",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -44,10 +44,13 @@ const init = () => {
|
|||||||
if (document.fullscreenElement) updateFullScreenNavigate();
|
if (document.fullscreenElement) updateFullScreenNavigate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('txtAddress').addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
|
||||||
|
document.getElementById('btnGetLatLng').addEventListener('click', () => formSubmit());
|
||||||
|
|
||||||
document.addEventListener('keydown', documentKeydown);
|
document.addEventListener('keydown', documentKeydown);
|
||||||
document.addEventListener('touchmove', (e) => { if (fullScreenOverride) e.preventDefault(); });
|
document.addEventListener('touchmove', (e) => { if (fullScreenOverride) e.preventDefault(); });
|
||||||
|
|
||||||
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({
|
$('#txtAddress').devbridgeAutocomplete({
|
||||||
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
||||||
deferRequestBy: 300,
|
deferRequestBy: 300,
|
||||||
paramName: 'text',
|
paramName: 'text',
|
||||||
@@ -71,11 +74,11 @@ const init = () => {
|
|||||||
width: 490,
|
width: 490,
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#frmGetLatLng').on('submit', () => {
|
const formSubmit = () => {
|
||||||
const ac = $('#frmGetLatLng #txtAddress').devbridgeAutocomplete();
|
const ac = $('#txtAddress').devbridgeAutocomplete();
|
||||||
if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click');
|
if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click');
|
||||||
return false;
|
return false;
|
||||||
});
|
};
|
||||||
|
|
||||||
// Auto load the previous query
|
// Auto load the previous query
|
||||||
const query = localStorage.getItem('latLonQuery');
|
const query = localStorage.getItem('latLonQuery');
|
||||||
@@ -90,8 +93,8 @@ const init = () => {
|
|||||||
btnGetGpsClick();
|
btnGetGpsClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
const twcPlay = localStorage.getItem('play');
|
const play = localStorage.getItem('play');
|
||||||
if (twcPlay === null || twcPlay === 'true') postMessage('navButton', 'play');
|
if (play === null || play === 'true') postMessage('navButton', 'play');
|
||||||
|
|
||||||
document.getElementById('btnClearQuery').addEventListener('click', () => {
|
document.getElementById('btnClearQuery').addEventListener('click', () => {
|
||||||
document.getElementById('spanCity').innerHTML = '';
|
document.getElementById('spanCity').innerHTML = '';
|
||||||
@@ -187,7 +190,7 @@ const enterFullScreen = () => {
|
|||||||
|
|
||||||
// change hover text and image
|
// change hover text and image
|
||||||
const img = document.getElementById('ToggleFullScreen');
|
const img = document.getElementById('ToggleFullScreen');
|
||||||
img.src = 'images/nav/ic_fullscreen_exit_white_24dp_1x.png';
|
img.src = 'images/nav/ic_fullscreen_exit_white_24dp_2x.png';
|
||||||
img.title = 'Exit fullscreen';
|
img.title = 'Exit fullscreen';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,7 +214,7 @@ const exitFullscreen = () => {
|
|||||||
resize();
|
resize();
|
||||||
// change hover text and image
|
// change hover text and image
|
||||||
const img = document.getElementById('ToggleFullScreen');
|
const img = document.getElementById('ToggleFullScreen');
|
||||||
img.src = 'images/nav/ic_fullscreen_white_24dp_1x.png';
|
img.src = 'images/nav/ic_fullscreen_white_24dp_2x.png';
|
||||||
img.title = 'Enter fullscreen';
|
img.title = 'Enter fullscreen';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,37 +293,41 @@ const updateFullScreenNavigate = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const documentKeydown = (e) => {
|
const documentKeydown = (e) => {
|
||||||
const code = (e.keyCode || e.which);
|
const { key } = e;
|
||||||
|
|
||||||
// 200ms repeat
|
|
||||||
if ((Date.now() - documentKeydown.lastButton ?? 0) < 200) return false;
|
|
||||||
documentKeydown.lastButton = Date.now();
|
|
||||||
|
|
||||||
if (document.fullscreenElement || document.activeElement === document.body) {
|
if (document.fullscreenElement || document.activeElement === document.body) {
|
||||||
switch (code) {
|
switch (key) {
|
||||||
case 32: // Space
|
case ' ': // Space
|
||||||
|
// don't scroll
|
||||||
|
e.preventDefault();
|
||||||
btnNavigatePlayClick();
|
btnNavigatePlayClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 39: // Right Arrow
|
case 'ArrowRight':
|
||||||
case 34: // Page Down
|
case 'PageDown':
|
||||||
|
// don't scroll
|
||||||
|
e.preventDefault();
|
||||||
btnNavigateNextClick();
|
btnNavigateNextClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 37: // Left Arrow
|
case 'ArrowLeft':
|
||||||
case 33: // Page Up
|
case 'PageUp':
|
||||||
|
// don't scroll
|
||||||
|
e.preventDefault();
|
||||||
btnNavigatePreviousClick();
|
btnNavigatePreviousClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 36: // Home
|
case 'ArrowUp': // Home
|
||||||
|
e.preventDefault();
|
||||||
btnNavigateMenuClick();
|
btnNavigateMenuClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 48: // Restart
|
case '0': // "O" Restart
|
||||||
btnNavigateRefreshClick();
|
btnNavigateRefreshClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 70: // F
|
case 'F':
|
||||||
|
case 'f':
|
||||||
btnFullScreenClick();
|
btnFullScreenClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -368,7 +375,6 @@ const btnGetGpsClick = async () => {
|
|||||||
txtAddress.value = `${round2(latitude, 4)}, ${round2(longitude, 4)}`;
|
txtAddress.value = `${round2(latitude, 4)}, ${round2(longitude, 4)}`;
|
||||||
|
|
||||||
doRedirectToGeometry({ y: latitude, x: longitude }, (point) => {
|
doRedirectToGeometry({ y: latitude, x: longitude }, (point) => {
|
||||||
console.log(point);
|
|
||||||
const location = point.properties.relativeLocation.properties;
|
const location = point.properties.relativeLocation.properties;
|
||||||
// Save the query
|
// Save the query
|
||||||
const query = `${location.city}, ${location.state}`;
|
const query = `${location.city}, ${location.state}`;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Almanac extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
if (!super.getData(_weatherParameters)) return;
|
const superResponse = super.getData(_weatherParameters);
|
||||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// get sun/moon data
|
// get sun/moon data
|
||||||
@@ -33,11 +33,13 @@ class Almanac extends WeatherDisplay {
|
|||||||
sun,
|
sun,
|
||||||
moon,
|
moon,
|
||||||
};
|
};
|
||||||
// update status
|
|
||||||
this.setStatus(STATUS.loaded);
|
|
||||||
|
|
||||||
// share data
|
// share data
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
|
|
||||||
|
if (!superResponse) return;
|
||||||
|
|
||||||
|
// update status
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
calcSunMoonData(weatherParameters) {
|
calcSunMoonData(weatherParameters) {
|
||||||
@@ -171,7 +173,7 @@ const imageName = (type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
const display = new Almanac(8, 'almanac');
|
const display = new Almanac(9, 'almanac');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
export default display.getSun.bind(display);
|
export default display.getSun.bind(display);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
// test for data received
|
// test for data received
|
||||||
if (!observations) {
|
if (!observations) {
|
||||||
console.error('All current weather stations exhausted');
|
console.error('All current weather stations exhausted');
|
||||||
if (this.enabled) this.setStatus(STATUS.failed);
|
if (this.isEnabled) this.setStatus(STATUS.failed);
|
||||||
// send failed to subscribers
|
// send failed to subscribers
|
||||||
this.getDataCallback(undefined);
|
this.getDataCallback(undefined);
|
||||||
return;
|
return;
|
||||||
@@ -204,7 +204,7 @@ const shortConditions = (_condition) => {
|
|||||||
return condition;
|
return condition;
|
||||||
};
|
};
|
||||||
|
|
||||||
const display = new CurrentWeather(0, 'current-weather');
|
const display = new CurrentWeather(1, 'current-weather');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
export default display.getCurrentWeather.bind(display);
|
export default display.getCurrentWeather.bind(display);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const incrementInterval = () => {
|
|||||||
|
|
||||||
const drawScreen = async () => {
|
const drawScreen = async () => {
|
||||||
// get the conditions
|
// get the conditions
|
||||||
const data = await getCurrentWeather(() => this.stillWaiting());
|
const data = await getCurrentWeather();
|
||||||
|
|
||||||
// nothing to do if there's no data yet
|
// nothing to do if there's no data yet
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|||||||
@@ -163,4 +163,4 @@ const shortenExtendedForecastText = (long) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new ExtendedForecast(7, 'extended-forecast'));
|
registerDisplay(new ExtendedForecast(8, 'extended-forecast'));
|
||||||
|
|||||||
140
server/scripts/modules/hazards.mjs
Normal file
140
server/scripts/modules/hazards.mjs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// hourly forecast list
|
||||||
|
|
||||||
|
import STATUS from './status.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
|
import { registerDisplay } from './navigation.mjs';
|
||||||
|
|
||||||
|
const hazardLevels = {
|
||||||
|
Extreme: 10,
|
||||||
|
Severe: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Hazards extends WeatherDisplay {
|
||||||
|
constructor(navId, elemId, defaultActive) {
|
||||||
|
// special height and width for scrolling
|
||||||
|
super(navId, elemId, 'Hazards', defaultActive);
|
||||||
|
this.showOnProgress = false;
|
||||||
|
|
||||||
|
// 0 screens skips this during "play"
|
||||||
|
this.timing.totalScreens = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData(weatherParameters) {
|
||||||
|
// super checks for enabled
|
||||||
|
const superResult = super.getData(weatherParameters);
|
||||||
|
|
||||||
|
const alert = this.checkbox.querySelector('.alert');
|
||||||
|
alert.classList.remove('show');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// get the forecast
|
||||||
|
const url = new URL('https://api.weather.gov/alerts/active');
|
||||||
|
url.searchParams.append('point', `${this.weatherParameters.latitude},${this.weatherParameters.longitude}`);
|
||||||
|
url.searchParams.append('limit', 5);
|
||||||
|
const alerts = await json(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
|
||||||
|
const unsortedAlerts = alerts.features ?? [];
|
||||||
|
const sortedAlerts = unsortedAlerts.sort((a, b) => (hazardLevels[b.properties.severity] ?? 0) - (hazardLevels[a.properties.severity] ?? 0));
|
||||||
|
const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown');
|
||||||
|
this.data = filteredAlerts;
|
||||||
|
|
||||||
|
// show alert indicator
|
||||||
|
if (this.data.length > 0) alert.classList.add('show');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Get hourly forecast failed');
|
||||||
|
console.error(e.status, e.responseJSON);
|
||||||
|
if (this.isEnabled) this.setStatus(STATUS.failed);
|
||||||
|
// return undefined to other subscribers
|
||||||
|
this.getDataCallback(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getDataCallback();
|
||||||
|
|
||||||
|
if (!superResult) {
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.drawLongCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
async drawLongCanvas() {
|
||||||
|
// get the list element and populate
|
||||||
|
const list = this.elem.querySelector('.hazard-lines');
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
const lines = this.data.map((data) => {
|
||||||
|
const fillValues = {};
|
||||||
|
// text
|
||||||
|
fillValues['hazard-text'] = `${data.properties.event}<br/><br/>${data.properties.description.replace('\n', '<br/><br/>')}`;
|
||||||
|
|
||||||
|
return this.fillTemplate('hazard', fillValues);
|
||||||
|
});
|
||||||
|
|
||||||
|
list.append(...lines);
|
||||||
|
|
||||||
|
// no alerts, skip this display by setting timing to zero
|
||||||
|
if (lines.length === 0) {
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
|
this.timing.totalScreens = 0;
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timing
|
||||||
|
// set up the timing
|
||||||
|
this.timing.baseDelay = 20;
|
||||||
|
// 24 hours = 6 pages
|
||||||
|
const pages = Math.max(Math.ceil(list.scrollHeight / 400) - 3, 1);
|
||||||
|
const timingStep = 400;
|
||||||
|
this.timing.delay = [150 + timingStep];
|
||||||
|
// add additional pages
|
||||||
|
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||||
|
// add the final 3 second delay
|
||||||
|
this.timing.delay.push(250);
|
||||||
|
this.calcNavTiming();
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCanvas() {
|
||||||
|
super.drawCanvas();
|
||||||
|
this.finishDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
showCanvas() {
|
||||||
|
// special to hourly to draw the remainder of the canvas
|
||||||
|
this.drawCanvas();
|
||||||
|
super.showCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
// screen index change callback just runs the base count callback
|
||||||
|
screenIndexChange() {
|
||||||
|
this.baseCountChange(this.navBaseCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// base count change callback
|
||||||
|
baseCountChange(count) {
|
||||||
|
// calculate scroll offset and don't go past end
|
||||||
|
let offsetY = Math.min(this.elem.querySelector('.hazard-lines').getBoundingClientRect().height - 390, (count - 150));
|
||||||
|
|
||||||
|
// don't let offset go negative
|
||||||
|
if (offsetY < 0) offsetY = 0;
|
||||||
|
|
||||||
|
// copy the scrolled portion of the canvas
|
||||||
|
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make data available outside this class
|
||||||
|
// promise allows for data to be requested before it is available
|
||||||
|
async getCurrentData(stillWaiting) {
|
||||||
|
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (this.data) resolve(this.data);
|
||||||
|
// data not available, put it into the data callback queue
|
||||||
|
this.getDataCallbacks.push(() => resolve(this.data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register display
|
||||||
|
registerDisplay(new Hazards(0, 'hazards', true));
|
||||||
@@ -146,4 +146,4 @@ const drawPath = (path, ctx, options) => {
|
|||||||
const formatTime = (time) => time.toFormat('ha').slice(0, -1);
|
const formatTime = (time) => time.toFormat('ha').slice(0, -1);
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new HourlyGraph(3, 'hourly-graph'));
|
registerDisplay(new HourlyGraph(4, 'hourly-graph'));
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Hourly extends WeatherDisplay {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Get hourly forecast failed');
|
console.error('Get hourly forecast failed');
|
||||||
console.error(e.status, e.responseJSON);
|
console.error(e.status, e.responseJSON);
|
||||||
if (this.enabled) this.setStatus(STATUS.failed);
|
if (this.isEnabled) this.setStatus(STATUS.failed);
|
||||||
// return undefined to other subscribers
|
// return undefined to other subscribers
|
||||||
this.getDataCallback(undefined);
|
this.getDataCallback(undefined);
|
||||||
return;
|
return;
|
||||||
@@ -191,7 +191,7 @@ const expand = (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
const display = new Hourly(2, 'hourly', false);
|
const display = new Hourly(3, 'hourly', false);
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
export default display.getCurrentData.bind(display);
|
export default display.getCurrentData.bind(display);
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
|||||||
return addPath('Clear-Wind-1994.gif');
|
return addPath('Clear-Wind-1994.gif');
|
||||||
|
|
||||||
case 'blizzard':
|
case 'blizzard':
|
||||||
|
case 'blizzard-n':
|
||||||
return addPath('Blowing Snow.gif');
|
return addPath('Blowing Snow.gif');
|
||||||
|
|
||||||
case 'cold':
|
case 'cold':
|
||||||
@@ -268,6 +269,7 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
|||||||
return addPath('CC_Windy.gif');
|
return addPath('CC_Windy.gif');
|
||||||
|
|
||||||
case 'blizzard':
|
case 'blizzard':
|
||||||
|
case 'blizzard-n':
|
||||||
return addPath('Blowing-Snow.gif');
|
return addPath('Blowing-Snow.gif');
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -120,4 +120,4 @@ const shortenCurrentConditions = (_condition) => {
|
|||||||
return condition;
|
return condition;
|
||||||
};
|
};
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new LatestObservations(1, 'latest-observations'));
|
registerDisplay(new LatestObservations(2, 'latest-observations'));
|
||||||
|
|||||||
@@ -92,4 +92,4 @@ const parse = (forecast) => forecast.properties.periods.slice(0, 6).map((text) =
|
|||||||
Text: text.detailedForecast,
|
Text: text.detailedForecast,
|
||||||
}));
|
}));
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new LocalForecast(6, 'local-forecast'));
|
registerDisplay(new LocalForecast(7, 'local-forecast'));
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ const init = async () => {
|
|||||||
resize();
|
resize();
|
||||||
|
|
||||||
// auto refresh
|
// auto refresh
|
||||||
const TwcAutoRefresh = localStorage.getItem('TwcAutoRefresh');
|
const autoRefresh = localStorage.getItem('autoRefresh');
|
||||||
if (!TwcAutoRefresh || TwcAutoRefresh === 'true') {
|
if (!autoRefresh || autoRefresh === 'true') {
|
||||||
document.getElementById('chkAutoRefresh').checked = true;
|
document.getElementById('chkAutoRefresh').checked = true;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('chkAutoRefresh').checked = false;
|
document.getElementById('chkAutoRefresh').checked = false;
|
||||||
@@ -65,6 +65,7 @@ const getWeather = async (latLon, haveDataCallback) => {
|
|||||||
if (StationId in StationInfo) {
|
if (StationId in StationInfo) {
|
||||||
city = StationInfo[StationId].city;
|
city = StationInfo[StationId].city;
|
||||||
[city] = city.split('/');
|
[city] = city.split('/');
|
||||||
|
city = city.replace(/\s+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate the weather parameters
|
// populate the weather parameters
|
||||||
@@ -76,7 +77,7 @@ const getWeather = async (latLon, haveDataCallback) => {
|
|||||||
weatherParameters.weatherOffice = point.properties.cwa;
|
weatherParameters.weatherOffice = point.properties.cwa;
|
||||||
weatherParameters.city = city;
|
weatherParameters.city = city;
|
||||||
weatherParameters.state = point.properties.relativeLocation.properties.state;
|
weatherParameters.state = point.properties.relativeLocation.properties.state;
|
||||||
weatherParameters.timeZone = point.properties.relativeLocation.properties.timeZone;
|
weatherParameters.timeZone = point.properties.timeZone;
|
||||||
weatherParameters.forecast = point.properties.forecast;
|
weatherParameters.forecast = point.properties.forecast;
|
||||||
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
||||||
weatherParameters.stations = stations.features;
|
weatherParameters.stations = stations.features;
|
||||||
@@ -102,8 +103,18 @@ const updateStatus = (value) => {
|
|||||||
if (!progress) return;
|
if (!progress) return;
|
||||||
progress.drawCanvas(displays, countLoadedDisplays());
|
progress.drawCanvas(displays, countLoadedDisplays());
|
||||||
|
|
||||||
|
// first display is hazards and it must load before evaluating the first display
|
||||||
|
if (displays[0].status === STATUS.loading) return;
|
||||||
|
|
||||||
// calculate first enabled display
|
// calculate first enabled display
|
||||||
const firstDisplayIndex = displays.findIndex((display) => display.enabled);
|
const firstDisplayIndex = displays.findIndex((display) => display.enabled && display.timing.totalScreens > 0);
|
||||||
|
|
||||||
|
// value.id = 0 is hazards, if they fail to load hot-wire a new value.id to the current display to see if it needs to be loaded
|
||||||
|
// typically this plays out as current conditions loads, then hazards fails.
|
||||||
|
if (value.id === 0 && (value.status === STATUS.failed || value.status === STATUS.retrying)) {
|
||||||
|
value.id = firstDisplayIndex;
|
||||||
|
value.status = displays[firstDisplayIndex].status;
|
||||||
|
}
|
||||||
|
|
||||||
// if this is the first display and we're playing, load it up so it starts playing
|
// if this is the first display and we're playing, load it up so it starts playing
|
||||||
if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) {
|
if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) {
|
||||||
@@ -164,13 +175,14 @@ const navTo = (direction) => {
|
|||||||
let firstDisplay;
|
let firstDisplay;
|
||||||
let displayCount = 0;
|
let displayCount = 0;
|
||||||
do {
|
do {
|
||||||
if (displays[displayCount].status === STATUS.loaded) firstDisplay = displays[displayCount];
|
if (displays[displayCount].status === STATUS.loaded && displays[displayCount].timing.totalScreens > 0) firstDisplay = displays[displayCount];
|
||||||
displayCount += 1;
|
displayCount += 1;
|
||||||
} while (!firstDisplay && displayCount < displays.length);
|
} while (!firstDisplay && displayCount < displays.length);
|
||||||
|
|
||||||
if (!firstDisplay) return;
|
if (!firstDisplay) return;
|
||||||
|
|
||||||
firstDisplay.navNext(msg.command.firstFrame);
|
firstDisplay.navNext(msg.command.firstFrame);
|
||||||
|
firstDisplay.showCanvas();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (direction === msg.command.nextFrame) currentDisplay().navNext();
|
if (direction === msg.command.nextFrame) currentDisplay().navNext();
|
||||||
@@ -185,7 +197,7 @@ const loadDisplay = (direction) => {
|
|||||||
for (let i = 0; i < totalDisplays; i += 1) {
|
for (let i = 0; i < totalDisplays; i += 1) {
|
||||||
// convert form simple 0-10 to start at current display index +/-1 and wrap
|
// convert form simple 0-10 to start at current display index +/-1 and wrap
|
||||||
idx = wrap(curIdx + (i + 1) * direction, totalDisplays);
|
idx = wrap(curIdx + (i + 1) * direction, totalDisplays);
|
||||||
if (displays[idx].status === STATUS.loaded) break;
|
if (displays[idx].status === STATUS.loaded && displays[idx].timing.totalScreens > 0) break;
|
||||||
}
|
}
|
||||||
// if new display index is less than current display a wrap occurred, test for reload timeout
|
// if new display index is less than current display a wrap occurred, test for reload timeout
|
||||||
if (idx <= curIdx) {
|
if (idx <= curIdx) {
|
||||||
@@ -201,7 +213,7 @@ const loadDisplay = (direction) => {
|
|||||||
|
|
||||||
// get the current display index or value
|
// get the current display index or value
|
||||||
const currentDisplayIndex = () => {
|
const currentDisplayIndex = () => {
|
||||||
const index = displays.findIndex((display) => display.isActive());
|
const index = displays.findIndex((display) => display.active);
|
||||||
return index;
|
return index;
|
||||||
};
|
};
|
||||||
const currentDisplay = () => displays[currentDisplayIndex()];
|
const currentDisplay = () => displays[currentDisplayIndex()];
|
||||||
@@ -209,16 +221,16 @@ const currentDisplay = () => displays[currentDisplayIndex()];
|
|||||||
const setPlaying = (newValue) => {
|
const setPlaying = (newValue) => {
|
||||||
playing = newValue;
|
playing = newValue;
|
||||||
const playButton = document.getElementById('NavigatePlay');
|
const playButton = document.getElementById('NavigatePlay');
|
||||||
localStorage.setItem('TwcPlay', playing);
|
localStorage.setItem('play', playing);
|
||||||
|
|
||||||
if (playing) {
|
if (playing) {
|
||||||
noSleep(true);
|
noSleep(true);
|
||||||
playButton.title = 'Pause';
|
playButton.title = 'Pause';
|
||||||
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
|
playButton.src = 'images/nav/ic_pause_white_24dp_2x.png';
|
||||||
} else {
|
} else {
|
||||||
noSleep(false);
|
noSleep(false);
|
||||||
playButton.title = 'Play';
|
playButton.title = 'Play';
|
||||||
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
|
playButton.src = 'images/nav/ic_play_arrow_white_24dp_2x.png';
|
||||||
}
|
}
|
||||||
// if we're playing and on the progress screen jump to the next screen
|
// if we're playing and on the progress screen jump to the next screen
|
||||||
if (!progress) return;
|
if (!progress) return;
|
||||||
@@ -260,9 +272,8 @@ const getDisplay = (index) => displays[index];
|
|||||||
|
|
||||||
// resize the container on a page resize
|
// resize the container on a page resize
|
||||||
const resize = () => {
|
const resize = () => {
|
||||||
const marginOffset = (document.fullscreenElement) ? 0 : 16;
|
const widthZoomPercent = (document.getElementById('divTwcBottom').getBoundingClientRect().width) / 640;
|
||||||
const widthZoomPercent = (window.innerWidth - marginOffset) / 640;
|
const heightZoomPercent = (window.innerHeight) / 480;
|
||||||
const heightZoomPercent = (window.innerHeight - marginOffset) / 480;
|
|
||||||
|
|
||||||
const scale = Math.min(widthZoomPercent, heightZoomPercent);
|
const scale = Math.min(widthZoomPercent, heightZoomPercent);
|
||||||
if (scale < 1.0 || document.fullscreenElement) {
|
if (scale < 1.0 || document.fullscreenElement) {
|
||||||
@@ -319,7 +330,7 @@ const autoRefreshChange = (e) => {
|
|||||||
stopAutoRefreshTimer();
|
stopAutoRefreshTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('TwcAutoRefresh', checked);
|
localStorage.setItem('autoRefresh', checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AssignLastUpdate = (date) => {
|
const AssignLastUpdate = (date) => {
|
||||||
@@ -374,7 +385,7 @@ const stopAutoRefreshTimer = () => {
|
|||||||
|
|
||||||
const refreshCheck = () => {
|
const refreshCheck = () => {
|
||||||
// Time has elapsed.
|
// Time has elapsed.
|
||||||
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS) {
|
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && isPlaying()) {
|
||||||
loadTwcData();
|
loadTwcData();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -389,6 +400,8 @@ const registerRefreshData = (callback) => {
|
|||||||
loadTwcData.callback = callback;
|
loadTwcData.callback = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const timeZone = () => weatherParameters.timeZone;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
updateStatus,
|
updateStatus,
|
||||||
displayNavMessage,
|
displayNavMessage,
|
||||||
@@ -404,4 +417,5 @@ export {
|
|||||||
latLonReceived,
|
latLonReceived,
|
||||||
stopAutoRefreshTimer,
|
stopAutoRefreshTimer,
|
||||||
registerRefreshData,
|
registerRefreshData,
|
||||||
|
timeZone,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class Progress extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas(displays, loadedCount) {
|
async drawCanvas(displays, loadedCount) {
|
||||||
|
if (!this.elem) return;
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
|
|
||||||
// get the progress bar cover (makes percentage)
|
// get the progress bar cover (makes percentage)
|
||||||
@@ -34,6 +35,7 @@ class Progress extends WeatherDisplay {
|
|||||||
// if no displays provided just draw the backgrounds (above)
|
// if no displays provided just draw the backgrounds (above)
|
||||||
if (!displays) return;
|
if (!displays) return;
|
||||||
const lines = displays.map((display, index) => {
|
const lines = displays.map((display, index) => {
|
||||||
|
if (display.showOnProgress === false) return false;
|
||||||
const fill = {};
|
const fill = {};
|
||||||
|
|
||||||
fill.name = display.name;
|
fill.name = display.name;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { loadImg } from './utils/image.mjs';
|
|||||||
import { text } from './utils/fetch.mjs';
|
import { text } from './utils/fetch.mjs';
|
||||||
import { rewriteUrl } from './utils/cors.mjs';
|
import { rewriteUrl } from './utils/cors.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
import { registerDisplay } from './navigation.mjs';
|
import { registerDisplay, timeZone } from './navigation.mjs';
|
||||||
import * as utils from './radar-utils.mjs';
|
import * as utils from './radar-utils.mjs';
|
||||||
|
|
||||||
class Radar extends WeatherDisplay {
|
class Radar extends WeatherDisplay {
|
||||||
@@ -159,7 +159,7 @@ class Radar extends WeatherDisplay {
|
|||||||
zone: 'UTC',
|
zone: 'UTC',
|
||||||
}).setZone();
|
}).setZone();
|
||||||
} else {
|
} else {
|
||||||
time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone();
|
time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone(timeZone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign to an html image element
|
// assign to an html image element
|
||||||
@@ -222,4 +222,4 @@ class Radar extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new Radar(9, 'radar'));
|
registerDisplay(new Radar(10, 'radar'));
|
||||||
|
|||||||
@@ -204,4 +204,4 @@ const getAndFormatPoint = async (lat, lon) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new RegionalForecast(5, 'regional-forecast'));
|
registerDisplay(new RegionalForecast(6, 'regional-forecast'));
|
||||||
|
|||||||
@@ -158,4 +158,4 @@ const getTravelCitiesDayName = (cities) => cities.reduce((dayName, city) => {
|
|||||||
}, '');
|
}, '');
|
||||||
|
|
||||||
// register display, not active by default
|
// register display, not active by default
|
||||||
registerDisplay(new TravelForecast(4, 'travel', false));
|
registerDisplay(new TravelForecast(5, 'travel', false));
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import STATUS, { calcStatusClass, statusClasses } from './status.mjs';
|
import STATUS, { calcStatusClass, statusClasses } from './status.mjs';
|
||||||
import { DateTime } from '../vendor/auto/luxon.mjs';
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
import { elemForEach } from './utils/elem.mjs';
|
|
||||||
import {
|
import {
|
||||||
msg, displayNavMessage, isPlaying, updateStatus,
|
msg, displayNavMessage, isPlaying, updateStatus, timeZone,
|
||||||
} from './navigation.mjs';
|
} from './navigation.mjs';
|
||||||
|
|
||||||
class WeatherDisplay {
|
class WeatherDisplay {
|
||||||
@@ -12,7 +11,6 @@ class WeatherDisplay {
|
|||||||
// navId is used in messaging and sort order
|
// navId is used in messaging and sort order
|
||||||
this.navId = navId;
|
this.navId = navId;
|
||||||
this.elemId = undefined;
|
this.elemId = undefined;
|
||||||
this.gifs = [];
|
|
||||||
this.data = undefined;
|
this.data = undefined;
|
||||||
this.loadingStatus = STATUS.loading;
|
this.loadingStatus = STATUS.loading;
|
||||||
this.name = name ?? elemId;
|
this.name = name ?? elemId;
|
||||||
@@ -21,6 +19,7 @@ class WeatherDisplay {
|
|||||||
this.defaultEnabled = defaultEnabled;
|
this.defaultEnabled = defaultEnabled;
|
||||||
this.okToDrawCurrentConditions = true;
|
this.okToDrawCurrentConditions = true;
|
||||||
this.okToDrawCurrentDateTime = true;
|
this.okToDrawCurrentDateTime = true;
|
||||||
|
this.showOnProgress = true;
|
||||||
|
|
||||||
// default navigation timing
|
// default navigation timing
|
||||||
this.timing = {
|
this.timing = {
|
||||||
@@ -34,7 +33,7 @@ class WeatherDisplay {
|
|||||||
// store elemId once
|
// store elemId once
|
||||||
this.storeElemId(elemId);
|
this.storeElemId(elemId);
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.isEnabled) {
|
||||||
this.setStatus(STATUS.loading);
|
this.setStatus(STATUS.loading);
|
||||||
} else {
|
} else {
|
||||||
this.setStatus(STATUS.disabled);
|
this.setStatus(STATUS.disabled);
|
||||||
@@ -55,13 +54,13 @@ class WeatherDisplay {
|
|||||||
let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`);
|
let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`);
|
||||||
if (savedStatus === null) savedStatus = defaultEnabled;
|
if (savedStatus === null) savedStatus = defaultEnabled;
|
||||||
if (savedStatus === 'true' || savedStatus === true) {
|
if (savedStatus === 'true' || savedStatus === true) {
|
||||||
this.enabled = true;
|
this.isEnabled = true;
|
||||||
} else {
|
} else {
|
||||||
this.enabled = false;
|
this.isEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh (or initially store the state of the checkbox)
|
// refresh (or initially store the state of the checkbox)
|
||||||
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.enabled);
|
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.isEnabled);
|
||||||
|
|
||||||
// create a checkbox in the selected displays area
|
// create a checkbox in the selected displays area
|
||||||
const label = document.createElement('label');
|
const label = document.createElement('label');
|
||||||
@@ -72,12 +71,15 @@ class WeatherDisplay {
|
|||||||
checkbox.value = true;
|
checkbox.value = true;
|
||||||
checkbox.id = `${this.elemId}-checkbox`;
|
checkbox.id = `${this.elemId}-checkbox`;
|
||||||
checkbox.name = `${this.elemId}-checkbox`;
|
checkbox.name = `${this.elemId}-checkbox`;
|
||||||
checkbox.checked = this.enabled;
|
checkbox.checked = this.isEnabled;
|
||||||
checkbox.addEventListener('change', (e) => this.checkboxChange(e));
|
checkbox.addEventListener('change', (e) => this.checkboxChange(e));
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.innerHTML = this.name;
|
span.innerHTML = this.name;
|
||||||
|
const alert = document.createElement('span');
|
||||||
|
alert.innerHTML = '!!!';
|
||||||
|
alert.classList.add('alert');
|
||||||
|
|
||||||
label.append(checkbox, span);
|
label.append(checkbox, span, alert);
|
||||||
|
|
||||||
this.checkbox = label;
|
this.checkbox = label;
|
||||||
|
|
||||||
@@ -86,9 +88,9 @@ class WeatherDisplay {
|
|||||||
|
|
||||||
checkboxChange(e) {
|
checkboxChange(e) {
|
||||||
// update the state
|
// update the state
|
||||||
this.enabled = e.target.checked;
|
this.isEnabled = e.target.checked;
|
||||||
// store the value for the next load
|
// store the value for the next load
|
||||||
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.enabled);
|
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.isEnabled);
|
||||||
// calling get data will update the status and actually get the data if we're set to enabled
|
// calling get data will update the status and actually get the data if we're set to enabled
|
||||||
this.getData();
|
this.getData();
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,7 @@ class WeatherDisplay {
|
|||||||
if (weatherParameters) this.weatherParameters = weatherParameters;
|
if (weatherParameters) this.weatherParameters = weatherParameters;
|
||||||
|
|
||||||
// set status
|
// set status
|
||||||
if (this.enabled) {
|
if (this.isEnabled) {
|
||||||
this.setStatus(STATUS.loading);
|
this.setStatus(STATUS.loading);
|
||||||
} else {
|
} else {
|
||||||
this.setStatus(STATUS.disabled);
|
this.setStatus(STATUS.disabled);
|
||||||
@@ -168,22 +170,24 @@ class WeatherDisplay {
|
|||||||
|
|
||||||
drawCurrentDateTime() {
|
drawCurrentDateTime() {
|
||||||
// only draw if canvas is active to conserve battery
|
// only draw if canvas is active to conserve battery
|
||||||
if (!this.isActive()) return;
|
if (!this.active) return;
|
||||||
// Get the current date and time.
|
// Get the current date and time.
|
||||||
const now = DateTime.local();
|
const now = DateTime.local().setZone(timeZone());
|
||||||
|
|
||||||
// time = "11:35:08 PM";
|
// time = "11:35:08 PM";
|
||||||
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
|
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
|
||||||
|
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
|
||||||
|
|
||||||
if (this.lastTime !== time) {
|
const dateElem = this.elem.querySelector('.date-time.date');
|
||||||
elemForEach('.date-time.time', (elem) => { elem.innerHTML = time.toUpperCase(); });
|
const timeElem = this.elem.querySelector('.date-time.time');
|
||||||
|
|
||||||
|
if (timeElem && this.lastTime !== time) {
|
||||||
|
timeElem.innerHTML = time.toUpperCase();
|
||||||
}
|
}
|
||||||
this.lastTime = time;
|
this.lastTime = time;
|
||||||
|
|
||||||
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
|
if (dateElem && this.lastDate !== date) {
|
||||||
|
dateElem.innerHTML = date.toUpperCase();
|
||||||
if (this.lastDate !== date) {
|
|
||||||
elemForEach('.date-time.date', (elem) => { elem.innerHTML = date.toUpperCase(); });
|
|
||||||
}
|
}
|
||||||
this.lastDate = date;
|
this.lastDate = date;
|
||||||
}
|
}
|
||||||
@@ -205,12 +209,12 @@ class WeatherDisplay {
|
|||||||
this.elem.classList.remove('show');
|
this.elem.classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive() {
|
get active() {
|
||||||
return this.elem.offsetHeight !== 0;
|
return this.elem.offsetHeight !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
get enabled() {
|
||||||
return this.enabled;
|
return this.isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// navigation timings
|
// navigation timings
|
||||||
@@ -223,7 +227,7 @@ class WeatherDisplay {
|
|||||||
// if the array forms are used totalScreens is overwritten by the size of the array
|
// if the array forms are used totalScreens is overwritten by the size of the array
|
||||||
navBaseTime() {
|
navBaseTime() {
|
||||||
// see if play is active and screen is active
|
// see if play is active and screen is active
|
||||||
if (!isPlaying() || !this.isActive()) return;
|
if (!isPlaying() || !this.active) return;
|
||||||
// increment the base count
|
// increment the base count
|
||||||
this.navBaseCount += 1;
|
this.navBaseCount += 1;
|
||||||
|
|
||||||
@@ -258,10 +262,10 @@ class WeatherDisplay {
|
|||||||
// call the appropriate screen index change method
|
// call the appropriate screen index change method
|
||||||
if (!this.screenIndexChange) {
|
if (!this.screenIndexChange) {
|
||||||
await this.drawCanvas();
|
await this.drawCanvas();
|
||||||
this.showCanvas();
|
|
||||||
} else {
|
} else {
|
||||||
this.screenIndexChange(this.screenIndex);
|
this.screenIndexChange(this.screenIndex);
|
||||||
}
|
}
|
||||||
|
this.showCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
// take the three timing formats shown above and break them into arrays for consistent usage in navigation functions
|
// take the three timing formats shown above and break them into arrays for consistent usage in navigation functions
|
||||||
@@ -341,6 +345,7 @@ class WeatherDisplay {
|
|||||||
screenIndexFromBaseCount() {
|
screenIndexFromBaseCount() {
|
||||||
// test for timing enabled
|
// test for timing enabled
|
||||||
if (!this.timing) return 0;
|
if (!this.timing) return 0;
|
||||||
|
if (this.timing.totalScreens === 0) return false;
|
||||||
// find the first timing in the timing array that is greater than the base count
|
// find the first timing in the timing array that is greater than the base count
|
||||||
if (this.timing && !this.timing.fullDelay) this.calcNavTiming();
|
if (this.timing && !this.timing.fullDelay) this.calcNavTiming();
|
||||||
const timingIndex = this.timing.fullDelay.findIndex((delay) => delay > this.navBaseCount);
|
const timingIndex = this.timing.fullDelay.findIndex((delay) => delay > this.navBaseCount);
|
||||||
@@ -412,7 +417,7 @@ class WeatherDisplay {
|
|||||||
|
|
||||||
// still waiting for data (retries triggered)
|
// still waiting for data (retries triggered)
|
||||||
stillWaiting() {
|
stillWaiting() {
|
||||||
if (this.enabled) this.setStatus(STATUS.retrying);
|
if (this.isEnabled) this.setStatus(STATUS.retrying);
|
||||||
// handle still waiting callbacks
|
// handle still waiting callbacks
|
||||||
this.stillWaitingCallbacks.forEach((callback) => callback());
|
this.stillWaitingCallbacks.forEach((callback) => callback());
|
||||||
this.stillWaitingCallbacks = [];
|
this.stillWaitingCallbacks = [];
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
27
server/styles/scss/_hazards.scss
Normal file
27
server/styles/scss/_hazards.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .main.hazards {
|
||||||
|
&.main {
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.hazard-lines {
|
||||||
|
min-height: 400px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
background-color: rgb(112, 35, 35);
|
||||||
|
|
||||||
|
.hazard {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
color: white;
|
||||||
|
@include u.text-shadow(0px);
|
||||||
|
position: relative;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 110px;
|
||||||
|
margin-left: 80px;
|
||||||
|
margin-right: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,72 +20,83 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
#divQuery {
|
||||||
button {
|
max-width: 640px;
|
||||||
font-family: "Star4000";
|
|
||||||
}
|
|
||||||
|
|
||||||
#imgGetGps {
|
.buttons {
|
||||||
height: 13px;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
width: 150px;
|
||||||
}
|
text-align: right;
|
||||||
|
|
||||||
#txtAddress {
|
#imgGetGps {
|
||||||
width: 490px;
|
height: 13px;
|
||||||
font-size: 16pt;
|
vertical-align: middle;
|
||||||
max-width: calc(100% - 8px);
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
background-color: #000000;
|
|
||||||
color: white;
|
|
||||||
border: 1px solid darkgray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#btnGetGps,
|
|
||||||
#btnGetLatLng,
|
|
||||||
#btnClearQuery {
|
|
||||||
font-size: 16pt;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
background-color: #000000;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
border: 1px solid darkgray;
|
|
||||||
}
|
|
||||||
|
|
||||||
#btnGetGps {
|
|
||||||
img {
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
button {
|
||||||
|
font-size: 16pt;
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
display: none;
|
background-color: #000000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
border: 1px solid darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnGetGps {
|
||||||
|
img {
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
input,
|
||||||
background-color: black;
|
button {
|
||||||
|
font-family: "Star4000";
|
||||||
|
}
|
||||||
|
|
||||||
|
#txtAddress {
|
||||||
|
width: calc(100% - 170px);
|
||||||
|
max-width: 490px;
|
||||||
|
font-size: 16pt;
|
||||||
|
min-width: 200px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
background-color: white;
|
background-color: #000000;
|
||||||
}
|
color: white;
|
||||||
|
border: 1px solid darkgray;
|
||||||
img {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
.autocomplete-suggestions {
|
.autocomplete-suggestions {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
@@ -93,19 +104,19 @@ button {
|
|||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-suggestion {
|
.autocomplete-suggestion {
|
||||||
/*padding: 2px 5px;*/
|
/*padding: 2px 5px;*/
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-selected {
|
.autocomplete-selected {
|
||||||
background-color: #0000ff;
|
background-color: #0000ff;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#divTwc {
|
#divTwc {
|
||||||
@@ -213,8 +224,7 @@ button {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#imgPause1x,
|
#imgPause1x {
|
||||||
#imgPause2x {
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
@@ -270,9 +280,11 @@ button {
|
|||||||
|
|
||||||
#container {
|
#container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 640px;
|
||||||
height: 100%;
|
height: 480px;
|
||||||
|
overflow: hidden;
|
||||||
background-image: url(../images/BackGround1_1.png);
|
background-image: url(../images/BackGround1_1.png);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#divTwc:fullscreen #container {
|
#divTwc:fullscreen #container {
|
||||||
@@ -320,15 +332,48 @@ button {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.retrying {
|
||||||
|
color: hsl(60, 100%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.press-here {
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
color: hsl(0, 100%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
color: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: inline;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#divTwcBottom img {
|
#divTwcBottom img {
|
||||||
zoom: 150%;
|
zoom: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#divTwc:fullscreen {
|
#divTwc:fullscreen {
|
||||||
|
|||||||
@@ -96,6 +96,10 @@
|
|||||||
width: 640px;
|
width: 640px;
|
||||||
height: 310px;
|
height: 310px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.no-header {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-box {
|
&.has-box {
|
||||||
|
|||||||
@@ -10,4 +10,5 @@
|
|||||||
@import 'progress';
|
@import 'progress';
|
||||||
@import 'radar';
|
@import 'radar';
|
||||||
@import 'regional-forecast';
|
@import 'regional-forecast';
|
||||||
@import 'almanac';
|
@import 'almanac';
|
||||||
|
@import 'hazards';
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
<script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script>
|
<script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script>
|
<script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
||||||
|
<script type="module" src="scripts/modules/hazards.mjs"></script>
|
||||||
<script type="module" src="scripts/modules/currentweatherscroll.mjs"></script>
|
<script type="module" src="scripts/modules/currentweatherscroll.mjs"></script>
|
||||||
<script type="module" src="scripts/modules/currentweather.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/almanac.mjs"></script>
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
<script type="module" src="scripts/modules/regionalforecast.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/modules/travelforecast.mjs"></script>
|
||||||
<script type="module" src="scripts/modules/progress.mjs"></script>
|
<script type="module" src="scripts/modules/progress.mjs"></script>
|
||||||
|
<script type="module" src="scripts/modules/radar.mjs"></script>
|
||||||
<script type="module" src="scripts/index.mjs"></script>
|
<script type="module" src="scripts/index.mjs"></script>
|
||||||
|
|
||||||
<!-- data -->
|
<!-- data -->
|
||||||
@@ -58,19 +60,15 @@
|
|||||||
|
|
||||||
|
|
||||||
<div id="divQuery">
|
<div id="divQuery">
|
||||||
<form id="frmGetLatLng">
|
<input id="txtAddress" type="text" value="" placeholder="Zip or City, State" />
|
||||||
<input id="txtAddress" type="text" value="" placeholder="Zip or City, State" /><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"/></button>
|
<div class="buttons">
|
||||||
<input id="btnGetLatLng" type="submit" value="GO" />
|
<button id="btnGetGps" type="button" title="Get GPS Location"><img src="images/nav/ic_gps_fixed_black_18dp_1x.png" class="light"/>
|
||||||
<input id="btnClearQuery" type="reset" value="Reset" />
|
<img src="images/nav/ic_gps_fixed_white_18dp_1x.png" class="dark"/>
|
||||||
</form>
|
</button>
|
||||||
<div id="divLat"></div>
|
<button id="btnGetLatLng" type="submit">GO</button>
|
||||||
<div id="divLng"></div>
|
<button id="btnClearQuery" type="reset">Reset</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png" />
|
|
||||||
<img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png" />
|
|
||||||
<div id="version" style="display:none">
|
<div id="version" style="display:none">
|
||||||
<%- version %>
|
<%- version %>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,19 +115,22 @@
|
|||||||
<div id="radar-html" class="weather-display">
|
<div id="radar-html" class="weather-display">
|
||||||
<%- include('partials/radar.ejs') %>
|
<%- include('partials/radar.ejs') %>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="hazards-html" class="weather-display show">
|
||||||
|
<%- include('partials/hazards.ejs') %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="divTwcBottom">
|
<div id="divTwcBottom">
|
||||||
<div id="divTwcBottomLeft">
|
<div id="divTwcBottomLeft">
|
||||||
<img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu" />
|
<img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_2x.png" title="Menu" />
|
||||||
<img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous" />
|
<img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_2x.png" title="Previous" />
|
||||||
<img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next" />
|
<img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_2x.png" title="Next" />
|
||||||
<img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play" />
|
<img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_2x.png" title="Play" />
|
||||||
</div>
|
</div>
|
||||||
<div id="divTwcBottomMiddle">
|
<div id="divTwcBottomMiddle">
|
||||||
<img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh" />
|
<img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_2x.png" title="Refresh" />
|
||||||
</div>
|
</div>
|
||||||
<div id="divTwcBottomRight">
|
<div id="divTwcBottomRight">
|
||||||
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_1x.png" title="Enter Fullscreen" />
|
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_2x.png" title="Enter Fullscreen" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true}) %>
|
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true, hasTime: true}) %>
|
||||||
<div class="main has-scroll has-box current-weather">
|
<div class="main has-scroll has-box current-weather">
|
||||||
<div class="weather template">
|
<div class="weather template">
|
||||||
<div class="left col">
|
<div class="left col">
|
||||||
|
|||||||
8
views/partials/hazards.ejs
Normal file
8
views/partials/hazards.ejs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="main has-scroll hazards no-header">
|
||||||
|
<div class="hazard-lines">
|
||||||
|
<div class="hazard template">
|
||||||
|
<div class="hazard-text"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true }) %>
|
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true, hasTime: true }) %>
|
||||||
<div class="main has-scroll latest-observations has-box">
|
<div class="main has-scroll latest-observations has-box">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="column-headers">
|
<div class="column-headers">
|
||||||
|
|||||||
Reference in New Issue
Block a user