mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-14 23:59:30 -07:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7505e3e6f | ||
|
|
705fa9f582 | ||
|
|
5edf5cc947 | ||
|
|
d0382e0de1 | ||
|
|
69d14236f1 | ||
|
|
64fb06d7b4 | ||
|
|
3ea6c0fd55 | ||
|
|
e13582b760 | ||
|
|
1a7734b620 | ||
|
|
0331de8b8a |
@@ -31,7 +31,7 @@ Open your web browser: http://localhost:8080/
|
||||
The change to 5.0 changes from drawing the weather graphics on canvas elements and instead uses HTML and CSS to style all of the weather graphics. A lot of other changes and fixes were implemented at the same time.
|
||||
|
||||
* Replace all canvas elements with HTML and CSS
|
||||
* City and airport names are better parsed to better show location in the available space
|
||||
* City and airport names are better parsed to fit the available space
|
||||
* Remove the dependency on libgif-js
|
||||
* Use browser for text wrapping where necessary
|
||||
* Some new weather icons
|
||||
@@ -61,14 +61,15 @@ The fork is a result of wanting a more manageable, modern code base to work with
|
||||
|
||||
I've made several changes to this Weather Star 4000 simulation compared to the original hardware unit and the code that this was forked from.
|
||||
|
||||
* Radar displays the timestamp of the image.
|
||||
* A new hour-by-hour graph of the temperature, cloud cover and precipitation chances for the next 24 hours.
|
||||
* A new hourly forecast display for the next 24 hours is available, and is shown in the style of the travel cities forecast. (off by default because it duplicates the hourly graph)
|
||||
* The "Local Forecast" and "Extended Forecast" provide several additional days of information compared to the original format in the 90's.
|
||||
* Narration was removed. In the original code narration made use of the computer's local text-to-speech engine which didn't sound great.
|
||||
* Music was removed. I don't want to deal with copyright issues and hosting MP3s. If you're looking for the music that played during forecasts please visit [TWCClassics](https://twcclassics.com/audio/).
|
||||
* Marine forecast (tides) is not available as it is not part of the new API.
|
||||
* The nearby cities displayed on screens such as "Latest Observations" and "Regional Forecast" are likely not the same as they were in the 90's. The weather monitoring equipment at these stations move over time for one reason or another, and coming up with a simple formulaic way of finding nearby stations is sufficient to give the same look-and-feel as the original.
|
||||
* The "Local Forecast" and "Extended Forecast" provide several additional days of information compared to the original format in the 90's.
|
||||
* "Flavors" are not present in this simulation. Flavors refer to the order of the weather information that was shown on the original units. Instead, the order of the displays has been fixed and a checkboxes can be used to turn on and off individual displays. The travel forecast has been defaulted to off so only local information shows for new users.
|
||||
* Radar displays the timestamp of the image.
|
||||
* A new hourly forecast display for the next 24 hours is available, and is shown in the style of the travel cities forecast.
|
||||
|
||||
## Wish list
|
||||
|
||||
|
||||
2
dist/index.html
vendored
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.css
vendored
2
dist/resources/ws.min.css
vendored
File diff suppressed because one or more lines are too long
3
dist/resources/ws.min.js
vendored
3
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
1
dist/resources/ws.min.js.map
vendored
1
dist/resources/ws.min.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -77,6 +77,7 @@ const mjsSources = [
|
||||
'server/scripts/modules/icons.mjs',
|
||||
'server/scripts/modules/extendedforecast.mjs',
|
||||
'server/scripts/modules/hourly.mjs',
|
||||
'server/scripts/modules/hourly-graph.mjs',
|
||||
'server/scripts/modules/latestobservations.mjs',
|
||||
'server/scripts/modules/localforecast.mjs',
|
||||
'server/scripts/modules/radar.mjs',
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.2.0",
|
||||
"version": "5.4.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ws4kp",
|
||||
"version": "5.2.0",
|
||||
"version": "5.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^8.21.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.2.0",
|
||||
"version": "5.4.0",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
BIN
server/images/BackGround1_1_Chart.png
Normal file
BIN
server/images/BackGround1_1_Chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
server/images/nav/ic_gps_fixed_white_18dp_1x.png
Normal file
BIN
server/images/nav/ic_gps_fixed_white_18dp_1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
server/images/nav/ic_gps_fixed_white_18dp_2x.png
Normal file
BIN
server/images/nav/ic_gps_fixed_white_18dp_2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
server/images/nav/ic_gps_fixed_white_24dp_1x.png
Normal file
BIN
server/images/nav/ic_gps_fixed_white_24dp_1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
server/images/nav/ic_gps_fixed_whte_24dp_2x.png
Normal file
BIN
server/images/nav/ic_gps_fixed_whte_24dp_2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
@@ -23,7 +23,7 @@ const categories = [
|
||||
'Airport', 'Ferry', 'Marina', 'Pier', 'Port', 'Resort', // POI/Travel
|
||||
'Postal', 'Populated Place',
|
||||
];
|
||||
const cats = categories.join(',');
|
||||
const category = categories.join(',');
|
||||
|
||||
const init = () => {
|
||||
document.getElementById('txtAddress').addEventListener('focus', (e) => {
|
||||
@@ -54,7 +54,7 @@ const init = () => {
|
||||
params: {
|
||||
f: 'json',
|
||||
countryCode: 'USA', // 'USA,PRI,VIR,GUM,ASM',
|
||||
category: cats,
|
||||
category,
|
||||
maxSuggestions: 10,
|
||||
},
|
||||
dataType: 'json',
|
||||
@@ -192,8 +192,10 @@ const EnterFullScreen = () => {
|
||||
resize();
|
||||
UpdateFullScreenNavigate();
|
||||
|
||||
// change hover text
|
||||
document.getElementById('ToggleFullScreen').title = 'Exit fullscreen';
|
||||
// change hover text and image
|
||||
const img = document.getElementById('ToggleFullScreen');
|
||||
img.src = 'images/nav/ic_fullscreen_exit_white_24dp_1x.png';
|
||||
img.title = 'Exit fullscreen';
|
||||
};
|
||||
|
||||
const ExitFullscreen = () => {
|
||||
@@ -214,8 +216,10 @@ const ExitFullscreen = () => {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
resize();
|
||||
// change hover text
|
||||
document.getElementById('ToggleFullScreen').title = 'Enter fullscreen';
|
||||
// change hover text and image
|
||||
const img = document.getElementById('ToggleFullScreen');
|
||||
img.src = 'images/nav/ic_fullscreen_white_24dp_1x.png';
|
||||
img.title = 'Enter fullscreen';
|
||||
};
|
||||
|
||||
const btnNavigateMenuClick = () => {
|
||||
|
||||
@@ -171,7 +171,7 @@ class Almanac extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display
|
||||
const display = new Almanac(7, 'almanac');
|
||||
const display = new Almanac(8, 'almanac');
|
||||
registerDisplay(display);
|
||||
|
||||
export default display.getSun.bind(display);
|
||||
|
||||
@@ -55,7 +55,9 @@ class CurrentWeather extends WeatherDisplay {
|
||||
// test for data received
|
||||
if (!observations) {
|
||||
console.error('All current weather stations exhausted');
|
||||
this.setStatus(STATUS.failed);
|
||||
if (this.enabled) this.setStatus(STATUS.failed);
|
||||
// send failed to subscribers
|
||||
this.getDataCallback(undefined);
|
||||
return;
|
||||
}
|
||||
// preload the icon
|
||||
|
||||
@@ -161,4 +161,4 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display
|
||||
registerDisplay(new ExtendedForecast(6, 'extended-forecast'));
|
||||
registerDisplay(new ExtendedForecast(7, 'extended-forecast'));
|
||||
|
||||
149
server/scripts/modules/hourly-graph.mjs
Normal file
149
server/scripts/modules/hourly-graph.mjs
Normal file
@@ -0,0 +1,149 @@
|
||||
// hourly forecast list
|
||||
|
||||
import STATUS from './status.mjs';
|
||||
import getHourlyData from './hourly.mjs';
|
||||
import WeatherDisplay from './weatherdisplay.mjs';
|
||||
import { registerDisplay } from './navigation.mjs';
|
||||
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||
|
||||
class HourlyGraph extends WeatherDisplay {
|
||||
constructor(navId, elemId, defaultActive) {
|
||||
// special height and width for scrolling
|
||||
super(navId, elemId, 'Hourly Graph', defaultActive);
|
||||
|
||||
// move the top right data into the correct location on load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.moveHeader();
|
||||
});
|
||||
}
|
||||
|
||||
moveHeader() {
|
||||
// get the header
|
||||
const header = this.fillTemplate('top-right', {});
|
||||
// place the header
|
||||
this.elem.querySelector('.header .right').append(header);
|
||||
}
|
||||
|
||||
async getData() {
|
||||
if (!super.getData()) return;
|
||||
|
||||
const data = await getHourlyData();
|
||||
if (data === undefined) {
|
||||
this.setStatus(STATUS.failed);
|
||||
return;
|
||||
}
|
||||
|
||||
// get interesting data
|
||||
const temperature = data.map((d) => d.temperature);
|
||||
const probabilityOfPrecipitation = data.map((d) => d.probabilityOfPrecipitation);
|
||||
const skyCover = data.map((d) => d.skyCover);
|
||||
|
||||
this.data = {
|
||||
skyCover, temperature, probabilityOfPrecipitation,
|
||||
};
|
||||
|
||||
this.setStatus(STATUS.loaded);
|
||||
}
|
||||
|
||||
drawCanvas() {
|
||||
if (!this.image) this.image = this.elem.querySelector('.chart img');
|
||||
|
||||
// get available space
|
||||
const availableWidth = 532;
|
||||
const availableHeight = 285;
|
||||
|
||||
this.image.width = availableWidth;
|
||||
this.image.height = availableHeight;
|
||||
|
||||
// get context
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = availableWidth;
|
||||
canvas.height = availableHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
// calculate time scale
|
||||
const timeScale = calcScale(0, 5, this.data.temperature.length - 1, availableWidth);
|
||||
const startTime = DateTime.now().startOf('hour');
|
||||
document.querySelector('.x-axis .l-1').innerHTML = formatTime(startTime);
|
||||
document.querySelector('.x-axis .l-2').innerHTML = formatTime(startTime.plus({ hour: 6 }));
|
||||
document.querySelector('.x-axis .l-3').innerHTML = formatTime(startTime.plus({ hour: 12 }));
|
||||
document.querySelector('.x-axis .l-4').innerHTML = formatTime(startTime.plus({ hour: 18 }));
|
||||
document.querySelector('.x-axis .l-5').innerHTML = formatTime(startTime.plus({ hour: 24 }));
|
||||
|
||||
// order is important last line drawn is on top
|
||||
// clouds
|
||||
const percentScale = calcScale(0, availableHeight - 10, 100, 10);
|
||||
const cloud = createPath(this.data.skyCover, timeScale, percentScale);
|
||||
drawPath(cloud, ctx, {
|
||||
strokeStyle: 'lightgrey',
|
||||
lineWidth: 3,
|
||||
});
|
||||
|
||||
// precip
|
||||
const precip = createPath(this.data.probabilityOfPrecipitation, timeScale, percentScale);
|
||||
drawPath(precip, ctx, {
|
||||
strokeStyle: 'aqua',
|
||||
lineWidth: 3,
|
||||
});
|
||||
|
||||
// temperature
|
||||
const minTemp = Math.min(...this.data.temperature);
|
||||
const maxTemp = Math.max(...this.data.temperature);
|
||||
const midTemp = Math.round((minTemp + maxTemp) / 2);
|
||||
const tempScale = calcScale(minTemp, availableHeight - 10, maxTemp, 10);
|
||||
const tempPath = createPath(this.data.temperature, timeScale, tempScale);
|
||||
drawPath(tempPath, ctx, {
|
||||
strokeStyle: 'red',
|
||||
lineWidth: 3,
|
||||
});
|
||||
|
||||
// temperature axis labels
|
||||
// limited to 3 characters, sacraficing degree character
|
||||
const degree = String.fromCharCode(176);
|
||||
this.elem.querySelector('.y-axis .l-1').innerHTML = (maxTemp + degree).substring(0, 3);
|
||||
this.elem.querySelector('.y-axis .l-2').innerHTML = (midTemp + degree).substring(0, 3);
|
||||
this.elem.querySelector('.y-axis .l-3').innerHTML = (minTemp + degree).substring(0, 3);
|
||||
|
||||
// set the image source
|
||||
this.image.src = canvas.toDataURL();
|
||||
|
||||
super.drawCanvas();
|
||||
this.finishDraw();
|
||||
}
|
||||
}
|
||||
|
||||
// create a scaling function from two points
|
||||
const calcScale = (x1, y1, x2, y2) => {
|
||||
const m = (y2 - y1) / (x2 - x1);
|
||||
const b = y1 - m * x1;
|
||||
return (x) => m * x + b;
|
||||
};
|
||||
|
||||
// create a path as an array of [x,y]
|
||||
const createPath = (data, xScale, yScale) => data.map((d, i) => [xScale(i), yScale(d)]);
|
||||
|
||||
// draw a path with shadow
|
||||
const drawPath = (path, ctx, options) => {
|
||||
// first shadow
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.lineWidth = (options?.lineWidth ?? 2) + 2;
|
||||
ctx.moveTo(path[0][0], path[0][1]);
|
||||
path.slice(1).forEach((point) => ctx.lineTo(point[0], point[1] + 2));
|
||||
ctx.stroke();
|
||||
|
||||
// then colored line
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = options?.strokeStyle ?? 'red';
|
||||
ctx.lineWidth = (options?.lineWidth ?? 2);
|
||||
ctx.moveTo(path[0][0], path[0][1]);
|
||||
path.slice(1).forEach((point) => ctx.lineTo(point[0], point[1]));
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
// format as 1p, 12a, etc.
|
||||
const formatTime = (time) => time.toFormat('ha').slice(0, -1);
|
||||
|
||||
// register display
|
||||
registerDisplay(new HourlyGraph(3, 'hourly-graph'));
|
||||
@@ -29,7 +29,7 @@ class Hourly extends WeatherDisplay {
|
||||
|
||||
async getData(weatherParameters) {
|
||||
// super checks for enabled
|
||||
if (!super.getData(weatherParameters)) return;
|
||||
const superResponse = super.getData(weatherParameters);
|
||||
let forecast;
|
||||
try {
|
||||
// get the forecast
|
||||
@@ -37,11 +37,15 @@ class Hourly extends WeatherDisplay {
|
||||
} catch (e) {
|
||||
console.error('Get hourly forecast failed');
|
||||
console.error(e.status, e.responseJSON);
|
||||
this.setStatus(STATUS.failed);
|
||||
if (this.enabled) this.setStatus(STATUS.failed);
|
||||
// return undefined to other subscribers
|
||||
this.getDataCallback(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = await Hourly.parseForecast(forecast.properties);
|
||||
this.getDataCallback();
|
||||
if (!superResponse) return;
|
||||
|
||||
this.setStatus(STATUS.loaded);
|
||||
this.drawLongCanvas();
|
||||
@@ -66,6 +70,8 @@ class Hourly extends WeatherDisplay {
|
||||
apparentTemperature: celsiusToFahrenheit(apparentTemperature[idx]),
|
||||
windSpeed: kilometersToMiles(windSpeed[idx]),
|
||||
windDirection: directionToNSEW(windDirection[idx]),
|
||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||
skyCover: skyCover[idx],
|
||||
icon: icons[idx],
|
||||
}));
|
||||
}
|
||||
@@ -184,7 +190,20 @@ class Hourly extends WeatherDisplay {
|
||||
return dayName;
|
||||
}, '');
|
||||
}
|
||||
|
||||
// make data available outside this class
|
||||
// promise allows for data to be requested before it is available
|
||||
async getCurrentData() {
|
||||
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 Hourly(2, 'hourly'));
|
||||
const display = new Hourly(2, 'hourly', false);
|
||||
registerDisplay(display);
|
||||
|
||||
export default display.getCurrentData.bind(display);
|
||||
|
||||
@@ -93,4 +93,4 @@ class LocalForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display
|
||||
registerDisplay(new LocalForecast(5, 'local-forecast'));
|
||||
registerDisplay(new LocalForecast(6, 'local-forecast'));
|
||||
|
||||
@@ -23,6 +23,7 @@ let AutoRefreshCountMs = 0;
|
||||
const init = async () => {
|
||||
// set up resize handler
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
// auto refresh
|
||||
const TwcAutoRefresh = localStorage.getItem('TwcAutoRefresh');
|
||||
@@ -281,7 +282,7 @@ const generateCheckboxes = () => {
|
||||
|
||||
if (!availableDisplays) return;
|
||||
// generate checkboxes
|
||||
const checkboxes = displays.map((d) => d.generateCheckbox()).filter((d) => d);
|
||||
const checkboxes = displays.map((d) => d.generateCheckbox(d.defaultEnabled)).filter((d) => d);
|
||||
|
||||
// write to page
|
||||
availableDisplays.innerHTML = '';
|
||||
|
||||
@@ -367,8 +367,6 @@ class Radar extends WeatherDisplay {
|
||||
}
|
||||
|
||||
RadarContext.putImageData(RadarImageData, 0, 0);
|
||||
|
||||
// MapContext.drawImage(RadarContext.canvas, 0, 0);
|
||||
}
|
||||
|
||||
static mergeDopplerRadarImage(mapContext, radarContext) {
|
||||
@@ -402,4 +400,4 @@ class Radar extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display
|
||||
registerDisplay(new Radar(8, 'radar'));
|
||||
registerDisplay(new Radar(9, 'radar'));
|
||||
|
||||
@@ -389,4 +389,4 @@ class RegionalForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display
|
||||
registerDisplay(new RegionalForecast(4, 'regional-forecast'));
|
||||
registerDisplay(new RegionalForecast(5, 'regional-forecast'));
|
||||
|
||||
@@ -160,4 +160,4 @@ class TravelForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// register display, not active by default
|
||||
registerDisplay(new TravelForecast(3, 'travel', false));
|
||||
registerDisplay(new TravelForecast(4, 'travel', false));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
150
server/styles/scss/_hourly-graph.scss
Normal file
150
server/styles/scss/_hourly-graph.scss
Normal file
@@ -0,0 +1,150 @@
|
||||
@use 'shared/_colors'as c;
|
||||
@use 'shared/_utils'as u;
|
||||
|
||||
#hourly-graph-html {
|
||||
background-image: url(../images/BackGround1_1_Chart.png);
|
||||
|
||||
.header {
|
||||
.right {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
right: 60px;
|
||||
width: 360px;
|
||||
font-family: 'Star4000 Small';
|
||||
font-size: 32px;
|
||||
@include u.text-shadow();
|
||||
text-align: right;
|
||||
|
||||
div {
|
||||
margin-top: -18px;
|
||||
}
|
||||
|
||||
.temperature {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
.rain {
|
||||
color: aqua;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weather-display .main.hourly-graph {
|
||||
|
||||
&.main {
|
||||
>div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: 'Star4000 Small';
|
||||
font-size: 24pt;
|
||||
color: c.$column-header-text;
|
||||
@include u.text-shadow();
|
||||
margin-top: -15px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.x-axis {
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 640px;
|
||||
height: 20px;
|
||||
|
||||
.label {
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
|
||||
&.l-1 {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
&.l-2 {
|
||||
left: 158px;
|
||||
}
|
||||
|
||||
&.l-3 {
|
||||
left: 291px;
|
||||
}
|
||||
|
||||
&.l-4 {
|
||||
left: 424px;
|
||||
}
|
||||
|
||||
&.l-5 {
|
||||
left: 557px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.chart {
|
||||
top: 0px;
|
||||
left: 50px;
|
||||
|
||||
img {
|
||||
width: 532px;
|
||||
height: 285px;
|
||||
}
|
||||
}
|
||||
|
||||
.y-axis {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 50px;
|
||||
height: 285px;
|
||||
|
||||
.label {
|
||||
text-align: right;
|
||||
right: 0px;
|
||||
|
||||
&.l-1 {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&.l-2 {
|
||||
top: 140px;
|
||||
}
|
||||
|
||||
&.l-3 {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-headers {
|
||||
background-color: c.$column-header;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.column-headers {
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
z-index: 5;
|
||||
|
||||
|
||||
.temp {
|
||||
left: 355px;
|
||||
}
|
||||
|
||||
.like {
|
||||
left: 435px;
|
||||
}
|
||||
|
||||
.wind {
|
||||
left: 535px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,17 @@
|
||||
|
||||
body {
|
||||
font-family: "Star4000";
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: lightblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
@@ -20,12 +31,42 @@ button {
|
||||
#txtAddress {
|
||||
width: 490px;
|
||||
font-size: 16pt;
|
||||
|
||||
@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 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete-suggestions {
|
||||
@@ -90,6 +131,11 @@ button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #000000;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: rgb(48, 48, 48);
|
||||
}
|
||||
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -269,12 +315,6 @@ jsgif {
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
#container canvas {
|
||||
/* position: absolute; */
|
||||
width: 100%;
|
||||
/* max-width: 640px; */
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@import 'current-weather';
|
||||
@import 'extended-forecast';
|
||||
@import 'hourly';
|
||||
@import 'hourly-graph';
|
||||
@import 'travel';
|
||||
@import 'latest-observations';
|
||||
@import 'local-forecast';
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<script type="module" src="scripts/modules/almanac.mjs"></script>
|
||||
<script type="module" src="scripts/modules/icons.mjs"></script>
|
||||
<script type="module" src="scripts/modules/extendedforecast.mjs"></script>
|
||||
<script type="module" src="scripts/modules/hourly-graph.mjs"></script>
|
||||
<script type="module" src="scripts/modules/hourly.mjs"></script>
|
||||
<script type="module" src="scripts/modules/latestobservations.mjs"></script>
|
||||
<script type="module" src="scripts/modules/localforecast.mjs"></script>
|
||||
@@ -60,7 +61,7 @@
|
||||
|
||||
<div id="divQuery">
|
||||
<form id="frmGetLatLng">
|
||||
<input id="txtAddress" type="text" value="" placeholder="Zip or City, State" /><button id="btnGetGps" type="button" title="Get GPS Location"><img id="imgGetGps" src="images/nav/ic_gps_fixed_black_18dp_1x.png" /></button>
|
||||
<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>
|
||||
<input id="btnGetLatLng" type="submit" value="GO" />
|
||||
<input id="btnClearQuery" type="reset" value="Reset" />
|
||||
</form>
|
||||
@@ -90,6 +91,9 @@
|
||||
<div id="hourly-html" class="weather-display">
|
||||
<%- include('partials/hourly.ejs') %>
|
||||
</div>
|
||||
<div id="hourly-graph-html" class="weather-display">
|
||||
<%- include('partials/hourly-graph.ejs') %>
|
||||
</div>
|
||||
<div id="travel-html" class="weather-display">
|
||||
<%- include('partials/travel.ejs') %>
|
||||
</div>
|
||||
@@ -126,7 +130,7 @@
|
||||
<img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh" />
|
||||
</div>
|
||||
<div id="divTwcBottomRight">
|
||||
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_exit_white_24dp_1x.png" title="Enter Fullscreen" />
|
||||
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_1x.png" title="Enter Fullscreen" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<% if (locals?.hasTime) { %>
|
||||
<div class="date-time date"></div>
|
||||
<div class="date-time time"></div>
|
||||
<% } else if (!locals?.noaaLogo) { %>
|
||||
<div class="right"></div>
|
||||
<% } %>
|
||||
<% if (locals?.noaaLogo) { %>
|
||||
<div class="noaa-logo">
|
||||
|
||||
24
views/partials/hourly-graph.ejs
Normal file
24
views/partials/hourly-graph.ejs
Normal file
@@ -0,0 +1,24 @@
|
||||
<%- include('header.ejs', {title: 'Hourly Graph' , hasTime: false }) %>
|
||||
<div class="main has-scroll hourly-graph">
|
||||
<div class="top-right template ">
|
||||
<div class="temperature">Temperature</div>
|
||||
<div class="cloud">Cloud %</div>
|
||||
<div class="rain">Precip %</div>
|
||||
</div>
|
||||
<div class="y-axis">
|
||||
<div class="label l-1">75</div>
|
||||
<div class="label l-2">65</div>
|
||||
<div class="label l-3">55</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img id="chart-area"></img>
|
||||
</div>
|
||||
<div class="x-axis">
|
||||
<div class="label l-1">12a</div>
|
||||
<div class="label l-2">6a</div>
|
||||
<div class="label l-3">12p</div>
|
||||
<div class="label l-4">6p</div>
|
||||
<div class="label l-5">12a</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('scroll.ejs') %>
|
||||
@@ -1,43 +1,43 @@
|
||||
<div class="header">
|
||||
<div class="logo"><img src="images/Logo3.png" /></div>
|
||||
<div class="title dual">
|
||||
<div class="top">
|
||||
Local
|
||||
</div>
|
||||
<div class="bottom">
|
||||
Radar
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="precip">
|
||||
<div class="precip-header">PRECIP</div>
|
||||
<div class="scale">
|
||||
<div class="text">Light</div>
|
||||
<div class="scale-table">
|
||||
<div class="box box-1"></div>
|
||||
<div class="box box-2"></div>
|
||||
<div class="box box-3"></div>
|
||||
<div class="box box-4"></div>
|
||||
<div class="box box-5"></div>
|
||||
<div class="box box-6"></div>
|
||||
<div class="box box-7"></div>
|
||||
<div class="box box-7"></div>
|
||||
</div>
|
||||
<div class="text">Heavy</div>
|
||||
</div>
|
||||
<div class="time"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logo"><img src="images/Logo3.png" /></div>
|
||||
<div class="title dual">
|
||||
<div class="top">
|
||||
Local
|
||||
</div>
|
||||
<div class="bottom">
|
||||
Radar
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="precip">
|
||||
<div class="precip-header">PRECIP</div>
|
||||
<div class="scale">
|
||||
<div class="text">Light</div>
|
||||
<div class="scale-table">
|
||||
<div class="box box-1"></div>
|
||||
<div class="box box-2"></div>
|
||||
<div class="box box-3"></div>
|
||||
<div class="box box-4"></div>
|
||||
<div class="box box-5"></div>
|
||||
<div class="box box-6"></div>
|
||||
<div class="box box-7"></div>
|
||||
<div class="box box-7"></div>
|
||||
</div>
|
||||
<div class="text">Heavy</div>
|
||||
</div>
|
||||
<div class="time"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main radar">
|
||||
<div class="container">
|
||||
<div class="scroll-area">
|
||||
<div class="frame template">
|
||||
<div class="map">
|
||||
<img src="images/4000RadarMap2.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="scroll-area">
|
||||
<div class="frame template">
|
||||
<div class="map">
|
||||
<img src="images/4000RadarMap2.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user