Skip to content

Commit 3bf50c9

Browse files
Merge pull request #1 from sebastiankrll/feature/loading-screen
Add Loader component with initialization status tracking
2 parents 0366254 + ff568d0 commit 3bf50c9

File tree

6 files changed

+150
-4
lines changed

6 files changed

+150
-4
lines changed

apps/web/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Manrope } from "next/font/google";
33
import "./globals.css";
44
import "@/assets/images/sprites/freakflags.css";
55
import Header from "@/components/Header/Header";
6+
import Loader from "@/components/Loader/Loader";
67
import OMap from "@/components/Map/Map";
78

89
export const metadata: Metadata = {
@@ -24,6 +25,7 @@ export default function RootLayout({
2425
<html lang="en" className={manrope.className}>
2526
<body>
2627
<Header />
28+
<Loader />
2729
<OMap />
2830
{children}
2931
</body>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#loader-wrapper {
2+
position: fixed;
3+
top: 0;
4+
left: 0;
5+
width: 100vw;
6+
height: 100vh;
7+
background: rgba(255, 255, 255, 0.5);
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
z-index: 9998;
12+
}
13+
14+
#loader {
15+
background: var(--color-blue);
16+
display: flex;
17+
flex-flow: column;
18+
justify-content: center;
19+
align-items: center;
20+
z-index: 9999;
21+
box-shadow: 0 0 10px rgb(156, 156, 167);
22+
border: 1px solid var(--color-border);
23+
padding: 2rem;
24+
gap: 1rem;
25+
}
26+
27+
#loader * {
28+
color: white;
29+
}
30+
31+
#loader-title {
32+
font-size: 1rem;
33+
font-weight: bold;
34+
margin-bottom: 1rem;
35+
}
36+
37+
#loader-items {
38+
display: flex;
39+
flex-flow: column;
40+
gap: 1rem;
41+
}
42+
43+
.loader-item {
44+
display: flex;
45+
align-items: center;
46+
gap: 0.5rem;
47+
}
48+
49+
.loader-spinner {
50+
width: 20px;
51+
height: 20px;
52+
display: inline-block;
53+
position: relative;
54+
background-color: var(--color-main-text);
55+
border-radius: 50%;
56+
border: 1px solid var(--color-border);
57+
}
58+
.loader-spinner.done {
59+
background-color: var(--color-green);
60+
}
61+
.loader-spinner.done::after,
62+
.loader-spinner.done::before {
63+
background: none;
64+
}
65+
.loader-spinner::after,
66+
.loader-spinner::before {
67+
content: "";
68+
box-sizing: border-box;
69+
width: 20px;
70+
height: 20px;
71+
border-radius: 50%;
72+
background: var(--color-yellow);
73+
position: absolute;
74+
left: 0;
75+
top: 0;
76+
animation: animloader .5s linear infinite;
77+
}
78+
79+
@keyframes animloader {
80+
0% {
81+
transform: scale(0);
82+
opacity: 1;
83+
}
84+
100% {
85+
transform: scale(1);
86+
opacity: 0;
87+
}
88+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { initData } from "@/storage/cache";
5+
import type { StatusMap } from "@/types/data";
6+
import "./Loader.css";
7+
import Image from "next/image";
8+
import simradar24Logo from "@/assets/images/simradar24_logo.svg";
9+
10+
export default function Loader() {
11+
const [status, setStatus] = useState<StatusMap>({});
12+
13+
useEffect(() => {
14+
initData(setStatus);
15+
return () => {};
16+
}, []);
17+
18+
return (
19+
<>
20+
{status.indexedDB && status.initData && status.initMap ? null : (
21+
<div id="loader-wrapper">
22+
<div id="loader">
23+
<figure id="loader-logo">
24+
<Image src={simradar24Logo} alt="simradar24 logo" priority={true} />
25+
</figure>
26+
<div id="loader-title">Please wait, while the application is loading.</div>
27+
<div id="loader-items">
28+
<div className="loader-item">
29+
<div className={`loader-spinner`}></div>
30+
<p>{status.indexedDB ? "Database ready!" : "Initializing databases ..."}</p>
31+
</div>
32+
<div className="loader-item">
33+
<div className={`loader-spinner${status.initData ? " done" : ""}`}></div>
34+
<p>{status.initData ? "Initial data fetched!" : "Fetching initial data ..."}</p>
35+
</div>
36+
<div className="loader-item">
37+
<div className={`loader-spinner${status.initMap ? " done" : ""}`}></div>
38+
<p>{status.initMap ? "Map initialized!" : "Initializing map ..."}</p>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
)}
44+
</>
45+
);
46+
}

apps/web/components/Map/Map.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22

33
import { useEffect } from "react";
44
import "./Map.css";
5-
import { initData } from "@/storage/cache";
65
import { onClick, onMoveEnd, onPointerMove } from "./utils/events";
76
import { initMap } from "./utils/init";
87

9-
initData();
10-
118
export default function OMap() {
129
useEffect(() => {
1310
const map = initMap();

apps/web/storage/cache.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { updateOverlays } from "@/components/Map/utils/events";
77
import { getMapView } from "@/components/Map/utils/init";
88
import { initPilotFeatures, updatePilotFeatures } from "@/components/Map/utils/pilotFeatures";
99
import { updateTrackFeatures } from "@/components/Map/utils/trackFeatures";
10+
import type { StatusMap } from "@/types/data";
1011
import { wsClient } from "@/utils/ws";
1112
import { dxGetAirline, dxGetAirport, dxGetFirs, dxGetTracons, dxInitDatabases } from "./dexie";
1213

@@ -19,9 +20,15 @@ const cachedAirlines: Map<string, StaticAirline> = new Map();
1920
const cachedTracons: Map<string, SimAwareTraconFeature> = new Map();
2021
const cachedFirs: Map<string, FIRFeature> = new Map();
2122

22-
export async function initData(): Promise<void> {
23+
type StatusSetter = (status: Partial<StatusMap> | ((prev: Partial<StatusMap>) => Partial<StatusMap>)) => void;
24+
25+
export async function initData(setStatus?: StatusSetter): Promise<void> {
2326
await dxInitDatabases();
27+
setStatus?.((prev) => ({ ...prev, indexedDB: true }));
28+
2429
const data = (await fetch(`${BASE_URL}/api/data/init`).then((res) => res.json())) as WsAll;
30+
setStatus?.((prev) => ({ ...prev, initData: true }));
31+
2532
await initAirportFeatures();
2633
initPilotFeatures(data.pilots);
2734
initControllerFeatures(data.controllers);
@@ -33,6 +40,7 @@ export async function initData(): Promise<void> {
3340
if (view) {
3441
setFeatures(view.calculateExtent(), view.getZoom() || 5);
3542
}
43+
setStatus?.((prev) => ({ ...prev, initMap: true }));
3644

3745
wsClient.addListener((msg) => {
3846
updateCache(msg);

apps/web/types/data.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type StatusMap = {
2+
indexedDB?: boolean;
3+
initData?: boolean;
4+
initMap?: boolean;
5+
};

0 commit comments

Comments
 (0)