mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-14 07:39:29 -07:00
Merge branch 'hazard-scroll-2' #92
This commit is contained in:
@@ -2,6 +2,7 @@ import { locationCleanup } from './utils/string.mjs';
|
|||||||
import { elemForEach } from './utils/elem.mjs';
|
import { elemForEach } from './utils/elem.mjs';
|
||||||
import getCurrentWeather from './currentweather.mjs';
|
import getCurrentWeather from './currentweather.mjs';
|
||||||
import { currentDisplay } from './navigation.mjs';
|
import { currentDisplay } from './navigation.mjs';
|
||||||
|
import getHazards from './hazards.mjs';
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
const degree = String.fromCharCode(176);
|
const degree = String.fromCharCode(176);
|
||||||
@@ -13,12 +14,18 @@ let interval;
|
|||||||
let screenIndex = 0;
|
let screenIndex = 0;
|
||||||
let sinceLastUpdate = 0;
|
let sinceLastUpdate = 0;
|
||||||
let nextUpdate = DEFAULT_UPDATE;
|
let nextUpdate = DEFAULT_UPDATE;
|
||||||
|
let resetFlag;
|
||||||
|
|
||||||
// start drawing conditions
|
// start drawing conditions
|
||||||
// reset starts from the first item in the text scroll list
|
// reset starts from the first item in the text scroll list
|
||||||
const start = () => {
|
const start = () => {
|
||||||
// store see if the context is new
|
// if already started, draw the screen on a reset flag and return
|
||||||
|
if (interval) {
|
||||||
|
if (resetFlag) drawScreen();
|
||||||
|
resetFlag = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resetFlag = false;
|
||||||
// set up the interval if needed
|
// set up the interval if needed
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
interval = setInterval(incrementInterval, 500);
|
interval = setInterval(incrementInterval, 500);
|
||||||
@@ -29,7 +36,10 @@ const start = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stop = (reset) => {
|
const stop = (reset) => {
|
||||||
if (reset) screenIndex = 0;
|
if (reset) {
|
||||||
|
screenIndex = 0;
|
||||||
|
resetFlag = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// increment interval, roll over
|
// increment interval, roll over
|
||||||
@@ -51,6 +61,7 @@ const incrementInterval = (force) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
screenIndex = (screenIndex + 1) % (lastScreen);
|
screenIndex = (screenIndex + 1) % (lastScreen);
|
||||||
|
|
||||||
// draw new text
|
// draw new text
|
||||||
drawScreen();
|
drawScreen();
|
||||||
};
|
};
|
||||||
@@ -59,10 +70,24 @@ const drawScreen = async () => {
|
|||||||
// get the conditions
|
// get the conditions
|
||||||
const data = await getCurrentWeather();
|
const data = await getCurrentWeather();
|
||||||
|
|
||||||
|
// add the hazards if on screen 0
|
||||||
|
if (screenIndex === 0) {
|
||||||
|
data.hazards = await getHazards(() => this.stillWaiting());
|
||||||
|
}
|
||||||
|
|
||||||
// nothing to do if there's no data yet
|
// nothing to do if there's no data yet
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
const thisScreen = screens[screenIndex](data);
|
const thisScreen = screens[screenIndex](data);
|
||||||
|
|
||||||
|
// update classes on the scroll area
|
||||||
|
elemForEach('.weather-display .scroll', (elem) => {
|
||||||
|
elem.classList.forEach((cls) => { if (cls !== 'scroll') elem.classList.remove(cls); });
|
||||||
|
// no scroll on progress
|
||||||
|
if (elem.parentElement.id === 'progress-html') return;
|
||||||
|
thisScreen?.classes?.forEach((cls) => elem.classList.add(cls));
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof thisScreen === 'string') {
|
if (typeof thisScreen === 'string') {
|
||||||
// only a string
|
// only a string
|
||||||
drawCondition(thisScreen);
|
drawCondition(thisScreen);
|
||||||
@@ -80,8 +105,23 @@ const drawScreen = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hazards = (data) => {
|
||||||
|
// test for data
|
||||||
|
if (!data.hazards || data.hazards.length === 0) return false;
|
||||||
|
|
||||||
|
const hazard = `${data.hazards[0].properties.event} ${data.hazards[0].properties.description}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: hazard,
|
||||||
|
type: 'scroll',
|
||||||
|
classes: ['hazard'],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// the "screens" are stored in an array for easy addition and removal
|
// the "screens" are stored in an array for easy addition and removal
|
||||||
const screens = [
|
const screens = [
|
||||||
|
// hazards
|
||||||
|
hazards,
|
||||||
// station name
|
// station name
|
||||||
(data) => `Conditions at ${locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
(data) => `Conditions at ${locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
||||||
|
|
||||||
@@ -128,9 +168,6 @@ const drawCondition = (text) => {
|
|||||||
elem.innerHTML = text;
|
elem.innerHTML = text;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
|
|
||||||
// store the original number of screens
|
// store the original number of screens
|
||||||
const originalScreens = screens.length;
|
const originalScreens = screens.length;
|
||||||
@@ -180,7 +217,24 @@ const drawScrollCondition = (screen) => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseMessage = (event) => {
|
||||||
|
if (event?.data?.type === 'current-weather-scroll') {
|
||||||
|
if (event.data?.method === 'start') start();
|
||||||
|
if (event.data?.method === 'reload') stop(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// add event listener for start message
|
||||||
|
window.addEventListener('message', parseMessage);
|
||||||
|
|
||||||
window.CurrentWeatherScroll = {
|
window.CurrentWeatherScroll = {
|
||||||
addScreen,
|
addScreen,
|
||||||
reset,
|
reset,
|
||||||
|
start,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
addScreen,
|
||||||
|
reset,
|
||||||
|
start,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,9 +21,17 @@ class Hazards extends WeatherDisplay {
|
|||||||
// special height and width for scrolling
|
// special height and width for scrolling
|
||||||
super(navId, elemId, 'Hazards', defaultActive);
|
super(navId, elemId, 'Hazards', defaultActive);
|
||||||
this.showOnProgress = false;
|
this.showOnProgress = false;
|
||||||
|
this.okToDrawCurrentConditions = false;
|
||||||
|
|
||||||
|
// force a 1-minute refresh time for the most up-to-date hazards
|
||||||
|
this.refreshTime = 60_000;
|
||||||
|
|
||||||
// 0 screens skips this during "play"
|
// 0 screens skips this during "play"
|
||||||
this.timing.totalScreens = 0;
|
this.timing.totalScreens = 0;
|
||||||
|
|
||||||
|
// take note of the already-shown alert ids
|
||||||
|
this.viewedAlerts = new Set();
|
||||||
|
this.viewedGetCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters, refresh) {
|
async getData(weatherParameters, refresh) {
|
||||||
@@ -32,9 +40,18 @@ class Hazards extends WeatherDisplay {
|
|||||||
// hazards performs a silent refresh, but does not fall back to a previous fetch if no data is available
|
// hazards performs a silent refresh, but does not fall back to a previous fetch if no data is available
|
||||||
// this is intentional to ensure the latest alerts only are displayed.
|
// this is intentional to ensure the latest alerts only are displayed.
|
||||||
|
|
||||||
|
// auto reload must be set up specifically for hazards in case it is disabled via checkbox (for the bottom line scroll)
|
||||||
|
if (this.autoRefreshHandle === null) this.setAutoReload();
|
||||||
|
|
||||||
const alert = this.checkbox.querySelector('.alert');
|
const alert = this.checkbox.querySelector('.alert');
|
||||||
alert.classList.remove('show');
|
alert.classList.remove('show');
|
||||||
|
|
||||||
|
// if not a refresh (new site), all alerts are new
|
||||||
|
if (!refresh) {
|
||||||
|
this.viewedGetCount = 0;
|
||||||
|
this.viewedAlerts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// get the forecast
|
// get the forecast
|
||||||
const url = new URL('https://api.weather.gov/alerts/active');
|
const url = new URL('https://api.weather.gov/alerts/active');
|
||||||
@@ -47,8 +64,23 @@ class Hazards extends WeatherDisplay {
|
|||||||
const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown' && (!hasImmediate || (hazard.properties.urgency === 'Immediate')));
|
const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown' && (!hasImmediate || (hazard.properties.urgency === 'Immediate')));
|
||||||
this.data = filteredAlerts;
|
this.data = filteredAlerts;
|
||||||
|
|
||||||
|
// every 10 times through the get process (10 minutes), reset the viewed messages
|
||||||
|
if (this.viewedGetCount >= 10) {
|
||||||
|
this.viewedGetCount = 0;
|
||||||
|
this.viewedAlerts.clear();
|
||||||
|
}
|
||||||
|
this.viewedGetCount += 1;
|
||||||
|
|
||||||
|
// count up un-viewed alerts
|
||||||
|
const unViewed = this.data.reduce((count, hazard) => {
|
||||||
|
if (!this.viewedAlerts.has(hazard.id)) return count + 1;
|
||||||
|
return count;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// show alert indicator
|
// show alert indicator
|
||||||
if (this.data.length > 0) alert.classList.add('show');
|
if (unViewed > 0) alert.classList.add('show');
|
||||||
|
// draw the canvas to calculate the new timings and activate hazards in the slide deck again
|
||||||
|
this.drawLongCanvas();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get hazards failed');
|
console.error('Get hazards failed');
|
||||||
console.error(error.status, error.responseJSON);
|
console.error(error.status, error.responseJSON);
|
||||||
@@ -72,7 +104,10 @@ class Hazards extends WeatherDisplay {
|
|||||||
const list = this.elem.querySelector('.hazard-lines');
|
const list = this.elem.querySelector('.hazard-lines');
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
|
|
||||||
const lines = this.data.map((data) => {
|
// filter viewed alerts
|
||||||
|
const unViewed = this.data.filter((data) => !this.viewedAlerts.has(data.id));
|
||||||
|
|
||||||
|
const lines = unViewed.map((data) => {
|
||||||
const fillValues = {};
|
const fillValues = {};
|
||||||
// text
|
// text
|
||||||
fillValues['hazard-text'] = `${data.properties.event}<br/><br/>${data.properties.description.replaceAll('\n\n', '<br/><br/>').replaceAll('\n', ' ')}`;
|
fillValues['hazard-text'] = `${data.properties.event}<br/><br/>${data.properties.description.replaceAll('\n\n', '<br/><br/>').replaceAll('\n', ' ')}`;
|
||||||
@@ -91,18 +126,22 @@ class Hazards extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update timing
|
// update timing
|
||||||
|
this.setTiming(list);
|
||||||
|
this.setStatus(STATUS.loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTiming(list) {
|
||||||
// set up the timing
|
// set up the timing
|
||||||
this.timing.baseDelay = 20;
|
this.timing.baseDelay = 20;
|
||||||
// 24 hours = 6 pages
|
// 24 hours = 6 pages
|
||||||
const pages = Math.max(Math.ceil(list.scrollHeight / 400) - 3, 1);
|
const pages = Math.max(Math.ceil(list.scrollHeight / 480) - 4);
|
||||||
const timingStep = 400;
|
const timingStep = 480;
|
||||||
this.timing.delay = [150 + timingStep];
|
this.timing.delay = [150 + timingStep];
|
||||||
// add additional pages
|
// add additional pages
|
||||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||||
// add the final 3 second delay
|
// add the final 3 second delay
|
||||||
this.timing.delay.push(250);
|
this.timing.delay.push(250);
|
||||||
this.calcNavTiming();
|
this.calcNavTiming();
|
||||||
this.setStatus(STATUS.loaded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCanvas() {
|
drawCanvas() {
|
||||||
@@ -151,10 +190,23 @@ class Hazards extends WeatherDisplay {
|
|||||||
if (superValue === false) {
|
if (superValue === false) {
|
||||||
// set total screens to zero to take this out of the rotation
|
// set total screens to zero to take this out of the rotation
|
||||||
this.timing.totalScreens = 0;
|
this.timing.totalScreens = 0;
|
||||||
|
// note the ids shown
|
||||||
|
this?.data?.forEach((alert) => this.viewedAlerts.add(alert.id));
|
||||||
}
|
}
|
||||||
// return the value as expected
|
// return the value as expected
|
||||||
return superValue;
|
return superValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make data available outside this class
|
||||||
|
// promise allows for data to be requested before it is available
|
||||||
|
async getHazards(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));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcSeverity = (severity, event) => {
|
const calcSeverity = (severity, event) => {
|
||||||
@@ -165,4 +217,7 @@ const calcSeverity = (severity, event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new Hazards(0, 'hazards', true));
|
const display = new Hazards(0, 'hazards', true);
|
||||||
|
registerDisplay(display);
|
||||||
|
|
||||||
|
export default display.getHazards.bind(display);
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ const getWeather = async (latLon, haveDataCallback) => {
|
|||||||
// update the main process for display purposes
|
// update the main process for display purposes
|
||||||
populateWeatherParameters(weatherParameters);
|
populateWeatherParameters(weatherParameters);
|
||||||
|
|
||||||
|
// reset the scroll
|
||||||
|
postMessage({ type: 'current-weather-scroll', method: 'reload' });
|
||||||
|
|
||||||
// draw the progress canvas and hide others
|
// draw the progress canvas and hide others
|
||||||
hideAllCanvases();
|
hideAllCanvases();
|
||||||
document.querySelector('#loading').style.display = 'none';
|
document.querySelector('#loading').style.display = 'none';
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ class WeatherDisplay {
|
|||||||
// clean up the first-run flag in screen index
|
// clean up the first-run flag in screen index
|
||||||
if (this.screenIndex < 0) this.screenIndex = 0;
|
if (this.screenIndex < 0) this.screenIndex = 0;
|
||||||
if (this.okToDrawCurrentDateTime) this.drawCurrentDateTime();
|
if (this.okToDrawCurrentDateTime) this.drawCurrentDateTime();
|
||||||
|
if (this.okToDrawCurrentConditions) postMessage({ type: 'current-weather-scroll', method: 'start' });
|
||||||
}
|
}
|
||||||
|
|
||||||
finishDraw() {
|
finishDraw() {
|
||||||
@@ -443,7 +444,9 @@ class WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setAutoReload() {
|
setAutoReload() {
|
||||||
this.autoRefreshHandle = this.autoRefreshHandle ?? setInterval(() => this.getData(false, true), settings.refreshTime.value);
|
// refresh time can be forced by the user (for hazards)
|
||||||
|
const refreshTime = this.refreshTime ?? settings.refreshTime.value;
|
||||||
|
this.autoRefreshHandle = this.autoRefreshHandle ?? setInterval(() => this.getData(false, true), refreshTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@
|
|||||||
.weather-display .main.hazards {
|
.weather-display .main.hazards {
|
||||||
&.main {
|
&.main {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
height: 480px;
|
||||||
|
|
||||||
.hazard-lines {
|
.hazard-lines {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
@@ -18,9 +19,10 @@
|
|||||||
@include u.text-shadow(0px);
|
@include u.text-shadow(0px);
|
||||||
position: relative;
|
position: relative;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-top: 110px;
|
margin-top: 10px;
|
||||||
margin-left: 80px;
|
margin-left: 80px;
|
||||||
margin-right: 80px;
|
margin-right: 80px;
|
||||||
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,15 +113,21 @@
|
|||||||
|
|
||||||
.scroll {
|
.scroll {
|
||||||
@include u.text-shadow(3px, 1.5px);
|
@include u.text-shadow(3px, 1.5px);
|
||||||
width: calc(640px - 2 * 30px);
|
width: 640px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 10px;
|
margin-top: 3px;
|
||||||
|
padding-top: 7px;
|
||||||
|
|
||||||
|
&.hazard {
|
||||||
|
background-color: rgb(112, 35, 35);
|
||||||
|
}
|
||||||
|
|
||||||
.fixed {
|
.fixed {
|
||||||
font-family: 'Star4000';
|
font-family: 'Star4000';
|
||||||
font-size: 24pt;
|
font-size: 24pt;
|
||||||
margin-left: 55px;
|
margin-left: 55px;
|
||||||
|
margin-right: 55px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.scroll-area {
|
.scroll-area {
|
||||||
@@ -132,5 +138,6 @@
|
|||||||
// left: calc((elem width) - 640px);
|
// left: calc((elem width) - 640px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<div class="main has-scroll hazards no-header">
|
<div class="main hazards no-header">
|
||||||
<div class="hazard-lines">
|
<div class="hazard-lines">
|
||||||
<div class="hazard template">
|
<div class="hazard template">
|
||||||
<div class="hazard-text"></div>
|
<div class="hazard-text"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%- include('scroll.ejs') %>
|
|
||||||
Reference in New Issue
Block a user