Merge main into refactor-weather-weekly - resolve type safety conflicts

This commit is contained in:
terraputix
2026-02-15 15:59:44 +01:00
12 changed files with 551 additions and 685 deletions
+76 -82
View File
@@ -6,7 +6,6 @@
import { dev } from '$app/environment';
import { storedLocation } from '$lib/stores/settings';
import { urlHashStore } from '$lib/stores/url-hash-store';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Label } from '$lib/components/ui/label';
@@ -17,16 +16,15 @@
import { defaultParameters } from './options';
let node: HTMLElement;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let charts: any[] = [];
let Highcharts = $state<any>();
let chart: any;
let Highcharts = $state<typeof import('highcharts') | null>(null);
let showLegend = $state(false);
let averageOnly = $state(false);
const location = get(storedLocation);
const params = urlHashStore({
let params = $state({
latitude: [52.52],
longitude: [13.41],
...defaultParameters,
@@ -48,38 +46,34 @@
if (dev) {
// const HighchartsDebugger = await import('highcharts/modules/debugger');
// HighchartsDebugger.default(Highcharts);
// @ts-ignore
const Debugger = (
(await import('highcharts/es-modules/Extensions/Debugger/Debugger.js')) as any
await import('highcharts/es-modules/Extensions/Debugger/Debugger.js' as any)
).default;
// @ts-ignore
const ErrorMessages = (
(await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js')) as any
await import('highcharts/es-modules/Extensions/Debugger/ErrorMessages.js' as any)
).default;
Highcharts.errorMessages = ErrorMessages;
Debugger.compose(Highcharts.Chart);
if (Highcharts) {
(Highcharts as any).errorMessages = ErrorMessages;
Debugger.compose(Highcharts.Chart);
}
}
});
$effect(() => {
count = 0;
if (Highcharts) {
charts.forEach((c) => c.destroy());
charts = [];
// eslint-disable-next-line svelte/no-dom-manipulating
(node as any).replaceChildren();
const loadData = async () => {
count = 0;
if (Highcharts) {
node.replaceChildren();
(async () => {
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();
let dailyFirstModelKeyParts = Object.keys(data.daily)[1].split('_');
dailyFirstModelKeyParts.shift();
const dailyFirstModelKey = dailyFirstModelKeyParts.join('_');
let dailyFirstModelKey: string[] | string = Object.keys(data.daily)[1].split('_');
dailyFirstModelKey.shift();
dailyFirstModelKey = dailyFirstModelKey.join('_');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let plotBands: any = [];
if (
'daily' in data &&
@@ -97,7 +91,7 @@
});
}
for (let variable of $params.hourly || []) {
for (let variable of params.hourly || []) {
const chartDiv = document.createElement('div');
let unit;
@@ -169,19 +163,20 @@
className: 'highcharts-average-series'
});
const chart = new Highcharts.Chart(chartDiv, {
credits: {
text: count === ($params.hourly?.length || 0) - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
new Highcharts!.Chart({
chart: {
renderTo: chartDiv,
height: showLegend ? '400px' : '300px',
styledMode: true,
marginLeft: '50',
marginLeft: 50,
marginRight: 0
},
credits: {
text: count === (params.hourly?.length || 0) - 1 ? 'Open-Meteo.com' : '',
href: 'http://open-meteo.com'
},
lang: {
locale: 'en-GB'
},
@@ -194,8 +189,8 @@
subtitle: {
text:
count === 0
? `Compare <span class="font-bold">${$params.hourly?.join(', ')}</span> in models: <span class="font-bold">` +
$params.models?.join(', ') +
? `Compare <span class="font-bold">${params.hourly?.join(', ') || ''}</span> in models: <span class="font-bold">` +
(params.models?.join(', ') || '') +
'</span>'
: '',
align: 'left'
@@ -243,7 +238,7 @@
verticalAlign: 'bottom'
},
series: series,
series: series as any,
responsive: {
rules: [
@@ -262,22 +257,23 @@
});
count++;
charts.push(chart);
// eslint-disable-next-line svelte/no-dom-manipulating
node.appendChild(chartDiv);
}
})();
}
}
};
loadData();
});
onDestroy(() => {
charts.forEach((c) => c.destroy());
if (chart) {
chart.destroy();
}
});
</script>
<!-- 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 || 0) +
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>
@@ -312,7 +308,7 @@
name="Show legend"
bind:checked={showLegend}
onCheckedChange={() => {
$params.hourly = $params.hourly;
params.hourly = params.hourly;
}}
/>
<Label for="show_legend" class="mb-[2px] cursor-pointer text-lg">Show legend</Label>
@@ -323,7 +319,7 @@
name="Average only"
bind:checked={averageOnly}
onCheckedChange={() => {
$params.hourly = $params.hourly;
params.hourly = params.hourly;
}}
/>
<Label for="average_only" class="mb-[2px] cursor-pointer text-lg">Average only</Label>
@@ -334,48 +330,46 @@
<div class="mt-4 md:mt-8">
<div class="flex">
<a href="#models"><h2 id="models" class="text-2xl md:text-3xl">Models</h2></a>
{#if $params.models && $params.models.length > 0}
{#if params.models && params.models.length > 0}
<div transition:fade={{ duration: 200 }} class="relative mt-[5px]">
<div
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.length}
</div>
</div>
{/if}
</div>
<div class="mt-2 grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
{#each models as group, i (i)}
<div class="mb-3">
{#each group as { value, label } (value)}
<div class="group flex items-center" title={label}>
<Checkbox
id="{value}_model"
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
{value}
checked={$params.models?.includes(value)}
aria-labelledby="{value}_label"
onCheckedChange={() => {
if ($params.models?.includes(value)) {
$params.models = $params.models.filter((item: string) => {
return item !== value;
});
} else if ($params.models) {
$params.models.push(value);
$params.models = $params.models;
}
}}
/>
<Label
id="{value}_model_label"
for="{value}_model"
class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
>
</div>
{/each}
</div>
{/each}
<div class="mb-3">
{#each models as { value, label } (value)}
<div class="group flex items-center" title={label}>
<Checkbox
id="{value}_model"
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
{value}
checked={params.models?.includes(value)}
aria-labelledby="{value}_label"
onCheckedChange={() => {
if (params.models?.includes(value)) {
params.models = params.models.filter((item) => {
return item !== value;
});
} else if (params.models) {
params.models.push(value);
params.models = params.models;
}
}}
/>
<Label
id="{value}_model_label"
for="{value}_model"
class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
>
</div>
{/each}
</div>
</div>
<!-- HOURLY -->
@@ -386,12 +380,12 @@
Hourly Weather Variables
</h2></a
>
{#if $params.hourly && $params.hourly.length > 0}
{#if params.hourly && params.hourly.length > 0}
<div transition:fade={{ duration: 200 }} class="relative mt-[5px]">
<div
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>
{/if}
@@ -408,16 +402,16 @@
id="{value}_hourly"
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
{value}
checked={$params.hourly?.includes(value)}
checked={params.hourly?.includes(value)}
aria-labelledby="{value}_label"
onCheckedChange={() => {
if ($params.hourly?.includes(value)) {
$params.hourly = $params.hourly.filter((item: string) => {
if (params.hourly?.includes(value)) {
params.hourly = params.hourly.filter((item) => {
return item !== value;
});
} else if ($params.hourly) {
$params.hourly.push(value);
$params.hourly = $params.hourly;
} else if (params.hourly) {
params.hourly.push(value);
params.hourly = params.hourly;
}
}}
/>