objtalk 0.3.0

a lightweight realtime database for IoT projects
Documentation
import { template } from "./utils.js";

const MAX_LOG_MESSAGES = 100;

class UuidColorer {
	constructor() {
		this.colors = ["#EB483F", "#90B136", "#E69B3D", "#5EA4D5", "#E1787B", "#6EBC9C", "#ED6F6B", "#C1E357", "#F7CB62", "#8DD5FB", "#F2A7AC", "#9CEECD"];
		this.assignments = [];
	}
	
	getColor(id) {
		let assignment = this.assignments.find(a => a.id == id);
		if (assignment) {
			return assignment.color;
		}
		
		if (this.colors.length > 0) {
			let color = this.colors.shift();
			this.assignments.push({ id, color })
			return color;
		}
		
		let { color } = this.assignments.shift();
		this.assignments.push({ id, color });
		return color;
	}
}

function shortId(id) {
	return id.slice(0, 7);
}

function objectUrl(name) {
	return "#objects/" + name;
}

function renderLogMessage(elem, message, colorer) {
	elem.querySelector(".log-message-client").innerText = shortId(message.client);
	elem.querySelector(".log-message-client").style.color = colorer.getColor(message.client);
	
	switch (message.type) {
		case "get":
			elem.querySelector(".log-message-pattern").innerText = message.pattern;
			break;
		case "query":
			elem.querySelector(".log-message-pattern").innerText = message.pattern;
			elem.querySelector(".log-message-query").innerText = shortId(message.query);
			elem.querySelector(".log-message-provide-rpc").innerText = "(provide rpc: "+!!message.provideRpc+")";
			break;
		case "unsubscribe":
			elem.querySelector(".log-message-query").innerText = shortId(message.query);
			break;
		case "set":
		case "patch":
			elem.querySelector(".log-message-object").innerText = message.object;
			elem.querySelector(".log-message-object").href = objectUrl(message.object);
			elem.querySelector(".log-message-value").innerText = JSON.stringify(message.value);
			break;
		case "remove":
			elem.querySelector(".log-message-object").innerText = message.object;
			elem.querySelector(".log-message-object").href = objectUrl(message.object);
			break;
		case "emit":
			elem.querySelector(".log-message-object").innerText = message.object;
			elem.querySelector(".log-message-object").href = objectUrl(message.object);
			elem.querySelector(".log-message-event").innerText = message.event;
			elem.querySelector(".log-message-data").innerText = JSON.stringify(message.data);
			break;
		case "invoke":
			elem.querySelector(".log-message-object").innerText = message.object;
			elem.querySelector(".log-message-object").href = objectUrl(message.object);
			elem.querySelector(".log-message-invocation").innerText = shortId(message.invocationId);
			elem.querySelector(".log-message-method").innerText = message.method;
			elem.querySelector(".log-message-args").innerText = JSON.stringify(message.args);
			break;
		case "invokeResult":
			elem.querySelector(".log-message-invocation").innerText = shortId(message.invocationId);
			elem.querySelector(".log-message-result").innerText = JSON.stringify(message.result);
			break;
	}
}

export default class LogPage {
	constructor(conn, system) {
		this.container = document.getElementById("log-messages");
		this.templates = {
			"clientConnect": template("template-log-message-client-connect"),
			"clientDisconnect": template("template-log-message-client-disconnect"),
			"get": template("template-log-message-get"),
			"query": template("template-log-message-query"),
			"unsubscribe": template("template-log-message-unsubscribe"),
			"set": template("template-log-message-set"),
			"patch": template("template-log-message-patch"),
			"remove": template("template-log-message-remove"),
			"emit": template("template-log-message-emit"),
			"invoke": template("template-log-message-invoke"),
			"invokeResult": template("template-log-message-invoke-result"),
		}
		this.elements = [];
		this.waitingMessages = [];
		this.enabled = true;
		this.startStopButtonIcon = document.querySelector("#log-start-stop i");
		this.startStopButtonText = document.querySelector("#log-start-stop span");
		this.colorer = new UuidColorer();
		
		system.addEventListener("event", ({ event, data }) => {
			if (event == "log") {
				this.addLogMessage(data);
			}
		});
		
		document.getElementById("log-start-stop").addEventListener("click", this.toggleStartStop.bind(this));
	}
	
	addLogMessage(message) {
		if (this.enabled) {
			let elem = this.templates[message.type].cloneNode(true);
			renderLogMessage(elem, message, this.colorer);
			
			this.elements.push(elem);
			this.container.append(elem);
			
			while (this.elements.length > MAX_LOG_MESSAGES) {
				this.elements.shift().remove();
			}
			
			this.scrollToBottom();
		} else {
			this.waitingMessages.push(message);
			this.waitingMessages.splice(0, this.waitingMessages.length - MAX_LOG_MESSAGES);
		}
	}
	
	start() {
		if (this.enabled) return;
		
		let removeCount = this.elements.length + this.waitingMessages.length - MAX_LOG_MESSAGES;
		for (let i = 0; i < removeCount; i++) {
			this.elements.shift().remove();
		}
		
		for (let message of this.waitingMessages) {
			let elem = this.templates[message.type].cloneNode(true);
			renderLogMessage(elem, message, this.colorer);
			
			this.elements.push(elem);
			this.container.append(elem);
		}
		
		this.waitingMessages = [];
		this.enabled = true;
		
		this.scrollToBottom();
		
		this.startStopButtonIcon.classList.remove("play");
		this.startStopButtonIcon.classList.add("pause");
		this.startStopButtonText.innerText = "stop";
	}
	
	stop() {
		this.enabled = false;
		
		this.startStopButtonIcon.classList.remove("pause");
		this.startStopButtonIcon.classList.add("play");
		this.startStopButtonText.innerText = "start";
	}
	
	toggleStartStop() {
		if (this.enabled) {
			this.stop();
		} else {
			this.start();
		}
	}
	
	scrollToBottom() {
		this.container.scrollTop = this.container.scrollHeight;
	}
}