Compare commits

...

28 Commits

Author SHA1 Message Date
Matt Walsh
dc77ba835c 5.9.4 2022-12-21 16:20:37 -06:00
Matt Walsh
a440990696 update top form html and css 2022-12-21 16:20:31 -06:00
Matt Walsh
fc4cbc1415 fix time zones close #21 2022-12-21 15:17:50 -06:00
Matt Walsh
20ba3ddaac capture dist 2022-12-21 14:52:03 -06:00
Matt Walsh
4205155f96 5.9.3 2022-12-21 14:51:04 -06:00
Matt Walsh
f4101f06cc fix hazards failed to load auto play issue 2022-12-21 14:50:56 -06:00
Matt Walsh
4c3fcfc358 correct hazard scroll time 2022-12-21 14:44:36 -06:00
Matt Walsh
366f527aee auto play when hazard is present 2022-12-21 14:05:14 -06:00
Matt Walsh
797b4d32fa capture dist 2022-12-19 15:24:34 -06:00
Matt Walsh
5092076050 5.9.2 2022-12-19 15:24:12 -06:00
Matt Walsh
5d891fb38f switch to 2x image sizes 2022-12-19 15:21:38 -06:00
Matt Walsh
97e0fda709 key navigation 2022-12-19 11:48:59 -06:00
Matt Walsh
7cf9dd6466 almanac delivers data when disabled 2022-12-19 11:27:02 -06:00
Matt Walsh
a44bd866ed regional forecast icon blizzard 2022-12-19 11:17:30 -06:00
Matt Walsh
21ef7f476a auto refresh fix 2022-12-19 11:15:48 -06:00
Matt Walsh
c5b715d631 checkbox label colors 2022-12-19 10:17:12 -06:00
Matt Walsh
dfd9facc79 Merge branch 'main' of github.com:netbymatt/ws4kp 2022-12-19 10:14:37 -06:00
Matt Walsh
5b926a358e no hazards, blizzard 2022-12-19 10:14:33 -06:00
Matt Walsh
ba1fbd7088 capture dist 2022-12-14 21:49:07 -06:00
Matt Walsh
f82980ed09 5.9.1 2022-12-14 21:47:35 -06:00
Matt Walsh
af17b3c690 progress light mode colors 2022-12-14 21:47:27 -06:00
Matt Walsh
2c394c2e4a correct gulp build 2022-12-14 16:31:11 -06:00
Matt Walsh
97f8eda236 capture dist 2022-12-14 16:29:09 -06:00
Matt Walsh
deb107e4ec 5.9.0 2022-12-14 16:28:47 -06:00
Matt Walsh
111f077e20 add hazards 2022-12-14 16:28:33 -06:00
Matt Walsh
806ef91000 fullscreen element fix 2022-12-14 13:32:55 -06:00
Matt Walsh
2a577aaea7 code cleanup 2022-12-14 13:08:49 -06:00
Matt Walsh
49296e53f0 make gulp output easier to read 2022-12-14 11:22:55 -06:00
34 changed files with 842 additions and 276 deletions

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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": {

View File

@@ -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}`;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -163,4 +163,4 @@ const shortenExtendedForecastText = (long) => {
}; };
// register display // register display
registerDisplay(new ExtendedForecast(7, 'extended-forecast')); registerDisplay(new ExtendedForecast(8, 'extended-forecast'));

View 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));

View File

@@ -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'));

View File

@@ -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);

View File

@@ -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:

View File

@@ -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'));

View File

@@ -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'));

View File

@@ -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,
}; };

View File

@@ -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;

View File

@@ -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'));

View File

@@ -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'));

View File

@@ -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));

View File

@@ -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

View 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;
}
}
}
}

View File

@@ -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 {

View File

@@ -96,6 +96,10 @@
width: 640px; width: 640px;
height: 310px; height: 310px;
overflow: hidden; overflow: hidden;
&.no-header {
height: 400px;
}
} }
&.has-box { &.has-box {

View File

@@ -10,4 +10,5 @@
@import 'progress'; @import 'progress';
@import 'radar'; @import 'radar';
@import 'regional-forecast'; @import 'regional-forecast';
@import 'almanac'; @import 'almanac';
@import 'hazards';

View File

@@ -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>

View File

@@ -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">

View 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') %>

View File

@@ -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">