implemented loading animation during requests

This commit is contained in:
RaffaelW 2024-11-05 09:11:36 +01:00
parent b8b87db0f2
commit f0204c2477
6 changed files with 178 additions and 53 deletions

View file

@ -6,14 +6,30 @@
<title>Konfiguration</title> <title>Konfiguration</title>
<link rel="stylesheet" href="/style.css" /> <link rel="stylesheet" href="/style.css" />
<script type="module" src="/input-visibility.js" defer></script> <script type="module" src="/input-visibility.js" defer></script>
<script type="module" src="/loading-screen.js" defer></script>
<script type="module" src="/load-data.js" defer></script> <script type="module" src="/load-data.js" defer></script>
<script type="module" src="/submit.js" defer></script> <script type="module" src="/submit.js" defer></script>
<script type="module" src="/reset.js" defer></script> <script type="module" src="/reset.js" defer></script>
</head> </head>
<body> <body>
<main> <main>
<section class="loading-screen">
<div class="spinner-container">
<!-- h2 is filled dynamically -->
<h2></h2>
<div class="spinner"></div>
<button
class="reload"
type="button"
onclick="window.location.reload()"
>
Seite neu laden
</button>
</div>
</section>
<form class="hidden">
<h1>Konfiguration</h1> <h1>Konfiguration</h1>
<form>
<fieldset> <fieldset>
<legend>Verbindung</legend> <legend>Verbindung</legend>
<label for="ip-method" <label for="ip-method"
@ -63,8 +79,8 @@
id="input-connection" id="input-connection"
title="Verbindung" title="Verbindung"
> >
<option value="0">WiFi-Station</option> <option value="0">WiFi-AccessPoint</option>
<option value="1">WiFi-AccessPoint</option> <option value="1">WiFi-Station</option>
<option value="2">Ethernet</option> <option value="2">Ethernet</option>
</select> </select>
</label> </label>

View file

@ -1,9 +1,15 @@
import {
showLoadingScreen,
showError,
hideLoadingScreen,
} from "./loading-screen.js";
const form = document.querySelector("form"); const form = document.querySelector("form");
async function loadData() { export async function loadData(timeout = null) {
try {
const req = await fetch("/config", { const req = await fetch("/config", {
method: "GET", method: "GET",
signal: timeout !== null ? AbortSignal.timeout(timeout) : undefined,
}); });
if (!req.ok) { if (!req.ok) {
throw new Error(`Response status: ${req.status}`); throw new Error(`Response status: ${req.status}`);
@ -12,14 +18,10 @@ async function loadData() {
const json = await req.json(); const json = await req.json();
console.log(json); console.log(json);
return json; return json;
} catch (error) {
console.log(error);
return null;
}
} }
export function writeDataToInput(data) { export function writeDataToInput(data) {
console.log("write data", typeof data); console.log("write data");
for (const [key, value] of Object.entries(data)) { for (const [key, value] of Object.entries(data)) {
const element = document.querySelector(`[name=${key}]`); const element = document.querySelector(`[name=${key}]`);
console.log(key, element); console.log(key, element);
@ -34,7 +36,12 @@ export function writeDataToInput(data) {
form.dispatchEvent(new Event("change", { bubbles: true })); form.dispatchEvent(new Event("change", { bubbles: true }));
} }
showLoadingScreen("Konfiguration wird geladen...");
try {
const data = await loadData(); const data = await loadData();
if (data !== null) { hideLoadingScreen();
writeDataToInput(data); writeDataToInput(data);
} catch (error) {
console.log(error.message);
showError("Die Konfiguration konnte nicht geladen werden.");
} }

40
data/loading-screen.js Normal file
View file

@ -0,0 +1,40 @@
const form = document.querySelector("form");
const loadingScreen = document.querySelector(".loading-screen");
const loadingMsg = loadingScreen.querySelector("h2");
const spinner = loadingScreen.querySelector(".spinner");
const reloadBtn = loadingScreen.querySelector(".reload");
export function showLoadingScreen(msg) {
hide(form, reloadBtn);
show(loadingScreen, spinner);
loadingMsg.classList.remove("error");
loadingMsg.textContent = msg;
}
export function showError(msg) {
showLoadingScreen(msg);
loadingMsg.innerHTML +=
"<br/>Stelle sicher, dass du mit dem DMX-Interface verbunden bist und die IP-Adresse stimmt.";
show(reloadBtn);
hide(spinner);
loadingMsg.classList.add("error");
}
export function hideLoadingScreen() {
hide(loadingScreen, reloadBtn);
show(form);
loadingMsg.classList.remove("error");
loadingMsg.textContent = "";
}
function show(...elements) {
for (const element of elements) {
element.classList.remove("hidden");
}
}
function hide(...elements) {
for (const element of elements) {
element.classList.add("hidden");
}
}

View file

@ -1,4 +1,4 @@
import { writeDataToInput } from "/load-data.js"; import { updateConfig } from "/submit.js";
const form = document.querySelector("form"); const form = document.querySelector("form");
@ -9,22 +9,8 @@ form.addEventListener("reset", async (event) => {
"Sicher, dass du alle Einstellungen zurücksetzen möchtest?" "Sicher, dass du alle Einstellungen zurücksetzen möchtest?"
); );
if (ok) { if (ok) {
reset(); updateConfig({
}
});
async function reset() {
try {
const res = await fetch("/config", {
method: "DELETE", method: "DELETE",
}); });
if (!res.ok) {
throw new Error(`Response status: ${res.status}`);
}
const json = await res.json();
writeDataToInput(json);
} catch (error) {
console.error(error.message);
}
} }
});

View file

@ -1,6 +1,7 @@
:root { :root {
--color-primary: #087e8b; --color-primary: #087e8b;
--color-on-primary: white; --color-on-primary: white;
--color-background: #222;
--color-danger: #fa2b58; --color-danger: #fa2b58;
} }
@ -14,7 +15,7 @@ body {
} }
main { main {
background-color: #222; background-color: var(--color-background);
max-width: 700px; max-width: 700px;
padding: 8px max(5%, 8px); padding: 8px max(5%, 8px);
margin: 0 auto; margin: 0 auto;
@ -45,7 +46,7 @@ label {
input, input,
select { select {
width: clamp(200px, 100%, 400px); width: clamp(200px, 100%, 400px);
background-color: #222; background-color: var(--color-background);
color: white; color: white;
border: 1px solid white; border: 1px solid white;
border-radius: 8px; border-radius: 8px;
@ -78,7 +79,7 @@ button[type="reset"] {
} }
.hidden { .hidden {
display: none; display: none !important;
} }
label.switch { label.switch {
@ -136,3 +137,61 @@ label.switch input:checked + .slider::before {
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
} }
.loading-screen {
display: grid;
justify-content: center;
}
h2.error {
color: var(--color-danger);
}
button.reload {
display: block;
margin: 0 auto;
}
.spinner-container {
width: min(max-content, 100%);
}
.spinner {
position: relative;
margin: 10px auto;
background: conic-gradient(transparent 150deg, var(--color-primary));
--outer-diameter: 50px;
width: var(--outer-diameter);
height: var(--outer-diameter);
border-radius: 50%;
animation-name: spin;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.spinner::after {
position: absolute;
content: "";
display: block;
--spinner-border: 5px;
top: var(--spinner-border);
left: var(--spinner-border);
--inner-diameter: calc(var(--outer-diameter) - 2 * var(--spinner-border));
width: var(--inner-diameter);
height: var(--inner-diameter);
background-color: var(--color-background);
border-radius: 50%;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -1,3 +1,10 @@
import { loadData, writeDataToInput } from "./load-data.js";
import {
showLoadingScreen,
hideLoadingScreen,
showError,
} from "./loading-screen.js";
const form = document.querySelector("form"); const form = document.querySelector("form");
function parseValue(input) { function parseValue(input) {
@ -31,25 +38,35 @@ form.addEventListener("submit", (event) => {
}, {}); }, {});
console.log(data); console.log(data);
putData(data); updateConfig({
});
async function putData(data) {
try {
const res = await fetch("/config", {
method: "PUT", method: "PUT",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
});
export async function updateConfig(fetchOptions) {
showLoadingScreen("Konfiguration anwenden und ESP neustarten...");
try {
const res = await fetch("/config", fetchOptions);
if (!res.ok) { if (!res.ok) {
throw new Error(`Response status: ${res.status}`); throw new Error(`Response status: ${res.status}`);
} }
const json = await res.json(); // wait for the esp to restart
console.log(json); const delay = new Promise((resolve) =>
setTimeout(() => resolve(), 500)
);
await delay;
const data = await loadData(30 * 1000);
writeDataToInput(data);
hideLoadingScreen();
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
showError(error.message);
} }
} }