Compare commits

..

2 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
28 changed files with 392 additions and 173 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

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ws4kp", "name": "ws4kp",
"version": "5.9.6", "version": "5.9.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ws4kp", "name": "ws4kp",
"version": "5.9.6", "version": "5.9.1",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"del": "^6.0.0", "del": "^6.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ws4kp", "name": "ws4kp",
"version": "5.9.6", "version": "5.9.1",
"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,13 +44,10 @@ 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(); });
$('#txtAddress').devbridgeAutocomplete({ $('#frmGetLatLng #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',
@@ -74,11 +71,11 @@ const init = () => {
width: 490, width: 490,
}); });
const formSubmit = () => { $('#frmGetLatLng').on('submit', () => {
const ac = $('#txtAddress').devbridgeAutocomplete(); const ac = $('#frmGetLatLng #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');

View File

@@ -53,9 +53,7 @@ class CurrentWeather extends WeatherDisplay {
// test data quality // test data quality
if (observations.features[0].properties.temperature.value === null if (observations.features[0].properties.temperature.value === null
|| observations.features[0].properties.windSpeed.value === null || observations.features[0].properties.windSpeed.value === null
|| observations.features[0].properties.textDescription === null || observations.features[0].properties.textDescription === null) {
|| observations.features[0].properties.textDescription === ''
|| observations.features[0].properties.icon === null) {
observations = undefined; observations = undefined;
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`); throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
} }

View File

@@ -43,7 +43,7 @@ const incrementInterval = () => {
const drawScreen = async () => { const drawScreen = async () => {
// get the conditions // get the conditions
const data = await getCurrentWeather(); const data = await getCurrentWeather(() => 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;

View File

@@ -85,15 +85,15 @@ class Hazards extends WeatherDisplay {
// 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.ceil(list.scrollHeight / 390); // first page is already displayed, last page doesn't happen
const timingStep = 400; const timingStep = 75 * 4;
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(150);
this.calcNavTiming();
this.setStatus(STATUS.loaded); this.setStatus(STATUS.loaded);
this.calcNavTiming();
} }
drawCanvas() { drawCanvas() {

View File

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

View File

@@ -87,7 +87,6 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
return addPath('Light-Snow.gif'); return addPath('Light-Snow.gif');
case 'rain_snow': case 'rain_snow':
case 'rain_snow-n':
return addPath('Rain-Snow-1992.gif'); return addPath('Rain-Snow-1992.gif');
case 'snow_fzra': case 'snow_fzra':

View File

@@ -32,20 +32,26 @@ class LatestObservations extends WeatherDisplay {
const regionalStations = sortedStations.slice(0, 30); const regionalStations = sortedStations.slice(0, 30);
// get data for regional stations // get data for regional stations
// get first 7 stations const allConditions = await Promise.all(regionalStations.map(async (station) => {
const actualConditions = []; try {
let lastStation = Math.min(regionalStations.length, 7); const data = await json(`https://api.weather.gov/stations/${station.id}/observations/latest`, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
let firstStation = 0; // test for temperature, weather and wind values present
while (actualConditions.length < 7 && (lastStation) <= regionalStations.length) { if (data.properties.temperature.value === null
// eslint-disable-next-line no-await-in-loop || data.properties.textDescription === ''
const someStations = await getStations(regionalStations.slice(firstStation, lastStation)); || data.properties.windSpeed.value === null) return false;
// format the return values
actualConditions.push(...someStations); return {
// update counters ...data.properties,
firstStation += lastStation; StationId: station.id,
lastStation = Math.min(regionalStations.length + 1, firstStation + 7 - actualConditions.length); city: station.city,
} };
} catch (e) {
console.log(`Unable to get latest observations for ${station.id}`);
return false;
}
}));
// remove and stations that did not return data
const actualConditions = allConditions.filter((condition) => condition);
// cut down to the maximum of 7 // cut down to the maximum of 7
this.data = actualConditions.slice(0, this.MaximumRegionalStations); this.data = actualConditions.slice(0, this.MaximumRegionalStations);
@@ -113,28 +119,5 @@ const shortenCurrentConditions = (_condition) => {
condition = condition.replace(/ with /, '/'); condition = condition.replace(/ with /, '/');
return condition; return condition;
}; };
const getStations = async (stations) => {
const stationData = await Promise.all(stations.map(async (station) => {
try {
const data = await json(`https://api.weather.gov/stations/${station.id}/observations/latest`, { retryCount: 1, stillWaiting: () => this.stillWaiting() });
// test for temperature, weather and wind values present
if (data.properties.temperature.value === null
|| data.properties.textDescription === ''
|| data.properties.windSpeed.value === null) return false;
// format the return values
return {
...data.properties,
StationId: station.id,
city: station.city,
};
} catch (e) {
console.log(`Unable to get latest observations for ${station.id}`);
return false;
}
}));
// filter false (no data or other error)
return stationData.filter((d) => d);
};
// register display // register display
registerDisplay(new LatestObservations(2, 'latest-observations')); registerDisplay(new LatestObservations(2, 'latest-observations'));

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

@@ -77,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.timeZone; weatherParameters.timeZone = point.properties.relativeLocation.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;
@@ -109,13 +109,6 @@ const updateStatus = (value) => {
// calculate first enabled display // calculate first enabled display
const firstDisplayIndex = displays.findIndex((display) => display.enabled && display.timing.totalScreens > 0); 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) {
navTo(msg.command.firstFrame); navTo(msg.command.firstFrame);
@@ -400,8 +393,6 @@ const registerRefreshData = (callback) => {
loadTwcData.callback = callback; loadTwcData.callback = callback;
}; };
const timeZone = () => weatherParameters.timeZone;
export { export {
updateStatus, updateStatus,
displayNavMessage, displayNavMessage,
@@ -417,5 +408,4 @@ export {
latLonReceived, latLonReceived,
stopAutoRefreshTimer, stopAutoRefreshTimer,
registerRefreshData, registerRefreshData,
timeZone,
}; };

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, timeZone } from './navigation.mjs'; import { registerDisplay } 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(timeZone()); time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone();
} }
// assign to an html image element // assign to an html image element

View File

@@ -23,9 +23,7 @@ const getRegionalObservation = async (point, city) => {
const observation = await json(`${station}/observations/latest`); const observation = await json(`${station}/observations/latest`);
// preload the image // preload the image
if (!observation.properties.icon) return false; if (!observation.properties.icon) return false;
const icon = getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime); preloadImg(getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
if (!icon) return false;
preloadImg(icon);
// return the observation // return the observation
return observation.properties; return observation.properties;
} catch (e) { } catch (e) {

View File

@@ -87,9 +87,6 @@ class RegionalForecast extends WeatherDisplay {
// wait for the regional observation if it's not done yet // wait for the regional observation if it's not done yet
const observation = await observationPromise; const observation = await observationPromise;
if (!observation) return false;
// format the observation the same as the forecast // format the observation the same as the forecast
const regionalObservation = { const regionalObservation = {
daytime: !!observation.icon.match(/\/day\//), daytime: !!observation.icon.match(/\/day\//),

View File

@@ -2,8 +2,9 @@
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, timeZone, msg, displayNavMessage, isPlaying, updateStatus,
} from './navigation.mjs'; } from './navigation.mjs';
class WeatherDisplay { class WeatherDisplay {
@@ -172,22 +173,20 @@ class WeatherDisplay {
// only draw if canvas is active to conserve battery // only draw if canvas is active to conserve battery
if (!this.active) return; if (!this.active) return;
// Get the current date and time. // Get the current date and time.
const now = DateTime.local().setZone(timeZone()); const now = DateTime.local();
// 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, ' ');
const dateElem = this.elem.querySelector('.date-time.date'); if (this.lastTime !== time) {
const timeElem = this.elem.querySelector('.date-time.time'); elemForEach('.date-time.time', (elem) => { elem.innerHTML = time.toUpperCase(); });
if (timeElem && this.lastTime !== time) {
timeElem.innerHTML = time.toUpperCase();
} }
this.lastTime = time; this.lastTime = time;
if (dateElem && this.lastDate !== date) { const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
dateElem.innerHTML = date.toUpperCase();
if (this.lastDate !== date) {
elemForEach('.date-time.date', (elem) => { elem.innerHTML = date.toUpperCase(); });
} }
this.lastDate = date; this.lastDate = date;
} }

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

@@ -20,83 +20,72 @@ body {
} }
} }
#divQuery { input,
max-width: 640px; button {
font-family: "Star4000";
}
.buttons { #imgGetGps {
display: inline-block; height: 13px;
width: 150px; vertical-align: middle;
text-align: right; }
#imgGetGps { #txtAddress {
height: 13px; width: 490px;
vertical-align: middle; font-size: 16pt;
} max-width: calc(100% - 8px);
button { @media (prefers-color-scheme: dark) {
font-size: 16pt; 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) { @media (prefers-color-scheme: dark) {
background-color: #000000; display: inline-block;
color: white;
} }
border: 1px solid darkgray;
} }
#btnGetGps { &.light {
img { @media (prefers-color-scheme: dark) {
display: none;
&.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);
}
} }
} }
} }
input, &.active {
button { background-color: black;
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: #000000; background-color: white;
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;
@@ -104,19 +93,19 @@ body {
@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 {

View File

@@ -11,4 +11,5 @@
@import 'radar'; @import 'radar';
@import 'regional-forecast'; @import 'regional-forecast';
@import 'almanac'; @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/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/modules/marineforecast.mjs"></script>
<script type="module" src="scripts/index.mjs"></script> <script type="module" src="scripts/index.mjs"></script>
<!-- data --> <!-- data -->
@@ -60,14 +60,13 @@
<div id="divQuery"> <div id="divQuery">
<input id="txtAddress" type="text" value="" placeholder="Zip or City, State" /> <form id="frmGetLatLng">
<div class="buttons"> <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>
<button id="btnGetGps" type="button" title="Get GPS Location"><img src="images/nav/ic_gps_fixed_black_18dp_1x.png" class="light"/> <input id="btnGetLatLng" type="submit" value="GO" />
<img src="images/nav/ic_gps_fixed_white_18dp_1x.png" class="dark"/> <input id="btnClearQuery" type="reset" value="Reset" />
</button> </form>
<button id="btnGetLatLng" type="submit">GO</button> <div id="divLat"></div>
<button id="btnClearQuery" type="reset">Reset</button> <div id="divLng"></div>
</div>
</div> </div>
<div id="version" style="display:none"> <div id="version" style="display:none">
<%- version %> <%- version %>
@@ -108,6 +107,9 @@
</div> </div>
<div id="almanac-html" class="weather-display"> <div id="almanac-html" class="weather-display">
<%- include('partials/almanac.ejs') %> <%- include('partials/almanac.ejs') %>
</div>
<div id="marine-forecast-html" class="weather-display">
<%- include('partials/marine-forecast.ejs') %>
</div> </div>
<div id="extended-forecast-html" class="weather-display"> <div id="extended-forecast-html" class="weather-display">
<%- include('partials/extended-forecast.ejs') %> <%- include('partials/extended-forecast.ejs') %>

View File

@@ -1,4 +1,4 @@
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true, hasTime: true}) %> <%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: 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

@@ -1,4 +1,4 @@
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true, hasTime: true }) %> <%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: 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">

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" "editor.defaultFormatter": "j69.ejs-beautify"
}, },
"files.exclude": {}, "files.exclude": {},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}, },
} }