mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 17:19:30 -07:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f17f69f60e | ||
|
|
fa16095355 | ||
|
|
cc3dbeb043 | ||
|
|
8ee1e954eb | ||
|
|
bfc4bddfef | ||
|
|
567325e3c5 | ||
|
|
4903b95fec | ||
|
|
b43fb32820 | ||
|
|
0d0c4ec452 | ||
|
|
093b6ac239 | ||
|
|
517c560ef6 |
@@ -2,7 +2,7 @@ FROM node:24-alpine
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --omit=dev --legacy-peer-deps
|
RUN npm ci --legacy-peer-deps
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -32,6 +32,18 @@ From a learning standpoint, this codebase make use of a lot of different methods
|
|||||||
* Hand written CSS made easier to mange with SASS
|
* Hand written CSS made easier to mange with SASS
|
||||||
* A linting library to keep code style consistent
|
* A linting library to keep code style consistent
|
||||||
|
|
||||||
|
## Quck Start
|
||||||
|
|
||||||
|
Ensure you have Node installed.
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/netbymatt/ws4kp.git
|
||||||
|
cd ws4kp
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Open your browser and navigate to https://localhost:8080
|
||||||
|
|
||||||
## Does WeatherStar 4000+ work outside of the USA?
|
## Does WeatherStar 4000+ work outside of the USA?
|
||||||
|
|
||||||
This project is tightly coupled to [NOAA's Weather API](https://www.weather.gov/documentation/services-web-api), which is exclusive to the United States. Using NOAA's Weather API is a crucial requirement to provide an authentic WeatherStar 4000+ experience.
|
This project is tightly coupled to [NOAA's Weather API](https://www.weather.gov/documentation/services-web-api), which is exclusive to the United States. Using NOAA's Weather API is a crucial requirement to provide an authentic WeatherStar 4000+ experience.
|
||||||
@@ -57,14 +69,7 @@ WeatherStar 4000+ supports two deployment modes:
|
|||||||
* Browser-based caching
|
* Browser-based caching
|
||||||
* Used by: static file hosting and default `Dockerfile`
|
* Used by: static file hosting and default `Dockerfile`
|
||||||
|
|
||||||
## Run Your WeatherStar
|
## Other methods to run Ws4kp
|
||||||
|
|
||||||
Ensure you have Node installed. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/netbymatt/ws4kp.git
|
|
||||||
cd ws4kp
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Mode (individual JS files, easier debugging)
|
### Development Mode (individual JS files, easier debugging)
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -97,23 +97,27 @@ const copyCss = () => src(cssSources)
|
|||||||
const htmlSources = [
|
const htmlSources = [
|
||||||
'views/*.ejs',
|
'views/*.ejs',
|
||||||
];
|
];
|
||||||
const compressHtml = async () => {
|
const packageJson = await readFile('package.json');
|
||||||
const packageJson = await readFile('package.json');
|
let { version } = JSON.parse(packageJson);
|
||||||
const { version } = JSON.parse(packageJson);
|
const previewVersion = async () => {
|
||||||
|
// generate a relatively unique timestamp for cache invalidation of the preview site
|
||||||
return src(htmlSources)
|
const now = new Date();
|
||||||
.pipe(ejs({
|
const msNow = now.getTime() % 1_000_000;
|
||||||
production: version,
|
version = msNow.toString();
|
||||||
serverAvailable: false,
|
|
||||||
version,
|
|
||||||
OVERRIDES,
|
|
||||||
query: {},
|
|
||||||
}))
|
|
||||||
.pipe(rename({ extname: '.html' }))
|
|
||||||
.pipe(htmlmin({ collapseWhitespace: true }))
|
|
||||||
.pipe(dest('./dist'));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const compressHtml = async () => src(htmlSources)
|
||||||
|
.pipe(ejs({
|
||||||
|
production: version,
|
||||||
|
serverAvailable: false,
|
||||||
|
version,
|
||||||
|
OVERRIDES,
|
||||||
|
query: {},
|
||||||
|
}))
|
||||||
|
.pipe(rename({ extname: '.html' }))
|
||||||
|
.pipe(htmlmin({ collapseWhitespace: true }))
|
||||||
|
.pipe(dest('./dist'));
|
||||||
|
|
||||||
const otherFiles = [
|
const otherFiles = [
|
||||||
'server/robots.txt',
|
'server/robots.txt',
|
||||||
'server/manifest.json',
|
'server/manifest.json',
|
||||||
@@ -205,7 +209,7 @@ const buildDist = series(clean, parallel(buildJs, compressJsVendor, copyCss, com
|
|||||||
// upload_images could be in parallel with upload, but _images logs a lot and has little changes
|
// 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
|
// by running upload last the majority of the changes will be at the bottom of the log for easy viewing
|
||||||
const publishFrontend = series(buildDist, uploadImages, upload, invalidate);
|
const publishFrontend = series(buildDist, uploadImages, upload, invalidate);
|
||||||
const stageFrontend = series(buildDist, uploadImagesPreview, uploadPreview, invalidatePreview);
|
const stageFrontend = series(previewVersion, buildDist, uploadImagesPreview, uploadPreview, invalidatePreview);
|
||||||
|
|
||||||
export default publishFrontend;
|
export default publishFrontend;
|
||||||
|
|
||||||
|
|||||||
3653
package-lock.json
generated
3653
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.1.6",
|
"version": "6.1.9",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"ejs": "^3.1.5",
|
"ejs": "^3.1.5",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"metar-taf-parser": "^9.0.0"
|
"metar-taf-parser": "^9.0.0",
|
||||||
|
"npm": "^11.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
server/images/backgrounds/7-wide.png
Normal file
BIN
server/images/backgrounds/7-wide.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
server/images/backgrounds/7.png
Normal file
BIN
server/images/backgrounds/7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -97,6 +97,12 @@ const drawScreen = async () => {
|
|||||||
if (elem.parentElement.id === 'progress-html') return;
|
if (elem.parentElement.id === 'progress-html') return;
|
||||||
thisScreen?.classes?.forEach((cls) => elem.classList.add(cls));
|
thisScreen?.classes?.forEach((cls) => elem.classList.add(cls));
|
||||||
});
|
});
|
||||||
|
// special case for red background on hazard scroll
|
||||||
|
const mainScrollBg = document.getElementById('scroll-bg');
|
||||||
|
mainScrollBg.className = '';
|
||||||
|
if (thisScreen?.classes?.includes('hazard')) {
|
||||||
|
mainScrollBg.classList.add('hazard');
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof thisScreen === 'string') {
|
if (typeof thisScreen === 'string') {
|
||||||
// only a string
|
// only a string
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class Hazards extends WeatherDisplay {
|
|||||||
// get the forecast using centralized safe handling
|
// get the forecast using centralized safe handling
|
||||||
const url = new URL('https://api.weather.gov/alerts/active');
|
const url = new URL('https://api.weather.gov/alerts/active');
|
||||||
url.searchParams.append('point', `${this.weatherParameters.latitude},${this.weatherParameters.longitude}`);
|
url.searchParams.append('point', `${this.weatherParameters.latitude},${this.weatherParameters.longitude}`);
|
||||||
|
url.searchParams.append('status', 'actual');
|
||||||
const alerts = await safeJson(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
|
const alerts = await safeJson(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
|
||||||
|
|
||||||
if (!alerts) {
|
if (!alerts) {
|
||||||
|
|||||||
@@ -3,13 +3,32 @@ import { parseMetar } from '../../vendor/auto/metar-taf-parser.mjs';
|
|||||||
// eslint-disable-next-line import/extensions
|
// eslint-disable-next-line import/extensions
|
||||||
import en from '../../vendor/auto/locale/en.js';
|
import en from '../../vendor/auto/locale/en.js';
|
||||||
|
|
||||||
|
// metar-taf-parser requires regex lookbehind
|
||||||
|
// this does not work in iOS < 16.4
|
||||||
|
// this is a detection algorithm for iOS versions
|
||||||
|
const isIos = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
|
||||||
|
let iosVersionOk = false;
|
||||||
|
if (isIos) {
|
||||||
|
// regex match the version string
|
||||||
|
const iosVersionRaw = /OS (\d+)_(\d+)/.exec(window.navigator.userAgent);
|
||||||
|
// check for match
|
||||||
|
if (iosVersionRaw) {
|
||||||
|
// break into parts
|
||||||
|
const iosVersionMajor = parseInt(iosVersionRaw[1], 10);
|
||||||
|
const iosVersionMinor = parseInt(iosVersionRaw[2], 10);
|
||||||
|
if (iosVersionMajor > 16) iosVersionOk = true;
|
||||||
|
if (iosVersionMajor === 16 && iosVersionMinor >= 4) iosVersionOk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Augment observation data by parsing METAR when API fields are missing
|
* Augment observation data by parsing METAR when API fields are missing
|
||||||
* @param {Object} observation - The observation object from the API
|
* @param {Object} observation - The observation object from the API
|
||||||
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
||||||
*/
|
*/
|
||||||
const augmentObservationWithMetar = (observation) => {
|
const augmentObservationWithMetar = (observation) => {
|
||||||
if (!observation?.rawMessage) {
|
// check for a metar message and for unusable ios versions
|
||||||
|
if (!observation?.rawMessage || (isIos && !iosVersionOk)) {
|
||||||
return observation;
|
return observation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,9 @@
|
|||||||
@use 'shared/_colors' as c;
|
@use 'shared/_colors'as c;
|
||||||
@use 'shared/_utils' as u;
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
#hazards-html.weather-display {
|
||||||
|
background-image: url('../images/backgrounds/7.png');
|
||||||
|
}
|
||||||
|
|
||||||
.weather-display .main.hazards {
|
.weather-display .main.hazards {
|
||||||
&.main {
|
&.main {
|
||||||
@@ -7,6 +11,7 @@
|
|||||||
height: 480px;
|
height: 480px;
|
||||||
background-color: rgb(112, 35, 35);
|
background-color: rgb(112, 35, 35);
|
||||||
|
|
||||||
|
|
||||||
.hazard-lines {
|
.hazard-lines {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
@@ -26,3 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wide.hazards #container {
|
||||||
|
background: url(../images/backgrounds/7-wide.png);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@use 'shared/_utils' as u;
|
@use 'shared/_utils'as u;
|
||||||
@use 'shared/_colors' as c;
|
@use 'shared/_colors'as c;
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Star4000";
|
font-family: "Star4000";
|
||||||
@@ -161,6 +161,7 @@ body {
|
|||||||
#divTwcMain {
|
#divTwcMain {
|
||||||
width: 640px;
|
width: 640px;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.wide & {
|
.wide & {
|
||||||
width: 854px;
|
width: 854px;
|
||||||
@@ -813,4 +814,4 @@ body.kiosk #loading .instructions {
|
|||||||
>*:not(#divTwc) {
|
>*:not(#divTwc) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@use 'shared/_colors' as c;
|
@use 'shared/_colors'as c;
|
||||||
@use 'shared/_utils' as u;
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
.weather-display {
|
.weather-display {
|
||||||
width: 640px;
|
width: 640px;
|
||||||
@@ -116,9 +116,11 @@
|
|||||||
.scroll {
|
.scroll {
|
||||||
@include u.text-shadow(3px, 1.5px);
|
@include u.text-shadow(3px, 1.5px);
|
||||||
width: 640px;
|
width: 640px;
|
||||||
height: 70px;
|
height: 77px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&.hazard {
|
&.hazard {
|
||||||
background-color: rgb(112, 35, 35);
|
background-color: rgb(112, 35, 35);
|
||||||
@@ -159,3 +161,18 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#scroll-bg {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
height: 77px;
|
||||||
|
width: 640px;
|
||||||
|
|
||||||
|
&.hazard {
|
||||||
|
background-color: rgb(112, 35, 35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide #scroll-bg {
|
||||||
|
width: 854px;
|
||||||
|
}
|
||||||
@@ -134,6 +134,7 @@
|
|||||||
<%- include('partials/hazards.ejs') %>
|
<%- include('partials/hazards.ejs') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="scroll-bg"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="divTwcBottom">
|
<div id="divTwcBottom">
|
||||||
<div id="divTwcBottomLeft">
|
<div id="divTwcBottomLeft">
|
||||||
|
|||||||
Reference in New Issue
Block a user