static routing fixed
This commit is contained in:
@@ -31,6 +31,8 @@
|
||||
chartHeight?: number;
|
||||
/** Extra vertical padding in pixels added to the total min-height (default: 2) */
|
||||
extraPadding?: number;
|
||||
/** Minimum chart width in pixels; narrower viewports scroll sideways (default: 560) */
|
||||
minWidth?: number;
|
||||
/** Optional CSS class for the outer wrapper */
|
||||
class?: string;
|
||||
/** Slot content (charts go here) */
|
||||
@@ -42,6 +44,7 @@
|
||||
chartCount = 1,
|
||||
chartHeight = 300,
|
||||
extraPadding = 2,
|
||||
minWidth = 560,
|
||||
class: className = '',
|
||||
children
|
||||
}: Props = $props();
|
||||
@@ -51,53 +54,63 @@
|
||||
let minHeight = $derived(chartHeight * chartCount + extraPadding);
|
||||
</script>
|
||||
|
||||
<div class="chart-container relative {className}" style:min-height="{minHeight}px">
|
||||
<!-- Chart content area -->
|
||||
<div class="chart-content" in:fade={{ duration: 300 }} out:fade={{ duration: 300 }}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
<div class="chart-bleed">
|
||||
<div
|
||||
class="loading-overlay absolute inset-0 z-30 flex items-center justify-center rounded-lg bg-background transition-opacity duration-300"
|
||||
class:pointer-events-none={!loading}
|
||||
class:opacity-0={!loading}
|
||||
class:opacity-100={loading}
|
||||
class="chart-container relative {className}"
|
||||
style:min-height="{minHeight}px"
|
||||
style:min-width="{minWidth}px"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<svg
|
||||
class="lucide lucide-loader-circle animate-spin text-muted-foreground"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
<span class="sr-only">Loading charts...</span>
|
||||
<!-- Chart content area -->
|
||||
<div class="chart-content" in:fade={{ duration: 300 }} out:fade={{ duration: 300 }}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
<div
|
||||
class="loading-overlay absolute inset-0 z-30 flex items-center justify-center rounded-lg bg-background transition-opacity duration-300"
|
||||
class:pointer-events-none={!loading}
|
||||
class:opacity-0={!loading}
|
||||
class:opacity-100={loading}
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<svg
|
||||
class="lucide lucide-loader-circle animate-spin text-muted-foreground"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
<span class="sr-only">Loading charts...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chart-container {
|
||||
/* Negative horizontal margin on mobile to use full width, reset on md+ */
|
||||
margin-left: -1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
.chart-bleed {
|
||||
/* Bleed exactly into the page padding on mobile (main has p-5 =
|
||||
1.25rem) for edge-to-edge charts, and a bit past the content
|
||||
column on md+ (main has 2rem padding) for extra readability.
|
||||
Charts narrower than their min-width scroll sideways. */
|
||||
margin-left: -1.25rem;
|
||||
margin-right: -1.25rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.chart-container {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
.chart-bleed {
|
||||
margin-left: -1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Translates drizzli's model ids (Open-Meteo API model names, see
|
||||
* src/routes/weather/options.ts) into domain values understood by the maps
|
||||
* viewer (open-meteo/maps, weather-map-layer src/domains.ts).
|
||||
*
|
||||
* The map advertises the domains it actually supports in its `om-maps:ready`
|
||||
* handshake; candidates are tried in order and the first advertised one wins.
|
||||
* Seamless API models list their seamless domain first, so they upgrade
|
||||
* automatically once the maps app ships seamless support; until then they fall
|
||||
* back to their widest-coverage member (a regional member could be entirely
|
||||
* off-screen). Models without an entry are tried under their own id; models
|
||||
* the map does not serve at all (best_match, bom, google, UKMO ensembles)
|
||||
* resolve to null.
|
||||
*/
|
||||
const modelDomainCandidates: Record<string, string[]> = {
|
||||
// DWD Germany
|
||||
icon_seamless: ['dwd_icon_seamless', 'dwd_icon'],
|
||||
icon_global: ['dwd_icon'],
|
||||
icon_eu: ['dwd_icon_eu'],
|
||||
icon_d2: ['dwd_icon_d2'],
|
||||
|
||||
// NOAA U.S.
|
||||
gfs_seamless: ['ncep_gfs_seamless', 'ncep_gfs013'],
|
||||
gfs_global: ['ncep_gfs013'],
|
||||
gfs_hrrr: ['ncep_hrrr_conus'],
|
||||
gfs_graphcast025: ['ncep_gfs_graphcast025'],
|
||||
|
||||
// Météo-France
|
||||
meteofrance_seamless: ['meteofrance_seamless', 'meteofrance_arpege_world025'],
|
||||
meteofrance_arpege_world: ['meteofrance_arpege_world025'],
|
||||
meteofrance_arome_france: ['meteofrance_arome_france0025'],
|
||||
|
||||
// UK Met Office
|
||||
ukmo_seamless: ['ukmo_seamless', 'ukmo_global_deterministic_10km'],
|
||||
|
||||
// KNMI Netherlands
|
||||
knmi_seamless: ['knmi_seamless', 'knmi_harmonie_arome_europe'],
|
||||
|
||||
// DMI Denmark (no DMI seamless domain in the maps project)
|
||||
dmi_seamless: ['dmi_harmonie_arome_europe'],
|
||||
|
||||
// MET Norway
|
||||
metno_seamless: ['metno_nordic_pp'],
|
||||
metno_nordic: ['metno_nordic_pp'],
|
||||
|
||||
// MeteoSwiss (CH2 covers a wider area than CH1)
|
||||
meteoswiss_icon_seamless: ['meteoswiss_icon_ch2'],
|
||||
|
||||
// KMA Korea (kma_ldps is not served by the maps project)
|
||||
kma_seamless: ['kma_gdps'],
|
||||
kma_ldps: ['kma_gdps'],
|
||||
|
||||
// JMA Japan
|
||||
jma_seamless: ['jma_seamless', 'jma_gsm'],
|
||||
|
||||
// GEM Canada (gdps/rdps carry resolution suffixes in newer map builds;
|
||||
// older builds advertise the plain names, so try both)
|
||||
gem_seamless: ['cmc_gem_seamless', 'cmc_gem_gdps_15km', 'cmc_gem_gdps'],
|
||||
gem_global: ['cmc_gem_gdps_15km', 'cmc_gem_gdps'],
|
||||
gem_regional: ['cmc_gem_rdps_10km', 'cmc_gem_rdps'],
|
||||
|
||||
// Ensemble models
|
||||
icon_seamless_eps: ['dwd_icon_eps'],
|
||||
icon_global_eps: ['dwd_icon_eps'],
|
||||
icon_eu_eps: ['dwd_icon_eu_eps'],
|
||||
icon_d2_eps: ['dwd_icon_d2_eps'],
|
||||
ncep_gefs_seamless: ['ncep_gefs025'],
|
||||
gem_global_ensemble: ['cmc_gem_geps']
|
||||
};
|
||||
|
||||
/** Resolve a model id to a maps domain the map advertised as supported. */
|
||||
export const mapsDomainForModel = (
|
||||
model: string,
|
||||
supportedDomains: ReadonlySet<string>
|
||||
): string | null => {
|
||||
for (const candidate of modelDomainCandidates[model] ?? [model]) {
|
||||
if (supportedDomains.has(candidate)) return candidate;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Reference in New Issue
Block a user