Merge pull request 'FEAT: Added weather card which nicely displays the weather conditions from a METAR object' (#2) from development into main
Reviewed-on: #2
This commit was merged in pull request #2.
	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