From 09e0ad25e3f39993c0db10c333ceb1229ff75d57 Mon Sep 17 00:00:00 2001
From: Elia Lazzari The configuration of the box. The configuration object The configuration of the popup. The configuration of the popup. The configuration object for the control. The content of the Control. The configuration of the popup. The title of the first page. The title of the second page. The ratio of the pages. (in horizontal direction) If the height of the pages should be the same.BoxConfig
](#BoxConfig) | ButtonConfig
](#ButtonConfig) | ButtonPopupConfig
](#ButtonPopupConfig) | ConfirmPopupConfig
](#ConfirmPopupConfig) | ControlConfig
](#ControlConfig) | Control
](#Control)
**Returns**: InPageWidgetBuilder
- Control
](#Control)
diff --git a/docs/CustomPopup.md b/docs/CustomPopup.md
index 8d4fb3e..c7a86e2 100644
--- a/docs/CustomPopup.md
+++ b/docs/CustomPopup.md
@@ -83,7 +83,12 @@
| config | [PopupConfig
](#PopupConfig) | string
| string
| Array.<number>
| boolean
|
FileSelectorPopupConfig
](#FileSelectorPopupConfig) | The configuration for the FileSelectorPopup class.
| **Example** -```ts const popup = new FileSelectorPopup({ id: "popup1", title: "Choose the file", basePath: "./examples" }).show().on("confirm", (selected) => { console.log(selected) }) // show the popup and wait for the user to confirm +```ts +const popup = new FileSelectorPopup({ + id: "popup1", + title: "Choose the file", + basePath: "./examples" +}).show().on("confirm", (selected) => { + console.log(selected) +}) // show the popup and wait for the user to confirm ### fileSelectorPopup.listDir(dir) ⇒Promise.<Array.<object>>
diff --git a/docs/LayoutManager.md b/docs/LayoutManager.md
index a1c6ef8..f74926a 100644
--- a/docs/LayoutManager.md
+++ b/docs/LayoutManager.md
@@ -40,6 +40,7 @@
| [direction] | "horizontal"
\| "vertical"
| The direction of the layout.
| | [pageTitles] |Array.<string>
| The title of the first page.
| | [pageRatio] |Array.<number>
| The ratio of the pages. (in horizontal direction)
| +| [fitHeight] |boolean
| If the height of the pages should be the same.
| diff --git a/docs/OptionPopup.md b/docs/OptionPopup.md index 52bf40c..ca52d7b 100644 --- a/docs/OptionPopup.md +++ b/docs/OptionPopup.md @@ -84,7 +84,14 @@ | visible |boolean
| If the popup is visible. Default is false (make it appears using show()).
| **Example** -```ts const popup = new OptionPopup({ id:"popup1", title: "Choose the option", options, selected }).show().on("confirm", (option) => { console.log(option) }) // show the popup and wait for the user to confirm ``` +```ts +const popup = new OptionPopup({ + id:"popup1", + title: "Choose the option", + options, + selected +}).show().on("confirm", (option) => { console.log(option) }) // show the popup and wait for the user to confirm +``` ### optionPopup.keyListener(str, key) diff --git a/docs/PageBuilder.md b/docs/PageBuilder.md index b1a5273..2e16306 100644 --- a/docs/PageBuilder.md +++ b/docs/PageBuilder.md @@ -44,7 +44,8 @@ It's a sort of collection of styled rows. **Example** ```js -page.addRow({ text: 'Hello World', color: 'white' }) page.addRow({ text: 'Hello World', color: 'white' }, { text: 'Hello World', color: 'white' }) +page.addRow({ text: 'Hello World', color: 'white' }) +page.addRow({ text: 'Hello World', color: 'white' }, { text: 'Hello World', color: 'white' }) ``` @@ -59,7 +60,8 @@ page.addRow({ text: 'Hello World', color: 'white' }) page.addRow({ text: 'Hello **Example** ```js -page.addEmptyRow() page.addEmptyRow(2) +page.addEmptyRow() +page.addEmptyRow(2) ``` diff --git a/docs/ProgressBar.md b/docs/ProgressBar.md index 09c820a..8a1b84c 100644 --- a/docs/ProgressBar.md +++ b/docs/ProgressBar.md @@ -104,7 +104,79 @@ | config | [ProgressConfig
](#ProgressConfig) | The configuration object for the progress bar
| **Example** -```js const pStyle = { boxed: true, showTitle: true, showValue: true, showPercentage: true, showMinMax: false, } const p = new Progress({ id: "prog1", x: 10, y: 2, style: pStyle, theme: "htop", length: 25, label: "Mem" }) const incr = setInterval(() => { const value = p.getValue() + 0.25 p.setValue(value) if (value >= p.getMax()) { clearInterval(incr) } }, 100) const p1Style = { background: "bgBlack", borderColor: "yellow", color: "green", boxed: true, showTitle: true, showValue: true, showPercentage: true, showMinMax: true, } const p1 = new Progress({ id: "prog1", x: 10, y: 4, style: pStyle, theme: "precision", length: 25, label: "Precision" }) const incr1 = setInterval(() => { const value = p1.getValue() + 0.25 p1.setValue(value) if (value >= p1.getMax()) { clearInterval(incr1) } }, 100) const p2Style = { background: "bgBlack", borderColor: "yellow", color: "magenta", boxed: true, showTitle: true, showValue: true, showPercentage: true, showMinMax: true, } const p2 = new Progress({ id: "prog3", x: 10, y: 6, style: pStyle, theme: "precision", length: 25, label: "Interactive", direction: "vertical", interactive: true, }) p2.on("valueChanged", (value) => { console.log(`Value changed: ${value}`) }) ``` +```js + const pStyle = { + boxed: true, + showTitle: true, + showValue: true, + showPercentage: true, + showMinMax: false, + } + const p = new Progress({ + id: "prog1", + x: 10, y: 2, + style: pStyle, + theme: "htop", + length: 25, + label: "Mem" + }) + const incr = setInterval(() => { + const value = p.getValue() + 0.25 + p.setValue(value) + if (value >= p.getMax()) { + clearInterval(incr) + } + }, 100) + + const p1Style = { + background: "bgBlack", + borderColor: "yellow", + color: "green", + boxed: true, + showTitle: true, + showValue: true, + showPercentage: true, + showMinMax: true, + } + const p1 = new Progress({ + id: "prog1", + x: 10, y: 4, + style: pStyle, + theme: "precision", + length: 25, + label: "Precision" + }) + const incr1 = setInterval(() => { + const value = p1.getValue() + 0.25 + p1.setValue(value) + if (value >= p1.getMax()) { + clearInterval(incr1) + } + }, 100) + const p2Style = { + background: "bgBlack", + borderColor: "yellow", + color: "magenta", + boxed: true, + showTitle: true, + showValue: true, + showPercentage: true, + showMinMax: true, + } + const p2 = new Progress({ + id: "prog3", + x: 10, y: 6, + style: pStyle, + theme: "precision", + length: 25, + label: "Interactive", + direction: "vertical", + interactive: true, + }) + p2.on("valueChanged", (value) => { + console.log(`Value changed: ${value}`) + }) +``` ## drawingChars :Object
diff --git a/docs/QuadLayout.md b/docs/QuadLayout.md
index 84bd459..4419b3f 100644
--- a/docs/QuadLayout.md
+++ b/docs/QuadLayout.md
@@ -47,6 +47,7 @@
| [page3Title] | string
| The title of the third page.
| | [page4Title] |string
| The title of the fourth page.
| | [pageRatio] |Array.<number>
| The ratio of the pages.
| +| [fitHeight] |boolean
| If the height of the pages should be the same.
| diff --git a/docs/Screen.md b/docs/Screen.md index 292aafd..818ed8d 100644 --- a/docs/Screen.md +++ b/docs/Screen.md @@ -82,7 +82,7 @@ const screen = new Screen(process.stdout) **Example** ```js -screen.write({ text: 'Hello World', color: 'white' }) +screen.write({ text: 'Hello World', color: 'white' }) screen.write({ text: 'Hello World', color: 'white' }, { text: 'Hello World', color: 'white' }) ``` @@ -170,7 +170,8 @@ screen.replaceAt('Hello Luca', 6, 'Elia') // returns 'Hello Elia' **Example** ```js -screen.mergeStyles([{ color: 'red', bg: 'black', italic: false, bold: false, index: [0, 5] }, { color: 'white', bg: 'black', italic: false, bold: false, index: [6, 10] }], [{ color: 'magenta', bg: 'black', italic: false, bold: false, index: [0, 30] }], 5, 15) returns [{ color: 'magenta', bg: 'black', italic: false, bold: false, index: [0, 4] }, { color: 'red', bg: 'black', italic: false, bold: false, index: [5, 10] }, { color: 'white', bg: 'black', italic: false, bold: false, index: [11, 15] }, { color: 'magenta', bg: 'black', italic: false, bold: false, index: [16, 30] }] +screen.mergeStyles([{ color: 'red', bg: 'black', italic: false, bold: false, index: [0, 5] }, { color: 'white', bg: 'black', italic: false, bold: false, index: [6, 10] }], [{ color: 'magenta', bg: 'black', italic: false, bold: false, index: [0, 30] }], 5, 15) +returns [{ color: 'magenta', bg: 'black', italic: false, bold: false, index: [0, 4] }, { color: 'red', bg: 'black', italic: false, bold: false, index: [5, 10] }, { color: 'white', bg: 'black', italic: false, bold: false, index: [11, 15] }, { color: 'magenta', bg: 'black', italic: false, bold: false, index: [16, 30] }] ``` diff --git a/docs/SingleLayout.md b/docs/SingleLayout.md index 9b36ea8..4386fb9 100644 --- a/docs/SingleLayout.md +++ b/docs/SingleLayout.md @@ -36,6 +36,7 @@ | [boxColor] |ForegroundColorName
\| HEX
\| RGB
\| ""
| The color of the box taken from the chalk library.
| | [boxStyle] |"bold"
| If the border of the box should be bold.
| | [pageTitle] |string
| The title of the first page.
| +| [fitHeight] |boolean
| If the height of the layout should be the same as the height of the screen.
| diff --git a/docs/Utils.md b/docs/Utils.md index 4beec22..a4a5c4b 100644 --- a/docs/Utils.md +++ b/docs/Utils.md @@ -154,7 +154,8 @@ CM.truncate("Hello world", 5, true) // "Hello..." **Example** ```js -const simplifiedStyledElement = styledToSimplifiedStyled({ text: "Hello world", style: { color: "red", backgroundColor: "blue", bold: true, italic: true } }) // returns { text: "Hello world", color: "red", backgroundColor: "blue", bold: true, italic: true } +const simplifiedStyledElement = styledToSimplifiedStyled({ text: "Hello world", style: { color: "red", backgroundColor: "blue", bold: true, italic: true } }) +// returns { text: "Hello world", color: "red", backgroundColor: "blue", bold: true, italic: true } ``` @@ -170,7 +171,8 @@ const simplifiedStyledElement = styledToSimplifiedStyled({ text: "Hello world", **Example** ```js -const styledElement = simplifiedStyledToStyled({ text: "Hello world", color: "red", bold: true }) // returns { text: "Hello world", style: { color: "red", bold: true } } +const styledElement = simplifiedStyledToStyled({ text: "Hello world", color: "red", bold: true }) +// returns { text: "Hello world", style: { color: "red", bold: true } } ``` diff --git a/examples/layout.mjs b/examples/layout.mjs new file mode 100644 index 0000000..d66b3f5 --- /dev/null +++ b/examples/layout.mjs @@ -0,0 +1,90 @@ +import { ConsoleManager,ConfirmPopup, PageBuilder } from "../dist/esm/ConsoleGui.mjs" + +const opt = { + title: "Layout Test", + layoutOptions: { + type: "quad", + boxed: true, // Set to true to enable boxed layout + showTitle: true, // Set to false to hide title + changeFocusKey: "ctrl+l", // Change layout with ctrl+l to switch to the logs page + boxColor: "yellow", + boxStyle: "bold", + fitHeight: true, + }, + logLocation: 1, + enableMouse: true +} + +const GUI = new ConsoleManager(opt) + +GUI.on("exit", () => { + closeApp() +}) + +GUI.on("keypressed", (key) => { + switch (key.name) { + case "q": + new ConfirmPopup({ + id: "popupQuit", title: "Are you sure you want to quit?" + }).show().on("confirm", () => closeApp()) + break + default: + break + } +}) + +const closeApp = () => { + console.clear() + process.exit() +} + +GUI.refresh() + +const loremIpsumPage = new PageBuilder() + +loremIpsumPage.addRow({ + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + color: "red", + bg: "blue", +}) + +loremIpsumPage.addRow({ + text: "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + color: "blue", + bg: "red", +}) + +loremIpsumPage.addRow({ + text: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", + color: "green", + bg: "yellow", +}) + +loremIpsumPage.addRow({ + text: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", + color: "yellow", + bg: "green", +}) + +loremIpsumPage.addRow({ + text: "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + color: "cyan", + bg: "magenta", +}) + +const loremIpsumOverflowedPage = new PageBuilder() + +// add some random text to the page +for (let i = 0; i < 100; i++) { + loremIpsumOverflowedPage.addRow({ + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + color: "red", + bg: "blue", + }) +} + +GUI.setPage(loremIpsumPage, 0, "Lorem Ipsum") +GUI.setPage(loremIpsumOverflowedPage, 3, "Lorem Ipsum Overflowed") +GUI.setPage(new PageBuilder(), 2, "Page 3") + +console.log("Done") \ No newline at end of file diff --git a/package.json b/package.json index 2cdb606..5a90362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "console-gui-tools", - "version": "3.1.1", + "version": "3.2.0", "description": "A simple library to draw option menu, text popup or other widgets and layout on a Node.js console.", "main": "dist/esm/ConsoleGui.mjs", "types": "dist/types/ConsoleGui.d.ts", diff --git a/src/components/layout/DoubleLayout.ts b/src/components/layout/DoubleLayout.ts index 477e194..2dc2767 100644 --- a/src/components/layout/DoubleLayout.ts +++ b/src/components/layout/DoubleLayout.ts @@ -21,6 +21,7 @@ import { * @prop {string} [page1Title] - The title of the first page. * @prop {string} [page2Title] - The title of the second page. * @prop {number[]} [pageRatio] - The ratio of the pages. (in horizontal direction) + * @prop {boolean} [fitHeight] - If the height of the pages should be the same. * * @export * @interface DoubleLayoutOptions @@ -36,6 +37,7 @@ export interface DoubleLayoutOptions { page1Title?: string; page2Title?: string; pageRatio?: [number, number]; + fitHeight?: boolean; } /** @@ -408,6 +410,26 @@ export class DoubleLayout { truncate(this.page1Title, this.realWidth[0] - 4, false), truncate(this.page2Title, this.realWidth[1] - 4, false), ] + + const pageHeights = [ + this.page1.getViewedPageHeight(), + this.page2.getViewedPageHeight(), + ] + + if (this.options.fitHeight) { + const decorationHeight = this.options.boxed || this.options.showTitle ? 2 : 0 + const halfHeight = (this.CM.Screen.height - decorationHeight) / 2 - 1 + if (Math.max(...pageHeights) > halfHeight) { + // Change pageHeights to fit the screen + const ratio = halfHeight / Math.max(...pageHeights) + pageHeights[0] = Math.round(pageHeights[0] * ratio) + pageHeights[1] = Math.round(pageHeights[1] * ratio) + } else { + pageHeights[0] = halfHeight + pageHeights[1] = halfHeight + } + } + if (this.options.boxed) { // Draw pages with borders if (this.options.showTitle) { @@ -435,9 +457,9 @@ export class DoubleLayout { }, }) } - this.page1.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line, undefined, 0) - }) + for (let i = 0; i < pageHeights[0]; i++) { + this.drawLine(this.page1.getContent()[i] || [{ text: "", style: { color: "" } }], undefined, 0) + } if (this.options.showTitle) { this.CM.Screen.write({ text: `${boxChars["normal"].left}${boxChars["normal"].horizontal}${ @@ -457,9 +479,9 @@ export class DoubleLayout { style: { color: this.options.boxColor, bold: this.boxBold }, }) } - this.page2.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line, undefined, 1) - }) + for (let i = 0; i < pageHeights[1]; i++) { + this.drawLine(this.page2.getContent()[i] || [{ text: "", style: { color: "" } }], undefined, 1) + } this.CM.Screen.write({ text: `${boxChars["normal"].bottomLeft}${boxChars[ "normal" @@ -482,9 +504,9 @@ export class DoubleLayout { }, }) } - this.page1.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line, undefined, 0) - }) + for (let i = 0; i < pageHeights[0]; i++) { + this.drawLine(this.page1.getContent()[i] || [{ text: "", style: { color: "" } }], undefined, 0) + } if (this.options.showTitle) { this.CM.Screen.write({ text: `${trimmedTitle[1]}`, @@ -494,9 +516,9 @@ export class DoubleLayout { }, }) } - this.page2.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line, undefined, 1) - }) + for (let i = 0; i < pageHeights[1]; i++) { + this.drawLine(this.page2.getContent()[i] || [{ text: "", style: { color: "" } }], undefined, 1) + } } } else { // Draw horizontally @@ -508,10 +530,16 @@ export class DoubleLayout { truncate(this.page1Title, this.realWidth[0] - 4, false), truncate(this.page2Title, this.realWidth[1] - 3, false), ] - const maxPageHeight = Math.max( + let maxPageHeight = Math.max( this.page1.getViewedPageHeight(), this.page2.getViewedPageHeight() ) + + if (this.options.fitHeight) { + const decorationHeight = this.options.boxed || this.options.showTitle ? 2 : 0 + maxPageHeight = this.CM.Screen.height - decorationHeight + } + const p1 = this.page1.getContent() const p2 = this.page2.getContent() if (this.options.boxed) { diff --git a/src/components/layout/LayoutManager.ts b/src/components/layout/LayoutManager.ts index b5ab356..db944b9 100644 --- a/src/components/layout/LayoutManager.ts +++ b/src/components/layout/LayoutManager.ts @@ -18,6 +18,7 @@ import SingleLayout, { SingleLayoutOptions } from "./SingleLayout.js" * @prop {"horizontal" | "vertical"} [direction] - The direction of the layout. * @prop {string[]} [pageTitles] - The title of the first page. * @prop {number[]} [pageRatio] - The ratio of the pages. (in horizontal direction) + * @prop {boolean} [fitHeight] - If the height of the pages should be the same. * * @export * @interface LayoutOptions @@ -33,6 +34,7 @@ export interface LayoutOptions { direction?: "horizontal" | "vertical"; pageTitles?: string[]; pageRatio?: [number, number] | [[number, number], [number, number]]; + fitHeight?: boolean; } /** @@ -79,6 +81,7 @@ export class LayoutManager { boxColor: this.options.boxColor, boxStyle: this.options.boxStyle, pageTitle: this.pageTitles ? this.pageTitles[0] : "", + fitHeight: this.options.fitHeight, } as SingleLayoutOptions this.layout = new SingleLayout(this.pages[0], this.optionsRelative) break @@ -93,6 +96,7 @@ export class LayoutManager { page1Title: this.pageTitles ? this.pageTitles[0] : "", page2Title: this.pageTitles ? this.pageTitles[1] : "", pageRatio: this.options.pageRatio, + fitHeight: this.options.fitHeight, } as DoubleLayoutOptions this.layout = new DoubleLayout(this.pages[0], this.pages[1], this.optionsRelative as DoubleLayoutOptions) break @@ -112,6 +116,7 @@ export class LayoutManager { page3Title: this.pageTitles ? this.pageTitles[2] : "", page4Title: this.pageTitles ? this.pageTitles[3] : "", pageRatio: this.options.pageRatio, + fitHeight: this.options.fitHeight, } as QuadLayoutOptions this.layout = new QuadLayout(this.pages[0], this.pages[1], this.pages[2], this.pages[3], this.optionsRelative as QuadLayoutOptions) break diff --git a/src/components/layout/QuadLayout.ts b/src/components/layout/QuadLayout.ts index 291031f..4df7d38 100644 --- a/src/components/layout/QuadLayout.ts +++ b/src/components/layout/QuadLayout.ts @@ -15,6 +15,7 @@ import { boxChars, HEX, RGB, StyledElement, truncate } from "../Utils.js" * @prop {string} [page3Title] - The title of the third page. * @prop {string} [page4Title] - The title of the fourth page. * @prop {number[]} [pageRatio] - The ratio of the pages. + * @prop {boolean} [fitHeight] - If the height of the pages should be the same. * * @export * @interface DoubleLayoutOptions @@ -31,6 +32,7 @@ export interface QuadLayoutOptions { page3Title?: string; page4Title?: string; pageRatio?: [[number, number], [number, number]]; + fitHeight?: boolean; } /** @@ -358,13 +360,18 @@ export class QuadLayout { truncate(this.page4Title, this.realWidth[1][1] - 3, false) ] ] - const maxPageHeight = Math.max( + let maxPageHeight = Math.max( this.page1.getViewedPageHeight(), this.page2.getViewedPageHeight(), this.page3.getViewedPageHeight(), this.page4.getViewedPageHeight() ) + if (this.options.fitHeight) { + const decorationHeight = this.options.boxed || this.options.showTitle ? 4 : 0 + maxPageHeight = (this.CM.Screen.height - decorationHeight) / 2 + } + const p = [ this.page1.getContent(), this.page2.getContent(), diff --git a/src/components/layout/SingleLayout.ts b/src/components/layout/SingleLayout.ts index ada4830..56130f3 100644 --- a/src/components/layout/SingleLayout.ts +++ b/src/components/layout/SingleLayout.ts @@ -10,6 +10,7 @@ import { boxChars, HEX, RGB, StyledElement, truncate } from "../Utils.js" * @prop {ForegroundColorName | HEX | RGB | ""} [boxColor] - The color of the box taken from the chalk library. * @prop {"bold"} [boxStyle] - If the border of the box should be bold. * @prop {string} [pageTitle] - The title of the first page. + * @prop {boolean} [fitHeight] - If the height of the layout should be the same as the height of the screen. * * @export * @interface SingleLayoutOptions @@ -21,6 +22,7 @@ export interface SingleLayoutOptions { boxColor?: ForegroundColorName | HEX | RGB | ""; // add color list from chalk boxStyle?: "bold"; pageTitle?: string; + fitHeight?: boolean; } /** @@ -134,17 +136,29 @@ export class SingleLayout { } else { this.CM.Screen.write({ text: `${boxChars["normal"].topLeft}${boxChars["normal"].horizontal}${boxChars["normal"].horizontal.repeat(this.CM.Screen.width - 3)}${boxChars["normal"].topRight}`, style: { color: this.options.boxColor, bold: this.boxBold } }) } - this.page.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line) - }) + if (this.options.fitHeight) { + for (let i = 0; i < this.CM.Screen.height - 2; i++) { + this.drawLine(this.page.getContent()[i] || [{ text: "", style: { color: "" } }]) + } + } else { + this.page.getContent().forEach((line: StyledElement[]) => { + this.drawLine(line) + }) + } this.CM.Screen.write({ text: `${boxChars["normal"].bottomLeft}${boxChars["normal"].horizontal.repeat(this.CM.Screen.width - 2)}${boxChars["normal"].bottomRight}`, style: { color: this.options.boxColor, bold: this.boxBold } }) } else { // Draw pages without borders if (this.options.showTitle) { this.CM.Screen.write({ text: `${trimmedTitle}`, style: { color: this.options.boxColor, bold: this.boxBold } }) } - this.page.getContent().forEach((line: StyledElement[]) => { - this.drawLine(line) - }) + if (this.options.fitHeight) { + for (let i = 0; i < this.CM.Screen.height - 2; i++) { + this.drawLine(this.page.getContent()[i] || [{ text: "", style: { color: "" } }]) + } + } else { + this.page.getContent().forEach((line: StyledElement[]) => { + this.drawLine(line) + }) + } } } }