console-gui-tools
Advanced tools
Comparing version 1.0.16 to 1.1.0
173
index.js
@@ -7,3 +7,3 @@ import { EventEmitter } from "events" | ||
class ConsoleManager extends EventEmitter { | ||
constructor() { | ||
constructor(options) { | ||
super() | ||
@@ -14,9 +14,29 @@ this.Terminal = process.stdout; | ||
ConsoleManager.instance = this | ||
this.addGenericListeners() | ||
this.widgetsCollection = [] | ||
this.stdOut = [] | ||
this.eventListenersContainer = {} | ||
this.guiLogsPage = 0 // 0 = last | ||
this.guiLogsRowsPerPage = 10 | ||
this.homePage = "" | ||
this.layout = new DoubleLayout(true, 0) | ||
this.layoutBorder = true | ||
this.changeLayoutKey = "ctrl+l" | ||
this.logScrollIndex = 0 | ||
this.applicationTitle = "" | ||
if (options) { | ||
if (options.logsPageSize) { | ||
this.guiLogsRowsPerPage = options.rowsPerPage | ||
} | ||
if (options.layoutBorder) { | ||
this.layoutBorder = options.layoutBorder | ||
} | ||
if (options.changeLayoutKey) { | ||
this.changeLayoutKey = options.changeLayoutKey | ||
} | ||
if (options.title) { | ||
this.applicationTitle = options.title | ||
} | ||
} | ||
this.changeLayoutkeys = this.changeLayoutKey.split('+') | ||
this.addGenericListeners() | ||
this.layout = new DoubleLayout(this.layoutBorder, 0) | ||
@@ -48,6 +68,48 @@ // I use readline to manage the keypress event | ||
this.Input.addListener('keypress', (str, key) => { | ||
let change = false | ||
if (this.changeLayoutkeys.length > 1) { | ||
if (this.changeLayoutkeys[0] == 'ctrl') { | ||
if (key.ctrl && key.name === this.changeLayoutkeys[1]) | ||
change = true | ||
} | ||
if (this.changeLayoutkeys[0] == 'meta') { | ||
if (key.alt && key.name === this.changeLayoutkeys[1]) | ||
change = true | ||
} | ||
if (this.changeLayoutkeys[0] == 'shift') { | ||
if (key.shift && key.name === this.changeLayoutkeys[1]) | ||
change = true | ||
} | ||
} else { | ||
if (key.name === this.changeLayoutkeys[0]) | ||
change = true | ||
} | ||
if (change) { | ||
this.layout.changeLayout() | ||
this.refresh() | ||
return | ||
} | ||
if (key.ctrl && key.name === 'c') { | ||
this.emit('exit') | ||
} else { | ||
this.emit("keypressed", key) | ||
if (Object.keys(this.widgetsCollection).length === 0) { | ||
if (this.layout.getSelected() === 1) { | ||
if (key.name === 'down') { | ||
this.logScrollIndex-- | ||
if (this.logScrollIndex < 0) | ||
this.logScrollIndex = 0 | ||
this.updateLogsConsole() | ||
return | ||
} else if (key.name === 'up') { | ||
this.logScrollIndex++ | ||
if (this.logScrollIndex > this.stdOut.length - this.guiLogsRowsPerPage) | ||
this.logScrollIndex = this.stdOut.length - this.guiLogsRowsPerPage | ||
this.updateLogsConsole() | ||
return | ||
} | ||
} | ||
this.emit("keypressed", key) | ||
} | ||
} | ||
@@ -57,8 +119,10 @@ }) | ||
setKeyListener(manageFunction) { | ||
this.Input.addListener('keypress', manageFunction) | ||
setKeyListener(id, manageFunction) { | ||
this.eventListenersContainer[id] = manageFunction | ||
this.Input.addListener('keypress', this.eventListenersContainer[id]) | ||
} | ||
removeKeyListener(manageFunction) { | ||
this.Input.removeListener('keypress', manageFunction) | ||
removeKeyListener(id) { | ||
this.Input.removeListener('keypress', this.eventListenersContainer[id]) | ||
delete this.eventListenersContainer[id] | ||
} | ||
@@ -94,24 +158,42 @@ | ||
log(message) { | ||
this.stdOut.unshift(chalk.white(message)) | ||
this.layout.setPage2(this.stdOut.slice(0, this.guiLogsRowsPerPage).join('\n')) | ||
this.refresh() | ||
this.stdOut.push(chalk.white(message)) | ||
this.updateLogsConsole() | ||
} | ||
error(message) { | ||
this.stdOut.unshift(chalk.red(message)) | ||
this.layout.setPage2(this.stdOut.slice(0, this.guiLogsRowsPerPage).join('\n')) | ||
this.refresh() | ||
this.stdOut.push(chalk.red(message)) | ||
this.updateLogsConsole() | ||
} | ||
warn(message) { | ||
this.stdOut.unshift(chalk.yellow(message)) | ||
this.layout.setPage2(this.stdOut.slice(0, this.guiLogsRowsPerPage).join('\n')) | ||
this.refresh() | ||
this.stdOut.push(chalk.yellow(message)) | ||
this.updateLogsConsole() | ||
} | ||
info(message) { | ||
this.stdOut.unshift(chalk.blue(message)) | ||
this.layout.setPage2(this.stdOut.slice(0, this.guiLogsRowsPerPage).join('\n')) | ||
this.refresh() | ||
this.stdOut.push(chalk.blue(message)) | ||
this.updateLogsConsole() | ||
} | ||
updateLogsConsole() { | ||
if (this.stdOut.length > this.guiLogsRowsPerPage) { | ||
this.layout.setPage2(this.stdOut.slice(this.stdOut.length - this.logScrollIndex - this.guiLogsRowsPerPage, this.stdOut.length - this.logScrollIndex).join('\n')) | ||
this.refresh() | ||
} else { | ||
this.layout.setPage2(this.stdOut.join('\n')) | ||
this.refresh() | ||
} | ||
} | ||
truncate(str, n, useWordBoundary) { | ||
if (str.length <= n) { return str; } | ||
const subString = str.substr(0, n - 1); // the original check | ||
return (useWordBoundary ? | ||
subString.substr(0, subString.lastIndexOf(" ")) : | ||
subString) + "…"; | ||
} | ||
removeColors(str) { | ||
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '') | ||
} | ||
} | ||
@@ -126,2 +208,4 @@ | ||
this.page2 = "" | ||
this.logPageTitle = "─LOGS" | ||
this.applicationTitle = this.CM.applicationTitle | ||
} | ||
@@ -133,12 +217,29 @@ | ||
setSelected(selected) { this.selected = selected } | ||
getSelected() { | ||
return this.selected | ||
} | ||
changeLayout() { | ||
if (this.selected == 0) { | ||
this.selected = 1 | ||
} else { | ||
this.selected = 0 | ||
} | ||
} | ||
drawLine(line, index) { | ||
const unformattedLine = this.CM.removeColors(line) | ||
const _line = unformattedLine.length > this.CM.Terminal.columns - 2 ? this.CM.truncate(unformattedLine, this.CM.Terminal.columns - 10, false) : line | ||
const renderedLine = `${this.selected === index ? chalk.cyan("│") : chalk.white("│")}${_line}${" ".repeat(this.CM.Terminal.columns - this.CM.removeColors(_line).length - 2)}${this.selected === index ? chalk.cyan("│") : chalk.white("│")}` | ||
this.CM.Terminal.write(`${renderedLine}\n`) | ||
} | ||
draw() { | ||
if (this.border) { // Draw pages with borders | ||
this.CM.Terminal.write(this.selected === 0 ? chalk.cyan(`┌${"─".repeat(this.CM.Terminal.columns - 2)}┐\n`) : chalk.white(`┌${"─".repeat(this.CM.Terminal.columns - 2)}┐\n`)) | ||
this.CM.Terminal.write(this.selected === 0 ? chalk.cyan(`┌─${this.applicationTitle}${"─".repeat(this.CM.Terminal.columns - this.applicationTitle.length - 3)}┐\n`) : chalk.white(`┌─${this.applicationTitle}${"─".repeat(this.CM.Terminal.columns - this.applicationTitle.length - 3)}┐\n`)) | ||
this.page1.split("\n").forEach(line => { | ||
this.CM.Terminal.write(`${this.selected === 0 ? chalk.cyan("│") : chalk.white("│")}${line}${" ".repeat(this.CM.Terminal.columns - line.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "").length - 2)}${this.selected === 0 ? chalk.cyan("│") : chalk.white("│")}\n`) | ||
this.drawLine(line, 0) | ||
}) | ||
this.CM.Terminal.write(this.selected === 1 ? chalk.cyan(`├${"─".repeat(this.CM.Terminal.columns - 2)}┤\n`) : chalk.white(`├${"─".repeat(this.CM.Terminal.columns - 2)}┤\n`)) | ||
this.CM.Terminal.write(chalk.cyan(`├${this.logPageTitle}${"─".repeat(this.CM.Terminal.columns - this.logPageTitle.length - 2)}┤\n`)) | ||
this.page2.split("\n").forEach(line => { | ||
this.CM.Terminal.write(`${this.selected === 1 ? chalk.cyan("│") : chalk.white("│")}${line}${" ".repeat(this.CM.Terminal.columns - line.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "").length - 2)}${this.selected === 1 ? chalk.cyan("│") : chalk.white("│")}\n`) | ||
this.drawLine(line, 1) | ||
}) | ||
@@ -167,2 +268,8 @@ this.CM.Terminal.write(this.selected === 1 ? chalk.cyan(`└${"─".repeat(this.CM.Terminal.columns - 2)}┘\n`) : chalk.white(`└${"─".repeat(this.CM.Terminal.columns - 2)}┘\n`)) | ||
this.visible = visible | ||
if (this.CM.widgetsCollection[this.id]) { | ||
this.CM.unRegisterWidget(this) | ||
const message = `OptionPopup ${this.id} already exists.` | ||
this.CM.error(message) | ||
throw new Error(message) | ||
} | ||
this.CM.registerWiget(this) | ||
@@ -243,3 +350,3 @@ } | ||
// Add a command input listener to change mode | ||
this.CM.setKeyListener(this.keyListner.bind(this)) | ||
this.CM.setKeyListener(this.id, this.keyListner.bind(this)) | ||
return this | ||
@@ -250,3 +357,3 @@ } | ||
// Add a command input listener to change mode | ||
this.CM.removeKeyListener(this.keyListner.bind(this)) | ||
this.CM.removeKeyListener(this.id) | ||
return this | ||
@@ -298,2 +405,8 @@ } | ||
this.visible = visible | ||
if (this.CM.widgetsCollection[this.id]) { | ||
this.CM.unRegisterWidget(this) | ||
const message = `InputPopup ${this.id} already exists.` | ||
this.CM.error(message) | ||
throw new Error(message) | ||
} | ||
this.CM.registerWiget(this) | ||
@@ -428,5 +541,5 @@ } | ||
if (this.numeric) { | ||
this.CM.setKeyListener(this.keyListnerNumeric.bind(this)) | ||
this.CM.setKeyListener(this.id, this.keyListnerNumeric.bind(this)) | ||
} else { | ||
this.CM.setKeyListener(this.keyListnerText.bind(this)) | ||
this.CM.setKeyListener(this.id) | ||
} | ||
@@ -439,5 +552,5 @@ return this | ||
if (this.numeric) { | ||
this.CM.removeKeyListener(this.keyListnerNumeric.bind(this)) | ||
this.CM.removeKeyListener(this.id, this.keyListnerNumeric.bind(this)) | ||
} else { | ||
this.CM.removeKeyListener(this.keyListnerText.bind(this)) | ||
this.CM.removeKeyListener(this.id) | ||
} | ||
@@ -444,0 +557,0 @@ return this |
{ | ||
"name": "console-gui-tools", | ||
"version": "1.0.16", | ||
"version": "1.1.0", | ||
"description": "A simple library to draw option menu or other popup inputs and layout on Node.js console.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -22,2 +22,17 @@ # console-gui-tools | ||
## Options | ||
The library has a few options that can be set in the constructor. | ||
### options.title | ||
The title of the application. It will be displayed in the top of the screen. | ||
### options.logsPageSize | ||
The number of lines that will be displayed in the logs page. | ||
### options.changeLayoutKey | ||
The key that will be used to change the layout. (To switch between the two pages, logs and main page) | ||
### options.layoutBorder | ||
To enable the border of the layout and the title. | ||
Example of usage: | ||
@@ -27,3 +42,7 @@ ```js | ||
import { ConsoleManager, OptionPopup, InputPopup } from '../index.js' | ||
const GUI = new ConsoleManager() | ||
const GUI = new ConsoleManager({ | ||
title: 'TCP Simulator', // Title of the console | ||
logsPageSize: 12, // Number of lines to show in logs page | ||
changeLayoutKey: 'ctrl+l', // Change layout with ctrl+l to switch to the logs page | ||
}) | ||
@@ -74,5 +93,2 @@ // Creating a main page updater: | ||
GUI.on("keypressed", (key) => { | ||
if (key.ctrl && key.name === 'c') { | ||
closeApp() | ||
} | ||
switch (key.name) { | ||
@@ -88,3 +104,3 @@ case 'space': | ||
case 'm': | ||
new OptionPopup("popupSelectMode", "Select simulation mode", modeList, mode).show().once("confirm", (_mode) => { | ||
new OptionPopup("popupSelectMode", "Select simulation mode", modeList, mode).show().on("confirm", (_mode) => { | ||
mode = _mode | ||
@@ -96,3 +112,3 @@ GUI.warn(`NEW MODE: ${mode}`) | ||
case 's': | ||
new OptionPopup("popupSelectPeriod", "Select simulation period", periodList, period).show().once("confirm", (_period) => { | ||
new OptionPopup("popupSelectPeriod", "Select simulation period", periodList, period).show().on("confirm", (_period) => { | ||
period = _period | ||
@@ -104,3 +120,3 @@ GUI.warn(`NEW PERIOD: ${period}`) | ||
case 'h': | ||
new InputPopup("popupTypeMax", "Type max value", max, true).show().once("confirm", (_max) => { | ||
new InputPopup("popupTypeMax", "Type max value", max, true).show().on("confirm", (_max) => { | ||
max = _max | ||
@@ -112,3 +128,3 @@ GUI.warn(`NEW MAX VALUE: ${max}`) | ||
case 'l': | ||
new InputPopup("popupTypeMin", "Type min value", min, true).show().once("confirm", (_min) => { | ||
new InputPopup("popupTypeMin", "Type min value", min, true).show().on("confirm", (_min) => { | ||
min = _min | ||
@@ -125,3 +141,2 @@ GUI.warn(`NEW MIN VALUE: ${min}`) | ||
} | ||
drawGui() | ||
}) | ||
@@ -137,3 +152,3 @@ | ||
```js | ||
new OptionPopup("popupSelectPeriod", "Select simulation period", periodList, period).show().once("confirm", (_period) => { | ||
new OptionPopup("popupSelectPeriod", "Select simulation period", periodList, period).show().on("confirm", (_period) => { | ||
period = _period | ||
@@ -144,3 +159,10 @@ GUI.warn(`NEW PERIOD: ${period}`) | ||
``` | ||
The response is triggered via EventEmitter using "once" (not "on") | ||
### Class OptionPopup: | ||
constructor(id, title, options, selected) | ||
- id: string | ||
- title: string | ||
- options: Array<string | number> | ||
- selected: string | number | ||
The response is triggered via EventEmitter using "on" | ||
The result is this: | ||
@@ -154,4 +176,20 @@ | ||
You can also use it to set a numeric threshold: | ||
## To create an input popup (numeric or string) | ||
```js | ||
new InputPopup("popupTypeMax", "Type max value", max, true).show().on("confirm", (_max) => { | ||
max = _max | ||
GUI.warn(`NEW MAX VALUE: ${max}`) | ||
drawGui() | ||
}) | ||
``` | ||
### Class InputPopup: | ||
constructor(id, title, value, isNumeric) | ||
- id: string | ||
- title: string | ||
- value: string | number | ||
- isNumeric: boolean | ||
You can use it for example to set a numeric threshold: | ||
![image](https://user-images.githubusercontent.com/14907987/161997181-07993f9a-6ad2-4c77-a834-2bbc4ed53a1e.png) | ||
@@ -163,2 +201,4 @@ | ||
All class of components will be destroyed when the popup is closed. The event listeners are removed from the store. Then the garbage collector will clean the memory. | ||
## Console.log and other logging tools | ||
@@ -174,3 +214,7 @@ To log you have to use the following functions: | ||
And they written to the bottom of the page. | ||
You can switch to the log view by pressing the "changeLayoutKey" key or combination: | ||
The maximum number of lines is set to 10 by default but you can change it by setting the option "logsPageSize". | ||
When the logs exceed the limit, you can scroll up and down with up and down arrows (if you are in the log view). | ||
This library is in development now. New componets will come asap. |
Sorry, the diff of this file is not supported yet
36474
703
208