diff --git a/server/scripts/modules/currentweatherscroll.mjs b/server/scripts/modules/currentweatherscroll.mjs index b3411e8..6700b16 100644 --- a/server/scripts/modules/currentweatherscroll.mjs +++ b/server/scripts/modules/currentweatherscroll.mjs @@ -19,6 +19,8 @@ let hazardData; // start drawing conditions // reset starts from the first item in the text scroll list const start = () => { + // if already started, nothing to do + if (interval) return; // set up the interval if needed if (!interval) { interval = setInterval(incrementInterval, 500); @@ -211,6 +213,7 @@ const drawScrollCondition = (screen) => { const parseMessage = (event) => { if (event?.data?.type === 'current-weather-scroll') { if (event.data?.method === 'start') start(); + if (event.data?.method === 'reload') stop(true); } }; diff --git a/server/scripts/modules/hazards.mjs b/server/scripts/modules/hazards.mjs index 1ec5b0c..2cd1084 100644 --- a/server/scripts/modules/hazards.mjs +++ b/server/scripts/modules/hazards.mjs @@ -23,8 +23,15 @@ class Hazards extends WeatherDisplay { 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" this.timing.totalScreens = 0; + + // take note of the already-shown alert ids + this.viewedAlerts = new Set(); + this.viewedGetCount = 0; } async getData(weatherParameters, refresh) { @@ -33,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 // 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'); alert.classList.remove('show'); + // if not a refresh (new site), all alerts are new + if (!refresh) { + this.viewedGetCount = 0; + this.viewedAlerts.clear(); + } + try { // get the forecast const url = new URL('https://api.weather.gov/alerts/active'); @@ -48,8 +64,23 @@ class Hazards extends WeatherDisplay { const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown' && (!hasImmediate || (hazard.properties.urgency === 'Immediate'))); 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 - 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) { console.error('Get hazards failed'); console.error(error.status, error.responseJSON); @@ -73,7 +104,10 @@ class Hazards extends WeatherDisplay { const list = this.elem.querySelector('.hazard-lines'); 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 = {}; // text fillValues['hazard-text'] = `${data.properties.event}

${data.properties.description.replaceAll('\n\n', '

').replaceAll('\n', ' ')}`; @@ -92,18 +126,22 @@ class Hazards extends WeatherDisplay { } // update timing + this.setTiming(list); + this.setStatus(STATUS.loaded); + } + + setTiming(list) { // 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; + const pages = Math.max(Math.ceil(list.scrollHeight / 480) - 4); + const timingStep = 480; 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() { @@ -152,6 +190,8 @@ class Hazards extends WeatherDisplay { if (superValue === false) { // set total screens to zero to take this out of the rotation this.timing.totalScreens = 0; + // note the ids shown + this?.data?.forEach((alert) => this.viewedAlerts.add(alert.id)); } // return the value as expected return superValue; diff --git a/server/scripts/modules/navigation.mjs b/server/scripts/modules/navigation.mjs index 4630638..d4e6f2f 100644 --- a/server/scripts/modules/navigation.mjs +++ b/server/scripts/modules/navigation.mjs @@ -67,6 +67,9 @@ const getWeather = async (latLon, haveDataCallback) => { // update the main process for display purposes populateWeatherParameters(weatherParameters); + // reset the scroll + postMessage({ type: 'current-weather-scroll', method: 'reload' }); + // draw the progress canvas and hide others hideAllCanvases(); document.querySelector('#loading').style.display = 'none'; diff --git a/server/scripts/modules/weatherdisplay.mjs b/server/scripts/modules/weatherdisplay.mjs index a9aa948..cd38ea9 100644 --- a/server/scripts/modules/weatherdisplay.mjs +++ b/server/scripts/modules/weatherdisplay.mjs @@ -444,7 +444,9 @@ class WeatherDisplay { } 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); } }