Files
BookBar/BookBar.js
T
RolimirKal a3dd9c6b19 Bug Fix
Changes:
- Only update if the target progress is different from the current progress
- fix anim loop binding logic
- Comment and warning clarifications
- delay animation start to let websocket connect
2026-07-30 10:30:26 -04:00

290 lines
7.5 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_v8_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 {
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_v8_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_v8_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}.`);
}
};
}