forked from spacebar/SplatNet
add better error handling w/ loading icon support
This commit is contained in:
parent
fcd62a8e4f
commit
997ab7c16e
4 changed files with 142 additions and 21 deletions
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
|
|
@ -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
110
stages.js
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
46
styles.css
46
styles.css
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue