Compare commits

..

11 Commits

Author SHA1 Message Date
Matt Walsh
c70d965347 some marine forecast updates 2024-04-23 15:32:19 -05:00
Matt Walsh
1faef1b589 marine hourly mjs 2022-12-19 15:23:36 -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
19 changed files with 728 additions and 146 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

494
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -187,7 +187,7 @@ const enterFullScreen = () => {
// change hover text and image
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';
};
@@ -211,7 +211,7 @@ const exitFullscreen = () => {
resize();
// change hover text and image
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';
};
@@ -290,37 +290,41 @@ const updateFullScreenNavigate = () => {
};
const documentKeydown = (e) => {
const code = (e.keyCode || e.which);
// 200ms repeat
if ((Date.now() - documentKeydown.lastButton ?? 0) < 200) return false;
documentKeydown.lastButton = Date.now();
const { key } = e;
if (document.fullscreenElement || document.activeElement === document.body) {
switch (code) {
case 32: // Space
switch (key) {
case ' ': // Space
// don't scroll
e.preventDefault();
btnNavigatePlayClick();
return false;
case 39: // Right Arrow
case 34: // Page Down
case 'ArrowRight':
case 'PageDown':
// don't scroll
e.preventDefault();
btnNavigateNextClick();
return false;
case 37: // Left Arrow
case 33: // Page Up
case 'ArrowLeft':
case 'PageUp':
// don't scroll
e.preventDefault();
btnNavigatePreviousClick();
return false;
case 36: // Home
case 'ArrowUp': // Home
e.preventDefault();
btnNavigateMenuClick();
return false;
case 48: // Restart
case '0': // "O" Restart
btnNavigateRefreshClick();
return false;
case 70: // F
case 'F':
case 'f':
btnFullScreenClick();
return false;
@@ -368,7 +372,6 @@ const btnGetGpsClick = async () => {
txtAddress.value = `${round2(latitude, 4)}, ${round2(longitude, 4)}`;
doRedirectToGeometry({ y: latitude, x: longitude }, (point) => {
console.log(point);
const location = point.properties.relativeLocation.properties;
// Save the query
const query = `${location.city}, ${location.state}`;

View File

@@ -22,7 +22,7 @@ class Almanac extends WeatherDisplay {
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const superResponse = super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// get sun/moon data
@@ -33,11 +33,13 @@ class Almanac extends WeatherDisplay {
sun,
moon,
};
// update status
this.setStatus(STATUS.loaded);
// share data
this.getDataCallback();
if (!superResponse) return;
// update status
this.setStatus(STATUS.loaded);
}
calcSunMoonData(weatherParameters) {

View File

@@ -51,7 +51,10 @@ class Hazards extends WeatherDisplay {
this.getDataCallback();
if (!superResult) return;
if (!superResult) {
this.setStatus(STATUS.loaded);
return;
}
this.drawLongCanvas();
}
@@ -72,6 +75,7 @@ class Hazards extends WeatherDisplay {
// 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;
@@ -88,8 +92,8 @@ class Hazards extends WeatherDisplay {
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
// add the final 3 second delay
this.timing.delay.push(150);
this.calcNavTiming();
this.setStatus(STATUS.loaded);
this.calcNavTiming();
}
drawCanvas() {

View File

@@ -141,6 +141,7 @@ const parseForecast = async (data) => {
const iceAccumulation = expand(data.iceAccumulation.values); // ice icon
const probabilityOfPrecipitation = expand(data.probabilityOfPrecipitation.values); // rain icon
const snowfallAmount = expand(data.snowfallAmount.values); // snow icon
const waveHeight = expand(data.waveHeight.values);
const icons = await determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
@@ -152,6 +153,7 @@ const parseForecast = async (data) => {
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
skyCover: skyCover[idx],
icon: icons[idx],
waveHeight: waveHeight[idx],
}));
};

View File

@@ -133,6 +133,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
return addPath('Clear-Wind-1994.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing Snow.gif');
case 'cold':
@@ -268,6 +269,7 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
return addPath('CC_Windy.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing-Snow.gif');
default:

View File

@@ -0,0 +1,139 @@
// display extended forecast graphically
// technically uses the same data as the local forecast, we'll let the browser do the caching of that
import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import getHourlyForecast from './hourly.mjs';
class MarineForecast extends WeatherDisplay {
constructor(navId, elemId) {
super(navId, elemId, 'Marine Forecast', false);
// this.showOnProgress = false;
// set timings
this.timing.totalScreens = 1;
}
async getData() {
if (!super.getData()) return;
const hourlyForecast = await getHourlyForecast(() => this.stillWaiting());
if (hourlyForecast === undefined) {
this.setStatus(STATUS.failed);
return;
}
// test for all wave heights = 0, no data for wave heights
if (hourlyForecast.every((value) => !value.waveHeight)) {
// total screens = 0 to skip this display
this.totalScreens = 0;
this.setStatus(STATUS.noData);
return;
}
this.data = hourlyForecast;
this.screenIndex = 0;
this.setStatus(STATUS.loaded);
}
async drawCanvas() {
super.drawCanvas();
// determine bounds
// grab the first three or second set of three array elements
const forecast = this.data.slice(0, 2);
// create each day template
const days = forecast.map((Day) => {
const fill = {};
const waveHeight = Math.round(Day.waveHeight * 3.281);
fill.date = Day.dayName;
fill['wind-dir'] = Day.windDirection;
fill['wind-speed'] = '10 - 15kts';
fill['wave-height'] = `${waveHeight}'`;
fill['wave-desc'] = waveDesc(waveHeight);
const { low } = Day;
if (low !== undefined) {
fill['value-lo'] = Math.round(low);
}
const { high } = Day;
fill['value-hi'] = Math.round(high);
fill.condition = Day.text;
// draw the icon
fill['wave-icon'] = { type: 'img', src: waveImage('') };
// return the filled template
return this.fillTemplate('day', fill);
});
// empty and update the container
const dayContainer = this.elem.querySelector('.day-container');
dayContainer.innerHTML = '';
dayContainer.append(...days);
this.finishDraw();
}
}
const waveImage = (conditions) => {
const color = 'rgb(172, 165, 251)';
const canvas = document.createElement('canvas');
canvas.width = 150;
canvas.height = 20;
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false;
let y = 0;
let r = 35;
let arc1 = Math.PI * 0.3;
let arc2 = Math.PI * 0.7;
switch (conditions) {
case 'CHOPPY':
y = -10;
arc1 = Math.PI * 0.2;
arc2 = Math.PI * 0.8;
r = 25;
break;
case 'ROUGH':
y = -5;
arc1 = Math.PI * 0.1;
arc2 = Math.PI * 0.9;
r = 20;
break;
case 'LIGHT':
default:
y = -20;
arc1 = Math.PI * 0.3;
arc2 = Math.PI * 0.7;
r = 35;
break;
}
context.beginPath();
context.arc(35, y, r, arc1, arc2);
context.strokeStyle = color;
context.lineWidth = 4;
context.stroke();
context.beginPath();
context.arc(75, y, r, arc1, arc2);
context.stroke();
context.beginPath();
context.arc(115, y, r, arc1, arc2);
context.stroke();
return canvas.toDataURL();
};
const waveDesc = (waveHeight) => {
if (waveHeight > 7) return 'ROUGH';
if (waveHeight > 4) return 'CHOPPY';
return 'LIGHT';
};
// register display
registerDisplay(new MarineForecast(11, 'marine-forecast'));

View File

@@ -175,6 +175,7 @@ const navTo = (direction) => {
if (!firstDisplay) return;
firstDisplay.navNext(msg.command.firstFrame);
firstDisplay.showCanvas();
return;
}
if (direction === msg.command.nextFrame) currentDisplay().navNext();
@@ -218,11 +219,11 @@ const setPlaying = (newValue) => {
if (playing) {
noSleep(true);
playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
playButton.src = 'images/nav/ic_pause_white_24dp_2x.png';
} else {
noSleep(false);
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 (!progress) return;
@@ -377,7 +378,7 @@ const stopAutoRefreshTimer = () => {
const refreshCheck = () => {
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS) {
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && isPlaying()) {
loadTwcData();
return true;
}

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,98 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
#marine-forecast-html.weather-display {
background-image: url('../images/BackGround8_1.png');
}
.weather-display .main.marine-forecast {
font-family: 'Star4000';
font-size: 24pt;
@include u.text-shadow();
.advisory {
width: 100%;
height: 90px;
overflow: hidden;
.advisory-text {
border: 4px solid black;
width: 75%;
margin-left: auto;
margin-right: auto;
text-align: center;
margin-top: 40px;
;
}
}
.headers {
display: inline-block;
vertical-align: top;
.winds {
text-align: right;
width: 150px;
margin-top: 42px;
margin-bottom: 60px;
}
}
.day-container {
display: inline-block;
}
.day {
padding: 5px;
width: 165px;
display: inline-block;
margin: 0px 15px;
text-align: center;
.date {
color: c.$title-color;
}
.wave {
border: 4px solid #b09ffb;
.wave-icon {
height: 20px;
img {
display: block;
margin-left: auto;
margin-right: auto;
}
}
}
.temperatures {
width: 100%;
margin-top: 5px;
.temperature-block {
display: inline-block;
width: 44%;
vertical-align: top;
>div {
text-align: center;
}
.value {
font-family: 'Star4000 Large';
margin-top: 4px;
}
&.lo .label {
color: c.$extended-low;
}
&.hi .label {
color: c.$title-color;
}
}
}
}
}

View File

@@ -213,8 +213,7 @@ button {
text-align: right;
}
#imgPause1x,
#imgPause2x {
#imgPause1x {
visibility: hidden;
position: absolute;
}
@@ -318,6 +317,10 @@ button {
margin-bottom: 15px;
@include u.status-colors();
.press-here {
color: white;
}
@media (prefers-color-scheme: light) {
.loading,
@@ -326,7 +329,7 @@ button {
}
.press-here {
color: hsl(120, 100%, 30%);
color: black;
cursor: pointer;
}
@@ -343,10 +346,6 @@ button {
}
}
.press-here {
color: black;
}
label {
display: block;
max-width: 300px;
@@ -363,7 +362,7 @@ button {
}
#divTwcBottom img {
zoom: 150%;
zoom: 75%;
}
#divTwc:fullscreen {

View File

@@ -11,4 +11,5 @@
@import 'radar';
@import 'regional-forecast';
@import 'almanac';
@import 'hazards';
@import 'hazards';
@import 'marine-forecast';

View File

@@ -42,7 +42,7 @@
<script type="module" src="scripts/modules/regionalforecast.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/radar.mjs"></script>
<script type="module" src="scripts/modules/marineforecast.mjs"></script>
<script type="module" src="scripts/index.mjs"></script>
<!-- data -->
@@ -68,11 +68,6 @@
<div id="divLat"></div>
<div id="divLng"></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">
<%- version %>
</div>
@@ -112,6 +107,9 @@
</div>
<div id="almanac-html" class="weather-display">
<%- include('partials/almanac.ejs') %>
</div>
<div id="marine-forecast-html" class="weather-display">
<%- include('partials/marine-forecast.ejs') %>
</div>
<div id="extended-forecast-html" class="weather-display">
<%- include('partials/extended-forecast.ejs') %>
@@ -125,16 +123,16 @@
</div>
<div id="divTwcBottom">
<div id="divTwcBottomLeft">
<img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu" />
<img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous" />
<img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next" />
<img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play" />
<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_2x.png" title="Previous" />
<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_2x.png" title="Play" />
</div>
<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 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>

View File

@@ -0,0 +1,23 @@
<%- include('header.ejs', { title: 'Marine Forecast' , hasTime: true }) %>
<div class="main has-scroll marine-forecast">
<div class="advisory">
<div class="advisory-text">Small Craft Advisory</div>
</div>
<div class="headers">
<div class="winds">WINDS:</div>
<div class="winds">WAVES:</div>
</div>
<div class="day-container">
<div class="day template">
<div class="date"></div>
<div class="wind-dir"></div>
<div class="wind-speed"></div>
<div class="wave">
<div class="wave-height"></div>
<div class="wave-icon"><img src="" /></div>
<div class="wave-desc"></div>
</div>
</div>
</div>
</div>
<%- include('scroll.ejs') %>

View File

@@ -51,5 +51,9 @@
"editor.defaultFormatter": "j69.ejs-beautify"
},
"files.exclude": {},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}