Daily fixes and loading animations
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import Loading from '@/components/Loading.vue';
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {api, BASE} from '../services/api';
|
||||
import {api} from '../services/api';
|
||||
|
||||
const data = ref<any>({});
|
||||
const data = ref<any>();
|
||||
|
||||
function dir(deg: number) {
|
||||
if(deg > 22.5 && deg <= 67.5) return 'NE';
|
||||
@@ -17,13 +18,12 @@ function dir(deg: number) {
|
||||
|
||||
onMounted(async () => {
|
||||
data.value = await api.current();
|
||||
console.log(data.value);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.today-card {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
@@ -123,18 +123,26 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="today-card">
|
||||
<div class="flex-r align-items-center justify-between px-3">
|
||||
<div class="flex-c justify-center align-items-center">
|
||||
<img v-if="data.icon" :src="'https://openweathermap.org/img/wn/' + data.icon + '@2x.png'" class="today-icon" alt="weather" />
|
||||
<div class="flex-r align-items-center justify-between">
|
||||
<div v-if="!data" class="pos-rel br-2 overflow-hidden" style="width: 75px; height: 50px"><Loading/></div>
|
||||
<div v-else class="flex-c justify-center align-items-center mx-3">
|
||||
<img :src="'https://openweathermap.org/img/wn/' + data.icon + '@2x.png'" class="today-icon" alt="weather" />
|
||||
<div class="today-label">{{ data.label }}</div>
|
||||
</div>
|
||||
<div class="flex-r align-items-center gap-2">
|
||||
<div class="fs-6">
|
||||
{{ data.temperature?.toFixed(0) }}°C
|
||||
<div class="fs-6 m-0 p-0 flex-r align-items-center">
|
||||
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 50px; height: 1em"><Loading/></div>
|
||||
<template v-else>{{ Math.round(data.temperature) }}°C</template>
|
||||
</div>
|
||||
<div class="flex-c fg-muted">
|
||||
<span class="hi">↑ {{ 0 }}°</span>
|
||||
<span class="lo">↓ {{ 0 }}°</span>
|
||||
<div class="hi">↑
|
||||
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 25px; height: 1em"><Loading/></div>
|
||||
<template v-else>{{ 0 }}°</template>
|
||||
</div>
|
||||
<div class="lo">↓
|
||||
<div v-if="!data" class="d-inline-block pos-rel br-2 overflow-hidden" style="width: 25px; height: 1em"><Loading/></div>
|
||||
<template v-else>{{ 0 }}°</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,12 +150,35 @@ onMounted(async () => {
|
||||
<div class="divider" />
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat"><span class="stat-label">Precipitation</span><span class="stat-value">{{ !data.precipitation && data.raining == 'True' ? 'Spitting' : (data.precipitation || 0).toFixed(1) + 'mm'}}</span></div>
|
||||
<div class="stat"><span class="stat-label">Humidity</span><span class="stat-value">{{ (data.humidity || 0).toFixed(0) }}%</span></div>
|
||||
<div class="stat"><span class="stat-label">Wind</span><span class="stat-value">{{ data.wind_gusts || 0 }} Kph <span class="fg-muted">({{ dir(data.wind_direction || 0) }})</span></span></div>
|
||||
<div class="stat"><span class="stat-label">Clouds</span><span class="stat-value">{{ (data.clouds || 0).toFixed(0) }}%</span></div>
|
||||
<div class="stat"><span class="stat-label">Air Quality</span><span class="stat-value">{{ data.air_quality }} <span class="fg-muted">({{data.air_quality_label}})</span></span></div>
|
||||
<div class="stat"><span class="stat-label">UV index</span><span class="stat-value">{{ (data.uv_index || 0).toFixed(1) }} <span class="fg-muted">({{data.uv_index_label}})</span></span></div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Precipitation</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ !data.precipitation && data.raining == 'True' ? 'Spitting' : data.precipitation.toFixed(1) + 'mm'}}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Humidity</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ Math.round(data.humidity) }}%</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Wind</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ data.wind_gusts }} Kph <span class="fg-muted">({{ dir(data.wind_direction) }})</span></span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Clouds</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ Math.round(data.clouds) }}%</span></div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Air Quality</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ data.air_quality }} <span class="fg-muted">({{data.air_quality_label}})</span></span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">UV index</span>
|
||||
<div v-if="!data" class="w-100 pos-rel br-2 overflow-hidden" style="height: 1.75em"><Loading/></div>
|
||||
<span v-else class="stat-value">{{ data.uv_index.toFixed(1) }} <span class="fg-muted">({{data.uv_index_label}})</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
43
client/src/components/Loading.vue
Normal file
43
client/src/components/Loading.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<style scoped lang="scss">
|
||||
.shimmer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.stroke {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #777;
|
||||
}
|
||||
|
||||
.dark .animate {
|
||||
animation: shimmer 3s infinite;
|
||||
background: linear-gradient(90deg, #ffffff10 25%, #ffffff30 50%, #ffffff10 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
.animate {
|
||||
animation: shimmer 3s infinite;
|
||||
background: linear-gradient(to right, #e6e6e6 0%, #cccccc 50%, #e6e6e6 100%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
from {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="shimmer">
|
||||
<div class="stroke animate"></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,7 +1,7 @@
|
||||
`<script setup lang="ts">
|
||||
import {formatDate} from '@ztimson/utils';
|
||||
import {onMounted, onUnmounted, ref, computed} from 'vue';
|
||||
import {current, hourly, daily, fetchAll, loading} from '../services/weather';
|
||||
import {current, daily, fetchAll, loading} from '../services/weather';
|
||||
import {METRICS, GROUPS} from '../services/units';
|
||||
import MapView from '../components/MapView.vue';
|
||||
import CurrentWeather from '../components/CurrentWeather.vue';
|
||||
@@ -9,9 +9,9 @@ import ForecastStrip from '../components/ForecastStrip.vue';
|
||||
import MetricCard from '../components/MetricCard.vue';
|
||||
import GraphModal from '../components/GraphModal.vue';
|
||||
|
||||
const day = ref();
|
||||
const date = ref();
|
||||
const time = ref();
|
||||
const day = ref(formatDate('dddd'));
|
||||
const date = ref(formatDate('MMMM D'));
|
||||
const time = ref(formatDate('h:mm A'));
|
||||
const props = defineProps<{dark: boolean}>();
|
||||
const selectedMetric = ref<string | null>(null);
|
||||
let interval: ReturnType<typeof setInterval>;
|
||||
|
||||
@@ -86,13 +86,12 @@ app.get('/api/daily', async (req, res) => {
|
||||
const {fields} = req.query;
|
||||
const start = req.query.start || new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
|
||||
let now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
now.setHours(0, 0, 0, 0);
|
||||
now = now.toISOString();
|
||||
const end = req.query.end || new Date().toISOString();
|
||||
const coords = await getCoords();
|
||||
const [sensor, meteo] = await Promise.allSettled([
|
||||
queryDaily(start, now),
|
||||
now > start ? queryDaily(start, now) : Promise.resolve([]),
|
||||
getOpenMeteo(coords.latitude, coords.longitude, now, end),
|
||||
]);
|
||||
const history = sensor.status === 'fulfilled' ? sensor.value : [];
|
||||
|
||||
Reference in New Issue
Block a user