FEAT: Added weather card which nicely displays the weather conditions from a METAR object
This commit is contained in:
99
src/lib/components/Cards/WeatherCard.svelte
Normal file
99
src/lib/components/Cards/WeatherCard.svelte
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Card from './Card.svelte';
|
||||||
|
import type { Metar } from '$lib/api/types/metar';
|
||||||
|
|
||||||
|
export let metar: Metar;
|
||||||
|
|
||||||
|
// Determine condition from METAR fields
|
||||||
|
let condition = 'Unknown';
|
||||||
|
|
||||||
|
$: {
|
||||||
|
// Prefer wxString, then clouds[0].cover, then metarType
|
||||||
|
if (metar.wxString) {
|
||||||
|
condition = metar.wxString;
|
||||||
|
} else if (metar.clouds && metar.clouds.length > 0 && metar.clouds[0].cover) {
|
||||||
|
condition = metar.clouds[0].cover;
|
||||||
|
} else if (metar.metarType) {
|
||||||
|
condition = metar.metarType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose icon and style based on condition
|
||||||
|
let icon = '';
|
||||||
|
let bg = '';
|
||||||
|
let effect = '';
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const cond = condition?.toLowerCase();
|
||||||
|
if (cond.includes('cavok') || cond.includes('clear') || cond.includes('clr')) {
|
||||||
|
icon = '☀️';
|
||||||
|
bg = 'bg-yellow-100 border-yellow-300 text-yellow-700';
|
||||||
|
effect = '';
|
||||||
|
} else if (cond.includes('rain') || cond.includes('shower') || cond.includes('tsra') || cond.includes('ra') || cond.includes('br')) {
|
||||||
|
icon = '🌧️';
|
||||||
|
bg = 'bg-blue-100 border-blue-300 text-blue-700';
|
||||||
|
effect = 'rain-effect';
|
||||||
|
} else if (cond.includes('cloud') || cond.includes('ovc') || cond.includes('bkn') || cond.includes('sct') || cond.includes('few')) {
|
||||||
|
icon = '☁️';
|
||||||
|
bg = 'bg-gray-100 border-gray-300 text-gray-700';
|
||||||
|
effect = '';
|
||||||
|
} else if (cond.includes('snow')) {
|
||||||
|
icon = '❄️';
|
||||||
|
bg = 'bg-blue-50 border-blue-200 text-blue-500';
|
||||||
|
effect = 'snow-effect';
|
||||||
|
} else if (cond.includes('fog') || cond.includes('mist')) {
|
||||||
|
icon = '🌫️';
|
||||||
|
bg = 'bg-gray-200 border-gray-400 text-gray-600';
|
||||||
|
effect = '';
|
||||||
|
} else {
|
||||||
|
icon = '🌡️';
|
||||||
|
bg = 'bg-slate-100 border-slate-300 text-slate-700';
|
||||||
|
effect = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card containerStyle={`rounded-xl shadow-md border-2 ${bg} relative overflow-hidden`}>
|
||||||
|
<div slot="content" class="flex flex-col items-center py-4">
|
||||||
|
<div class="text-4xl mb-2">{icon}</div>
|
||||||
|
<div class="text-xl font-bold">{metar.icaoId}</div>
|
||||||
|
<div class="text-md mb-1">{metar.name}</div>
|
||||||
|
<div class="text-2xl font-semibold">{metar.temp}°C</div>
|
||||||
|
<div class="text-sm italic mb-2">{metar.altim}hPa</div>
|
||||||
|
<div class="text-xs text-gray-500 truncate w-full text-center"><code>{metar.rawOb}</code></div>
|
||||||
|
</div>
|
||||||
|
{#if effect === 'rain-effect'}
|
||||||
|
<div class="absolute inset-0 pointer-events-none rain"></div>
|
||||||
|
{/if}
|
||||||
|
{#if effect === 'snow-effect'}
|
||||||
|
<div class="absolute inset-0 pointer-events-none snow"></div>
|
||||||
|
{/if}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.rain {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
120deg,
|
||||||
|
rgba(0, 0, 255, 0.1) 0 2px,
|
||||||
|
transparent 2px 8px
|
||||||
|
);
|
||||||
|
animation: rainmove 0.7s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes rainmove {
|
||||||
|
0% { background-position: 0 0; }
|
||||||
|
100% { background-position: 20px 40px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.snow {
|
||||||
|
background-image: radial-gradient(white 1.5px, transparent 2px),
|
||||||
|
radial-gradient(white 1.5px, transparent 2px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position: 0 0, 10px 10px;
|
||||||
|
opacity: 0.5;
|
||||||
|
animation: snowmove 1.5s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes snowmove {
|
||||||
|
0% { background-position: 0 0, 10px 10px; }
|
||||||
|
100% { background-position: 0 20px, 10px 30px; }
|
||||||
|
}
|
||||||
|
</style>
|
@ -8,6 +8,7 @@
|
|||||||
import { aviationWeatherApi } from '$lib/api/weatherservice';
|
import { aviationWeatherApi } from '$lib/api/weatherservice';
|
||||||
import type { Metar } from '$lib/api/types/metar'
|
import type { Metar } from '$lib/api/types/metar'
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import WeatherCard from '$lib/components/Cards/WeatherCard.svelte';
|
||||||
|
|
||||||
let metar: Metar | null = null;
|
let metar: Metar | null = null;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
@ -54,15 +55,8 @@
|
|||||||
<span class="text-red-600 text-xl">{error}</span>
|
<span class="text-red-600 text-xl">{error}</span>
|
||||||
{:else if metar}
|
{:else if metar}
|
||||||
<div class="text-left text-lg space-y-2">
|
<div class="text-left text-lg space-y-2">
|
||||||
<div><strong>ICAO:</strong> {metar.icaoId}</div>
|
<WeatherCard metar={metar}/>
|
||||||
<div><strong>Airport:</strong> {metar.name}</div>
|
{console.log(metar)}
|
||||||
<div><strong>Observed:</strong> {metar.reportTime}</div>
|
|
||||||
<div><strong>Temperature:</strong> {metar.temp}°C</div>
|
|
||||||
<div><strong>Dew Point:</strong> {metar.dewp}°C</div>
|
|
||||||
<div><strong>Wind:</strong> {metar.wdir}° at {metar.wspd}kt</div>
|
|
||||||
<div><strong>Visibility:</strong> {metar.visib}</div>
|
|
||||||
<div><strong>Pressure:</strong> {metar.altim} hPa</div>
|
|
||||||
<div><strong>Raw METAR:</strong> <code>{metar.rawOb}</code></div>
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Loading />
|
<Loading />
|
||||||
|
Reference in New Issue
Block a user