import { writable, readable, get } from 'svelte/store'; import { parseDateAsUTC } from './lib/DateUtils'; // Choose dark or light mode. export const theme = writable('light'); function check_system_theme() { if (typeof window === "undefined") { return; } let system_theme = window.matchMedia("(prefers-color-scheme:dark)").matches ? "dark" : "light"; if (system_theme != get(theme)) { theme.set(system_theme); } } setInterval(check_system_theme, 1000); theme.subscribe(value => { if (typeof window === "undefined") { return; } if (value == "dark") { window.document.body.classList.add("dark"); } else { window.document.body.classList.remove("dark"); } }); /* * the different data sources are * - Geocode Used to get user location based on coordinates, this is needed for METNor data. * - METNor (Yr) Earth weather * - USNOAA (Nasa ish) Space weather, Aurora info * * The methots here does exit early if the code is running server-side and not * in the browser. This is to attempt to fix. **/ const base_attributes = {"updated": false, "updating": true} export const navigator_location = writable({...base_attributes, "available": false, "longitude": null, "latitude": null, "city": null}); export const earth_weather = writable({...base_attributes, "available": false}); export const space_weather = writable({...base_attributes}); // Kickstart store updates updateNavigatorLocation(); navigator_location.subscribe(updateEarthWeather); updateSpaceWeather(); // Save data const saveToLocalstorage = (name, value) => { if (typeof window === "undefined") { return; } localStorage.setItem(name, JSON.stringify(value)); } const getFromLocalstorage = (name) => { if (typeof window === "undefined") { return; } return JSON.parse(localStorage.getItem(name)); }; navigator_location.subscribe(v => saveToLocalstorage("navigator_location", v)); earth_weather.subscribe(v => saveToLocalstorage("earth_weather", v)); space_weather.subscribe(v => saveToLocalstorage("space_weather", v)); /** * Will update the store "navigator_location" with the users geolocation if * possible. */ async function updateNavigatorLocation() { if(typeof window === "undefined") { return; } setUpdated(navigator_location, true); let coords; try { coords = await getBrowserGeolocation(); } catch (e) { console.log(e); navigator_location.update(v => ({...v, "available": false, "city": null, "longitude": null, "latitude": null})); setUpdated(navigator_location, false); return; } let res = await fetch(`https://geocode.xyz/${coords.latitude},${coords.longitude}?geoit=json`); let locDat = await res.json(); navigator_location.update(v => ({...v, "available": true, "city": locDat["city"], ...coords})); setUpdated(navigator_location, false); } /** * Wraps navigator.geolocation in a promise, this promise will be rejected if * code is not run in a browser. */ async function getBrowserGeolocation() { return new Promise((resolve, reject) => { if (typeof navigator !== "undefined") { navigator.geolocation.getCurrentPosition( position => { resolve({ "longitude": position.coords.longitude, "latitude": position.coords.latitude }); }, error => { reject(error); } ); } else { reject(Error()); } }); } /** * Update the "earth_weather" store with weather information about earth :) */ async function updateEarthWeather(location=null) { if (typeof window === "undefined") { return; } if (location === null) { return; } if (location.updating) { return; } if (!location.available) { earth_weather.update(v => ({ ...v, "available": false })); setUpdated(earth_weather, false); return; } setUpdated(earth_weather, true); let res = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${location.latitude}&lon=${location.longitude}`); let yr_data = await res.json(); let current_weather = {"clouds": null, "temp": null} try { // @TODO: Make a similar thing as for kp-index to get the closest time to now. current_weather.clouds = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["cloud_area_fraction"]; current_weather.temp = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["air_temperature"]; yr_data["properties"]["timeseries"] = yr_data["properties"]["timeseries"].map(x => ({...x, "time": parseDateAsUTC(x.time)})); } catch (e) {} earth_weather.update(v => ({ ...v, "yr_data_raw": yr_data, "now": current_weather, "available": true })); setUpdated(earth_weather, false); } /** * This will update the "space_weather" store with the newest data. */ async function updateSpaceWeather() { if(typeof window === "undefined") { return; } setUpdated(space_weather, true); let spaceWeather = await getSpaceWeather(); space_weather.update(v => ({...v, ...spaceWeather})); setUpdated(space_weather, false); } /** * This will return all the data we are interested in for the space weather * store. */ async function getSpaceWeather() { let ret = { "now": { "bz": "-", "bt": "-", "kp": "-", "kp_min": "-", "kp_max": "-" }, "usnoaa_data_raw": { "solar_wind_mag_field": false, "noaa_planetary_k_index_forecast": false, "geospace_pred_est_kp_1_hour": false, "outlook_27_day": false } }; let tmp; let res = await fetch("https://services.swpc.noaa.gov/products/summary/solar-wind-mag-field.json"); ret.usnoaa_data_raw.solar_wind_mag_field = await res.json(); ret.usnoaa_data_raw.solar_wind_mag_field.TimeStamp = parseDateAsUTC(ret.usnoaa_data_raw.solar_wind_mag_field.TimeStamp); ret.now.bz = ret.usnoaa_data_raw.solar_wind_mag_field["Bz"]; ret.now.bt = ret.usnoaa_data_raw.solar_wind_mag_field["Bt"]; res = await fetch("https://services.swpc.noaa.gov/json/geospace/geospace_pred_est_kp_1_hour.json"); tmp = await res.json(); tmp = tmp.map(x => ({ ...x, "model_prediction_time": parseDateAsUTC(x.model_prediction_time) })); ret.usnoaa_data_raw.geospace_pred_est_kp_1_hour = tmp res = await fetch("https://services.swpc.noaa.gov/text/27-day-outlook.txt"); tmp = await res.text(); tmp = [...tmp.matchAll( /^(?