import bsod from "../lib/bsod"; import html from "../lib/html"; import Log from "./log"; import eventable from "../mixins/eventable"; /** * Name of the "internal" log, both for system messages * and as a fallback logging mechanism. */ const INTERNAL_LOG = "-tickborg-"; /** * The "Gui" for the app. * * This handles the tab-like controls to switch the shown log. * This handles keeping track of whether it should follow scroll or not. * * The whole body is scrolled, always. */ class Gui { constructor() { eventable(this); console.log("Creating log interface...."); // eslint-disable-line this.setFollowing(true); // To use as event listener targets. this.handle_select = this.handle_select.bind(this); this.maybe_scroll = this.maybe_scroll.bind(this); // Registry of Log instances. // ({`attempt_id`: instance}) this.logs = {}; this.$app = window.document.querySelectorAll("#tickborg-logviewer .app")[0]; if (!this.$app) { return bsod("Couldn't hook app."); } // Empties app... this.$app.innerHTML = ""; this.$header = html(`
`)[0]; this.$nav = html(``)[0]; this.$header.appendChild(this.$nav); this.$app.appendChild(this.$header); // Logs DOM instance holder. this.$logs = html(`
`)[0]; this.$app.appendChild(this.$logs); this.addLog(INTERNAL_LOG); // Hooks on scroll window.addEventListener("scroll", () => this.watchScroll()); console.log("... log interface created."); // eslint-disable-line } addLog(name, metadata) { const log = new Log(name, metadata); this.logs[name] = log; this.$logs.appendChild(log.$node); this.$nav.appendChild(log.$tab); if (Object.keys(this.logs).length === 1) { log.select(); } log.addEventListener("select", this.handle_select); log.addEventListener("backlog", this.maybe_scroll) return log; } handle_select(selected) { this.maybe_scroll(); // Uses map as a cheap foreach. Object.values(this.logs).map((l) => { if (selected !== l) { l.unselect(); } return null; }); this.sendEvent("select", selected); } setFollowing(following) { if (following !== this.following) { this.following = following; const body = window.document.body; if (following) { body.classList.add("following"); body.classList.remove("not-following"); } else { body.classList.add("not-following"); body.classList.remove("following"); } } } /** * Marks the window as auto-scrollable or not when scrolling. */ watchScroll() { const body = window.document.documentElement; const scroll_bottom = Math.round(body.scrollTop) + Math.round(window.innerHeight); const total_height = body.scrollHeight; // Some fudging around because of higher and fractional DPI issues. // On 1.5 DPI chrome, it is possible to get the scroll to bottom // not matching with the total height *some* times. this.setFollowing(Math.abs(total_height - scroll_bottom) < 5); } /** * Logs the message `msg`, tagged with tag `tag` in log instance `log`. */ log({msg, tag, log, title}) { // `null` logs to internal log. const used_log = log || INTERNAL_LOG; // Can't find a log? if (!this.logs[used_log]) { // Warn in the console console.warn(`Couldn't find log "${log}"...`); // eslint-disable-line // Makes sure we aren't missing the system log... if (used_log === INTERNAL_LOG) { bsod(`Log "${INTERNAL_LOG}" log. This shouldn't have happened.`); } // Log instead to the internal log. this.log({ msg, tag, log: INTERNAL_LOG, title, }); return; } this.logs[used_log].log(msg, tag, {title}); this.maybe_scroll(); } // Scroll as needed. maybe_scroll() { const body = window.document.documentElement; if (this.following) { body.scrollTop = body.scrollHeight; } } } export default Gui;