feat: add loading state to view
Some checks failed
TrafficCue CI / check (push) Failing after 57s
TrafficCue CI / build (push) Successful in 1m38s
TrafficCue CI / build-android (push) Successful in 15m1s

This commit is contained in:
2025-09-16 18:22:48 +02:00
parent 222216f172
commit 28796ee267
7 changed files with 105 additions and 14 deletions

View File

@ -0,0 +1,75 @@
<div class="progress-bar indeterminate bg-card/50 backdrop-blur-md">
<div class="short-bar"></div>
<div class="long-bar"></div>
</div>
<style>
@keyframes progressbarindeterminateshort {
0% {
left: -40%;
}
75% {
left: 120%;
}
100% {
left: 120%;
}
}
@keyframes progressbarindeterminatelong {
0% {
left: -90%;
}
37.5% {
left: -90%;
}
100% {
left: 100%;
}
}
.progress-bar {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
top: 0;
left: 0;
right: 0;
min-height: 5px;
position: absolute;
border-radius: 1.5px;
overflow: hidden !important;
}
.progress-bar > .short-bar {
background-color: var(--accent);
width: 40%;
height: 100%;
left: -40%;
border-radius: 2.5px;
position: absolute;
display: inline-block;
-webkit-animation: progressbarindeterminateshort 2s ease-in-out infinite;
-moz-animation: progressbarindeterminateshort 2s ease-in-out infinite;
-o-animation: progressbarindeterminateshort 2s ease-in-out infinite;
animation: progressbarindeterminateshort 2s ease-in-out infinite;
}
.progress-bar > .long-bar {
background-color: var(--accent);
width: 60%;
height: 100%;
left: -90%;
border-radius: 2.5px;
position: absolute;
display: inline-block;
-webkit-animation: progressbarindeterminatelong 2s ease-in-out infinite;
-moz-animation: progressbarindeterminatelong 2s ease-in-out infinite;
-o-animation: progressbarindeterminatelong 2s ease-in-out infinite;
animation: progressbarindeterminatelong 2s ease-in-out infinite;
}
</style>

View File

@ -24,6 +24,7 @@
import LoadingSidebar from "./sidebar/LoadingSidebar.svelte"; import LoadingSidebar from "./sidebar/LoadingSidebar.svelte";
import { Tween } from "svelte/motion"; import { Tween } from "svelte/motion";
import { quintOut } from "svelte/easing"; import { quintOut } from "svelte/easing";
import Progressbar from "../Progressbar.svelte";
const views: Record<string, string> = { const views: Record<string, string> = {
main: "MainSidebar", main: "MainSidebar",
@ -134,10 +135,6 @@
return () => value; return () => value;
} }
// TODO: implement loading state
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let loading = $state(false);
let searchText = $derived.by(debounce(() => searchbar.text, 300)); let searchText = $derived.by(debounce(() => searchbar.text, 300));
let searchResults: Feature[] = $state([]); let searchResults: Feature[] = $state([]);
let mobileView = $derived(window.innerWidth < 768 || routing.currentTrip); let mobileView = $derived(window.innerWidth < 768 || routing.currentTrip);
@ -149,10 +146,10 @@
return; return;
} }
if (searchText.length > 0) { if (searchText.length > 0) {
loading = true; view.loading = true;
search(searchText, 0, 0).then((results) => { search(searchText, 0, 0).then((results) => {
searchResults = results; searchResults = results;
loading = false; view.loading = false;
view.switch("search", { view.switch("search", {
results: searchResults, results: searchResults,
query: searchText, query: searchText,
@ -211,6 +208,10 @@
</div> </div>
{/if} {/if}
{#if view.loading}
<Progressbar />
{/if}
{#if routing.currentTrip} {#if routing.currentTrip}
<InRouteSidebar /> <InRouteSidebar />
{:else} {:else}

View File

@ -27,6 +27,7 @@
import InternetAccess from "../info/InternetAccess.svelte"; import InternetAccess from "../info/InternetAccess.svelte";
import RestaurantInfo from "../info/RestaurantInfo.svelte"; import RestaurantInfo from "../info/RestaurantInfo.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { onMount } from "svelte";
// let { feature }: { feature: Feature } = $props(); // let { feature }: { feature: Feature } = $props();
@ -81,9 +82,16 @@
); );
}); });
} }
onMount(() => {
view.loading = true;
})
</script> </script>
{#await fetchPOI(lat, lng, 20)} {#await fetchPOI(lat, lng, 20).then(r => {
view.loading = false;
return r;
})}
<SidebarHeader <SidebarHeader
onback={() => { onback={() => {
pin.liftPin(); pin.liftPin();

View File

@ -4,19 +4,20 @@
import { fetchNearbyPOI, type OverpassElement } from "$lib/services/Overpass"; import { fetchNearbyPOI, type OverpassElement } from "$lib/services/Overpass";
import { location } from "../location.svelte"; import { location } from "../location.svelte";
import { map, pin } from "../map.svelte"; import { map, pin } from "../map.svelte";
import { view } from "../view.svelte";
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
let { tags }: { tags?: string } = $props(); let { tags }: { tags?: string } = $props();
let pois: OverpassElement[] = $state([]); let pois: OverpassElement[] = $state([]);
let loading = $state(true);
$effect(() => { $effect(() => {
if (!tags) { if (!tags) {
loading = false; view.loading = false;
pois = []; pois = [];
return; return;
} }
view.loading = true;
fetchNearbyPOI(location.lat, location.lng, tags.split(","), 500).then( fetchNearbyPOI(location.lat, location.lng, tags.split(","), 500).then(
(results) => { (results) => {
pois = results.elements.sort((a, b) => { pois = results.elements.sort((a, b) => {
@ -30,7 +31,7 @@
); );
return distA - distB; return distA - distB;
}); });
loading = false; view.loading = false;
}, },
); );
}); });
@ -60,7 +61,7 @@
{/if} {/if}
</SidebarHeader> </SidebarHeader>
{#if loading} {#if view.loading}
<div class="text-sm text-muted-foreground">{m.loading()}</div> <div class="text-sm text-muted-foreground">{m.loading()}</div>
{:else} {:else}
<div class="flex flex-col gap-2 p-2"> <div class="flex flex-col gap-2 p-2">

View File

@ -69,6 +69,7 @@
</div> </div>
<Button <Button
onclick={async () => { onclick={async () => {
view.loading = true;
console.log(fromLocation, toLocation); console.log(fromLocation, toLocation);
const FROM: WorldLocation = const FROM: WorldLocation =
fromLocation == "current" fromLocation == "current"
@ -103,6 +104,7 @@
} }
drawAllRoutes(routes); drawAllRoutes(routes);
zoomToPoints(FROM, TO, map.value!); zoomToPoints(FROM, TO, map.value!);
view.loading = false;
}}>{m["sidebar.route.calculate"]()}</Button }}>{m["sidebar.route.calculate"]()}</Button
> >

View File

@ -2,9 +2,9 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import SidebarHeader from "../SidebarHeader.svelte"; import SidebarHeader from "../SidebarHeader.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { view } from "../../view.svelte";
let licenses: License[] = $state([]); let licenses: License[] = $state([]);
let loading = $state(true);
interface License { interface License {
name: string; name: string;
@ -20,15 +20,16 @@
} }
onMount(async () => { onMount(async () => {
view.loading = true;
const res = await fetch("/licenses.json"); const res = await fetch("/licenses.json");
licenses = await res.json(); licenses = await res.json();
loading = false; view.loading = false;
}); });
</script> </script>
<SidebarHeader>{m["sidebar.about.licenses"]()}</SidebarHeader> <SidebarHeader>{m["sidebar.about.licenses"]()}</SidebarHeader>
{#if loading} {#if view.loading}
<p>Loading...</p> <p>Loading...</p>
{:else if licenses.length == 0} {:else if licenses.length == 0}
<p>No licenses found.</p> <p>No licenses found.</p>

View File

@ -6,9 +6,11 @@ export interface View {
} }
export const view = $state({ export const view = $state({
loading: false,
current: { type: getOnboardingView("main") } as View, current: { type: getOnboardingView("main") } as View,
history: [] as View[], history: [] as View[],
back: () => { back: () => {
view.loading = false;
if (view.history.length > 0) { if (view.history.length > 0) {
view.current = view.history.pop()!; view.current = view.history.pop()!;
} else { } else {
@ -19,6 +21,7 @@ export const view = $state({
if (view.current.type !== to) { if (view.current.type !== to) {
view.history.push(view.current); view.history.push(view.current);
} }
view.loading = false;
view.current = { type: to, props } as View; view.current = { type: to, props } as View;
}, },
}); });