Compare commits

..

18 Commits

Author SHA1 Message Date
Matt Walsh
b3faf95e39 5.16.3 2025-04-06 21:35:12 -05:00
Matt Walsh
3a304d7c08 remove debugging refresh rate #74 2025-04-06 21:32:59 -05:00
Matt Walsh
c6918f0c6c 5.16.2 2025-04-06 09:44:32 -05:00
Matt Walsh
fb93ade4d2 refresh data sources close #73 2025-04-06 09:42:58 -05:00
Matt Walsh
6f260a6ac7 5.16.1 2025-04-05 18:11:57 -05:00
Matt Walsh
50e526639c Merge pull request #72 from devalexwhite/main
Fix bug where music playlist does not loop
2025-04-05 18:10:10 -05:00
Alex White
38db5cb6a6 Check currentTrack against availableFiles length 2025-04-05 00:04:37 -04:00
Matt Walsh
8c278928b9 5.16.0 2025-04-02 22:36:38 -05:00
Matt Walsh
130a1bfad3 update to express 5 2025-04-02 22:34:59 -05:00
Matt Walsh
bd6c5430c4 regional forecast silent reload 2025-04-02 22:18:35 -05:00
Matt Walsh
cc896bf18d latest observations silent refresh 2025-04-02 22:10:59 -05:00
Matt Walsh
f7a15a93c6 extended forecast silent refresh 2025-04-02 22:02:12 -05:00
Matt Walsh
0baa31a92c local forecast silent refresh 2025-04-02 20:52:33 -05:00
Matt Walsh
8fa00b34b4 hourly and travel forecast silent reload 2025-04-02 16:45:11 -05:00
Matt Walsh
23cc1a1f7a add refresh flag to getdata funcions 2025-04-02 11:10:58 -05:00
Matt Walsh
b272aa298a Merge branch 'main' into background-reload 2025-03-27 13:12:45 -05:00
Matt Walsh
f0166ec2df removed existing auto-reload 2025-03-24 22:57:30 -05:00
Matt Walsh
1983d025a4 only load custom.js if present 2025-03-24 22:55:07 -05:00
45 changed files with 22230 additions and 27827 deletions

6
.vscode/launch.json vendored
View File

@@ -18,7 +18,7 @@
},
{
"name": "Data:stations",
"program": "${workspaceFolder}/datagenerators/stations.js",
"program": "${workspaceFolder}/datagenerators/stations.mjs",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
@@ -27,7 +27,7 @@
},
{
"name": "Data:regionalcities",
"program": "${workspaceFolder}/datagenerators/regionalcities.js",
"program": "${workspaceFolder}/datagenerators/regionalcities.mjs",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
@@ -36,7 +36,7 @@
},
{
"name": "Data:travelcities",
"program": "${workspaceFolder}/datagenerators/travelcities.js",
"program": "${workspaceFolder}/datagenerators/travelcities.mjs",
"request": "launch",
"skipFiles": [
"<node_internals>/**"

View File

@@ -22,5 +22,8 @@
},
"eslint.validate": [
"javascript"
],
"cSpell.words": [
"Tucsan"
]
}

View File

@@ -19,4 +19,4 @@ const chunk = (data, chunkSize = 10) => {
return chunks;
};
module.exports = chunk;
export default chunk;

View File

@@ -1,8 +1,7 @@
// async https wrapper
import https from 'https';
const https = require('https');
module.exports = (url) => new Promise((resolve, reject) => {
const get = (url) => new Promise((resolve, reject) => {
const headers = {};
headers['user-agent'] = '(WeatherStar 4000+ data generator, ws4000@netbymatt.com)';
@@ -22,3 +21,5 @@ module.exports = (url) => new Promise((resolve, reject) => {
reject(e);
});
});
export default get;

View File

@@ -4,8 +4,8 @@
"lat": 33.749,
"lon": -84.388,
"point": {
"x": 50,
"y": 86,
"x": 51,
"y": 87,
"wfo": "FFC"
}
},
@@ -24,8 +24,8 @@
"lat": 41.9796,
"lon": -87.9045,
"point": {
"x": 65,
"y": 76,
"x": 66,
"y": 77,
"wfo": "LOT"
}
},
@@ -34,8 +34,8 @@
"lat": 41.4995,
"lon": -81.6954,
"point": {
"x": 82,
"y": 64,
"x": 83,
"y": 65,
"wfo": "CLE"
}
},
@@ -44,8 +44,8 @@
"lat": 32.8959,
"lon": -97.0372,
"point": {
"x": 79,
"y": 108,
"x": 80,
"y": 109,
"wfo": "FWD"
}
},
@@ -54,8 +54,8 @@
"lat": 39.7391,
"lon": -104.9847,
"point": {
"x": 62,
"y": 60,
"x": 63,
"y": 61,
"wfo": "BOU"
}
},
@@ -64,8 +64,8 @@
"lat": 42.3314,
"lon": -83.0457,
"point": {
"x": 65,
"y": 33,
"x": 66,
"y": 34,
"wfo": "DTX"
}
},
@@ -94,8 +94,8 @@
"lat": 39.7684,
"lon": -86.158,
"point": {
"x": 57,
"y": 68,
"x": 58,
"y": 69,
"wfo": "IND"
}
},
@@ -104,8 +104,8 @@
"lat": 34.0522,
"lon": -118.2437,
"point": {
"x": 154,
"y": 44,
"x": 155,
"y": 45,
"wfo": "LOX"
}
},
@@ -114,8 +114,8 @@
"lat": 25.7743,
"lon": -80.1937,
"point": {
"x": 109,
"y": 50,
"x": 110,
"y": 51,
"wfo": "MFL"
}
},
@@ -124,8 +124,8 @@
"lat": 44.98,
"lon": -93.2638,
"point": {
"x": 107,
"y": 71,
"x": 108,
"y": 72,
"wfo": "MPX"
}
},
@@ -134,8 +134,8 @@
"lat": 40.78,
"lon": -73.88,
"point": {
"x": 36,
"y": 38,
"x": 37,
"y": 39,
"wfo": "OKX"
}
},
@@ -144,8 +144,8 @@
"lat": 36.8468,
"lon": -76.2852,
"point": {
"x": 89,
"y": 51,
"x": 90,
"y": 52,
"wfo": "AKQ"
}
},
@@ -164,8 +164,8 @@
"lat": 39.9523,
"lon": -75.1638,
"point": {
"x": 49,
"y": 75,
"x": 50,
"y": 76,
"wfo": "PHI"
}
},
@@ -174,8 +174,8 @@
"lat": 40.4406,
"lon": -79.9959,
"point": {
"x": 77,
"y": 65,
"x": 78,
"y": 66,
"wfo": "PBZ"
}
},
@@ -184,8 +184,8 @@
"lat": 38.6273,
"lon": -90.1979,
"point": {
"x": 94,
"y": 73,
"x": 95,
"y": 74,
"wfo": "LSX"
}
},
@@ -204,8 +204,8 @@
"lat": 47.6062,
"lon": -122.3321,
"point": {
"x": 124,
"y": 67,
"x": 125,
"y": 68,
"wfo": "SEW"
}
},
@@ -214,8 +214,8 @@
"lat": 43.0481,
"lon": -76.1474,
"point": {
"x": 51,
"y": 98,
"x": 52,
"y": 99,
"wfo": "BGM"
}
},
@@ -224,8 +224,8 @@
"lat": 27.9756,
"lon": -82.5329,
"point": {
"x": 67,
"y": 97,
"x": 68,
"y": 98,
"wfo": "TBW"
}
},
@@ -244,8 +244,8 @@
"lat": 42.6526,
"lon": -73.7562,
"point": {
"x": 58,
"y": 58,
"x": 72,
"y": 63,
"wfo": "ALY"
}
},
@@ -254,8 +254,8 @@
"lat": 35.0845,
"lon": -106.6511,
"point": {
"x": 97,
"y": 118,
"x": 98,
"y": 121,
"wfo": "ABQ"
}
},
@@ -264,8 +264,8 @@
"lat": 35.222,
"lon": -101.8313,
"point": {
"x": 47,
"y": 25,
"x": 48,
"y": 26,
"wfo": "AMA"
}
},
@@ -284,8 +284,8 @@
"lat": 30.2671,
"lon": -97.7431,
"point": {
"x": 155,
"y": 90,
"x": 156,
"y": 91,
"wfo": "EWX"
}
},
@@ -294,8 +294,8 @@
"lat": 44.7502,
"lon": -117.6677,
"point": {
"x": 93,
"y": 145,
"x": 94,
"y": 146,
"wfo": "BOI"
}
},
@@ -314,7 +314,7 @@
"lat": 44.8012,
"lon": -68.7778,
"point": {
"x": 72,
"x": 66,
"y": 62,
"wfo": "CAR"
}
@@ -324,8 +324,8 @@
"lat": 33.5207,
"lon": -86.8025,
"point": {
"x": 58,
"y": 83,
"x": 59,
"y": 84,
"wfo": "BMX"
}
},
@@ -334,8 +334,8 @@
"lat": 46.8083,
"lon": -100.7837,
"point": {
"x": 109,
"y": 46,
"x": 110,
"y": 47,
"wfo": "BIS"
}
},
@@ -344,8 +344,8 @@
"lat": 43.6135,
"lon": -116.2034,
"point": {
"x": 132,
"y": 85,
"x": 133,
"y": 86,
"wfo": "BOI"
}
},
@@ -354,8 +354,8 @@
"lat": 42.8864,
"lon": -78.8784,
"point": {
"x": 35,
"y": 46,
"x": 36,
"y": 47,
"wfo": "BUF"
}
},
@@ -374,8 +374,8 @@
"lat": 32.7766,
"lon": -79.9309,
"point": {
"x": 86,
"y": 76,
"x": 87,
"y": 77,
"wfo": "CHS"
}
},
@@ -384,8 +384,8 @@
"lat": 38.3498,
"lon": -81.6326,
"point": {
"x": 62,
"y": 66,
"x": 63,
"y": 67,
"wfo": "RLX"
}
},
@@ -394,8 +394,8 @@
"lat": 35.2271,
"lon": -80.8431,
"point": {
"x": 118,
"y": 64,
"x": 119,
"y": 65,
"wfo": "GSP"
}
},
@@ -404,8 +404,8 @@
"lat": 41.14,
"lon": -104.8202,
"point": {
"x": 109,
"y": 13,
"x": 110,
"y": 14,
"wfo": "CYS"
}
},
@@ -414,8 +414,8 @@
"lat": 39.162,
"lon": -84.4569,
"point": {
"x": 36,
"y": 40,
"x": 37,
"y": 41,
"wfo": "ILN"
}
},
@@ -434,8 +434,8 @@
"lat": 39.9612,
"lon": -82.9988,
"point": {
"x": 84,
"y": 80,
"x": 85,
"y": 81,
"wfo": "ILN"
}
},
@@ -444,8 +444,8 @@
"lat": 41.6005,
"lon": -93.6091,
"point": {
"x": 73,
"y": 49,
"x": 74,
"y": 50,
"wfo": "DMX"
}
},
@@ -454,8 +454,8 @@
"lat": 42.5006,
"lon": -90.6646,
"point": {
"x": 62,
"y": 110,
"x": 63,
"y": 111,
"wfo": "DVN"
}
},
@@ -474,7 +474,7 @@
"lat": 44.9062,
"lon": -66.99,
"point": {
"x": 129,
"x": 123,
"y": 79,
"wfo": "CAR"
}
@@ -484,8 +484,8 @@
"lat": 32.792,
"lon": -115.563,
"point": {
"x": 26,
"y": 46,
"x": 27,
"y": 47,
"wfo": "PSR"
}
},
@@ -494,8 +494,8 @@
"lat": 31.7587,
"lon": -106.4869,
"point": {
"x": 99,
"y": 55,
"x": 100,
"y": 56,
"wfo": "EPZ"
}
},
@@ -504,8 +504,8 @@
"lat": 44.0521,
"lon": -123.0867,
"point": {
"x": 84,
"y": 38,
"x": 85,
"y": 39,
"wfo": "PQR"
}
},
@@ -514,8 +514,8 @@
"lat": 46.8772,
"lon": -96.7898,
"point": {
"x": 99,
"y": 56,
"x": 100,
"y": 57,
"wfo": "FGF"
}
},
@@ -524,8 +524,8 @@
"lat": 35.1981,
"lon": -111.6513,
"point": {
"x": 73,
"y": 88,
"x": 74,
"y": 89,
"wfo": "FGZ"
}
},
@@ -544,8 +544,8 @@
"lat": 39.0639,
"lon": -108.5506,
"point": {
"x": 94,
"y": 101,
"x": 95,
"y": 102,
"wfo": "GJT"
}
},
@@ -554,8 +554,8 @@
"lat": 42.9634,
"lon": -85.6681,
"point": {
"x": 40,
"y": 46,
"x": 41,
"y": 47,
"wfo": "GRR"
}
},
@@ -564,8 +564,8 @@
"lat": 48.55,
"lon": -109.6841,
"point": {
"x": 154,
"y": 187,
"x": 155,
"y": 188,
"wfo": "TFX"
}
},
@@ -574,8 +574,8 @@
"lat": 46.5927,
"lon": -112.0361,
"point": {
"x": 68,
"y": 103,
"x": 69,
"y": 104,
"wfo": "TFX"
}
},
@@ -584,8 +584,8 @@
"lat": 21.3069,
"lon": -157.8583,
"point": {
"x": 153,
"y": 144,
"x": 154,
"y": 145,
"wfo": "HFO"
}
},
@@ -594,8 +594,8 @@
"lat": 34.5037,
"lon": -93.0552,
"point": {
"x": 53,
"y": 60,
"x": 54,
"y": 61,
"wfo": "LZK"
}
},
@@ -604,8 +604,8 @@
"lat": 43.4666,
"lon": -112.0341,
"point": {
"x": 115,
"y": 72,
"x": 124,
"y": 73,
"wfo": "PIH"
}
},
@@ -614,8 +614,8 @@
"lat": 32.2988,
"lon": -90.1848,
"point": {
"x": 75,
"y": 62,
"x": 76,
"y": 63,
"wfo": "JAN"
}
},
@@ -624,8 +624,8 @@
"lat": 30.3322,
"lon": -81.6556,
"point": {
"x": 65,
"y": 64,
"x": 66,
"y": 65,
"wfo": "JAX"
}
},
@@ -644,8 +644,8 @@
"lat": 39.1142,
"lon": -94.6275,
"point": {
"x": 41,
"y": 50,
"x": 42,
"y": 51,
"wfo": "EAX"
}
},
@@ -654,8 +654,8 @@
"lat": 24.5557,
"lon": -81.7826,
"point": {
"x": 61,
"y": 47,
"x": 62,
"y": 48,
"wfo": "KEY"
}
},
@@ -684,8 +684,8 @@
"lat": 36.175,
"lon": -115.1372,
"point": {
"x": 122,
"y": 97,
"x": 123,
"y": 98,
"wfo": "VEF"
}
},
@@ -704,8 +704,8 @@
"lat": 40.8,
"lon": -96.667,
"point": {
"x": 56,
"y": 38,
"x": 57,
"y": 39,
"wfo": "OAX"
}
},
@@ -714,8 +714,8 @@
"lat": 33.767,
"lon": -118.1892,
"point": {
"x": 154,
"y": 31,
"x": 155,
"y": 32,
"wfo": "LOX"
}
},
@@ -724,8 +724,8 @@
"lat": 38.2542,
"lon": -85.7594,
"point": {
"x": 49,
"y": 77,
"x": 50,
"y": 78,
"wfo": "LMK"
}
},
@@ -734,8 +734,8 @@
"lat": 42.9956,
"lon": -71.4548,
"point": {
"x": 41,
"y": 20,
"x": 42,
"y": 21,
"wfo": "GYX"
}
},
@@ -744,8 +744,8 @@
"lat": 35.1495,
"lon": -90.049,
"point": {
"x": 41,
"y": 66,
"x": 42,
"y": 67,
"wfo": "MEG"
}
},
@@ -754,8 +754,8 @@
"lat": 43.0389,
"lon": -87.9065,
"point": {
"x": 87,
"y": 64,
"x": 88,
"y": 65,
"wfo": "MKX"
}
},
@@ -764,8 +764,8 @@
"lat": 30.6944,
"lon": -88.043,
"point": {
"x": 51,
"y": 66,
"x": 52,
"y": 67,
"wfo": "MOB"
}
},
@@ -774,8 +774,8 @@
"lat": 32.3668,
"lon": -86.3,
"point": {
"x": 80,
"y": 34,
"x": 81,
"y": 35,
"wfo": "BMX"
}
},
@@ -784,8 +784,8 @@
"lat": 44.2601,
"lon": -72.5754,
"point": {
"x": 110,
"y": 49,
"x": 111,
"y": 50,
"wfo": "BTV"
}
},
@@ -794,8 +794,8 @@
"lat": 36.1659,
"lon": -86.7844,
"point": {
"x": 49,
"y": 56,
"x": 50,
"y": 57,
"wfo": "OHX"
}
},
@@ -804,8 +804,8 @@
"lat": 40.7357,
"lon": -74.1724,
"point": {
"x": 26,
"y": 34,
"x": 27,
"y": 35,
"wfo": "OKX"
}
},
@@ -814,8 +814,8 @@
"lat": 41.3081,
"lon": -72.9282,
"point": {
"x": 65,
"y": 67,
"x": 66,
"y": 68,
"wfo": "OKX"
}
},
@@ -845,7 +845,7 @@
"lon": -97.5164,
"point": {
"x": 97,
"y": 93,
"y": 94,
"wfo": "OUN"
}
},
@@ -854,8 +854,8 @@
"lat": 41.2586,
"lon": -95.9378,
"point": {
"x": 82,
"y": 59,
"x": 83,
"y": 60,
"wfo": "OAX"
}
},
@@ -864,8 +864,8 @@
"lat": 33.4484,
"lon": -112.074,
"point": {
"x": 158,
"y": 57,
"x": 159,
"y": 58,
"wfo": "PSR"
}
},
@@ -874,8 +874,8 @@
"lat": 44.3683,
"lon": -100.351,
"point": {
"x": 54,
"y": 43,
"x": 55,
"y": 44,
"wfo": "ABR"
}
},
@@ -884,8 +884,8 @@
"lat": 43.6615,
"lon": -70.2553,
"point": {
"x": 75,
"y": 58,
"x": 76,
"y": 59,
"wfo": "GYX"
}
},
@@ -894,8 +894,8 @@
"lat": 45.5234,
"lon": -122.6762,
"point": {
"x": 112,
"y": 103,
"x": 113,
"y": 104,
"wfo": "PQR"
}
},
@@ -914,8 +914,8 @@
"lat": 35.7721,
"lon": -78.6386,
"point": {
"x": 74,
"y": 56,
"x": 75,
"y": 57,
"wfo": "RAH"
}
},
@@ -924,8 +924,8 @@
"lat": 39.4986,
"lon": -119.7681,
"point": {
"x": 45,
"y": 104,
"x": 46,
"y": 105,
"wfo": "REV"
}
},
@@ -934,8 +934,8 @@
"lat": 38.7725,
"lon": -112.0841,
"point": {
"x": 81,
"y": 86,
"x": 82,
"y": 87,
"wfo": "SLC"
}
},
@@ -944,8 +944,8 @@
"lat": 37.5538,
"lon": -77.4603,
"point": {
"x": 44,
"y": 76,
"x": 45,
"y": 77,
"wfo": "AKQ"
}
},
@@ -954,8 +954,8 @@
"lat": 37.271,
"lon": -79.9414,
"point": {
"x": 73,
"y": 68,
"x": 74,
"y": 69,
"wfo": "RNK"
}
},
@@ -964,8 +964,8 @@
"lat": 38.5816,
"lon": -121.4944,
"point": {
"x": 40,
"y": 67,
"x": 41,
"y": 68,
"wfo": "STO"
}
},
@@ -974,8 +974,8 @@
"lat": 40.7608,
"lon": -111.891,
"point": {
"x": 99,
"y": 174,
"x": 100,
"y": 175,
"wfo": "SLC"
}
},
@@ -984,8 +984,8 @@
"lat": 29.4241,
"lon": -98.4936,
"point": {
"x": 125,
"y": 53,
"x": 126,
"y": 54,
"wfo": "EWX"
}
},
@@ -994,8 +994,8 @@
"lat": 32.7153,
"lon": -117.1573,
"point": {
"x": 56,
"y": 13,
"x": 57,
"y": 14,
"wfo": "SGX"
}
},
@@ -1014,8 +1014,8 @@
"lat": 35.687,
"lon": -105.9378,
"point": {
"x": 125,
"y": 143,
"x": 126,
"y": 146,
"wfo": "ABQ"
}
},
@@ -1024,8 +1024,8 @@
"lat": 32.0835,
"lon": -81.0998,
"point": {
"x": 46,
"y": 40,
"x": 47,
"y": 41,
"wfo": "CHS"
}
},
@@ -1034,7 +1034,7 @@
"lat": 32.5251,
"lon": -93.7502,
"point": {
"x": 76,
"x": 74,
"y": 69,
"wfo": "SHV"
}
@@ -1074,8 +1074,8 @@
"lat": 39.8017,
"lon": -89.6437,
"point": {
"x": 47,
"y": 55,
"x": 48,
"y": 56,
"wfo": "ILX"
}
},
@@ -1094,8 +1094,8 @@
"lat": 37.2153,
"lon": -93.2982,
"point": {
"x": 66,
"y": 34,
"x": 67,
"y": 35,
"wfo": "SGF"
}
},
@@ -1104,8 +1104,8 @@
"lat": 41.6639,
"lon": -83.5552,
"point": {
"x": 18,
"y": 66,
"x": 19,
"y": 67,
"wfo": "CLE"
}
},
@@ -1114,8 +1114,8 @@
"lat": 36.154,
"lon": -95.9928,
"point": {
"x": 40,
"y": 104,
"x": 41,
"y": 105,
"wfo": "TSA"
}
},
@@ -1124,8 +1124,8 @@
"lat": 36.8529,
"lon": -75.978,
"point": {
"x": 100,
"y": 52,
"x": 101,
"y": 53,
"wfo": "AKQ"
}
},
@@ -1134,8 +1134,8 @@
"lat": 37.6922,
"lon": -97.3375,
"point": {
"x": 61,
"y": 33,
"x": 62,
"y": 34,
"wfo": "ICT"
}
},
@@ -1144,18 +1144,18 @@
"lat": 34.2257,
"lon": -77.9447,
"point": {
"x": 88,
"y": 67,
"x": 89,
"y": 68,
"wfo": "ILM"
}
},
{
"city": "Tuscan",
"city": "Tucsan",
"lat": 32.2216,
"lon": -110.9698,
"point": {
"x": 90,
"y": 48,
"x": 91,
"y": 49,
"wfo": "TWC"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
"Latitude": 33.749,
"Longitude": -84.388,
"point": {
"x": 50,
"y": 86,
"x": 51,
"y": 87,
"wfo": "FFC"
}
},
@@ -24,8 +24,8 @@
"Latitude": 41.9796,
"Longitude": -87.9045,
"point": {
"x": 65,
"y": 76,
"x": 66,
"y": 77,
"wfo": "LOT"
}
},
@@ -34,8 +34,8 @@
"Latitude": 41.4995,
"Longitude": -81.6954,
"point": {
"x": 82,
"y": 64,
"x": 83,
"y": 65,
"wfo": "CLE"
}
},
@@ -44,8 +44,8 @@
"Latitude": 32.8959,
"Longitude": -97.0372,
"point": {
"x": 79,
"y": 108,
"x": 80,
"y": 109,
"wfo": "FWD"
}
},
@@ -54,8 +54,8 @@
"Latitude": 39.7391,
"Longitude": -104.9847,
"point": {
"x": 62,
"y": 60,
"x": 63,
"y": 61,
"wfo": "BOU"
}
},
@@ -64,8 +64,8 @@
"Latitude": 42.3314,
"Longitude": -83.0457,
"point": {
"x": 65,
"y": 33,
"x": 66,
"y": 34,
"wfo": "DTX"
}
},
@@ -94,8 +94,8 @@
"Latitude": 39.7684,
"Longitude": -86.158,
"point": {
"x": 57,
"y": 68,
"x": 58,
"y": 69,
"wfo": "IND"
}
},
@@ -104,8 +104,8 @@
"Latitude": 34.0522,
"Longitude": -118.2437,
"point": {
"x": 154,
"y": 44,
"x": 155,
"y": 45,
"wfo": "LOX"
}
},
@@ -114,8 +114,8 @@
"Latitude": 25.7743,
"Longitude": -80.1937,
"point": {
"x": 109,
"y": 50,
"x": 110,
"y": 51,
"wfo": "MFL"
}
},
@@ -124,8 +124,8 @@
"Latitude": 44.98,
"Longitude": -93.2638,
"point": {
"x": 107,
"y": 71,
"x": 108,
"y": 72,
"wfo": "MPX"
}
},
@@ -134,8 +134,8 @@
"Latitude": 40.7142,
"Longitude": -74.0059,
"point": {
"x": 32,
"y": 34,
"x": 33,
"y": 35,
"wfo": "OKX"
}
},
@@ -144,8 +144,8 @@
"Latitude": 36.8468,
"Longitude": -76.2852,
"point": {
"x": 89,
"y": 51,
"x": 90,
"y": 52,
"wfo": "AKQ"
}
},
@@ -164,8 +164,8 @@
"Latitude": 39.9523,
"Longitude": -75.1638,
"point": {
"x": 49,
"y": 75,
"x": 50,
"y": 76,
"wfo": "PHI"
}
},
@@ -174,8 +174,8 @@
"Latitude": 40.4406,
"Longitude": -79.9959,
"point": {
"x": 77,
"y": 65,
"x": 78,
"y": 66,
"wfo": "PBZ"
}
},
@@ -184,8 +184,8 @@
"Latitude": 38.6273,
"Longitude": -90.1979,
"point": {
"x": 94,
"y": 73,
"x": 95,
"y": 74,
"wfo": "LSX"
}
},
@@ -204,8 +204,8 @@
"Latitude": 47.6062,
"Longitude": -122.3321,
"point": {
"x": 124,
"y": 67,
"x": 125,
"y": 68,
"wfo": "SEW"
}
},
@@ -214,8 +214,8 @@
"Latitude": 43.0481,
"Longitude": -76.1474,
"point": {
"x": 51,
"y": 98,
"x": 52,
"y": 99,
"wfo": "BGM"
}
},
@@ -224,8 +224,8 @@
"Latitude": 27.9475,
"Longitude": -82.4584,
"point": {
"x": 70,
"y": 96,
"x": 71,
"y": 97,
"wfo": "TBW"
}
},

View File

@@ -575,7 +575,7 @@
"lon": -77.9447
},
{
"city": "Tuscan",
"city": "Tucsan",
"lat": 32.2216,
"lon": -110.9698
}

View File

@@ -1,41 +0,0 @@
// look up points for each regional city
const fs = require('fs/promises');
const chunk = require('./chunk');
const https = require('./https');
(async () => {
// source data
const regionalCities = JSON.parse(await fs.readFile('./datagenerators/regionalcities-raw.json'));
const result = [];
const dataChunks = chunk(regionalCities, 5);
// for loop intentional for use of await
// this keeps the api from getting overwhelmed
for (let i = 0; i < dataChunks.length; i += 1) {
const cityChunk = dataChunks[i];
// eslint-disable-next-line no-await-in-loop
const chunkResult = await Promise.all(cityChunk.map(async (city) => {
try {
const data = await https(`https://api.weather.gov/points/${city.lat},${city.lon}`);
const point = JSON.parse(data);
return {
...city,
point: {
x: point.properties.gridX,
y: point.properties.gridY,
wfo: point.properties.gridId,
},
};
} catch (e) {
console.error(e);
return city;
}
}));
result.push(...chunkResult);
}
await fs.writeFile('./datagenerators/output/regionalcities.json', JSON.stringify(result, null, ' '));
})();

View File

@@ -0,0 +1,40 @@
// look up points for each regional city
import fs from 'fs/promises';
import chunk from './chunk.mjs';
import https from './https.mjs';
// source data
const regionalCities = JSON.parse(await fs.readFile('./datagenerators/regionalcities-raw.json'));
const result = [];
const dataChunks = chunk(regionalCities, 5);
// for loop intentional for use of await
// this keeps the api from getting overwhelmed
for (let i = 0; i < dataChunks.length; i += 1) {
const cityChunk = dataChunks[i];
// eslint-disable-next-line no-await-in-loop
const chunkResult = await Promise.all(cityChunk.map(async (city) => {
try {
const data = await https(`https://api.weather.gov/points/${city.lat},${city.lon}`);
const point = JSON.parse(data);
return {
...city,
point: {
x: point.properties.gridX,
y: point.properties.gridY,
wfo: point.properties.gridId,
},
};
} catch (e) {
console.error(e);
return city;
}
}));
result.push(...chunkResult);
}
await fs.writeFile('./datagenerators/output/regionalcities.json', JSON.stringify(result, null, ' '));

View File

@@ -1,4 +1,4 @@
module.exports = [
export default [
'AZ',
'AL',
'AK',
@@ -50,4 +50,4 @@ module.exports = [
'WI',
'WY',
'PR',
];
];

View File

@@ -1,11 +1,10 @@
// list all stations in a single file
// only find stations with 4 letter codes
const fs = require('fs');
const path = require('path');
const https = require('./https');
const states = require('./stations-states');
const chunk = require('./chunk');
import { writeFileSync } from 'fs';
import https from './https.mjs';
import states from './stations-states.mjs';
import chunk from './chunk.mjs';
// skip stations starting with these letters
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
@@ -28,7 +27,7 @@ const start = async () => {
let stations;
let next = `https://api.weather.gov/stations?state=${state}`;
do {
// get list and parse the JSON
// get list and parse the JSON
// eslint-disable-next-line no-await-in-loop
const stationsRaw = await https(next);
stations = JSON.parse(stationsRaw);
@@ -53,8 +52,7 @@ const start = async () => {
});
next = stations?.pagination?.next;
// write the output
// write the output
fs.writeFileSync(path.join(__dirname, 'output/stations.json'), JSON.stringify(output, null, 2));
writeFileSync('./datagenerators/output/stations.json', JSON.stringify(output, null, 2));
}
while (next && stations.features.length > 0);
console.log(`Complete: ${state}`);
@@ -68,6 +66,4 @@ const start = async () => {
};
// immediately invoked function allows access to async
(async () => {
await start();
})();
await start();

View File

@@ -1,41 +0,0 @@
// look up points for each travel city
const fs = require('fs/promises');
const chunk = require('./chunk');
const https = require('./https');
(async () => {
// source data
const travelCities = JSON.parse(await fs.readFile('./datagenerators/travelcities-raw.json'));
const result = [];
const dataChunks = chunk(travelCities, 5);
// for loop intentional for use of await
// this keeps the api from getting overwhelmed
for (let i = 0; i < dataChunks.length; i += 1) {
const cityChunk = dataChunks[i];
// eslint-disable-next-line no-await-in-loop
const chunkResult = await Promise.all(cityChunk.map(async (city) => {
try {
const data = await https(`https://api.weather.gov/points/${city.Latitude},${city.Longitude}`);
const point = JSON.parse(data);
return {
...city,
point: {
x: point.properties.gridX,
y: point.properties.gridY,
wfo: point.properties.gridId,
},
};
} catch (e) {
console.error(e);
return city;
}
}));
result.push(...chunkResult);
}
await fs.writeFile('./datagenerators/output/travelcities.json', JSON.stringify(result, null, ' '));
})();

View File

@@ -0,0 +1,39 @@
// look up points for each travel city
import { readFile, writeFile } from 'fs/promises';
import chunk from './chunk.mjs';
import https from './https.mjs';
// source data
const travelCities = JSON.parse(await readFile('./datagenerators/travelcities-raw.json'));
const result = [];
const dataChunks = chunk(travelCities, 5);
// for loop intentional for use of await
// this keeps the api from getting overwhelmed
for (let i = 0; i < dataChunks.length; i += 1) {
const cityChunk = dataChunks[i];
// eslint-disable-next-line no-await-in-loop
const chunkResult = await Promise.all(cityChunk.map(async (city) => {
try {
const data = await https(`https://api.weather.gov/points/${city.Latitude},${city.Longitude}`);
const point = JSON.parse(data);
return {
...city,
point: {
x: point.properties.gridX,
y: point.properties.gridY,
wfo: point.properties.gridId,
},
};
} catch (e) {
console.error(e);
return city;
}
}));
result.push(...chunkResult);
}
await writeFile('./datagenerators/output/travelcities.json', JSON.stringify(result, null, ' '));

View File

@@ -12,9 +12,9 @@ const port = process.env.WS4KP_PORT ?? 8080;
app.set('view engine', 'ejs');
// cors pass-thru to api.weather.gov
app.get('/stations/*', corsPassThru);
app.get('/Conus/*', radarPassThru);
app.get('/products/*', outlookPassThru);
app.get('/stations/*station', corsPassThru);
app.get('/Conus/*radar', radarPassThru);
app.get('/products/*product', outlookPassThru);
app.get('/playlist.json', playlist);
// version
@@ -38,7 +38,7 @@ if (process.env?.DIST === '1') {
// debugging
app.get('/index.html', index);
app.get('/', index);
app.get('*', express.static('./server'));
app.get('*name', express.static('./server'));
}
const server = app.listen(port, () => {

1770
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ws4kp",
"version": "5.15.1",
"version": "5.16.3",
"description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.mjs",
"type": "module",
@@ -49,6 +49,6 @@
},
"dependencies": {
"ejs": "^3.1.5",
"express": "^4.17.1"
"express": "^5.1.0"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,243 +1,243 @@
// eslint-disable-next-line no-unused-vars
const TravelCities = [
{
Name: 'Atlanta',
Latitude: 33.749,
Longitude: -84.388,
point: {
x: 50,
y: 86,
wfo: 'FFC',
},
"Name": "Atlanta",
"Latitude": 33.749,
"Longitude": -84.388,
"point": {
"x": 51,
"y": 87,
"wfo": "FFC"
}
},
{
Name: 'Boston',
Latitude: 42.3584,
Longitude: -71.0598,
point: {
x: 71,
y: 90,
wfo: 'BOX',
},
"Name": "Boston",
"Latitude": 42.3584,
"Longitude": -71.0598,
"point": {
"x": 71,
"y": 90,
"wfo": "BOX"
}
},
{
Name: 'Chicago',
Latitude: 41.9796,
Longitude: -87.9045,
point: {
x: 65,
y: 76,
wfo: 'LOT',
},
"Name": "Chicago",
"Latitude": 41.9796,
"Longitude": -87.9045,
"point": {
"x": 66,
"y": 77,
"wfo": "LOT"
}
},
{
Name: 'Cleveland',
Latitude: 41.4995,
Longitude: -81.6954,
point: {
x: 82,
y: 64,
wfo: 'CLE',
},
"Name": "Cleveland",
"Latitude": 41.4995,
"Longitude": -81.6954,
"point": {
"x": 83,
"y": 65,
"wfo": "CLE"
}
},
{
Name: 'Dallas',
Latitude: 32.8959,
Longitude: -97.0372,
point: {
x: 79,
y: 108,
wfo: 'FWD',
},
"Name": "Dallas",
"Latitude": 32.8959,
"Longitude": -97.0372,
"point": {
"x": 80,
"y": 109,
"wfo": "FWD"
}
},
{
Name: 'Denver',
Latitude: 39.7391,
Longitude: -104.9847,
point: {
x: 62,
y: 60,
wfo: 'BOU',
},
"Name": "Denver",
"Latitude": 39.7391,
"Longitude": -104.9847,
"point": {
"x": 63,
"y": 61,
"wfo": "BOU"
}
},
{
Name: 'Detroit',
Latitude: 42.3314,
Longitude: -83.0457,
point: {
x: 65,
y: 33,
wfo: 'DTX',
},
"Name": "Detroit",
"Latitude": 42.3314,
"Longitude": -83.0457,
"point": {
"x": 66,
"y": 34,
"wfo": "DTX"
}
},
{
Name: 'Hartford',
Latitude: 41.7637,
Longitude: -72.6851,
point: {
x: 21,
y: 54,
wfo: 'BOX',
},
"Name": "Hartford",
"Latitude": 41.7637,
"Longitude": -72.6851,
"point": {
"x": 21,
"y": 54,
"wfo": "BOX"
}
},
{
Name: 'Houston',
Latitude: 29.7633,
Longitude: -95.3633,
point: {
x: 65,
y: 97,
wfo: 'HGX',
},
"Name": "Houston",
"Latitude": 29.7633,
"Longitude": -95.3633,
"point": {
"x": 65,
"y": 97,
"wfo": "HGX"
}
},
{
Name: 'Indianapolis',
Latitude: 39.7684,
Longitude: -86.158,
point: {
x: 57,
y: 68,
wfo: 'IND',
},
"Name": "Indianapolis",
"Latitude": 39.7684,
"Longitude": -86.158,
"point": {
"x": 58,
"y": 69,
"wfo": "IND"
}
},
{
Name: 'Los Angeles',
Latitude: 34.0522,
Longitude: -118.2437,
point: {
x: 154,
y: 44,
wfo: 'LOX',
},
"Name": "Los Angeles",
"Latitude": 34.0522,
"Longitude": -118.2437,
"point": {
"x": 155,
"y": 45,
"wfo": "LOX"
}
},
{
Name: 'Miami',
Latitude: 25.7743,
Longitude: -80.1937,
point: {
x: 109,
y: 50,
wfo: 'MFL',
},
"Name": "Miami",
"Latitude": 25.7743,
"Longitude": -80.1937,
"point": {
"x": 110,
"y": 51,
"wfo": "MFL"
}
},
{
Name: 'Minneapolis',
Latitude: 44.98,
Longitude: -93.2638,
point: {
x: 107,
y: 71,
wfo: 'MPX',
},
"Name": "Minneapolis",
"Latitude": 44.98,
"Longitude": -93.2638,
"point": {
"x": 108,
"y": 72,
"wfo": "MPX"
}
},
{
Name: 'New York',
Latitude: 40.7142,
Longitude: -74.0059,
point: {
x: 32,
y: 34,
wfo: 'OKX',
},
"Name": "New York",
"Latitude": 40.7142,
"Longitude": -74.0059,
"point": {
"x": 33,
"y": 35,
"wfo": "OKX"
}
},
{
Name: 'Norfolk',
Latitude: 36.8468,
Longitude: -76.2852,
point: {
x: 89,
y: 51,
wfo: 'AKQ',
},
"Name": "Norfolk",
"Latitude": 36.8468,
"Longitude": -76.2852,
"point": {
"x": 90,
"y": 52,
"wfo": "AKQ"
}
},
{
Name: 'Orlando',
Latitude: 28.5383,
Longitude: -81.3792,
point: {
x: 26,
y: 68,
wfo: 'MLB',
},
"Name": "Orlando",
"Latitude": 28.5383,
"Longitude": -81.3792,
"point": {
"x": 26,
"y": 68,
"wfo": "MLB"
}
},
{
Name: 'Philadelphia',
Latitude: 39.9523,
Longitude: -75.1638,
point: {
x: 49,
y: 75,
wfo: 'PHI',
},
"Name": "Philadelphia",
"Latitude": 39.9523,
"Longitude": -75.1638,
"point": {
"x": 50,
"y": 76,
"wfo": "PHI"
}
},
{
Name: 'Pittsburgh',
Latitude: 40.4406,
Longitude: -79.9959,
point: {
x: 77,
y: 65,
wfo: 'PBZ',
},
"Name": "Pittsburgh",
"Latitude": 40.4406,
"Longitude": -79.9959,
"point": {
"x": 78,
"y": 66,
"wfo": "PBZ"
}
},
{
Name: 'St. Louis',
Latitude: 38.6273,
Longitude: -90.1979,
point: {
x: 94,
y: 73,
wfo: 'LSX',
},
"Name": "St. Louis",
"Latitude": 38.6273,
"Longitude": -90.1979,
"point": {
"x": 95,
"y": 74,
"wfo": "LSX"
}
},
{
Name: 'San Francisco',
Latitude: 37.7749,
Longitude: -122.4194,
point: {
x: 85,
y: 105,
wfo: 'MTR',
},
"Name": "San Francisco",
"Latitude": 37.7749,
"Longitude": -122.4194,
"point": {
"x": 85,
"y": 105,
"wfo": "MTR"
}
},
{
Name: 'Seattle',
Latitude: 47.6062,
Longitude: -122.3321,
point: {
x: 124,
y: 67,
wfo: 'SEW',
},
"Name": "Seattle",
"Latitude": 47.6062,
"Longitude": -122.3321,
"point": {
"x": 125,
"y": 68,
"wfo": "SEW"
}
},
{
Name: 'Syracuse',
Latitude: 43.0481,
Longitude: -76.1474,
point: {
x: 51,
y: 98,
wfo: 'BGM',
},
"Name": "Syracuse",
"Latitude": 43.0481,
"Longitude": -76.1474,
"point": {
"x": 52,
"y": 99,
"wfo": "BGM"
}
},
{
Name: 'Tampa',
Latitude: 27.9475,
Longitude: -82.4584,
point: {
x: 70,
y: 96,
wfo: 'TBW',
},
"Name": "Tampa",
"Latitude": 27.9475,
"Longitude": -82.4584,
"point": {
"x": 71,
"y": 97,
"wfo": "TBW"
}
},
{
Name: 'Washington DC',
Latitude: 38.8951,
Longitude: -77.0364,
point: {
x: 97,
y: 71,
wfo: 'LWX',
},
},
];
"Name": "Washington DC",
"Latitude": 38.8951,
"Longitude": -77.0364,
"point": {
"x": 97,
"y": 71,
"wfo": "LWX"
}
}
]

View File

@@ -1,7 +1,7 @@
import { json } from './modules/utils/fetch.mjs';
import noSleep from './modules/utils/nosleep.mjs';
import {
message as navMessage, isPlaying, resize, resetStatuses, latLonReceived, stopAutoRefreshTimer, registerRefreshData,
message as navMessage, isPlaying, resize, resetStatuses, latLonReceived,
} from './modules/navigation.mjs';
import { round2 } from './modules/utils/units.mjs';
import { parseQueryString } from './modules/share.mjs';
@@ -32,8 +32,6 @@ const init = () => {
e.target.select();
});
registerRefreshData(loadData);
document.querySelector('#NavigateMenu').addEventListener('click', btnNavigateMenuClick);
document.querySelector('#NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
document.querySelector('#NavigateNext').addEventListener('click', btnNavigateNextClick);
@@ -249,7 +247,6 @@ const loadData = (_latLon, haveDataCallback) => {
if (!latLon) return;
document.querySelector(TXT_ADDRESS_SELECTOR).blur();
stopAutoRefreshTimer();
latLonReceived(latLon, haveDataCallback);
};

View File

@@ -21,12 +21,11 @@ class Almanac extends WeatherDisplay {
this.timing.totalScreens = 1;
}
async getData(_weatherParameters) {
const superResponse = super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
const superResponse = super.getData(weatherParameters, refresh);
// get sun/moon data
const { sun, moon } = this.calcSunMoonData(weatherParameters);
const { sun, moon } = this.calcSunMoonData(this.weatherParameters);
// store the data
this.data = {

View File

@@ -21,13 +21,14 @@ class CurrentWeather extends WeatherDisplay {
this.backgroundImage = loadImg('images/BackGround1_1.png');
}
async getData(_weatherParameters) {
async getData(weatherParameters, refresh) {
// always load the data for use in the lower scroll
const superResult = super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
const superResult = super.getData(weatherParameters, refresh);
// note: current weather does not use old data on a silent refresh
// this is deliberate because it can pull data from more than one station in sequence
// filter for 4-letter observation stations, only those contain sky conditions and thus an icon
const filteredStations = weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
const filteredStations = this.weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
// Load the observations
let observations;

View File

@@ -18,14 +18,12 @@ class ExtendedForecast extends WeatherDisplay {
this.timing.totalScreens = 2;
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
// request us or si units
let forecast;
try {
forecast = await json(weatherParameters.forecast, {
this.data = await json(this.weatherParameters.forecast, {
data: {
units: settings.units.value,
},
@@ -35,11 +33,13 @@ class ExtendedForecast extends WeatherDisplay {
} catch (error) {
console.error('Unable to get extended forecast');
console.error(error.status, error.responseJSON);
this.setStatus(STATUS.failed);
return;
// if there's no previous data, fail
if (!this.data) {
this.setStatus(STATUS.failed);
return;
}
}
// we only get here if there was no error above
this.data = parse(forecast.properties.periods);
this.screenIndex = 0;
this.setStatus(STATUS.loaded);
}
@@ -49,7 +49,7 @@ class ExtendedForecast extends WeatherDisplay {
// determine bounds
// grab the first three or second set of three array elements
const forecast = this.data.slice(0 + 3 * this.screenIndex, 3 + this.screenIndex * 3);
const forecast = parse(this.data.properties.periods).slice(0 + 3 * this.screenIndex, 3 + this.screenIndex * 3);
// create each day template
const days = forecast.map((Day) => {

View File

@@ -26,9 +26,11 @@ class Hazards extends WeatherDisplay {
this.timing.totalScreens = 0;
}
async getData(weatherParameters) {
async getData(weatherParameters, refresh) {
// super checks for enabled
const superResult = super.getData(weatherParameters);
const superResult = super.getData(weatherParameters, refresh);
// hazards performs a silent refresh, but does not fall back to a previous fetch if no data is available
// this is intentional to ensure the latest alerts only are displayed.
const alert = this.checkbox.querySelector('.alert');
alert.classList.remove('show');

View File

@@ -23,8 +23,8 @@ class HourlyGraph extends WeatherDisplay {
this.elem.querySelector('.header .right').append(header);
}
async getData() {
if (!super.getData()) return;
async getData(weatherParameters, refresh) {
if (!super.getData(undefined, refresh)) return;
const data = await getHourlyData(() => this.stillWaiting());
if (data === undefined) {

View File

@@ -27,23 +27,30 @@ class Hourly extends WeatherDisplay {
this.timing.delay.push(150);
}
async getData(weatherParameters) {
async getData(weatherParameters, refresh) {
// super checks for enabled
const superResponse = super.getData(weatherParameters);
const superResponse = super.getData(weatherParameters, refresh);
let forecast;
try {
// get the forecast
forecast = await json(weatherParameters.forecastGridData, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
forecast = await json(this.weatherParameters.forecastGridData, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
// parse the forecast
this.data = await parseForecast(forecast.properties);
} catch (error) {
console.error('Get hourly forecast failed');
console.error(error.status, error.responseJSON);
if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers
this.getDataCallback(undefined);
return;
// use old data if available
if (this.data) {
console.log('Using previous hourly forecast');
// don't return, this.data is usable from the previous update
} else {
if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers
this.getDataCallback(undefined);
return;
}
}
this.data = await parseForecast(forecast.properties);
this.getDataCallback();
if (!superResponse) return;

View File

@@ -16,14 +16,15 @@ class LatestObservations extends WeatherDisplay {
this.MaximumRegionalStations = 7;
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
// latest observations does a silent refresh but will not fall back to previously fetched data
// this is intentional because up to 30 stations are available to pull data from
// calculate distance to each station
const stationsByDistance = Object.keys(StationInfo).map((key) => {
const station = StationInfo[key];
const distance = calcDistance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
const distance = calcDistance(station.lat, station.lon, this.weatherParameters.latitude, this.weatherParameters.longitude);
return { ...station, distance };
});

View File

@@ -14,19 +14,21 @@ class LocalForecast extends WeatherDisplay {
this.timing.baseDelay = 5000;
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
// get raw data
const rawData = await this.getRawData(weatherParameters);
// check for data
if (!rawData) {
const rawData = await this.getRawData(this.weatherParameters);
// check for data, or if there's old data available
if (!rawData && !this.data) {
// fail for no old or new data
this.setStatus(STATUS.failed);
return;
}
// store the data
this.data = rawData || this.data;
// parse raw data
const conditions = parse(rawData);
const conditions = parse(this.data);
// read each text
this.screenTexts = conditions.map((condition) => {
@@ -70,7 +72,6 @@ class LocalForecast extends WeatherDisplay {
} catch (error) {
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
console.error(error.status, error.responseJSON);
this.setStatus(STATUS.failed);
return false;
}
}

View File

@@ -149,7 +149,7 @@ const playerEnded = () => {
// next track
currentTrack += 1;
// roll over and re-randomize the tracks
if (currentTrack >= playlist.availableFiles) {
if (currentTrack >= playlist.availableFiles.length) {
randomizePlaylist();
currentTrack = 0;
}

View File

@@ -15,26 +15,11 @@ let playing = false;
let progress;
const weatherParameters = {};
// auto refresh
const AUTO_REFRESH_INTERVAL_MS = 500;
const AUTO_REFRESH_TIME_MS = 600_000; // 10 min.
const CHK_AUTO_REFRESH_SELECTOR = '#chkAutoRefresh';
let AutoRefreshIntervalId = null;
let AutoRefreshCountMs = 0;
const init = async () => {
// set up resize handler
window.addEventListener('resize', resize);
resize();
// auto refresh
const autoRefresh = localStorage.getItem('autoRefresh');
if (!autoRefresh || autoRefresh === 'true') {
document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked = true;
} else {
document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked = false;
}
document.querySelector(CHK_AUTO_REFRESH_SELECTOR).addEventListener('change', autoRefreshChange);
generateCheckboxes();
};
@@ -123,12 +108,6 @@ const updateStatus = (value) => {
if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) {
navTo(msg.command.firstFrame);
}
// send loaded messaged to parent
if (countLoadedDisplays() < displays.length) return;
// everything loaded, set timestamps
AssignLastUpdate(new Date());
};
// note: a display that is "still waiting"/"retrying" is considered loaded intentionally
@@ -202,8 +181,6 @@ const loadDisplay = (direction) => {
idx = wrap(curIdx + (i + 1) * direction, totalDisplays);
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 (idx <= curIdx && refreshCheck()) return;
const newDisplay = displays[idx];
// hide all displays
hideAllCanvases();
@@ -320,83 +297,8 @@ const populateWeatherParameters = (params) => {
document.querySelector('#spanZoneId').innerHTML = params.zoneId;
};
const autoRefreshChange = (e) => {
const { checked } = e.target;
if (checked) {
startAutoRefreshTimer();
} else {
stopAutoRefreshTimer();
}
localStorage.setItem('autoRefresh', checked);
};
const AssignLastUpdate = (date) => {
if (date) {
document.querySelector('#spanLastRefresh').innerHTML = date.toLocaleString('en-US', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
if (document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked) startAutoRefreshTimer();
} else {
document.querySelector('#spanLastRefresh').innerHTML = '(none)';
}
};
const latLonReceived = (data, haveDataCallback) => {
getWeather(data, haveDataCallback);
AssignLastUpdate(null);
};
const startAutoRefreshTimer = () => {
// Ensure that any previous timer has already stopped.
// check if timer is running
if (AutoRefreshIntervalId) return;
// Reset the time elapsed.
AutoRefreshCountMs = 0;
const AutoRefreshTimer = () => {
// Increment the total time elapsed.
AutoRefreshCountMs += AUTO_REFRESH_INTERVAL_MS;
// Display the count down.
let RemainingMs = (AUTO_REFRESH_TIME_MS - AutoRefreshCountMs);
if (RemainingMs < 0) {
RemainingMs = 0;
}
const dt = new Date(RemainingMs);
document.querySelector('#spanRefreshCountDown').innerHTML = `${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}`;
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && !isPlaying()) loadTwcData();
};
AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, AUTO_REFRESH_INTERVAL_MS);
AutoRefreshTimer();
};
const stopAutoRefreshTimer = () => {
if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId);
document.querySelector('#spanRefreshCountDown').innerHTML = '--:--';
AutoRefreshIntervalId = null;
}
};
const refreshCheck = () => {
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && isPlaying()) {
loadTwcData();
return true;
}
return false;
};
const loadTwcData = () => {
if (loadTwcData.callback) loadTwcData.callback();
};
const registerRefreshData = (callback) => {
loadTwcData.callback = callback;
};
const timeZone = () => weatherParameters.timeZone;
@@ -414,7 +316,5 @@ export {
msg,
message,
latLonReceived,
stopAutoRefreshTimer,
registerRefreshData,
timeZone,
};

View File

@@ -42,19 +42,17 @@ class Radar extends WeatherDisplay {
];
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
// ALASKA AND HAWAII AREN'T SUPPORTED!
if (weatherParameters.state === 'AK' || weatherParameters.state === 'HI') {
if (this.weatherParameters.state === 'AK' || this.weatherParameters.state === 'HI') {
this.setStatus(STATUS.noData);
return;
}
// get the base map
let src = 'images/4000RadarMap2.jpg';
if (weatherParameters.State === 'HI') src = 'images/HawaiiRadarMap2.png';
const src = 'images/4000RadarMap2.jpg';
this.baseMap = await loadImg(src);
const baseUrl = 'https://mesonet.agron.iastate.edu/archive/data/';
@@ -70,7 +68,7 @@ class Radar extends WeatherDisplay {
const lists = (await Promise.all(baseUrls.map(async (url) => {
try {
// get a list of available radars
// get a list of available radars
return text(url, { cors: true });
} catch (error) {
console.log('Unable to get list of radars');
@@ -91,7 +89,7 @@ class Radar extends WeatherDisplay {
const anchors = xmlDoc.querySelectorAll('a');
const urls = [];
Array.from(anchors).forEach((elem) => {
if (elem.innerHTML?.match(/n0r_\d{12}\.png/)) {
if (elem.innerHTML?.match(/n0r_\d{12}\.png/)) {
urls.push(elem.href);
}
});
@@ -110,19 +108,12 @@ class Radar extends WeatherDisplay {
const height = 1600;
offsetX *= 2;
offsetY *= 2;
const sourceXY = utils.getXYFromLatitudeLongitudeMap(weatherParameters, offsetX, offsetY);
// create working context for manipulation
const workingCanvas = document.createElement('canvas');
workingCanvas.width = width;
workingCanvas.height = height;
const workingContext = workingCanvas.getContext('2d');
workingContext.imageSmoothingEnabled = false;
const sourceXY = utils.getXYFromLatitudeLongitudeMap(this.weatherParameters, offsetX, offsetY);
// calculate radar offsets
const radarOffsetX = 120;
const radarOffsetY = 70;
const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(weatherParameters, offsetX, offsetY);
const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(this.weatherParameters, offsetX, offsetY);
const radarSourceX = radarSourceXY.x / 2;
const radarSourceY = radarSourceXY.y / 2;
@@ -135,6 +126,13 @@ class Radar extends WeatherDisplay {
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false;
// create working context for manipulation
const workingCanvas = document.createElement('canvas');
workingCanvas.width = width;
workingCanvas.height = height;
const workingContext = workingCanvas.getContext('2d');
workingContext.imageSmoothingEnabled = false;
// get the image
const response = await fetch(rewriteUrl(url));
@@ -170,7 +168,7 @@ class Radar extends WeatherDisplay {
workingContext.drawImage(imgBlob, 0, 0, width, 1600);
// get the base map
context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, 640, 367);
context.drawImage(this.baseMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, 640, 367);
// crop the radar image
const cropCanvas = document.createElement('canvas');

View File

@@ -21,9 +21,11 @@ class RegionalForecast extends WeatherDisplay {
this.timing.totalScreens = 3;
}
async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return;
const weatherParameters = _weatherParameters ?? this.weatherParameters;
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
// regional forecast implements a silent reload
// but it will not fall back to previously loaded data if data can not be loaded
// there are enough other cities available to populate the map sufficiently even if some do not load
// pre-load the base map
let baseMap = 'images/Basemap2.png';
@@ -40,14 +42,14 @@ class RegionalForecast extends WeatherDisplay {
y: 117,
};
// get user's location in x/y
const sourceXY = utils.getXYFromLatitudeLongitude(weatherParameters.latitude, weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
const sourceXY = utils.getXYFromLatitudeLongitude(this.weatherParameters.latitude, this.weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
// get latitude and longitude limits
const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, weatherParameters.state);
const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, this.weatherParameters.state);
// get a target distance
let targetDistance = 2.5;
if (weatherParameters.state === 'HI') targetDistance = 1;
if (this.weatherParameters.state === 'HI') targetDistance = 1;
// make station info into an array
const stationInfoArray = Object.values(StationInfo).map((value) => ({ ...value, targetDistance }));
@@ -86,7 +88,7 @@ class RegionalForecast extends WeatherDisplay {
const forecast = await json(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/forecast`);
// get XY on map for city
const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, this.weatherParameters.state);
// wait for the regional observation if it's not done yet
const observation = await observationPromise;

View File

@@ -22,6 +22,13 @@ const init = () => {
['us', 'US'],
['si', 'Metric'],
]);
settings.refreshTime = new Setting('refreshTime', 'Refresh Time', 'select', 600_000, null, false, [
[30_000, 'TESTING'],
[300_000, '5 minutes'],
[600_000, '10 minutes'],
[900_000, '15 minutes'],
[1_800_000, '30 minutes'],
]);
// generate html objects
const settingHtml = Object.values(settings).map((d) => d.generate());

View File

@@ -26,20 +26,42 @@ class TravelForecast extends WeatherDisplay {
if (extra !== 0) this.timing.delay.push(Math.round(this.extra * this.cityHeight));
// add the final 3 second delay
this.timing.delay.push(150);
// add previous data cache
this.previousData = [];
}
async getData() {
async getData(weatherParameters, refresh) {
// super checks for enabled
if (!super.getData()) return;
const forecastPromises = TravelCities.map(async (city) => {
if (!super.getData(weatherParameters, refresh)) return;
// clear stored data if not refresh
if (!refresh) {
this.previousData = [];
}
const forecastPromises = TravelCities.map(async (city, index) => {
try {
// get point then forecast
if (!city.point) throw new Error('No pre-loaded point');
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`, {
data: {
units: settings.units.value,
},
});
let forecast;
try {
forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`, {
data: {
units: settings.units.value,
},
});
// store for the next run
this.previousData[index] = forecast;
} catch (e) {
// if there's previous data use it
if (this.previousData?.[index]) {
forecast = this.previousData?.[index];
} else {
// otherwise re-throw for the standard error handling
throw (e);
}
}
// determine today or tomorrow (shift periods by 1 if tomorrow)
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
// return a pared-down forecast

View File

@@ -73,7 +73,7 @@ const doFetch = (url, params) => new Promise((resolve, reject) => {
// out of retries
return resolve(response);
})
.catch((error) => reject(error));
.catch(reject);
});
const delay = (time, func, ...args) => new Promise((resolve) => {

View File

@@ -181,7 +181,7 @@ class Setting {
selectHighlight(newValue) {
// set the dropdown to the provided value
this.element.querySelectorAll('option').forEach((elem) => {
this?.element?.querySelectorAll('option')?.forEach?.((elem) => {
elem.selected = (newValue?.toFixed?.(2) === elem.value) || (newValue === elem.value);
});
}

View File

@@ -22,6 +22,7 @@ class WeatherDisplay {
this.okToDrawCurrentConditions = true;
this.okToDrawCurrentDateTime = true;
this.showOnProgress = true;
this.autoRefreshHandle = null;
// default navigation timing
this.timing = {
@@ -129,9 +130,14 @@ class WeatherDisplay {
}
// get necessary data for this display
getData(weatherParameters) {
// clear current data
this.data = undefined;
getData(weatherParameters, refresh) {
// refresh doesn't delete existing data, and is reused if the silent refresh fails
if (!refresh) {
this.data = undefined;
}
// clear any refresh timers
clearTimeout(this.autoRefreshHandle);
this.autoRefreshHandle = null;
// store weatherParameters locally in case we need them later
if (weatherParameters) this.weatherParameters = weatherParameters;
@@ -144,6 +150,9 @@ class WeatherDisplay {
return false;
}
// set up auto reload
this.autoRefreshHandle = setTimeout(() => this.getData(false, true), settings.refreshTime.value);
// recalculate navigation timing (in case it was modified in the constructor)
this.calcNavTiming();
return true;

File diff suppressed because one or more lines are too long

View File

@@ -392,12 +392,13 @@ class SystemZone extends Zone {
}
}
let dtfCache = {};
function makeDTF(zone) {
if (!dtfCache[zone]) {
dtfCache[zone] = new Intl.DateTimeFormat("en-US", {
const dtfCache = new Map();
function makeDTF(zoneName) {
let dtf = dtfCache.get(zoneName);
if (dtf === undefined) {
dtf = new Intl.DateTimeFormat("en-US", {
hour12: false,
timeZone: zone,
timeZone: zoneName,
year: "numeric",
month: "2-digit",
day: "2-digit",
@@ -406,8 +407,9 @@ function makeDTF(zone) {
second: "2-digit",
era: "short",
});
dtfCache.set(zoneName, dtf);
}
return dtfCache[zone];
return dtf;
}
const typeToPos = {
@@ -443,7 +445,7 @@ function partsOffset(dtf, date) {
return filled;
}
let ianaZoneCache = {};
const ianaZoneCache = new Map();
/**
* A zone identified by an IANA identifier, like America/New_York
* @implements {Zone}
@@ -454,10 +456,11 @@ class IANAZone extends Zone {
* @return {IANAZone}
*/
static create(name) {
if (!ianaZoneCache[name]) {
ianaZoneCache[name] = new IANAZone(name);
let zone = ianaZoneCache.get(name);
if (zone === undefined) {
ianaZoneCache.set(name, (zone = new IANAZone(name)));
}
return ianaZoneCache[name];
return zone;
}
/**
@@ -465,8 +468,8 @@ class IANAZone extends Zone {
* @return {void}
*/
static resetCache() {
ianaZoneCache = {};
dtfCache = {};
ianaZoneCache.clear();
dtfCache.clear();
}
/**
@@ -569,6 +572,7 @@ class IANAZone extends Zone {
* @return {number}
*/
offset(ts) {
if (!this.valid) return NaN;
const date = new Date(ts);
if (isNaN(date)) return NaN;
@@ -634,36 +638,36 @@ function getCachedLF(locString, opts = {}) {
return dtf;
}
let intlDTCache = {};
const intlDTCache = new Map();
function getCachedDTF(locString, opts = {}) {
const key = JSON.stringify([locString, opts]);
let dtf = intlDTCache[key];
if (!dtf) {
let dtf = intlDTCache.get(key);
if (dtf === undefined) {
dtf = new Intl.DateTimeFormat(locString, opts);
intlDTCache[key] = dtf;
intlDTCache.set(key, dtf);
}
return dtf;
}
let intlNumCache = {};
const intlNumCache = new Map();
function getCachedINF(locString, opts = {}) {
const key = JSON.stringify([locString, opts]);
let inf = intlNumCache[key];
if (!inf) {
let inf = intlNumCache.get(key);
if (inf === undefined) {
inf = new Intl.NumberFormat(locString, opts);
intlNumCache[key] = inf;
intlNumCache.set(key, inf);
}
return inf;
}
let intlRelCache = {};
const intlRelCache = new Map();
function getCachedRTF(locString, opts = {}) {
const { base, ...cacheKeyOpts } = opts; // exclude `base` from the options
const key = JSON.stringify([locString, cacheKeyOpts]);
let inf = intlRelCache[key];
if (!inf) {
let inf = intlRelCache.get(key);
if (inf === undefined) {
inf = new Intl.RelativeTimeFormat(locString, opts);
intlRelCache[key] = inf;
intlRelCache.set(key, inf);
}
return inf;
}
@@ -678,14 +682,28 @@ function systemLocale() {
}
}
let weekInfoCache = {};
const intlResolvedOptionsCache = new Map();
function getCachedIntResolvedOptions(locString) {
let opts = intlResolvedOptionsCache.get(locString);
if (opts === undefined) {
opts = new Intl.DateTimeFormat(locString).resolvedOptions();
intlResolvedOptionsCache.set(locString, opts);
}
return opts;
}
const weekInfoCache = new Map();
function getCachedWeekInfo(locString) {
let data = weekInfoCache[locString];
let data = weekInfoCache.get(locString);
if (!data) {
const locale = new Intl.Locale(locString);
// browsers currently implement this as a property, but spec says it should be a getter function
data = "getWeekInfo" in locale ? locale.getWeekInfo() : locale.weekInfo;
weekInfoCache[locString] = data;
// minimalDays was removed from WeekInfo: https://github.com/tc39/proposal-intl-locale-info/issues/86
if (!("minimalDays" in data)) {
data = { ...fallbackWeekSettings, ...data };
}
weekInfoCache.set(locString, data);
}
return data;
}
@@ -784,7 +802,7 @@ function supportsFastNumbers(loc) {
loc.numberingSystem === "latn" ||
!loc.locale ||
loc.locale.startsWith("en") ||
new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn"
getCachedIntResolvedOptions(loc.locale).numberingSystem === "latn"
);
}
}
@@ -943,7 +961,6 @@ const fallbackWeekSettings = {
/**
* @private
*/
class Locale {
static fromOpts(opts) {
return Locale.create(
@@ -967,9 +984,11 @@ class Locale {
static resetCache() {
sysLocaleCache = null;
intlDTCache = {};
intlNumCache = {};
intlRelCache = {};
intlDTCache.clear();
intlNumCache.clear();
intlRelCache.clear();
intlResolvedOptionsCache.clear();
weekInfoCache.clear();
}
static fromObject({ locale, numberingSystem, outputCalendar, weekSettings } = {}) {
@@ -1123,7 +1142,7 @@ class Locale {
return (
this.locale === "en" ||
this.locale.toLowerCase() === "en-us" ||
new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us")
getCachedIntResolvedOptions(this.intl).locale.startsWith("en-us")
);
}
@@ -1461,22 +1480,26 @@ function parseDigits(str) {
}
// cache of {numberingSystem: {append: regex}}
let digitRegexCache = {};
const digitRegexCache = new Map();
function resetDigitRegexCache() {
digitRegexCache = {};
digitRegexCache.clear();
}
function digitRegex({ numberingSystem }, append = "") {
const ns = numberingSystem || "latn";
if (!digitRegexCache[ns]) {
digitRegexCache[ns] = {};
let appendCache = digitRegexCache.get(ns);
if (appendCache === undefined) {
appendCache = new Map();
digitRegexCache.set(ns, appendCache);
}
if (!digitRegexCache[ns][append]) {
digitRegexCache[ns][append] = new RegExp(`${numberingSystems[ns]}${append}`);
let regex = appendCache.get(append);
if (regex === undefined) {
regex = new RegExp(`${numberingSystems[ns]}${append}`);
appendCache.set(append, regex);
}
return digitRegexCache[ns][append];
return regex;
}
let now = () => Date.now(),
@@ -4227,6 +4250,14 @@ class Interval {
return this.isValid ? this.e : null;
}
/**
* Returns the last DateTime included in the interval (since end is not part of the interval)
* @type {DateTime}
*/
get lastDateTime() {
return this.isValid ? (this.e ? this.e.minus(1) : null) : null;
}
/**
* Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.
* @type {boolean}
@@ -4491,8 +4522,11 @@ class Interval {
}
/**
* Merge an array of Intervals into a equivalent minimal set of Intervals.
* Merge an array of Intervals into an equivalent minimal set of Intervals.
* Combines overlapping and adjacent Intervals.
* The resulting array will contain the Intervals in ascending order, that is, starting with the earliest Interval
* and ending with the latest.
*
* @param {Array} intervals
* @return {Array}
*/
@@ -5815,15 +5849,27 @@ function normalizeUnitWithLocalWeeks(unit) {
// This is safe for quickDT (used by local() and utc()) because we don't fill in
// higher-order units from tsNow (as we do in fromObject, this requires that
// offset is calculated from tsNow).
/**
* @param {Zone} zone
* @return {number}
*/
function guessOffsetForZone(zone) {
if (!zoneOffsetGuessCache[zone]) {
if (zoneOffsetTs === undefined) {
zoneOffsetTs = Settings.now();
}
zoneOffsetGuessCache[zone] = zone.offset(zoneOffsetTs);
if (zoneOffsetTs === undefined) {
zoneOffsetTs = Settings.now();
}
return zoneOffsetGuessCache[zone];
// Do not cache anything but IANA zones, because it is not safe to do so.
// Guessing an offset which is not present in the zone can cause wrong results from fixOffset
if (zone.type !== "iana") {
return zone.offset(zoneOffsetTs);
}
const zoneName = zone.name;
let offsetGuess = zoneOffsetGuessCache.get(zoneName);
if (offsetGuess === undefined) {
offsetGuess = zone.offset(zoneOffsetTs);
zoneOffsetGuessCache.set(zoneName, offsetGuess);
}
return offsetGuess;
}
// this is a dumbed down version of fromObject() that runs about 60% faster
@@ -5913,7 +5959,7 @@ let zoneOffsetTs;
* This optimizes quickDT via guessOffsetForZone to avoid repeated calls of
* zone.offset().
*/
let zoneOffsetGuessCache = {};
const zoneOffsetGuessCache = new Map();
/**
* A DateTime is an immutable data structure representing a specific date and time and accompanying methods. It contains class and instance methods for creating, parsing, interrogating, transforming, and formatting them.
@@ -6478,7 +6524,7 @@ class DateTime {
static resetCache() {
zoneOffsetTs = undefined;
zoneOffsetGuessCache = {};
zoneOffsetGuessCache.clear();
}
// INFO
@@ -7247,7 +7293,7 @@ class DateTime {
* @example DateTime.now().toISO() //=> '2017-04-22T20:47:05.335-04:00'
* @example DateTime.now().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'
* @example DateTime.now().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'
* @return {string}
* @return {string|null}
*/
toISO({
format = "extended",
@@ -7274,7 +7320,7 @@ class DateTime {
* @param {string} [opts.format='extended'] - choose between the basic and extended format
* @example DateTime.utc(1982, 5, 25).toISODate() //=> '1982-05-25'
* @example DateTime.utc(1982, 5, 25).toISODate({ format: 'basic' }) //=> '19820525'
* @return {string}
* @return {string|null}
*/
toISODate({ format = "extended" } = {}) {
if (!this.isValid) {
@@ -7359,7 +7405,7 @@ class DateTime {
/**
* Returns a string representation of this DateTime appropriate for use in SQL Date
* @example DateTime.utc(2014, 7, 13).toSQLDate() //=> '2014-07-13'
* @return {string}
* @return {string|null}
*/
toSQLDate() {
if (!this.isValid) {
@@ -7454,7 +7500,7 @@ class DateTime {
}
/**
* Returns the epoch seconds of this DateTime.
* Returns the epoch seconds (including milliseconds in the fractional part) of this DateTime.
* @return {number}
*/
toSeconds() {
@@ -7561,7 +7607,7 @@ class DateTime {
/**
* Return an Interval spanning between this DateTime and another DateTime
* @param {DateTime} otherDateTime - the other end point of the Interval
* @return {Interval}
* @return {Interval|DateTime}
*/
until(otherDateTime) {
return this.isValid ? Interval.fromDateTimes(this, otherDateTime) : this;
@@ -7979,7 +8025,7 @@ function friendlyDateTime(dateTimeish) {
}
}
const VERSION = "3.5.0";
const VERSION = "3.6.1";
export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
//# sourceMappingURL=luxon.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -745,8 +745,7 @@ body {
>.heading,
#enabledDisplays,
#settings,
#divInfo,
#divRefresh {
#divInfo {
display: none;
}
}

View File

@@ -14,7 +14,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="manifest" href="manifest.json" />
<link rel="icon" href="images/Logo192.png" />
<link rel="preload" href="playlist.json" as="fetch"/>
<link rel="preload" href="playlist.json" as="fetch" crossorigin="anonymous"/>
<meta property="og:image" content="https://weatherstar.netbymatt.com/images/social/1200x600.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
@@ -133,7 +133,7 @@
<div id="divTwcBottomRight">
<div id="ToggleMedia">
<img class="navButton off" src="images/nav/ic_volume_off_white_24dp_2x.png" title="Unmute" />
<img class="navButton on" src="images/nav/ic_volume_on_white_24dp_2x.png" title="Unmute" />
<img class="navButton on" src="images/nav/ic_volume_on_white_24dp_2x.png" title="Mute" />
</div>
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_2x.png" title="Enter Fullscreen" />
</div>
@@ -173,11 +173,6 @@
Zone Id: <span id="spanZoneId"></span><br />
</div>
<div id="divRefresh">
Last Update: <span id="spanLastRefresh">(None)</span><br />
<input id="chkAutoRefresh" name="chkAutoRefresh" type="checkbox" /><label id="lblRefreshCountDown" for="chkAutoRefresh">Auto Refresh: <span id="spanRefreshCountDown">--:--</span></label>
</div>
</body>
</html>

View File

@@ -52,11 +52,7 @@
"[html]": {
"editor.defaultFormatter": "j69.ejs-beautify"
},
"files.exclude": {
"**/node_modules": true,
"**/debug.log": true,
"server/scripts/custom.js": true
},
"files.exclude": {},
"files.eol": "\n",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {