fix: type errors (#3)

Co-authored-by: terraputix <terraputix@mailbox.org>
Reviewed-on: useweb/open-meteo-blue#3
This commit was merged in pull request #3.
This commit is contained in:
2026-02-15 15:53:54 +01:00
co-authored by terraputix
parent be0f7f822d
commit 6872cac00e
11 changed files with 569 additions and 578 deletions
-24
View File
@@ -1,24 +0,0 @@
import { writable } from 'svelte/store';
// Placeholder function for urlHashStore
// In a real application, this would handle URL hash parameters
// and return a Svelte store that reflects those parameters.
export function urlHashStore(initialValue: Record<string, unknown>) {
const { subscribe, set, update } = writable(initialValue);
// In a full implementation, you would add logic here to:
// 1. Read the URL hash on initialization
// 2. Parse the hash into an object
// 3. Update the store with these values
// 4. Listen for changes to the store and update the URL hash accordingly
// 5. Listen for URL hash changes (e.g., back/forward buttons) and update the store
return {
subscribe,
set,
update,
// You might want to add methods to easily update specific hash parameters
updateParam: (key: string, value: unknown) =>
update((current) => ({ ...current, [key]: value }))
};
}
+10 -3
View File
@@ -16,9 +16,16 @@
let { children }: Props = $props(); let { children }: Props = $props();
interface CurrentWeather {
current: {
temperature_2m: number;
weather_code: number;
};
}
let location = $state(get(storedLocation)); let location = $state(get(storedLocation));
let mounted = $state(false); let mounted = $state(false);
let currentWeather = $state(null); let currentWeather = $state<CurrentWeather | null>(null);
// Subscribe to location changes // Subscribe to location changes
storedLocation.subscribe((value) => { storedLocation.subscribe((value) => {
@@ -47,8 +54,8 @@
} }
}; };
const getWeatherIcon = (code) => { const getWeatherIcon = (code: number): string => {
const iconMap = { const iconMap: Record<number, string> = {
0: '☀️', 0: '☀️',
1: '🌤️', 1: '🌤️',
2: '⛅', 2: '⛅',
+1 -1
View File
@@ -1,6 +1,6 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from '$types'; import type { PageLoad } from './$types';
export const prerender = true; export const prerender = true;
+40 -28
View File
@@ -6,7 +6,6 @@
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { storedLocation } from '$lib/stores/settings'; import { storedLocation } from '$lib/stores/settings';
import { urlHashStore } from '$lib/stores/url-hash-store';
import { Label } from '$lib/components/ui/label'; import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch'; import { Switch } from '$lib/components/ui/switch';
@@ -16,14 +15,15 @@
let node: HTMLElement; let node: HTMLElement;
let chart: any; let chart: any;
let Highcharts = $state(null); let Highcharts = $state<typeof import('highcharts') | null>(null);
let showLegend = $state(false); let showLegend = $state(false);
let averageOnly = $state(false); let averageOnly = $state(false);
const location = get(storedLocation); const location = get(storedLocation);
const params = urlHashStore({ // Local component state for chart configuration
let params = $state({
latitude: [52.52], latitude: [52.52],
longitude: [13.41], longitude: [13.41],
...defaultParameters, ...defaultParameters,
@@ -41,20 +41,24 @@
if (dev) { if (dev) {
// const HighchartsDebugger = await import('highcharts/modules/debugger'); // const HighchartsDebugger = await import('highcharts/modules/debugger');
// HighchartsDebugger.default(Highcharts); // HighchartsDebugger.default(Highcharts);
const Debugger = (await import('highcharts/es-modules/Extensions/Debugger/Debugger.js')) const Debugger = (
.default; await import('highcharts/es-modules/Extensions/Debugger/Debugger.js' as any)
const ErrorMessages = (
await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js')
).default; ).default;
Highcharts.errorMessages = ErrorMessages; const ErrorMessages = (
await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js' as any)
).default;
if (Highcharts) {
(Highcharts as any).errorMessages = ErrorMessages;
Debugger.compose(Highcharts.Chart); Debugger.compose(Highcharts.Chart);
} }
}
}); });
$effect(async () => { $effect(() => {
const loadData = async () => {
count = 0; count = 0;
if (Highcharts) { if (Highcharts) {
node.replaceChildren([]); node.replaceChildren();
const dataDaily = await fetch( const dataDaily = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&timeformat=unixtime&daily=sunset,sunrise&forecast_days=14` `https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&timeformat=unixtime&daily=sunset,sunrise&forecast_days=14`
@@ -62,7 +66,7 @@
const wd = await dataDaily.json(); const wd = await dataDaily.json();
const dataReq = await fetch( const dataReq = await fetch(
`https://ensemble-api.open-meteo.com/v1/ensemble?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${$params.hourly.join(',')}&models=${$params.models.join(',')}&timeformat=unixtime&forecast_days=14` `https://ensemble-api.open-meteo.com/v1/ensemble?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${params.hourly?.join(',') || ''}&models=${params.models?.join(',') || ''}&timeformat=unixtime&forecast_days=14`
); );
const data = await dataReq.json(); const data = await dataReq.json();
@@ -70,7 +74,7 @@
if ('daily' in wd && 'sunrise' in wd.daily && 'sunset' in wd.daily) { if ('daily' in wd && 'sunrise' in wd.daily && 'sunset' in wd.daily) {
let rise = wd.daily.sunrise; let rise = wd.daily.sunrise;
let set = wd.daily.sunset; let set = wd.daily.sunset;
plotBands = rise.map(function (r, i) { plotBands = rise.map(function (r: any, i: number) {
return { return {
color: 'rgba(255, 255, 194, 0.5)', color: 'rgba(255, 255, 194, 0.5)',
from: (r + data.utc_offset_seconds) * 1000, from: (r + data.utc_offset_seconds) * 1000,
@@ -82,7 +86,7 @@
let minValues = new Array(data.hourly.time.length).fill(undefined); let minValues = new Array(data.hourly.time.length).fill(undefined);
let maxValues = new Array(data.hourly.time.length).fill(undefined); let maxValues = new Array(data.hourly.time.length).fill(undefined);
for (let variable of $params.hourly) { for (let variable of params.hourly || []) {
const chartDiv = document.createElement('div'); const chartDiv = document.createElement('div');
let unit; let unit;
@@ -99,7 +103,7 @@
continue; continue;
} }
if (model.startsWith(variable)) { if (model.startsWith(variable)) {
for (let [index, val] of values.entries()) { for (let [index, val] of (values as any[]).entries()) {
if (val) { if (val) {
let avVal = average[index]; let avVal = average[index];
average[index] = avVal + val; average[index] = avVal + val;
@@ -145,7 +149,9 @@
dashStyle: 'ShortDashDot', dashStyle: 'ShortDashDot',
color: '#5e5e5e', color: '#5e5e5e',
type: type:
unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²' ? 'column' : 'spline', unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²'
? 'column'
: 'spline',
tooltip: { tooltip: {
valueSuffix: ' ' + unit valueSuffix: ' ' + unit
}, },
@@ -160,19 +166,20 @@
className: 'highcharts-average-series' className: 'highcharts-average-series'
}); });
new Highcharts.Chart(chartDiv, { new Highcharts!.Chart({
credits: {
text: count === $params.hourly.length - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
chart: { chart: {
renderTo: chartDiv,
height: showLegend ? '400px' : '300px', height: showLegend ? '400px' : '300px',
styledMode: true, styledMode: true,
marginLeft: '50', marginLeft: 50,
marginRight: 0 marginRight: 0
}, },
credits: {
text: count === (params.hourly?.length || 0) - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
lang: { lang: {
locale: 'en-GB' locale: 'en-GB'
}, },
@@ -185,8 +192,8 @@
subtitle: { subtitle: {
text: text:
count === 0 count === 0
? `Compare <span class="font-bold">${$params.hourly.join(', ')}</span> in models: <span class="font-bold">` + ? `Compare <span class="font-bold">${params.hourly?.join(', ') || ''}</span> in models: <span class="font-bold">` +
$params.models.join(', ') + (params.models?.join(', ') || '') +
'</span>' '</span>'
: '', : '',
align: 'left' align: 'left'
@@ -234,7 +241,7 @@
verticalAlign: 'bottom' verticalAlign: 'bottom'
}, },
series: series, series: series as any,
responsive: { responsive: {
rules: [ rules: [
@@ -256,6 +263,8 @@
node.appendChild(chartDiv); node.appendChild(chartDiv);
} }
} }
};
loadData();
}); });
onDestroy(() => { onDestroy(() => {
@@ -266,7 +275,10 @@
</script> </script>
<!-- min-h-[302px] min-h-[602px] min-h-[902px] min-h-[1202px] --> <!-- min-h-[302px] min-h-[602px] min-h-[902px] min-h-[1202px] -->
<div class="container-wrapper relative -mx-6 md:mx-0 min-h-[{300 * $params.hourly.length + 2}px]"> <div
class="container-wrapper relative -mx-6 md:mx-0 min-h-[{300 * (params.hourly?.length || 0) +
2}px]"
>
<div in:fade={{ duration: 300 }} out:fade={{ duration: 300 }} bind:this={node}></div> <div in:fade={{ duration: 300 }} out:fade={{ duration: 300 }} bind:this={node}></div>
<div <div
class="{count > 0 class="{count > 0
@@ -299,7 +311,7 @@
name="Show legend" name="Show legend"
bind:checked={showLegend} bind:checked={showLegend}
onCheckedChange={() => { onCheckedChange={() => {
$params.hourly = $params.hourly; params.hourly = params.hourly;
}} }}
/> />
<Label for="show_legend" class="mb-[2px] cursor-pointer text-lg">Show legend</Label> <Label for="show_legend" class="mb-[2px] cursor-pointer text-lg">Show legend</Label>
@@ -310,7 +322,7 @@
name="Average only" name="Average only"
bind:checked={averageOnly} bind:checked={averageOnly}
onCheckedChange={() => { onCheckedChange={() => {
$params.hourly = $params.hourly; params.hourly = params.hourly;
}} }}
/> />
<Label for="average_only" class="mb-[2px] cursor-pointer text-lg">Average only</Label> <Label for="average_only" class="mb-[2px] cursor-pointer text-lg">Average only</Label>
+1 -27
View File
@@ -1,31 +1,5 @@
// Default configuration for 14-day ensemble forecast charts
export const defaultParameters = { export const defaultParameters = {
daily: [],
hourly: [],
models: [],
current: [],
minutely_15: [],
timezone: 'UTC',
location_mode: 'location_search',
csv_coordinates: undefined,
time_mode: 'forecast_days',
past_days: '0',
forecast_days: '14',
end_date: undefined,
start_date: undefined,
past_hours: undefined,
cell_selection: undefined,
forecast_hours: undefined,
past_minutely_15: undefined,
temporal_resolution: undefined,
forecast_minutely_15: undefined,
tilt: '0',
azimuth: '0',
timeformat: 'iso8601', timeformat: 'iso8601',
wind_speed_unit: 'kmh', wind_speed_unit: 'kmh',
temperature_unit: 'celsius', temperature_unit: 'celsius',
+62 -47
View File
@@ -6,26 +6,28 @@
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { storedLocation } from '$lib/stores/settings'; import { storedLocation } from '$lib/stores/settings';
import { urlHashStore } from '$lib/stores/url-hash-store';
import { Checkbox } from '$lib/components/ui/checkbox'; import { Checkbox } from '$lib/components/ui/checkbox';
import { Label } from '$lib/components/ui/label'; import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch'; import { Switch } from '$lib/components/ui/switch';
import { hourly, models } from '../options'; import { hourly, models as modelsFlat } from '../options';
import './highcharts.css'; import './highcharts.css';
import { defaultParameters } from './options'; import { defaultParameters } from './options';
// Wrap models in array to match template expectation of nested arrays like hourly
const models = [modelsFlat];
let node: HTMLElement; let node: HTMLElement;
let chart: any; let chart: any;
let Highcharts = $state(); let Highcharts = $state<typeof import('highcharts') | null>(null);
let showLegend = $state(false); let showLegend = $state(false);
let averageOnly = $state(false); let averageOnly = $state(false);
const location = get(storedLocation); const location = get(storedLocation);
const params = urlHashStore({ let params = $state({
latitude: [52.52], latitude: [52.52],
longitude: [13.41], longitude: [13.41],
...defaultParameters, ...defaultParameters,
@@ -47,27 +49,31 @@
if (dev) { if (dev) {
// const HighchartsDebugger = await import('highcharts/modules/debugger'); // const HighchartsDebugger = await import('highcharts/modules/debugger');
// HighchartsDebugger.default(Highcharts); // HighchartsDebugger.default(Highcharts);
const Debugger = (await import('highcharts/es-modules/Extensions/Debugger/Debugger.js')) const Debugger = (
.default; await import('highcharts/es-modules/Extensions/Debugger/Debugger.js' as any)
const ErrorMessages = (
await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js')
).default; ).default;
Highcharts.errorMessages = ErrorMessages; const ErrorMessages = (
await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js' as any)
).default;
if (Highcharts) {
(Highcharts as any).errorMessages = ErrorMessages;
Debugger.compose(Highcharts.Chart); Debugger.compose(Highcharts.Chart);
} }
}
}); });
$effect(async () => { $effect(() => {
const loadData = async () => {
count = 0; count = 0;
if (Highcharts) { if (Highcharts) {
node.replaceChildren([]); node.replaceChildren();
const dataReq = await fetch( const dataReq = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${$params.hourly.join(',')}&models=${$params.models.join(',')}&timeformat=unixtime&daily=sunset,sunrise` `https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${params.hourly?.join(',') || ''}&models=${params.models?.join(',') || ''}&timeformat=unixtime&daily=sunset,sunrise`
); );
const data = await dataReq.json(); const data = await dataReq.json();
let dailyFirstModelKey = Object.keys(data.daily)[1].split('_'); let dailyFirstModelKey: string[] | string = Object.keys(data.daily)[1].split('_');
dailyFirstModelKey.shift(); dailyFirstModelKey.shift();
dailyFirstModelKey = dailyFirstModelKey.join('_'); dailyFirstModelKey = dailyFirstModelKey.join('_');
@@ -79,7 +85,7 @@
) { ) {
let rise = data.daily['sunrise_' + dailyFirstModelKey]; let rise = data.daily['sunrise_' + dailyFirstModelKey];
let set = data.daily['sunset_' + dailyFirstModelKey]; let set = data.daily['sunset_' + dailyFirstModelKey];
plotBands = rise.map(function (r, i) { plotBands = rise.map(function (r: any, i: number) {
return { return {
color: 'rgba(255, 255, 194, 0.5)', color: 'rgba(255, 255, 194, 0.5)',
from: (r + data.utc_offset_seconds) * 1000, from: (r + data.utc_offset_seconds) * 1000,
@@ -88,7 +94,7 @@
}); });
} }
for (let variable of $params.hourly) { for (let variable of params.hourly || []) {
const chartDiv = document.createElement('div'); const chartDiv = document.createElement('div');
let unit; let unit;
@@ -105,7 +111,7 @@
continue; continue;
} }
if (model.startsWith(variable)) { if (model.startsWith(variable)) {
for (let [index, val] of values.entries()) { for (let [index, val] of (values as any[]).entries()) {
if (val) { if (val) {
let avVal = average[index]; let avVal = average[index];
average[index] = avVal + val; average[index] = avVal + val;
@@ -143,7 +149,9 @@
dashStyle: 'ShortDashDot', dashStyle: 'ShortDashDot',
color: '#5e5e5e', color: '#5e5e5e',
type: type:
unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²' ? 'column' : 'spline', unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²'
? 'column'
: 'spline',
tooltip: { tooltip: {
valueSuffix: ' ' + unit valueSuffix: ' ' + unit
}, },
@@ -158,19 +166,20 @@
className: 'highcharts-average-series' className: 'highcharts-average-series'
}); });
new Highcharts.Chart(chartDiv, { new Highcharts!.Chart({
credits: {
text: count === $params.hourly.length - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
chart: { chart: {
renderTo: chartDiv,
height: showLegend ? '400px' : '300px', height: showLegend ? '400px' : '300px',
styledMode: true, styledMode: true,
marginLeft: '50', marginLeft: 50,
marginRight: 0 marginRight: 0
}, },
credits: {
text: count === (params.hourly?.length || 0) - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
lang: { lang: {
locale: 'en-GB' locale: 'en-GB'
}, },
@@ -183,8 +192,8 @@
subtitle: { subtitle: {
text: text:
count === 0 count === 0
? `Compare <span class="font-bold">${$params.hourly.join(', ')}</span> in models: <span class="font-bold">` + ? `Compare <span class="font-bold">${params.hourly?.join(', ') || ''}</span> in models: <span class="font-bold">` +
$params.models.join(', ') + (params.models?.join(', ') || '') +
'</span>' '</span>'
: '', : '',
align: 'left' align: 'left'
@@ -232,7 +241,7 @@
verticalAlign: 'bottom' verticalAlign: 'bottom'
}, },
series: series, series: series as any,
responsive: { responsive: {
rules: [ rules: [
@@ -254,6 +263,8 @@
node.appendChild(chartDiv); node.appendChild(chartDiv);
} }
} }
};
loadData();
}); });
onDestroy(() => { onDestroy(() => {
@@ -264,7 +275,10 @@
</script> </script>
<!-- min-h-[302px] min-h-[602px] min-h-[902px] min-h-[1202px] --> <!-- min-h-[302px] min-h-[602px] min-h-[902px] min-h-[1202px] -->
<div class="container-wrapper relative -mx-6 md:mx-0 min-h-[{300 * $params.hourly.length + 2}px]"> <div
class="container-wrapper relative -mx-6 md:mx-0 min-h-[{300 * (params.hourly?.length || 0) +
2}px]"
>
<div in:fade={{ duration: 300 }} out:fade={{ duration: 300 }} bind:this={node}></div> <div in:fade={{ duration: 300 }} out:fade={{ duration: 300 }} bind:this={node}></div>
<div <div
class="{count > 0 class="{count > 0
@@ -297,7 +311,7 @@
name="Show legend" name="Show legend"
bind:checked={showLegend} bind:checked={showLegend}
onCheckedChange={() => { onCheckedChange={() => {
$params.hourly = $params.hourly; params.hourly = params.hourly;
}} }}
/> />
<Label for="show_legend" class="mb-[2px] cursor-pointer text-lg">Show legend</Label> <Label for="show_legend" class="mb-[2px] cursor-pointer text-lg">Show legend</Label>
@@ -308,7 +322,7 @@
name="Average only" name="Average only"
bind:checked={averageOnly} bind:checked={averageOnly}
onCheckedChange={() => { onCheckedChange={() => {
$params.hourly = $params.hourly; params.hourly = params.hourly;
}} }}
/> />
<Label for="average_only" class="mb-[2px] cursor-pointer text-lg">Average only</Label> <Label for="average_only" class="mb-[2px] cursor-pointer text-lg">Average only</Label>
@@ -319,12 +333,12 @@
<div class="mt-4 md:mt-8"> <div class="mt-4 md:mt-8">
<div class="flex"> <div class="flex">
<a href="#models"><h2 id="models" class="text-2xl md:text-3xl">Models</h2></a> <a href="#models"><h2 id="models" class="text-2xl md:text-3xl">Models</h2></a>
{#if $params.models.length > 0} {#if params.models && params.models.length > 0}
<div transition:fade={{ duration: 200 }} class="relative mt-[5px]"> <div transition:fade={{ duration: 200 }} class="relative mt-[5px]">
<div <div
class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline" class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline"
> >
{$params.models.length}&nbsp;/&nbsp;{models.flat().length} {params.models?.length || 0}&nbsp;/&nbsp;{models.flat().length}
</div> </div>
</div> </div>
{/if} {/if}
@@ -333,22 +347,23 @@
<div class="mt-2 grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4"> <div class="mt-2 grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
{#each models as group, i (i)} {#each models as group, i (i)}
<div class="mb-3"> <div class="mb-3">
{#each group as { value, label } (value)} {#each group as item (item.value)}
{@const { value, label } = item as { value: string; label: string }}
<div class="group flex items-center" title={label}> <div class="group flex items-center" title={label}>
<Checkbox <Checkbox
id="{value}_model" id="{value}_model"
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]" class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
{value} {value}
checked={$params.models?.includes(value)} checked={params.models?.includes(value)}
aria-labelledby="{value}_label" aria-labelledby="{value}_label"
onCheckedChange={() => { onCheckedChange={() => {
if ($params.models?.includes(value)) { if (params.models?.includes(value)) {
$params.models = $params.models.filter((item) => { params.models = params.models.filter((item) => {
return item !== value; return item !== value;
}); });
} else if ($params.models) { } else if (params.models) {
$params.models.push(value); params.models.push(value);
$params.models = $params.models; params.models = params.models;
} }
}} }}
/> />
@@ -371,12 +386,12 @@
Hourly Weather Variables Hourly Weather Variables
</h2></a </h2></a
> >
{#if $params.hourly.length > 0} {#if params.hourly && params.hourly.length > 0}
<div transition:fade={{ duration: 200 }} class="relative mt-[5px]"> <div transition:fade={{ duration: 200 }} class="relative mt-[5px]">
<div <div
class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline" class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline"
> >
{$params.hourly.length}&nbsp;/&nbsp;{hourly.flat().length} {params.hourly?.length || 0}&nbsp;/&nbsp;{hourly.flat().length}
</div> </div>
</div> </div>
{/if} {/if}
@@ -393,16 +408,16 @@
id="{value}_hourly" id="{value}_hourly"
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]" class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
{value} {value}
checked={$params.hourly?.includes(value)} checked={params.hourly?.includes(value)}
aria-labelledby="{value}_label" aria-labelledby="{value}_label"
onCheckedChange={() => { onCheckedChange={() => {
if ($params.hourly?.includes(value)) { if (params.hourly?.includes(value)) {
$params.hourly = $params.hourly.filter((item) => { params.hourly = params.hourly.filter((item) => {
return item !== value; return item !== value;
}); });
} else if ($params.hourly) { } else if (params.hourly) {
$params.hourly.push(value); params.hourly.push(value);
$params.hourly = $params.hourly; params.hourly = params.hourly;
} }
}} }}
/> />
+1 -27
View File
@@ -1,31 +1,5 @@
// Default configuration for weather comparison charts
export const defaultParameters = { export const defaultParameters = {
daily: [],
hourly: [],
models: [],
current: [],
minutely_15: [],
timezone: 'UTC',
location_mode: 'location_search',
csv_coordinates: undefined,
time_mode: 'forecast_days',
past_days: '0',
forecast_days: '7',
end_date: undefined,
start_date: undefined,
past_hours: undefined,
cell_selection: undefined,
forecast_hours: undefined,
past_minutely_15: undefined,
temporal_resolution: undefined,
forecast_minutely_15: undefined,
tilt: '0',
azimuth: '0',
timeformat: 'iso8601', timeformat: 'iso8601',
wind_speed_unit: 'kmh', wind_speed_unit: 'kmh',
temperature_unit: 'celsius', temperature_unit: 'celsius',
+3 -1
View File
@@ -1,4 +1,4 @@
export default { const weatherCodes: Record<number, string> = {
0: 'clear', 0: 'clear',
1: 'clear', 1: 'clear',
2: 'cloudy', 2: 'cloudy',
@@ -79,3 +79,5 @@ export default {
96: 'thunderstorm', 96: 'thunderstorm',
99: 'tornado' 99: 'tornado'
}; };
export default weatherCodes;
+1 -1
View File
@@ -6,7 +6,7 @@ import { storedLocation } from '$lib/stores/settings';
import { geoLocationNameToRoute } from '$lib/utils/meteo'; import { geoLocationNameToRoute } from '$lib/utils/meteo';
import type { PageLoad } from '$types'; import type { PageLoad } from './$types';
export const prerender = true; export const prerender = true;
+92 -75
View File
@@ -6,7 +6,6 @@
import { fetchWeatherApi } from 'openmeteo'; import { fetchWeatherApi } from 'openmeteo';
import { type GeoLocation, storedLocation } from '$lib/stores/settings'; import { type GeoLocation, storedLocation } from '$lib/stores/settings';
import { urlHashStore } from '$lib/stores/url-hash-store';
import { pad } from '$lib/utils/index'; import { pad } from '$lib/utils/index';
@@ -22,10 +21,10 @@
import { getColor } from '../../utils/colors'; import { getColor } from '../../utils/colors';
import weatherCodes from '../../utils/weather-codes'; import weatherCodes from '../../utils/weather-codes';
const params = urlHashStore({ let params = $state({
latitude: [$storedLocation.latitude], latitude: [$storedLocation.latitude],
longitude: [$storedLocation.longitude], longitude: [$storedLocation.longitude],
models: 'best_match', models: ['best_match'],
...defaultParameters ...defaultParameters
}); });
@@ -53,7 +52,7 @@
longitude: location.longitude, longitude: location.longitude,
elevation: location.elevation, elevation: location.elevation,
// timezone: location.timezone, ??? // timezone: location.timezone, ???
models: [$params.models], models: [params.models],
hourly: [ hourly: [
'precipitation', 'precipitation',
'precipitation_probability', 'precipitation_probability',
@@ -66,9 +65,9 @@
].join(','), ].join(','),
forecast_days: 6, forecast_days: 6,
past_days: 1, past_days: 1,
temperature_unit: $params.temperature_unit, temperature_unit: params.temperature_unit,
wind_speed_unit: $params.wind_speed_unit, wind_speed_unit: params.wind_speed_unit,
precipitation_unit: $params.precipitation_unit precipitation_unit: params.precipitation_unit
}; };
const url = 'https://api.open-meteo.com/v1/forecast'; const url = 'https://api.open-meteo.com/v1/forecast';
const responses = await fetchWeatherApi(url, reqParams); const responses = await fetchWeatherApi(url, reqParams);
@@ -96,7 +95,7 @@
const maxX = 10000; const maxX = 10000;
const maxY = 500; const maxY = 500;
const deltaX = 10000 / hourly.variables(0)?.valuesArray()?.length; const deltaX = 10000 / (hourly.variables(0)?.valuesArray()?.length || 1);
const ctx = canvasElement?.getContext('2d'); const ctx = canvasElement?.getContext('2d');
if (ctx) { if (ctx) {
@@ -119,10 +118,10 @@
// create canvas // create canvas
daylight(ctx, config, hourlyTime); daylight(ctx, config, hourlyTime);
raster(ctx, config, hourlyTime, today, canvasElement); raster(ctx, config, hourlyTime, today, canvasElement!);
tempGradient(ctx, config, hourlyTemps, $params.temperature_unit); tempGradient(ctx, config, hourlyTemps, params.temperature_unit);
cloudCover(ctx, config, hourlyCloudCover, canvasElement); cloudCover(ctx, config, hourlyCloudCover, canvasElement!);
precip(ctx, config, hourlyPrecip, canvasElement); precip(ctx, config, hourlyPrecip, canvasElement!);
} }
return { return {
@@ -134,7 +133,7 @@
values: hourly values: hourly
.variables(2) .variables(2)
?.valuesArray() ?.valuesArray()
?.map((t) => t.toFixed(1)) ?.map((t) => Number(t.toFixed(1)))
}, },
{ {
id: 1, id: 1,
@@ -143,7 +142,7 @@
values: hourly values: hourly
.variables(0) .variables(0)
?.valuesArray() ?.valuesArray()
?.map((p) => p.toFixed(1)) ?.map((p) => Number(p.toFixed(1)))
}, },
{ {
id: 2, id: 2,
@@ -152,7 +151,7 @@
values: hourly values: hourly
.variables(1) .variables(1)
?.valuesArray() ?.valuesArray()
?.map((p) => p.toFixed(0)) ?.map((p) => Number(p.toFixed(0)))
}, },
{ {
id: 3, id: 3,
@@ -161,7 +160,7 @@
values: hourly values: hourly
.variables(4) .variables(4)
?.valuesArray() ?.valuesArray()
?.map((p) => p.toFixed(0)) ?.map((p) => Number(p.toFixed(0)))
}, },
{ {
id: 4, id: 4,
@@ -170,7 +169,7 @@
values: hourly values: hourly
.variables(7) .variables(7)
?.valuesArray() ?.valuesArray()
?.map((p) => p.toFixed(0)) ?.map((p) => Number(p.toFixed(0)))
} }
], ],
entriesLength: hourly.variables(0)?.valuesArray()?.length, entriesLength: hourly.variables(0)?.valuesArray()?.length,
@@ -188,7 +187,7 @@
longitude: location.longitude, longitude: location.longitude,
elevation: location.elevation, elevation: location.elevation,
// timezone: location.timezone, ??? // timezone: location.timezone, ???
models: [$params.models], models: [params.models],
daily: [ daily: [
'weather_code', 'weather_code',
'temperature_2m_max', 'temperature_2m_max',
@@ -203,9 +202,9 @@
].join(','), ].join(','),
forecast_days: 6, forecast_days: 6,
past_days: 1, past_days: 1,
temperature_unit: $params.temperature_unit, temperature_unit: params.temperature_unit,
wind_speed_unit: $params.wind_speed_unit, wind_speed_unit: params.wind_speed_unit,
precipitation_unit: $params.precipitation_unit precipitation_unit: params.precipitation_unit
}; };
const url = 'https://api.open-meteo.com/v1/forecast'; const url = 'https://api.open-meteo.com/v1/forecast';
const responses = await fetchWeatherApi(url, reqParams); const responses = await fetchWeatherApi(url, reqParams);
@@ -237,17 +236,18 @@
let winddir = true; let winddir = true;
entries = 6; entries = 6;
let scrollDiv: HTMLElement = $state(); let scrollDiv: HTMLElement | undefined = $state();
let tableCells; let tableCells;
const switchDay = (date: SvelteDate, index: number) => { const switchDay = (date: Date, index: number) => {
selectedDay = date; selectedDay = date;
tableCells = document.querySelectorAll('td.time'); tableCells = document.querySelectorAll('td.time');
for (let tableCell of tableCells) { for (let tableCell of tableCells) {
if (Number(tableCell.dataset['date']) === selectedDay.getDate()) { const htmlCell = tableCell as HTMLElement;
scrollDiv.scrollTo({ left: tableCell.offsetLeft - 110, behavior: 'smooth' }); if (Number(htmlCell.dataset['date']) === selectedDay.getDate()) {
scrollDiv?.scrollTo({ left: htmlCell.offsetLeft - 110, behavior: 'smooth' });
break; break;
} }
} }
@@ -259,35 +259,36 @@
setTimeout(() => { setTimeout(() => {
tableCells = document.querySelectorAll('td.time'); tableCells = document.querySelectorAll('td.time');
for (let tableCell of tableCells) { for (let tableCell of tableCells) {
if (Number(tableCell.dataset['date']) === selectedDay.getDate()) { const htmlCell = tableCell as HTMLElement;
scrollDiv.scrollTo({ left: tableCell.offsetLeft - 110 }); if (Number(htmlCell.dataset['date']) === selectedDay.getDate()) {
scrollDiv?.scrollTo({ left: htmlCell.offsetLeft - 110 });
break; break;
} }
} }
}, 150); }, 150);
document.onkeydown = (e) => { document.onkeydown = (e) => {
if (!scrollDiv === document.activeElement || !scrollDiv.contains(document.activeElement)) { if (scrollDiv !== document.activeElement && !scrollDiv?.contains(document.activeElement)) {
if (e.key === 'ArrowLeft') { if (e.key === 'ArrowLeft') {
if (selectedDay.getDate() >= today.getDate()) { if (selectedDay.getDate() >= today.getDate()) {
let newDate = new SvelteDate(); let newDate = new Date();
newDate.setDate(selectedDay.getDate() - 1); newDate.setDate(selectedDay.getDate() - 1);
switchDay(newDate); switchDay(newDate, selectedDayIndex - 1);
} }
} }
if (e.key === 'ArrowRight') { if (e.key === 'ArrowRight') {
if (selectedDay.getDate() <= today.getDate() + 4) { if (selectedDay.getDate() <= today.getDate() + 4) {
let newDate = new SvelteDate(); let newDate = new Date();
newDate.setDate(selectedDay.getDate() + 1); newDate.setDate(selectedDay.getDate() + 1);
switchDay(newDate); switchDay(newDate, selectedDayIndex + 1);
} }
} }
} }
}; };
}); });
let modelSelected = $derived(models.find((mo) => String(mo.value) === $params.models)); let modelSelected = $derived(models.find((mo) => String(mo.value) === params.models?.[0]));
// let modelSelectedValue = $derived($params.models[0]); // let modelSelectedValue = $derived(params.models[0]);
// //
</script> </script>
@@ -308,7 +309,9 @@
{#await weatherDaily then wd} {#await weatherDaily then wd}
{#each wd.daily.time as time, index (index)} {#each wd.daily.time as time, index (index)}
{@const selected = time.getDate() === selectedDay.getDate()} {@const selected = time.getDate() === selectedDay.getDate()}
{#if !isNaN(wd.daily.temperature_2m_max.values(index).toFixed(1))} {#if wd.daily.temperature_2m_max.values(index) != null && !isNaN(Number(wd.daily.temperature_2m_max
.values(index)!
.toFixed(1)))}
<button <button
style="transition: 300ms; min-width: 13%; {selected ? 'transform: scale(1.025)' : ''}" style="transition: 300ms; min-width: 13%; {selected ? 'transform: scale(1.025)' : ''}"
class="cursor-pointer" class="cursor-pointer"
@@ -338,24 +341,24 @@
<svg class="fill-foreground" width="60px" height="60px"> <svg class="fill-foreground" width="60px" height="60px">
<use <use
xlink:href="/images/weather-icons/wi-day-{weatherCodes[ xlink:href="/images/weather-icons/wi-day-{weatherCodes[
wd.daily.weather_code.values(index) (wd.daily.weather_code.values(index) ?? 0) as number
]}.svg#Layer_1" ]}.svg#Layer_1"
></use> ></use>
</svg> </svg>
</div> </div>
<div <div
class="weather-temp-max flex min-w-[65px] justify-center rounded-t p-1 text-sm" class="weather-temp-max flex min-w-[65px] justify-center rounded-t p-1 text-sm"
style={`background-color: ${getColor(wd.daily.temperature_2m_max.values(index).toFixed(0), $params.temperature_unit)}; color: ${wd.daily.temperature_2m_min.values(index) < ($params.temperature_unit === 'celsius' ? 4 : 7) || wd.daily.temperature_2m_min.values(index) >= ($params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`} style={`background-color: ${getColor((wd.daily.temperature_2m_max.values(index) ?? 0).toFixed(0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
> >
{wd.daily.temperature_2m_max.values(index).toFixed(1)} {wd.daily.temperature_2m_max.values(index)?.toFixed(1)}
{$params.temperature_unit === 'celsius' ? '°C' : '°F'} {params.temperature_unit === 'celsius' ? '°C' : '°F'}
</div> </div>
<div <div
class="weather-temp-min flex min-w-[65px] justify-center rounded-b p-1 text-sm" class="weather-temp-min flex min-w-[65px] justify-center rounded-b p-1 text-sm"
style={`background: ${getColor(wd.daily.temperature_2m_min.values(index).toFixed(0), $params.temperature_unit)}; color: ${wd.daily.temperature_2m_min.values(index) < ($params.temperature_unit === 'celsius' ? 4 : 7) || wd.daily.temperature_2m_min.values(index) >= ($params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`} style={`background: ${getColor((wd.daily.temperature_2m_min.values(index) ?? 0).toFixed(0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
> >
{wd.daily.temperature_2m_min.values(index).toFixed(1)} {wd.daily.temperature_2m_min.values(index)?.toFixed(1)}
{$params.temperature_unit === 'celsius' ? '°C' : '°F'} {params.temperature_unit === 'celsius' ? '°C' : '°F'}
</div> </div>
<div class="mt-2 flex items-center justify-center gap-1"> <div class="mt-2 flex items-center justify-center gap-1">
<div class="relative flex h-6 w-6 items-center justify-center"> <div class="relative flex h-6 w-6 items-center justify-center">
@@ -369,7 +372,7 @@
</div> </div>
</div> </div>
{Number(wd.daily.sunshine_duration.values(index) / 3600).toFixed(0)}h {Number((wd.daily.sunshine_duration.values(index) ?? 0) / 3600).toFixed(0)}h
</div> </div>
<div class="mt-1 flex items-center justify-center"> <div class="mt-1 flex items-center justify-center">
<div class="relative flex h-6 w-6 items-center justify-center"> <div class="relative flex h-6 w-6 items-center justify-center">
@@ -384,7 +387,7 @@
</div> </div>
{Number(wd.daily.precipitation_sum.values(index)).toFixed( {Number(wd.daily.precipitation_sum.values(index)).toFixed(
1 1
)}{$params.precipitation_unit === 'mm' ? 'mm' : "'"} )}{params.precipitation_unit === 'mm' ? 'mm' : "'"}
</div> </div>
</div> </div>
</button> </button>
@@ -442,8 +445,9 @@
data-time={weather.hourlyTime[index].getHours() + ':00'} data-time={weather.hourlyTime[index].getHours() + ':00'}
style="font-size: 11px; position: absolute; bottom: {188 + style="font-size: 11px; position: absolute; bottom: {188 +
27 * entries}px; left:{111 + 27 * entries}px; left:{111 +
(5000 / weather.entriesLength) * index}px; min-width: {5000 / (5000 / (weather.entriesLength || 1)) * index}px; min-width: {5000 /
weather.entriesLength}px; max-width: {5000 / weather.entriesLength}px;" (weather.entriesLength || 1)}px; max-width: {5000 /
(weather.entriesLength || 1)}px;"
>{weather.hourlyTime[index].getHours() < 10 ? '0' : ''}{weather.hourlyTime[ >{weather.hourlyTime[index].getHours() < 10 ? '0' : ''}{weather.hourlyTime[
index index
].getHours()}</td ].getHours()}</td
@@ -467,16 +471,17 @@
0.8 * 200 - 0.8 * 200 -
0.54 * 0.54 *
200 * 200 *
((maxTemp - weather.entries[0].values[index]) / diffTemp)}px; left:{116 + ((maxTemp! - weather.entries[0].values![index]) / diffTemp!)}px; left:{116 +
(5000 / weather.entriesLength) * index}px; min-width: {5000 / (5000 / (weather.entriesLength || 1)) * index}px; min-width: {5000 /
weather.entriesLength}px; max-width: {5000 / weather.entriesLength}px;" (weather.entriesLength || 1)}px; max-width: {5000 /
(weather.entriesLength || 1)}px;"
><svg class="fill-foreground {now ? 'scale-125' : ''}" width="20px" height="20px"> ><svg class="fill-foreground {now ? 'scale-125' : ''}" width="20px" height="20px">
<use <use
class="stroke-2" class="stroke-2"
xlink:href="/images/weather-icons/wi-{weather.hourlyTime[index].getHours() > xlink:href="/images/weather-icons/wi-{weather.hourlyTime[index].getHours() >
6 && weather.hourlyTime[index].getHours() < 21 6 && weather.hourlyTime[index].getHours() < 21
? 'day' ? 'day'
: 'night'}-{weatherCodes[weatherCodesHourly[index]]}.svg#Layer_1" : 'night'}-{weatherCodes[weatherCodesHourly![index] as number]}.svg#Layer_1"
></use> ></use>
</svg></td </svg></td
> >
@@ -491,9 +496,9 @@
>Temp graph</th >Temp graph</th
> >
{#each weather.indexes as index, j (j)} {#each weather.indexes as index, j (j)}
{@const temp = weather.entries[0].values[index]} {@const temp = weather.entries?.[0]?.values?.[index]}
{#if !isNaN(temp)} {#if temp !== undefined && !isNaN(temp)}
<td <td
class={weather.hourlyTime[index].getDate() === today.getDate() && class={weather.hourlyTime[index].getDate() === today.getDate() &&
weather.hourlyTime[index].getHours() === today.getHours() weather.hourlyTime[index].getHours() === today.getHours()
@@ -502,10 +507,10 @@
style="position: absolute; bottom: {27.5 * entries - style="position: absolute; bottom: {27.5 * entries -
49 + 49 +
0.8 * 200 - 0.8 * 200 -
0.55 * 200 * ((maxTemp - temp) / diffTemp)}px; left:{111 + 0.55 * 200 * ((maxTemp! - temp!) / diffTemp!)}px; left:{111 +
(5000 / weather.entriesLength) * index}px; min-width: {5000 / (5000 / (weather.entriesLength || 1)) * index}px; min-width: {5000 /
weather.entriesLength}px; max-width: {5000 / weather.entriesLength}px;" (weather.entriesLength || 1)}px; max-width: {5000 /
>{temp.toFixed(0)}</td (weather.entriesLength || 1)}px;">{temp?.toFixed(0)}</td
> >
{/if} {/if}
{/each} {/each}
@@ -520,49 +525,49 @@
> >
{#each weather.indexes as index, j (j)} {#each weather.indexes as index, j (j)}
{#if !isNaN(entry.values[index])} {#if entry.values && !isNaN(entry.values[index])}
<td <td
class="border-r border-border {weather.hourlyTime[index].getDate() === class="border-r border-border {weather.hourlyTime[index].getDate() ===
today.getDate() && weather.hourlyTime[index].getHours() === today.getHours() today.getDate() && weather.hourlyTime[index].getHours() === today.getHours()
? 'now' ? 'now'
: ''}" : ''}"
style="min-width: {5000 / weather.entriesLength}px; max-width: {5000 / style="min-width: {5000 / (weather.entriesLength || 1)}px; max-width: {5000 /
weather.entriesLength}px; (weather.entriesLength || 1)}px;
{entry.name === 'temperature_2m' {entry.name === 'temperature_2m'
? 'background: ' + ? 'background: ' +
getColor( getColor(
weather.entries[0].values[index].toFixed(0), weather.entries[0].values![index].toFixed(0),
$params.temperature_unit params.temperature_unit
) )
: ''}; : ''};
{entry.name === 'temperature_2m' {entry.name === 'temperature_2m'
? 'color: ' + ? 'color: ' +
(weather.entries[0].values[index] < (weather.entries[0].values![index] <
($params.temperature_unit === 'celsius' ? -13 : 7) || (params.temperature_unit === 'celsius' ? -13 : 7) ||
weather.entries[0].values[index] >= weather.entries[0].values![index] >=
($params.temperature_unit === 'celsius' ? 40 : 104) (params.temperature_unit === 'celsius' ? 40 : 104)
? 'white' ? 'white'
: 'black') : 'black')
: ''}; : ''};
{entry.name === 'precipitation_probability' {entry.name === 'precipitation_probability'
? 'background: rgba(0, 0, 230,' + ? 'background: rgba(0, 0, 230,' +
weather.entries[2].values[index] / 120 + weather.entries[2].values![index] / 120 +
')' ')'
: ''}; : ''};
{entry.name === 'precipitation_probability' {entry.name === 'precipitation_probability'
? 'color: ' + ? 'color: ' +
(weather.entries[2].values[index] > 50 (weather.entries[2].values![index] > 50
? 'white' ? 'white'
: 'hsl(var(--foreground)') : 'hsl(var(--foreground)')
: ''}; : ''};
{entry.name === 'relative_humidity_2m' {entry.name === 'relative_humidity_2m'
? 'background: rgba(0, 240, 240,' + ? 'background: rgba(0, 240, 240,' +
weather.entries[4].values[index] ** 3.8 / 10 ** 8.2 + weather.entries[4].values![index] ** 3.8 / 10 ** 8.2 +
')' ')'
: ''};" : ''};"
>{entry.name === 'precipitation' || entry.name === 'temperature_2m' >{entry.name === 'precipitation' || entry.name === 'temperature_2m'
? entry.values[index].toFixed(1) ? entry.values![index].toFixed(1)
: entry.values[index]}</td : entry.values![index]}</td
> >
{/if} {/if}
{/each} {/each}
@@ -578,16 +583,16 @@
>Wind Dir.</th >Wind Dir.</th
> >
{#each weather.indexes as index, j (j)} {#each weather.indexes as index, j (j)}
{#if !isNaN(weather.windDirections[index])} {#if weather.windDirections && !isNaN(weather.windDirections[index])}
<td <td
class="border-r border-border {weather.hourlyTime[index].getDate() === class="border-r border-border {weather.hourlyTime[index].getDate() ===
today.getDate() && weather.hourlyTime[index].getHours() === today.getHours() today.getDate() && weather.hourlyTime[index].getHours() === today.getHours()
? 'now' ? 'now'
: ''}" : ''}"
style="transform: rotate({weather.windDirections[ style="transform: rotate({weather.windDirections![
index index
]}deg);min-width: {5000 / weather.entriesLength}px; max-width: {5000 / ]}deg);min-width: {5000 / (weather.entriesLength || 1)}px; max-width: {5000 /
weather.entriesLength}px;" (weather.entriesLength || 1)}px;"
><svg class="fill-foreground" width="25px" height="25px"> ><svg class="fill-foreground" width="25px" height="25px">
<use xlink:href="/images/weather-icons/wi-direction-down.svg#Layer_1"></use> <use xlink:href="/images/weather-icons/wi-direction-down.svg#Layer_1"></use>
</svg></td </svg></td
@@ -621,7 +626,18 @@
<div> <div>
<div class="mt-6 flex gap-6 md:mt-12"> <div class="mt-6 flex gap-6 md:mt-12">
<div class="relative w-1/2"> <div class="relative w-1/2">
<Select.Root name="model_selection" type="single" bind:value={$params.models}> {#if params.models && params.models.length > 0}
{@const modelValue = params.models[0]}
<Select.Root
name="model_selection"
type="single"
value={modelValue}
onValueChange={(val) => {
if (params.models && val) {
params.models = [val];
}
}}
>
<Select.Trigger <Select.Trigger
aria-label="Forecast days input" aria-label="Forecast days input"
class="h-12 cursor-pointer pt-6 [&_svg]:mb-3">{modelSelected?.label}</Select.Trigger class="h-12 cursor-pointer pt-6 [&_svg]:mb-3">{modelSelected?.label}</Select.Trigger
@@ -635,6 +651,7 @@
>Weather model</Label >Weather model</Label
> >
</Select.Root> </Select.Root>
{/if}
</div> </div>
</div> </div>
</div> </div>
+21 -7
View File
@@ -4,11 +4,11 @@ import { type GeoLocation, storedLocation } from '$lib/stores/settings';
import { geoLocationNameToRoute } from '$lib/utils/meteo'; import { geoLocationNameToRoute } from '$lib/utils/meteo';
import type { PageLoad } from '$types'; import type { PageLoad } from './$types';
export const prerender = true; export const prerender = true;
export const load = (async (event) => { export const load: PageLoad = async (event) => {
const urlLocation = event.params.location; const urlLocation = event.params.location;
let urlLocationSplit, urlLocationName, urlLocationId; let urlLocationSplit, urlLocationName, urlLocationId;
@@ -31,14 +31,28 @@ export const load = (async (event) => {
// lat, long coordinates // lat, long coordinates
if (urlLocation.includes('N') && urlLocation.includes('E')) { if (urlLocation.includes('N') && urlLocation.includes('E')) {
urlLocationSplit = urlLocation.split(/N|E/); urlLocationSplit = urlLocation.split(/N|E/);
const latitude = urlLocationSplit[0]; const latitude = parseFloat(urlLocationSplit[0]);
const longitude = urlLocationSplit[1]; const longitude = parseFloat(urlLocationSplit[1]);
location = { location = {
//id: undefined, id: 0,
name: `${latitude}${longitude}`, name: `${latitude}${longitude}`,
latitude: latitude, latitude: latitude,
longitude: longitude longitude: longitude,
elevation: 0,
feature_code: 'COORD',
country_code: undefined,
admin1_id: undefined,
admin3_id: undefined,
admin4_id: undefined,
timezone: 'UTC',
population: undefined,
postcodes: undefined,
country_id: undefined,
country: undefined,
admin1: undefined,
admin3: undefined,
admin4: undefined
}; };
} else { } else {
if (urlLocationId) { if (urlLocationId) {
@@ -74,4 +88,4 @@ export const load = (async (event) => {
storedLocation.set(location); storedLocation.set(location);
return { location: location }; return { location: location };
}) satisfies PageLoad; };