Console = (function() { var BORDER = 10; var HEIGHT = 200; function Console(wm, server) { // Create the window and its controls this.Window = wm.AddWindow("Console", 10, 10, 100, 100); this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText"); this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText"); this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", "")); this.UserInput.SetChangeHandler(Bind(ProcessInput, this)); this.Window.ShowNoAnim(); // This accumulates log text as fast as is required this.PageTextBuffer = ""; this.PageTextUpdatePending = false; this.AppTextBuffer = ""; this.AppTextUpdatePending = false; // Setup command history control this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]); this.CommandIndex = 0; this.MaxNbCommands = 200; DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this)); DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this)); // At a much lower frequency this will update the console window window.setInterval(Bind(UpdateHTML, this), 500); // Setup log requests from the server this.Server = server; server.SetConsole(this); server.AddMessageHandler("LOGM", Bind(OnLog, this)); this.Window.SetOnResize(Bind(OnUserResize, this)); } Console.prototype.Log = function(text) { this.PageTextBuffer = LogText(this.PageTextBuffer, text); this.PageTextUpdatePending = true; } Console.prototype.WindowResized = function(width, height) { // Place window this.Window.SetPosition(BORDER, height - BORDER - 200); this.Window.SetSize(width - 2 * BORDER, HEIGHT); ResizeInternals(this); } function OnLog(self, socket, data_view) { var data_view_reader = new DataViewReader(data_view, 4); var text = data_view_reader.GetString(); self.AppTextBuffer = LogText(self.AppTextBuffer, text); self.AppTextUpdatePending = true; } function LogText(existing_text, new_text) { // Filter the text a little to make it safer if (new_text == null) new_text = "NULL"; // Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code // This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators) new_text = Convert.string_to_html_entities(new_text); // Prefix date and end with new line var d = new Date(); new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "
"; // Append to local text buffer and ensure clip the oldest text to ensure a max size existing_text = existing_text + new_text; var max_len = 10 * 1024; var len = existing_text.length; if (len > max_len) existing_text = existing_text.substr(len - max_len, max_len); return existing_text; } function OnUserResize(self, evt) { ResizeInternals(self); } function ResizeInternals(self) { // Place controls var parent_size = self.Window.Size; var mid_w = parent_size[0] / 3; self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30); self.UserInput.SetSize(parent_size[0] - 100, 18); var output_height = self.UserInput.Position[1] - 2 * BORDER; self.PageContainer.SetPosition(BORDER, BORDER); self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height); self.AppContainer.SetPosition(mid_w, BORDER); self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height); } function UpdateHTML(self) { // Reset the current text buffer as html if (self.PageTextUpdatePending) { var page_node = self.PageContainer.Node; page_node.innerHTML = self.PageTextBuffer; page_node.scrollTop = page_node.scrollHeight; self.PageTextUpdatePending = false; } if (self.AppTextUpdatePending) { var app_node = self.AppContainer.Node; app_node.innerHTML = self.AppTextBuffer; app_node.scrollTop = app_node.scrollHeight; self.AppTextUpdatePending = false; } } function ProcessInput(self, node) { // Send the message exactly var msg = node.value; self.Server.Send("CONI" + msg); // Emit to console and clear self.Log("> " + msg); self.UserInput.SetValue(""); // Keep track of recently issued commands, with an upper bound self.CommandHistory.push(msg); var extra_commands = self.CommandHistory.length - self.MaxNbCommands; if (extra_commands > 0) self.CommandHistory.splice(0, extra_commands); // Set command history index to the most recent command self.CommandIndex = self.CommandHistory.length; // Backup to local store LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory); // Keep focus with the edit box return true; } function OnKeyPress(self, evt) { evt = DOM.Event.Get(evt); if (evt.keyCode == Keyboard.Codes.UP) { if (self.CommandHistory.length > 0) { // Cycle backwards through the command history self.CommandIndex--; if (self.CommandIndex < 0) self.CommandIndex = self.CommandHistory.length - 1; var command = self.CommandHistory[self.CommandIndex]; self.UserInput.SetValue(command); } // Stops default behaviour of moving cursor to the beginning DOM.Event.StopDefaultAction(evt); } else if (evt.keyCode == Keyboard.Codes.DOWN) { if (self.CommandHistory.length > 0) { // Cycle fowards through the command history self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length; var command = self.CommandHistory[self.CommandIndex]; self.UserInput.SetValue(command); } // Stops default behaviour of moving cursor to the end DOM.Event.StopDefaultAction(evt); } } function OnFocus(self) { // Reset command index on focus self.CommandIndex = self.CommandHistory.length; } return Console; })();