Files
BookBar/BookBar.js
T
RolimirKal 02cdfc98dd Bug Fix
Changes:
- If an out of range update is received, set the current progress to zero or max to avoid getting stuck on the default value when the page is loaded after exceeding the goal.
2026-08-20 20:16:00 -04:00

304 lines
8.0 KiB
JavaScript

window.addEventListener("DOMContentLoaded", () => {
//Create progress bar
const book_bar = new ProgressBar();
//Pat Lilac
const websocket = new ReconnectingWebSocket("wss://lilac.thewisewolf.dev/tracker/timer/ws");
communicate(websocket, book_bar);
});
class ProgressBar {
constructor() {
this.current_progress = 25;
this.max_progress = 45;
this.anim_progress = 0;
this.animating = false;
this.simming = false;
this.target_element = document.getElementById("progressBar");
this.bar_back = document.getElementById("barBack");
this.bar_front = document.getElementById("barFront");
//Pre-load and hold background images
let image_cache = [];
for (let i = 0; i < 46; i++) {
const img_fetcher = new Image();
let index_string = i.toString();
if (index_string.length < 2) {
index_string = "0" + index_string;
}
img_fetcher.src = "src/Books/BookBar_f00" + index_string + ".png";
image_cache.push(img_fetcher);
};
}
//SET NUMBER OF COLORED BOOKS
//Set current campaign progress
setProgress(val) {
if (Number.isInteger(val)
&& (val >= 0)
&& (val <= this.max_progress)
&& (val != this.current_progress)) {
this.current_progress = val;
this.anim_progress = 0;
} else {
if (Number.isInteger(val)
&& (val >= this.max_progress)
&& (val != this.current_progress)) {
this.current_progress = this.max_progress;
this.anim_progress = 0;
console.warn("Progress update: Goal Exceeded.")
}
if (Number.isInteger(val)
&& (val <= 0)
&& (val != this.current_progress)) {
this.current_progress = 0;
this.anim_progress = 0;
console.warn("Progress update: Goal Subceeded.")
}
console.warn("Progress update failed.")
}
}
//Render current animation state to the screen
renderProgress(val) {
if (Number.isInteger(val)
&& (this.target_element != null)
&& (this.bar_back != null)
&& (this.bar_front != null)
&& (val >= 0)
&& (val <= this.current_progress)
&& (val <= this.max_progress)) {
let val_string = val.toString();
if (val_string.length < 2) {
val_string = "0" + val_string;
}
this.bar_back.style.backgroundImage = "url(src/Books/BookBar_f00" + val_string + ".png)";
setTimeout(this.antiFlash.bind(this), 150, val_string, val);
} else {
console.warn("Progress render failed.")
}
}
//Prevent image flicker by pre-loading a version of the image then rendering the top image with a delay
antiFlash(val_string, new_val) {
this.bar_front.style.backgroundImage = "url(src/Books/BookBar_f00" + val_string + ".png)";
//Increment the animation ticker (plus an extra round of safety checks)
if ((new_val >= 0)
&& (new_val <= this.current_progress)
&& (this.anim_progress <= this.max_progress)
&& (new_val <= this.max_progress)) {
this.anim_progress = new_val;
}
else {
console.warn("Anim antiFlash render is out of range.");
}
}
//Progress Animation
animProgress() {
//animate
if (this.animating == true) {
if ((this.anim_progress >= 0)
&& (this.anim_progress < this.current_progress)
&& (this.anim_progress < this.max_progress)) {
this.renderProgress(this.anim_progress + 1);
setTimeout(this.animProgress.bind(this), 500);
}
//loop
if (this.anim_progress == this.current_progress) {
setTimeout(this.renderProgress.bind(this, 0), 5000);
setTimeout(this.animProgress.bind(this), 5500);
}
}
}
//Toggle animation on
animStart() {
if (this.animating != true){
this.animating = true;
this.animProgress();
} else {
console.warn("Animation already running");
}
}
//Toggle animation off
animEnd() {
this.animating = false;
}
//TEST SIMULATION
simProgress() {
//sim
if (this.simming == true) {
if (this.current_progress < this.max_progress) {
this.setProgress(this.current_progress + 1);
setTimeout(this.simProgress.bind(this), 38500);
}
//loop
if (this.current_progress == this.max_progress) {
setTimeout(() => this.setProgress(0), 10000);
setTimeout(this.simProgress.bind(this), 95500);
}
}
}
simStart() {
console.log("starting");
this.simming = true;
this.simProgress();
console.log("started");
}
simEnd() {
console.log("ending");
this.simming = false;
}
}
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.ws = null;
this.attempt = 0;
this.connect();
this.closing = false;
this.onopen = null;
this.onmessage = null;
this.onclose = null;
this.onerror = null;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket connecting');
this.attempt = 0;
if (this.onopen != null) {
this.onopen();
}
};
this.ws.onmessage = (msg) => {
console.log('Received:', msg.data);
if (this.onmessage != null) {
this.onmessage(msg);
}
};
this.ws.onclose = () => {
if (this.closing) {
console.log('Closing Websocket.')
} {
console.warn('WebSocket closed. Reconnecting...');
this.reconnect();
}
};
this.ws.onerror = (err) => {
console.error('WebSocket error:', err);
if (this.onerror != null) {
this.onerror();
}
this.close();
};
}
reconnect() {
const delay = this.getBackoffDelay(this.attempt);
console.log(`Reconnecting in ${delay}ms`);
setTimeout(() => {
this.attempt++;
this.connect();
}, delay);
}
close() {
this.closing = true;
this.ws.close();
if (this.onclose != null) {
this.onclose();
}
}
getBackoffDelay(attempt) {
const base = 500; // 0.5 second
const max = 30000; // 30 seconds
const jitter = Math.random() * 1000;
return Math.min(base * 2 ** attempt + jitter, max);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.warn('WebSocket not connected');
}
}
}
function communicate(websocket, book_bar) {
console.log("Communicating");
const params = new URLSearchParams(window.location.search);
// INITIALIZE CLASS HERE
//queue animation start after a few seconds to allow for initial status from server
setTimeout(book_bar.animStart.bind(book_bar),5000);
websocket.onopen = () => {
websocket.send(
JSON.stringify(
{
type: "init",
channel: "CampaignRewards",
community: params.get('community')
}
)
);
};
websocket.onmessage = ({ data }) => {
console.log("messaged");
const event = JSON.parse(data);
switch (event.type) {
case "DO":
let args = event.args;
// Call the specified method
switch (event.method) {
case "setTimer":
//do something with the new data
console.log("set timer: " + args.reward_progress);
book_bar.setProgress(args.reward_progress);
//animEnd();
//setTimeout(book_bar.setProgress(args.reward_progress), 7500);
//setTimeout(book_bar.animStart, 7800);
break;
case "noTimer":
//do something default
console.log("no timer: " + args);
break;
case "endTimer":
//do something for the final time
console.log("end timer: " + args);
//book_bar.renderProgress(args.reward_progress);
//book_bar.animEnd();
//setTimeout(book_bar.renderProgress(args.reward_progress), 7500);
//setTimeout(book_bar.animStart, 7800);
break
default:
throw new Error(`Unsupported method requested: ${event.method}.`)
}
break;
default:
throw new Error(`Unsupported event type: ${event.type}.`);
}
};
}