118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import { Controller } from "./main";
|
|
import {LiveLogResponse, LogArray, LogLocalisation} from "./api";
|
|
|
|
const LIVE_LOG_POLL_INTERVAL_MS = 2000;
|
|
|
|
export class LogView {
|
|
private readonly logpanel: HTMLElement;
|
|
private readonly livelogpanel: HTMLElement;
|
|
private readonly accordionHeader: HTMLElement;
|
|
loglocale: LogLocalisation | undefined;
|
|
|
|
private liveLogNextSeq: number | undefined = undefined;
|
|
private liveLogTimer: ReturnType<typeof setTimeout> | undefined = undefined;
|
|
private structuredLogLoaded = false;
|
|
|
|
constructor(controller: Controller) {
|
|
(document.getElementById("logview") as HTMLElement).innerHTML = require('./log.html') as string;
|
|
this.logpanel = document.getElementById("logpanel") as HTMLElement;
|
|
this.livelogpanel = document.getElementById("livelogpanel") as HTMLElement;
|
|
this.accordionHeader = document.getElementById("logAccordionHeader") as HTMLElement;
|
|
|
|
this.accordionHeader.onclick = () => {
|
|
const isOpen = this.logpanel.style.display !== "none";
|
|
if (isOpen) {
|
|
this.logpanel.style.display = "none";
|
|
this.accordionHeader.classList.remove("open");
|
|
} else {
|
|
this.logpanel.style.display = "";
|
|
this.accordionHeader.classList.add("open");
|
|
if (!this.structuredLogLoaded) {
|
|
this.structuredLogLoaded = true;
|
|
controller.loadLog();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
setLogLocalisation(loglocale: LogLocalisation) {
|
|
this.loglocale = loglocale;
|
|
}
|
|
|
|
setLog(logs: LogArray) {
|
|
this.logpanel.textContent = "";
|
|
logs.forEach(entry => {
|
|
let message = this.loglocale!![entry.message_id];
|
|
let template = message.message;
|
|
template = template.replace("${number_a}", entry.a.toString());
|
|
template = template.replace("${number_b}", entry.b.toString());
|
|
template = template.replace("${txt_short}", entry.txt_short.toString());
|
|
template = template.replace("${txt_long}", entry.txt_long.toString());
|
|
|
|
let ts = new Date(entry.timestamp);
|
|
|
|
let div = document.createElement("div");
|
|
let timestampDiv = document.createElement("div");
|
|
let messageDiv = document.createElement("div");
|
|
timestampDiv.innerText = ts.toISOString();
|
|
messageDiv.innerText = template;
|
|
div.appendChild(timestampDiv);
|
|
div.appendChild(messageDiv);
|
|
this.logpanel.appendChild(div);
|
|
});
|
|
}
|
|
|
|
startLivePoll(publicUrl: string) {
|
|
if (this.liveLogTimer !== undefined) {
|
|
return;
|
|
}
|
|
const poll = async () => {
|
|
try {
|
|
const url = this.liveLogNextSeq !== undefined
|
|
? `${publicUrl}/live_log?after=${this.liveLogNextSeq}`
|
|
: `${publicUrl}/live_log`;
|
|
const response = await fetch(url);
|
|
const data = await response.json() as LiveLogResponse;
|
|
this.appendLiveLog(data);
|
|
} catch (_e) {
|
|
// network error — silently ignore, will retry next interval
|
|
}
|
|
this.liveLogTimer = setTimeout(poll, LIVE_LOG_POLL_INTERVAL_MS);
|
|
};
|
|
// Kick off immediately
|
|
this.liveLogTimer = setTimeout(poll, 0);
|
|
}
|
|
|
|
stopLivePoll() {
|
|
if (this.liveLogTimer !== undefined) {
|
|
clearTimeout(this.liveLogTimer);
|
|
this.liveLogTimer = undefined;
|
|
}
|
|
}
|
|
|
|
private appendLiveLog(data: LiveLogResponse) {
|
|
const panel = this.livelogpanel;
|
|
const wasAtBottom = panel.scrollHeight - panel.scrollTop <= panel.clientHeight + 4;
|
|
|
|
if (data.dropped) {
|
|
const marker = document.createElement("div");
|
|
marker.className = "livelog-dropped";
|
|
marker.textContent = "[..]";
|
|
panel.appendChild(marker);
|
|
}
|
|
|
|
for (const entry of data.entries) {
|
|
const line = document.createElement("div");
|
|
line.textContent = entry.text;
|
|
panel.appendChild(line);
|
|
}
|
|
|
|
this.liveLogNextSeq = data.next_seq;
|
|
|
|
// Auto-scroll to bottom only if user was already at the bottom
|
|
if (wasAtBottom) {
|
|
panel.scrollTop = panel.scrollHeight;
|
|
}
|
|
}
|
|
}
|