add better error handling w/ loading icon support

This commit is contained in:
kittentm 2026-01-16 21:59:39 +01:00
commit 997ab7c16e
4 changed files with 142 additions and 21 deletions

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

View file

@ -3,15 +3,20 @@
<head>
<meta charset="UTF-8">
<title>SplatNet | Stages</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="header">
<img src="../../assets/SplatNetLogo.png" class="logo" alt="SplatNet Logo">
</div>
<div id="loading-overlay" class="loading-overlay">
<canvas id="loading-canvas" width="100" height="100"></canvas>
<div id="loading-text">Loading</div>
</div>
<div class="content">
<img src="../../assets/stageinfo.png" class="stage-info" alt="Stage Info">

110
stages.js
View file

@ -14,40 +14,110 @@ function stageImagePath(stageName) {
}
async function fetchRotations() {
const res = await fetch(API_URL);
const data = await res.json();
try {
const res = await fetch(API_URL);
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
const data = await res.json();
const rotations = data?.pretendo?.rotations ?? {};
const now = new Date();
const rotations = data?.pretendo?.rotations ?? {};
const now = new Date();
const timestamps = Object.keys(rotations).sort(
(a, b) => Number(a) - Number(b)
);
const timestamps = Object.keys(rotations).sort(
(a, b) => Number(a) - Number(b)
);
for (const ts of timestamps) {
const start = new Date(Number(ts));
const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
for (const ts of timestamps) {
const start = new Date(Number(ts));
const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
if (end < now) continue;
if (end < now) continue;
const r = rotations[ts];
const r = rotations[ts];
return [{
turf: stageNames(r.turfStages).map(stageImagePath),
ranked: stageNames(r.rankedStages).map(stageImagePath),
ranked_mode: r.rankedMode?.translatedNames?.["en-US"] ?? "Ranked"
}];
return [{
turf: stageNames(r.turfStages).map(stageImagePath),
ranked: stageNames(r.rankedStages).map(stageImagePath),
ranked_mode: r.rankedMode?.translatedNames?.["en-US"] ?? "Ranked"
}];
}
return [];
} catch (error) {
console.error("Failed to fetch rotations:", error);
return [];
}
}
return [];
function animateLoadingCanvas() {
const canvas = document.getElementById("loading-canvas");
const loadingOverlay = document.getElementById("loading-overlay");
if (!canvas || !loadingOverlay) return;
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = "/assets/loading.png";
const frameWidth = 100;
const frameHeight = 100;
const totalFrames = 17;
let currentFrame = 0;
let lastUpdateTime = 0;
const frameDuration = 50; // ms per frame
let animationId;
image.onload = () => {
function animate(timestamp = 0) {
if (loadingOverlay.style.display === "none") {
cancelAnimationFrame(animationId);
return;
}
if (!lastUpdateTime) lastUpdateTime = timestamp;
const elapsed = timestamp - lastUpdateTime;
if (elapsed > frameDuration) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
image,
0, currentFrame * frameHeight,
frameWidth, frameHeight,
0, 0,
frameWidth, frameHeight
);
currentFrame = (currentFrame + 1) % totalFrames;
lastUpdateTime = timestamp;
}
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
};
}
async function renderStages() {
const loadingOverlay = document.getElementById("loading-overlay");
const container = document.getElementById("stages-container");
const rotations = await fetchRotations();
let rotations = [];
try {
rotations = await fetchRotations();
} catch (err) {
console.error("Error in renderStages:", err);
} finally {
loadingOverlay.style.display = "none"; // always hide loading
}
if (!rotations.length) {
container.innerHTML = "<h2>No rotations available</h2>";
container.innerHTML = `
<h2 class="error-message">
Something went wrong! Try reloading the page. If this problem persists you may be ratelimited.
</h2>
`;
return;
}

View file

@ -5,6 +5,45 @@
font-style: normal;
}
.loading-overlay {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0,0,0,0.8);
border: 3px solid white;
border-radius: 12px;
padding: 20px 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
user-select: none;
pointer-events: none;
width: 200px; /* Updated width */
height: 250px; /* Updated height */
box-sizing: border-box;
}
#loading-canvas {
image-rendering: pixelated;
/* Make sure canvas doesn't get resized by CSS */
width: 100px;
height: 100px;
display: block;
margin: 0 auto; /* center horizontally */
}
#loading-text {
margin-top: 12px;
color: white;
font-family: "Splatoon1", sans-serif;
font-size: 20px;
letter-spacing: 1.2px;
text-align: center;
}
html, body {
margin: 0;
padding: 0;
@ -93,3 +132,10 @@ body {
display: block;
border-radius: 8px;
}
.error-message {
color: white;
font-family: "Splatoon1", sans-serif;
text-align: center;
margin-top: 40px;
}