console-gui-tools
Version:
A simple library to draw option menu, text popup or other widgets and layout on a Node.js console.
4 lines • 449 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/ConsoleGui.ts", "../../src/components/Utils.ts", "../../src/components/PageBuilder.ts", "../../src/components/InPageWidgetBuilder.ts", "../../src/components/Screen.ts", "../../node_modules/chalk/source/vendor/ansi-styles/index.js", "../../node_modules/chalk/source/vendor/supports-color/index.js", "../../node_modules/chalk/source/utilities.js", "../../node_modules/chalk/source/index.js", "../../src/components/widgets/CustomPopup.ts", "../../src/components/widgets/ButtonPopup.ts", "../../src/components/widgets/ConfirmPopup.ts", "../../src/components/widgets/FileSelectorPopup.ts", "../../src/components/widgets/InputPopup.ts", "../../src/components/widgets/OptionPopup.ts", "../../src/components/widgets/Control.ts", "../../src/components/widgets/Box.ts", "../../src/components/widgets/Button.ts", "../../src/components/widgets/ProgressBar.ts", "../../src/components/layout/DoubleLayout.ts", "../../src/components/layout/QuadLayout.ts", "../../src/components/layout/SingleLayout.ts", "../../src/components/layout/LayoutManager.ts", "../../src/components/MouseManager.ts"],
"sourcesContent": ["import { EventEmitter } from \"events\"\nimport readline from \"readline\"\nimport PageBuilder from \"./components/PageBuilder.js\"\nimport InPageWidgetBuilder from \"./components/InPageWidgetBuilder.js\"\nimport Screen from \"./components/Screen.js\"\nimport { CustomPopup, PopupConfig } from \"./components/widgets/CustomPopup.js\"\nimport { ButtonPopup, ButtonPopupConfig } from \"./components/widgets/ButtonPopup.js\"\nimport { ConfirmPopup, ConfirmPopupConfig } from \"./components/widgets/ConfirmPopup.js\"\nimport { FileSelectorPopup, FileSelectorPopupConfig } from \"./components/widgets/FileSelectorPopup.js\"\nimport { InputPopup, InputPopupConfig } from \"./components/widgets/InputPopup.js\"\nimport { OptionPopup, OptionPopupConfig } from \"./components/widgets/OptionPopup.js\"\nimport { Control, ControlConfig } from \"./components/widgets/Control.js\"\nimport { Box, BoxConfig, BoxStyle } from \"./components/widgets/Box.js\"\nimport { Button, ButtonConfig, ButtonKey, ButtonStyle } from \"./components/widgets/Button.js\"\nimport { Progress, ProgressConfig, Orientation, ProgressStyle } from \"./components/widgets/ProgressBar.js\"\nimport LayoutManager, { LayoutOptions } from \"./components/layout/LayoutManager.js\"\nimport { MouseEvent, MouseManager, MouseEventArgs, RelativeMouseEvent } from \"./components/MouseManager.js\"\nimport { PhisicalValues, StyledElement, SimplifiedStyledElement, StyleObject } from \"./components/Utils.js\"\nimport { EOL } from \"node:os\"\n\n\n/**\n * @description This type is used to define the parameters of the KeyListener event (keypress).\n * @typedef {Object} KeyListenerArgs\n * @prop {string} name - The name of the key pressed.\n * @prop {boolean} ctrl - If the ctrl key is pressed.\n * @prop {boolean} shift - If the shift key is pressed.\n * @prop {boolean} alt - If the alt key is pressed.\n * @prop {boolean} meta - If the meta key is pressed.\n * @prop {boolean} sequence - If the sequence of keys is pressed.\n *\n * @export\n * @interface KeyListenerArgs\n */\n// @type definition\nexport interface KeyListenerArgs {\n name: string;\n sequence: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n code: string;\n}\n\n/**\n * @description This type is used to define the ConsoleGui options.\n * @typedef {Object} ConsoleGuiOptions\n * @prop {string} [title] - The title of the ConsoleGui.\n * @prop {0 | 1 | 2 | 3 | \"popup\"} [logLocation] - The location of the logs.\n * @prop {string} [showLogKey] - The key to show the log.\n * @prop {number} [logPageSize] - The size of the log page.\n * @prop {LayoutOptions} [layoutOptions] - The options of the layout.\n * @prop {boolean} [enableMouse] - If the mouse should be enabled.\n * @prop {boolean} [overrideConsole = true] - If the console.log|warn|error|info should be overridden.\n * @prop {string} [focusKey = \"tab\"] - The key to focus the next widget.\n * \n * @export\n * @interface ConsoleGuiOptions\n */\n// @type definition\nexport interface ConsoleGuiOptions {\n logLocation?: 0 | 1 | 2 | 3 | \"popup\";\n showLogKey?: string;\n logPageSize?: number;\n layoutOptions?: LayoutOptions;\n title?: string;\n enableMouse?: boolean; // enable the mouse support (default: true) - Only for Linux and other Mouse Tracking terminals\n overrideConsole?: boolean; // override the console.log, console.warn, console.error, console.info, console.debug (default: true)\n focusKey?: string; // the key to focus the next widget (default: tab)\n}\n\n/**\n * @class ConsoleManager\n * @extends EventEmitter\n * @description This class is used to manage the console GUI and all the widgets.\n * This is a singleton class, so you can use it like this: const CM = new ConsoleManager()\n * Emits the following events: \n * - \"keypressed\" to propagate the key pressed event to the application\n * - \"layoutratiochanged\" when the layout ratio is changed\n * - \"exit\" when the user wants to exit the application\n * @param {object} options - The options of the ConsoleManager.\n * @example const CM = new ConsoleManager({ logPageSize: 10, layoutBorder: true, changeLayoutKey: 'ctrl+l', title: 'Console Application' })\n */\nclass ConsoleManager extends EventEmitter {\n Terminal: NodeJS.WriteStream & { fd: 1 }\n Input: NodeJS.ReadStream & { fd: 0 }\n static instance: ConsoleManager\n Screen!: Screen\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n popupCollection: { [key: string]: any } = {}\n controlsCollection: { [key: string]: Control } = {}\n eventListenersContainer: { [key: string]: (_str: string, key: KeyListenerArgs) => void } | { [key: string]: (key: MouseEvent) => void } = {}\n logLocation!: 0 | 1 | 2 | 3 | \"popup\"\n logPageSize!: number\n logPageTitle!: string\n pages!: PageBuilder[]\n layoutOptions!: LayoutOptions\n layout!: LayoutManager\n changeLayoutKey!: string\n maxListeners = 128\n private changeLayoutkeys!: string[]\n applicationTitle!: string\n private showLogKey!: string\n stdOut!: PageBuilder\n mouse!: MouseManager\n private parsingMouseFrame = false // used to avoid the mouse event to be triggered multiple times\n private previousFocusedWidgetsId: string[] = []\n private focusKey!: string\n\n public constructor(options: ConsoleGuiOptions | undefined = undefined) {\n super()\n this.Terminal = process.stdout\n this.Input = process.stdin\n this.Input.setMaxListeners(this.maxListeners)\n if (!ConsoleManager.instance) {\n ConsoleManager.instance = this\n\n /** @const {Screen} Screen - The screen instance */\n this.Screen = new Screen(this.Terminal)\n this.Screen.on(\"resize\", () => {\n this.emit(\"resize\")\n })\n this.Screen.on(\"error\", (err) => {\n this.error(err)\n })\n\n this.mouse = new MouseManager(this.Terminal, this.Input)\n this.mouse.setMaxListeners(this.maxListeners)\n this.popupCollection = {}\n this.controlsCollection = {}\n this.eventListenersContainer = {}\n\n /** @const {number | 'popup'} logLocation - Choose where the logs are displayed: number (0,1) - to pot them on one of the two layouts, string (\"popup\") - to put them on a CustomPopup that can be displayed on the window. */\n this.logLocation = 1\n this.logPageTitle = \"LOGS\"\n\n this.layoutOptions = {\n showTitle: true,\n boxed: true,\n boxColor: \"cyan\",\n boxStyle: \"bold\",\n changeFocusKey: \"ctrl+l\",\n type: \"double\",\n direction: \"vertical\",\n }\n \n if (options) {\n if (options.logLocation !== undefined) {\n if (typeof options.logLocation === \"number\") {\n this.logLocation = options.logLocation > 0 ? options.logLocation : 0\n } else {\n if (options.logLocation === \"popup\") {\n this.logLocation = \"popup\"\n this.showLogKey = options.showLogKey || \"o\"\n } else {\n this.logLocation = 1\n }\n }\n }\n if (options.enableMouse) {\n this.mouse.enableMouse()\n }\n if (options.overrideConsole) {\n if (options.overrideConsole === true) {\n this.overrideConsole()\n }\n } else {\n this.overrideConsole()\n }\n if (typeof options.layoutOptions !== \"undefined\") {\n this.setLayoutOptions(options.layoutOptions, false)\n }\n }\n \n this.logPageSize = options && options.logPageSize || 10\n this.applicationTitle = options && options.title || \"\"\n this.focusKey = options && options.focusKey || \"tab\"\n \n /** @const {PageBuilder} stdOut - The logs page */\n this.stdOut = new PageBuilder()\n this.stdOut.setRowsPerPage(this.logPageSize)\n\n this.updateLayout()\n this.addGenericListeners()\n\n // Create a Interface from STDIN so this interface can be close later otherwise the Process hangs\n this.inputInterface = readline.createInterface ({ input: process.stdin, escapeCodeTimeout: 50 })\n // I use readline to manage the keypress event\n readline.emitKeypressEvents(this.Input, this.inputInterface)\n this.Input.setRawMode(true) // With this I only get the key value\n }\n return ConsoleManager.instance\n }\n private inputInterface : readline.Interface | undefined\n\n /**\n * @description This method is used to change the layout options.\n * if update is true, the layout will be updated.\n *\n * @param {LayoutOptions} options\n * @param {boolean} [update=true]\n * @memberof ConsoleManager\n * \n * @example CM.setLayoutOptions({ showTitle: true, boxed: true, boxColor: 'cyan', boxStyle: 'bold', changeFocusKey: 'ctrl+l', type: 'double', direction: 'vertical' })\n * @example CM.setLayoutOptions({ ...CM.getLayoutOptions(), type: \"quad\" })\n */\n public setLayoutOptions(options: LayoutOptions, update = true): void {\n this.layoutOptions = options\n if (update) this.updateLayout()\n }\n\n /** \n * @description This method is used to update the layout\n * @memberof ConsoleManager\n */\n public updateLayout(): void {\n /** @const {string} changeLayoutKey - The key or combination to switch the selected page */\n this.changeLayoutKey = this.layoutOptions.changeFocusKey || \"\"\n this.changeLayoutkeys = this.changeLayoutKey ? this.changeLayoutKey.split(\"+\") : []\n /** @const {Array<PageBuilder>} homePage - The main application */\n switch (this.layoutOptions.type) {\n case \"single\":\n this.pages = [new PageBuilder()]\n break\n case \"double\":\n this.pages = [new PageBuilder(), new PageBuilder()]\n break\n case \"triple\":\n this.pages = [new PageBuilder(), new PageBuilder(), new PageBuilder()]\n break\n case \"quad\":\n this.pages = [new PageBuilder(), new PageBuilder(), new PageBuilder(), new PageBuilder()]\n break\n default:\n this.pages = [new PageBuilder(), new PageBuilder()]\n break\n }\n\n /** @const {LayoutManager} layout - The layout instance */\n this.layout = new LayoutManager(this.pages, this.layoutOptions)\n\n if (this.logLocation === \"popup\") {\n this.setPages(this.pages)\n } else if (typeof this.logLocation === \"number\") {\n this.setPage(this.stdOut, this.logLocation)\n this.pages.forEach((page, index) => {\n if (index !== this.logLocation) {\n this.setPage(page, index)\n }\n })\n this.layout.setTitle(this.logPageTitle, this.logLocation)\n } else {\n this.setPages([...this.pages, this.stdOut])\n this.layout.setTitle(this.applicationTitle, 0)\n this.layout.setTitle(this.logPageTitle, 1)\n }\n }\n\n /**\n * @description This method is used to get the layout options.\n * @returns {LayoutOptions} The layout options.\n * @memberof ConsoleManager\n * @example CM.getLayoutOptions()\n * @example CM.getLayoutOptions().boxed\n */\n public getLayoutOptions(): LayoutOptions {\n return this.layoutOptions\n }\n\n /**\n * @description This method is used to get the log page size.\n * @returns {number} The log page size.\n * @memberof ConsoleManager\n * @example CM.getLogPageSize()\n */\n public getLogPageSize(): number {\n return this.logPageSize\n }\n\n /**\n * @description This method is used to set the log page size.\n * @param {number} size - The new log page size.\n * @returns {void}\n * @example CM.setLogPageSize(10)\n */\n public setLogPageSize(size: number): void {\n this.logPageSize = size\n }\n\n /**\n * @description This method is used to remove focus from other widgets.\n *\n * @param {string} widget\n * @memberof ConsoleManager\n */\n public unfocusOtherWidgets(widget: string): void {\n Object.keys(this.controlsCollection).forEach((key: string) => {\n if (key !== widget) {\n this.controlsCollection[key].unfocus()\n this.previousFocusedWidgetsId.push(key)\n }\n })\n Object.keys(this.popupCollection).forEach((key: string) => {\n if (key !== widget) {\n if (this.popupCollection[key].unfocus) this.popupCollection[key].unfocus()\n this.previousFocusedWidgetsId.push(key)\n }\n })\n }\n\n public restoreFocusInWidgets(): void {\n this.previousFocusedWidgetsId.forEach((key: string) => {\n if (this.controlsCollection[key]) {\n this.controlsCollection[key].focus()\n } else if (this.popupCollection[key]) {\n if (this.popupCollection[key].focus) this.popupCollection[key].focus()\n }\n })\n this.previousFocusedWidgetsId = []\n }\n\n /**\n * @description This function is used to make the ConsoleManager handle the key events when no widgets are showed.\n * Inside this function are defined all the keys that can be pressed and the actions to do when they are pressed.\n * @memberof ConsoleManager\n */\n private addGenericListeners(): void {\n this.Input.addListener(\"keypress\", (_str: string, key: KeyListenerArgs): void => {\n //this.log(`Key pressed: ${JSON.stringify(key)}`)\n const checkResult = this.mouse.isMouseFrame(key, this.parsingMouseFrame)\n if (checkResult === 1) {\n this.parsingMouseFrame = true\n return\n } else if (checkResult === -1) {\n this.parsingMouseFrame = false\n return\n } // Continue only if the result is 0\n let change = false\n if (this.changeLayoutkeys.length > 1) {\n if (this.changeLayoutkeys[0] == \"ctrl\") {\n if (key.ctrl && key.name === this.changeLayoutkeys[1])\n change = true\n }\n if (this.changeLayoutkeys[0] == \"meta\") {\n if (key.alt && key.name === this.changeLayoutkeys[1])\n change = true\n }\n if (this.changeLayoutkeys[0] == \"shift\") {\n if (key.shift && key.name === this.changeLayoutkeys[1])\n change = true\n }\n } else {\n if (key.name === this.changeLayoutkeys[0])\n change = true\n }\n\n if (this.focusKey && key.name === this.focusKey) {\n // Find current focused widget\n let focusedWidget = \"\"\n Object.keys(this.controlsCollection).forEach((key: string) => {\n if (this.controlsCollection[key].isFocused()) {\n focusedWidget = key\n }\n })\n // If there is a focused widget, unfocus it\n if (focusedWidget !== \"\") {\n this.controlsCollection[focusedWidget].unfocus()\n }\n // Focus the next widget\n let found = false\n Object.keys(this.controlsCollection).forEach((key: string) => {\n if (found) {\n this.controlsCollection[key].focus()\n found = false\n }\n if (key === focusedWidget) {\n found = true\n }\n })\n if (found) {\n this.controlsCollection[Object.keys(this.controlsCollection)[0]].focus()\n }\n }\n\n if (this.showLogKey && key.name === this.showLogKey) {\n this.showLogPopup()\n }\n\n if (change) {\n this.layout.changeLayout()\n this.refresh()\n return\n }\n\n if (key.ctrl && key.name === \"c\") {\n this.inputInterface?.close()\n this.emit(\"exit\")\n } else {\n if (Object.keys(this.popupCollection).length === 0) {\n if (key.name === \"down\") {\n this.layout.pages[this.layout.getSelected()].decreaseScrollIndex()\n this.refresh()\n return\n } else if (key.name === \"up\") {\n this.layout.pages[this.layout.getSelected()].increaseScrollIndex()\n this.refresh()\n return\n }\n if (this.layoutOptions.type !== \"single\") {\n if (key.name === \"left\") {\n this.emit(\"layoutratiochanged\", key)\n this.layout.decreaseRatio(0.01)\n this.refresh()\n return\n } else if (key.name === \"right\") {\n this.emit(\"layoutratiochanged\", key)\n this.layout.increaseRatio(0.01)\n this.refresh()\n return\n }\n }\n this.emit(\"keypressed\", key)\n }\n }\n })\n\n /** @eventlistener this is used to set the focus over the top viewed widget if the mouse is over it */\n this.mouse.addListener(\"mouseevent\", (e: MouseEvent) => {\n if (e.name === \"MOUSE_LEFT_BUTTON_PRESSED\") {\n // Check if the mouse is over a widget. if there are more widgets, the one on top is selected.\n Object.keys(this.controlsCollection).forEach((key: string) => {\n if (this.controlsCollection[key].absoluteValues.x <= e.data.x && this.controlsCollection[key].absoluteValues.x + this.controlsCollection[key].absoluteValues.width >= e.data.x) {\n if (this.controlsCollection[key].absoluteValues.y <= e.data.y && this.controlsCollection[key].absoluteValues.y + this.controlsCollection[key].absoluteValues.height >= e.data.y) {\n if (!this.controlsCollection[key].isFocused()) {\n this.controlsCollection[key].focus()\n }\n }\n }\n })\n }\n })\n }\n\n /**\n * @description This function is used to set a key listener for a specific widget. The event listener is stored in the eventListenersContainer object.\n * @param {string} id - The id of the widget.\n * @param {function} manageFunction - The function to call when the key is pressed.\n * @memberof ConsoleManager\n * @example CM.setKeyListener('inputPopup', popup.keyListener)\n */\n public setKeyListener(id: string, manageFunction: (_str: string, key: KeyListenerArgs) => void): void {\n if (this.eventListenersContainer[id] !== undefined) {\n this.removeKeyListener(id)\n }\n this.eventListenersContainer[id] = manageFunction\n this.Input.addListener(\"keypress\", this.eventListenersContainer[id])\n }\n\n /**\n * @description This function is used to remove a key listener for a specific widget. The event listener is removed from the eventListenersContainer object.\n * @param {string} id - The id of the widget.\n * @memberof ConsoleManager\n * @example CM.removeKeyListener('inputPopup')\n */\n public removeKeyListener(id: string): void {\n this.Input.removeListener(\"keypress\", this.eventListenersContainer[id])\n delete this.eventListenersContainer[id]\n }\n\n /**\n * @description This function is used to set a mouse listener for a specific widget. The event listener is stored in the eventListenersContainer object.\n * @param {string} id - The id of the widget.\n * @param {function} manageFunction - The function to call when the key is pressed.\n * @memberof ConsoleManager\n * @example CM.setMouseListener('inputPopup', popup.mouseListener)\n */\n public setMouseListener(id: string, manageFunction: (key: MouseEvent) => void): void {\n if (this.eventListenersContainer[id] !== undefined) {\n this.removeMouseListener(id)\n }\n this.eventListenersContainer[id] = manageFunction\n this.mouse.addListener(\"mouseevent\", this.eventListenersContainer[id])\n }\n\n /**\n * @description This function is used to remove a mouse listener for a specific widget. The event listener is removed from the eventListenersContainer object.\n * @param {string} id - The id of the widget.\n * @memberof ConsoleManager\n * @example CM.removeMouseListener('inputPopup')\n */\n public removeMouseListener(id: string): void {\n this.mouse.removeListener(\"mouseevent\", this.eventListenersContainer[id])\n delete this.eventListenersContainer[id]\n }\n\n /**\n * @description This function is used to register a popup. The popup is stored in the popupCollection object. That is called by the popups in show().\n * @param {popup} popup - The popup to register.\n * @memberof ConsoleManager\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public registerPopup(popup: any): void {\n this.popupCollection[popup.id] = popup\n }\n\n /**\n * @description This function is used to unregister a popup. The popup is removed from the popupCollection object. That is called by the popups in hide().\n * @param {string} id - The id of the popup.\n * @memberof ConsoleManager\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public unregisterPopup(popup: any): void {\n if (this.popupCollection[popup.id]) {\n delete this.popupCollection[popup.id]\n }\n }\n\n /**\n * @description This function is used to register a control. The control is stored in the controlCollection object. That is called by the controls in show().\n * @param {control} control - The control to register.\n * @memberof ConsoleManager\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public registerControl(control: any): void {\n this.controlsCollection[control.id] = control\n }\n\n /**\n * @description This function is used to unregister a control. The control is removed from the controlCollection object. That is called by the controls in hide().\n * @param {string} id - The id of the control.\n * @memberof ConsoleManager\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public unregisterControl(control: any): void {\n if (this.controlsCollection[control.id]) {\n delete this.controlsCollection[control.id]\n }\n }\n\n /**\n * @description This function is used to set the home page. It also refresh the screen.\n * @param {PageBuilder} page - The page to set as home page.\n * @memberof ConsoleManager\n * @example CM.setHomePage(p)\n * @deprecated since version 1.1.12 - Use setPage or setPages instead\n */\n public setHomePage(page: PageBuilder): void {\n this.pages[0] = page\n if (this.logLocation === \"popup\") {\n this.layout.setPage(page, 0)\n } else if (typeof this.logLocation === \"number\") {\n if (this.logLocation === 0) {\n this.layout.setPage(page, 1)\n } else {\n this.layout.setPage(page, 0)\n }\n } else {\n this.layout.setPage(page, 1)\n }\n this.refresh()\n }\n\n /**\n * @description This function is used to set a page of layout. It also refresh the screen.\n * @param {PageBuilder} page - The page to set as home page.\n * @param {number} [pageNumber] - The page number to set. 0 is the first page, 1 is the second page.\n * @param {string | null} [title] - The title of the page to overwrite the default title. Default is null.\n * @memberof ConsoleManager\n * @example CM.setPage(p, 0)\n */\n public setPage(page: PageBuilder, pageNumber = 0, title: string | null = null): void {\n this.pages[pageNumber] = page\n if (typeof this.logLocation === \"number\") {\n if (this.logLocation === pageNumber) {\n this.pages[this.logLocation] = this.stdOut\n }\n }\n this.layout.setPage(this.pages[pageNumber], pageNumber)\n if (title) this.layout.setTitle(title, pageNumber)\n this.refresh()\n }\n\n /**\n * @description This function is used to set both pages of layout. It also refresh the screen.\n * @param {Array<PageBuilder>} pages - The page to set as home page.\n * @param {string[] | null} [titles] - The titles of the page to overwrite the default titles. Default is null.\n * @memberof ConsoleManager\n * @example CM.setPages([p1, p2], 0)\n */\n public setPages(pages: Array<PageBuilder>, titles: string[] | null = null): void {\n pages.forEach((page, index) => {\n if (typeof this.logLocation === \"number\" && this.logLocation === index) {\n return\n } else {\n this.pages[index] = page\n }\n })\n this.layout.setPages(this.pages)\n if (titles) this.layout.setTitles(titles)\n this.refresh()\n }\n\n /**\n * @description This function is used to refresh the screen. It do the following sequence: Clear the screen, draw layout, draw widgets and finally print the screen to the stdOut.\n * @memberof ConsoleManager\n * @example CM.refresh()\n */\n public refresh(): void {\n this.Screen.update()\n this.layout.draw()\n for (const widget in this.controlsCollection) {\n if (this.controlsCollection[widget].isVisible())\n this.controlsCollection[widget].draw()\n }\n for (const widget in this.popupCollection) {\n if (this.popupCollection[widget].isVisible())\n this.popupCollection[widget].draw()\n }\n this.Screen.print()\n }\n\n /**\n * @description This function is used to show a popup containing all the stdOut of the console.\n * @memberof ConsoleManager\n * @returns the instance of the generated popup.\n * @example CM.showLogPopup()\n */\n public showLogPopup(): CustomPopup {\n return new CustomPopup({\n id: \"logPopup\", \n title: \"Application Logs\", \n content: this.stdOut, \n width: this.Screen.width - 12\n }).show()\n }\n\n /**\n * @description This function is used to log a message. It is used to log messages in the log page. Don't add colors to the message.\n * @param {string} message - The message to log.\n * @memberof ConsoleManager\n * @example CM.log(\"Hello world\")\n */\n public log(message: string): void {\n this.stdOut.addRow({ text: message, color: \"white\" })\n this.updateLogsConsole(true)\n }\n\n /** \n * @description This function is used to log an error message. It is used to log red messages in the log page. Don't add colors to the message.\n * @param {string} message - The message to log.\n * @memberof ConsoleManager\n * @example CM.error(\"Anomaly detected\")\n */\n public error(message: string): void {\n this.stdOut.addRow({ text: message, color: \"red\" })\n this.updateLogsConsole(true)\n }\n\n /**\n * @description This function is used to log a warning message. It is used to log yellow messages in the log page. Don't add colors to the message.\n * @param {string} message - The message to log.\n * @memberof ConsoleManager\n * @example CM.warn(\"Anomaly detected\")\n */\n public warn(message: string): void {\n this.stdOut.addRow({ text: message, color: \"yellow\" })\n this.updateLogsConsole(true)\n }\n\n /**\n * @description This function is used to log an info message. It is used to log blue messages in the log page. Don't add colors to the message.\n * @param {string} message - The message to log.\n * @memberof ConsoleManager\n * @example CM.info(\"Anomaly detected\")\n */\n public info(message: string): void {\n this.stdOut.addRow({ text: message, color: \"blue\" })\n this.updateLogsConsole(true)\n }\n\n /**\n * @description This function is used to update the logs console. It is called by the log functions.\n * @param {boolean} resetCursor - If true, the log scroll index is resetted.\n * @memberof ConsoleManager\n */\n private updateLogsConsole(resetCursor: boolean): void {\n if (resetCursor) {\n this.stdOut.setScrollIndex(0)\n }\n this.refresh()\n }\n\n /**\n * @description This function is used to override the console.log, console.error, console.warn and console.info functions.\n * @memberof ConsoleManager\n * @example CM.overrideConsole()\n * @example console.log(\"Hello world\") // Will be logged in the log page.\n * @example console.error(\"Anomaly detected\") // Will be logged in the log page.\n * @example console.warn(\"Anomaly detected\") // Will be logged in the log page.\n * @example console.info(\"Anomaly detected\") // Will be logged in the log page.\n * @example console.debug(\"Anomaly detected\") // Will be logged in the log page.\n * @since 1.1.42\n */\n private overrideConsole(): void {\n console.log = (message: string) => {\n this.log(message)\n }\n console.error = (message: string) => {\n this.error(message)\n }\n console.warn = (message: string) => {\n this.warn(message)\n }\n console.info = (message: string) => {\n this.info(message)\n }\n console.debug = (message: string) => {\n this.log(message)\n }\n }\n}\n\nexport {\n EOL,\n RelativeMouseEvent, MouseEventArgs, MouseEvent,\n PhisicalValues,\n StyledElement, SimplifiedStyledElement, StyleObject,\n PageBuilder, InPageWidgetBuilder,\n Control, ControlConfig,\n Box, BoxConfig, BoxStyle,\n Button, ButtonConfig, ButtonStyle, ButtonKey,\n Progress, ProgressConfig, ProgressStyle, Orientation,\n ConsoleManager,\n OptionPopup, OptionPopupConfig,\n InputPopup, InputPopupConfig,\n ConfirmPopup, ConfirmPopupConfig,\n ButtonPopup, ButtonPopupConfig,\n CustomPopup, PopupConfig,\n FileSelectorPopup, FileSelectorPopupConfig,\n}", "import { BackgroundColorName, ForegroundColorName } from \"chalk\"\n\n/**\n * @typedef {string} HEX - The type of the HEX color.\n * @example const hexColor = \"#FF0000\"\n *\n * @typedef {string} RGB - The type of the RGB color.\n * @example const rgbColor = \"rgb(255, 0, 0)\"\n */\nexport type HEX = `#${string}`;\nexport type RGB =\n | `rgb(${number}, ${number}, ${number})`\n | `rgb(${number},${number},${number})`;\n\n/**\n * @description The type containing all the possible styles for the text.\n *\n * @typedef {Object} StyleObject\n * @prop {chalk.ForegroundColorName | HEX | RGB | \"\"} [color] - The color of the text taken from the chalk library.\n * @prop {chalk.BackgroundColorName | HEX | RGB | \"\"} [backgroundColor] - The background color of the text taken from the chalk library.\n * @prop {boolean} [italic] - If the text is italic.\n * @prop {boolean} [bold] - If the text is bold.\n * @prop {boolean} [dim] - If the text is dim.\n * @prop {boolean} [underline] - If the text is underlined.\n * @prop {boolean} [inverse] - If the text is inverse.\n * @prop {boolean} [hidden] - If the text is hidden.\n * @prop {boolean} [strikethrough] - If the text is strikethrough.\n * @prop {boolean} [overline] - If the text is overlined.\n *\n * @example const textStyle = { color: \"red\", backgroundColor: \"blue\", bold: true, italic: true }\n *\n * @export\n * @interface StyleObject\n */\n// @type definition\nexport interface StyleObject {\n color?: ForegroundColorName | HEX | RGB | \"\";\n bg?: BackgroundColorName | HEX | RGB | \"\";\n italic?: boolean;\n bold?: boolean;\n dim?: boolean;\n underline?: boolean;\n inverse?: boolean;\n hidden?: boolean;\n strikethrough?: boolean;\n overline?: boolean;\n}\n\n/**\n * @description The type of the single styled text, stored in a line of the PageBuilder.\n *\n * @typedef {Object} StyledElement\n * @prop {string} text - The text of the styled text.\n * @prop {StyleObject} style - The style of the styled text.\n *\n * @example const styledText = { text: \"Hello\", style: { color: \"red\", backgroundColor: \"blue\", bold: true, italic: true } }\n *\n * @export\n * @interface StyledElement\n */\n// @type definition\nexport interface StyledElement {\n text: string;\n style: StyleObject;\n}\n\n/**\n * @description The type containing all the possible styles for the text and the text on the same level. It's used on the higher level.\n *\n * @typedef {Object} SimplifiedStyledElement\n * @prop {string} text - The text of the styled text.\n * @prop {chalk.ForegroundColorName | HEX | RGB | \"\"} [color] - The color of the text taken from the chalk library.\n * @prop {chalk.BackgroundColorName | HEX | RGB | \"\" | \"\"} [backgroundColor] - The background color of the text taken from the chalk library.\n * @prop {boolean} [italic] - If the text is italic.\n * @prop {boolean} [bold] - If the text is bold.\n * @prop {boolean} [dim] - If the text is dim.\n * @prop {boolean} [underline] - If the text is underlined.\n * @prop {boolean} [inverse] - If the text is inverse.\n * @prop {boolean} [hidden] - If the text is hidden.\n * @prop {boolean} [strikethrough] - If the text is strikethrough.\n * @prop {boolean} [overline] - If the text is overlined.\n *\n * @example const textStyle = { color: \"red\", backgroundColor: \"blue\", bold: true, italic: true }\n *\n * @export\n * @interface SimplifiedStyledElement\n */\n// @type definition\nexport interface SimplifiedStyledElement {\n text: string;\n color?: ForegroundColorName | HEX | RGB | \"\";\n bg?: BackgroundColorName | HEX | RGB | \"\" | \"\";\n italic?: boolean;\n bold?: boolean;\n dim?: boolean;\n underline?: boolean;\n inverse?: boolean;\n hidden?: boolean;\n strikethrough?: boolean;\n overline?: boolean;\n}\n\n/**\n * @description The type that contains the phisical values of an element (x, y, width, height)\n *\n * @export\n * @interface PhisicalValues\n */\n// @type definition\nexport interface PhisicalValues {\n x: number;\n y: number;\n width: number;\n height: number;\n id?: number;\n}\n\n/** @const {Object} boxChars - The characters used to draw the box. */\nexport const boxChars = {\n normal: {\n topLeft: \"\u250C\",\n topRight: \"\u2510\",\n bottomLeft: \"\u2514\",\n bottomRight: \"\u2518\",\n horizontal: \"\u2500\",\n vertical: \"\u2502\",\n cross: \"\u253C\",\n left: \"\u251C\",\n right: \"\u2524\",\n top: \"\u252C\",\n bottom: \"\u2534\",\n start: \"\",\n end: \"\",\n color: \"\" as ForegroundColorName | HEX | RGB | \"\",\n },\n selected: {\n topLeft: \"\u2554\",\n topRight: \"\u2557\",\n bottomLeft: \"\u255A\",\n bottomRight: \"\u255D\",\n horizontal: \"\u2550\",\n vertical: \"\u2551\",\n cross: \"\u256C\",\n left: \"\u2560\",\n right: \"\u2563\",\n top: \"\u2566\",\n bottom: \"\u2569\",\n start: \"\",\n end: \"\",\n color: \"\" as ForegroundColorName | HEX | RGB | \"\",\n },\n hovered: {\n topLeft: \"\u2553\",\n topRight: \"\u2556\",\n bottomLeft: \"\u2559\",\n bottomRight: \"\u255C\",\n horizontal: \"\u2500\",\n vertical: \"\u2502\",\n cross: \"\u256B\",\n left: \"\u255F\",\n right: \"\u2562\",\n top: \"\u2565\",\n bottom: \"\u2568\",\n start: \"\",\n end: \"\",\n color: \"\" as ForegroundColorName | HEX | RGB | \"\",\n },\n}\n\n/**\n * @description This function is used to truncate a string adding ... at the end.\n * @param {string} str - The string to truncate.\n * @param {number} n - The number of characters to keep.\n * @param {boolean} useWordBoundary - If true, the truncation will be done at the end of the word.\n * @example CM.truncate(\"Hello world\", 5, true) // \"Hello...\"\n */\nexport function truncate(\n str: string,\n n: number,\n useWordBoundary: boolean\n): string {\n if (visibleLength(str) <= n) {\n return str\n }\n const subString = str.substring(0, n - 1) // the original check\n return (\n (useWordBoundary\n ? subString.substring(0, subString.lastIndexOf(\" \"))\n : subString) + \"\u2026\"\n )\n}\n\n/**\n * @description This function is used to convert a styled element to a simplified styled element.\n *\n * @export\n * @param {StyledElement} styled\n * @return {*} {SimplifiedStyledElement}\n *\n * @example const simplifiedStyledElement = styledToSimplifiedStyled({ text: \"Hello world\", style: { color: \"red\", backgroundColor: \"blue\", bold: true, italic: true } })\n * // returns { text: \"Hello world\", color: \"red\", backgroundColor: \"blue\", bold: true, italic: true }\n */\nexport function styledToSimplifiedStyled(\n styled: StyledElement\n): SimplifiedStyledElement {\n return {\n text: styled.text,\n color: styled.style?.color,\n bg: styled.style?.bg,\n italic: styled.style?.italic,\n bold: styled.style?.bold,\n dim: styled.style?.dim,\n underline: styled.style?.underline,\n inverse: styled.style?.inverse,\n hidden: styled.style?.hidden,\n strikethrough: styled.style?.strikethrough,\n overline: styled.style?.overline,\n }\n}\n\n/**\n * @description This function is used to convert a simplified styled element to a styled element.\n *\n * @export\n * @param {SimplifiedStyledElement} simplifiedStyled\n * @return {*} {StyledElement}\n *\n * @example const styledElement = simplifiedStyledToStyled({ text: \"Hello world\", color: \"red\", bold: true })\n * // returns { text: \"Hello world\", style: { color: \"red\", bold: true } }\n */\nexport function simplifiedStyledToStyled(\n simplifiedStyled: SimplifiedStyledElement\n): StyledElement {\n return {\n text: simplifiedStyled.text,\n style: {\n color: simplifiedStyled?.color,\n bg: simplifiedStyled?.bg,\n italic: simplifiedStyled?.italic,\n bold: simplifiedStyled?.bold,\n dim: simplifiedStyled?.dim,\n underline: simplifiedStyled?.underline,\n inverse: simplifiedStyled?.inverse,\n hidden: simplifiedStyled?.hidden,\n strikethrough: simplifiedStyled?.strikethrough,\n overline: simplifiedStyled?.overline,\n },\n }\n}\n\n/**\n * @description Count true visible length of a string\n *\n * @export\n * @param {string} input\n * @return {number}\n * \n * @author Vitalik Gordon (xpl)\n */\nexport function visibleLength(input: string): number {\n // eslint-disable-next-line no-control-regex\n const regex = new RegExp(\n /* eslint-disable-next-line no-control-regex */\n \"[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]\",\n \"g\"\n )\n // Array.from is used to correctly count emojis\n return Array.from(input.replace(regex, \"\")).length\n}\n", "import { SimplifiedStyledElement, StyledElement, simplifiedStyledToStyled } from \"./Utils.js\"\n\n/**\n * @class PageBuilder\n * @description Defines a new page:\n * It's a sort of collection of styled rows.\n * @param {number} rowsPerPage - The number of rows per page. Default is 100. Useful for scrolling.\n *\n * @export\n * @class PageBuilder\n */\nexport class PageBuilder {\n rowsPerPage: number\n scrollIndex: number\n content: StyledElement[][]\n\n public constructor(rowsPerPage = 100) {\n this.rowsPerPage = rowsPerPage\n\n /**\n * @const {number} scrollIndex - The index of the scroll bar.\n * @memberOf PageBuilder \n */\n this.scrollIndex = 0\n\n /**\n * @const {Array<Array<object>>} content The content of the page.\n * @memberOf PageBuilder\n */\n this.content = []\n }\n\n /**\n * @description Add a new styled row to the page.\n * @param {parameters<object>} row - The styled row to add.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example\n * page.addRow({ text: 'Hello World', color: 'white' })\n * page.addRow({ text: 'Hello World', color: 'white' }, { text: 'Hello World', color: 'white' })\n */\n public addRow(...args: SimplifiedStyledElement[]): PageBuilder {\n // each argument is an object like {text: string, color: string}\n const _row: StyledElement[] = args.map((arg) => simplifiedStyledToStyled(arg)) // convert to StyledElement\n this.content.push(_row)\n return this\n }\n\n /**\n * @description Add an empty row to the page. (like <br /> in HTML)\n * @param {number} [count=1] - The number of empty rows to add.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.addEmptyRow()\n * page.addEmptyRow(2)\n */\n public addSpacer(height = 1): PageBuilder {\n if (height > 0) {\n for (let i = 0; i < height; i++) {\n this.addRow({ text: \"\", color: \"\" })\n }\n }\n return this\n }\n\n /**\n * @description Returns the content of the page.\n * @returns {Array<Array<object>>}\n * @memberOf PageBuilder\n * @example page.getContent()\n */\n public getContent(): StyledElement[][] {\n if (this.getPageHeight() > this.rowsPerPage) {\n return this.content.slice(this.getPageHeight() - this.scrollIndex - this.rowsPerPage, this.getPageHeight() - this.scrollIndex)\n } else {\n return this.content\n }\n }\n\n /**\n * @description Returns the height of the page.\n * @returns {number}\n * @memberOf PageBuilder\n * @example page.getPageHeight()\n */\n public getPageHeight(): number {\n return this.content.length\n }\n\n /**\n * @description Returns the height of the viewed page. It excludes the rows that are not visible.\n * @returns {number}\n * @memberOf PageBuilder\n * @example page.getViewedPageHeight() // returns the height of the page that is visible\n */\n public getViewedPageHeight(): number {\n return this.getContent().length\n }\n\n /**\n * @description Changes the index of the scroll bar.\n * @param {number} index - The index of the scroll bar.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.setScrollIndex(10)\n */\n public setScrollIndex(index: number): PageBuilder {\n this.scrollIndex = index\n return this\n }\n\n /**\n * @description Changes the number of rows per page.\n * @param {number} rowsPerPage - The number of rows per page.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.setRowsPerPage(10)\n */\n public setRowsPerPage(rpp: number): PageBuilder {\n this.rowsPerPage = rpp\n return this\n }\n\n /**\n * @description Increases the index of the scroll bar.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.increaseScrollIndex()\n */\n public increaseScrollIndex(): PageBuilder {\n if (this.scrollIndex < this.getPageHeight() - this.rowsPerPage) {\n this.scrollIndex++\n }\n return this\n }\n\n /**\n * @description Decreases the index of the scroll bar.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.increaseScrollIndex()\n */\n public decreaseScrollIndex(): PageBuilder {\n if (this.scrollIndex > 0) {\n this.scrollIndex--\n }\n return this\n }\n\n /**\n * @description Clears the page.\n * @returns {PageBuilder}\n * @memberOf PageBuilder\n * @example page.clear()\n * @since 1.2.0\n */\n public clear(): PageBuilder {\n this.content = []\n return this\n }\n}\n\nexport default PageBuilder", "import PageBuilder from \"./PageBuilder.js\"\n\n/**\n * @class InPageWidgetBuilder\n * @extends PageBuilder\n * @description Defines a new widget content:\n * It's a sort of collection of styled rows.\n * @param {number} rowsPerPage - The number of rows per page. Default is 100. Useful for scrolling.\n *\n * @export\n * @class InPageWidgetBuilder\n */\nexport class InPageWidgetBuilder extends PageBuilder {\n public constructor(rowsPerPage = 100) {\n super(rowsPerPage)\n }\n}\n\nexport default InPageWidgetBuilder\nexport * from \"./PageBuilder.js\"", "import { EventEmitter } from \"events\"\nimport chalk, { BackgroundColorName, ForegroundColorName } from \"chalk\"\nimport { StyledElement, StyleObject, visibleLength } from \"./Utils.js\"\nchalk.level = 3\n\n/**\n * @description The type containing all the possible styles for the text and the index array.\n * @typedef {Object} StyleIndexObject\n * @prop {Array<number>} index - The index of the style in the style array.\n * \n * @interface StyleIndexObject\n * @extends {StyleObject}\n */\ninterface StyleIndexObject extends StyleObject {\n index: [number, number];\n}\n\n/**\n * @description The type containing all the possible styles for the text and the index array and the text.\n * @typedef {Object} StyledElementWithIndex\n * @prop {string} text - The text of the styled element.\n * @prop {StyleIndexObject[]} styleIndex - The styles array with index.\n * \n * @interface StyledElementWithIndex\n */\ninterface StyledElementWithIndex {\n text: string;\n styleIndex: StyleIndexObject[];\n}\n\n/**\n * @class Screen\n * @description This class is used to manage the screen buffer.\n * @param {object} Terminal - The terminal object (process.stdout).\n * @extends EventEmitter\n * @example const screen = new Screen(process.stdout)\n */\nexport class Screen extends EventEmitter {\n Terminal: NodeJS.WriteStream\n width: number\n height: number\n buffer: StyledElementWithIndex[]\n cursor: { x: number; y: number }\n currentY = 0\n\n constructor(_Terminal: NodeJS.WriteStream) {\n super()\n this.Terminal = _Terminal\n\n /** @const {number} width - The width of the screen. */\n this.width = this.Terminal.columns\n\n /** @const {number} height - The height of the screen. */\n this.height = this.Terminal.rows\n\n /** @const {Array} buffer - The screen buffer object. */\n this.buffer = []\n\n /** @const {object} cursor - The cursor object. */\n this.cursor = { x: 0, y: 0 }\n\n this.Terminal.on(\"resize\", () => {\n this.emit(\"resize\")\n })\n }\n\n /**\n * @description This method is used to write or overwrite a row in the screen buffer at a specific position.\n * @param {arguments<object>} args - The row to write.\n * @returns {void}\n * @memberOf Screen\n * @example screen.write({ text: 'Hello World', color: 'white' })\n screen.write({ text: 'Hello World