summary refs log tree commit diff
path: root/v2
diff options
context:
space:
mode:
Diffstat (limited to 'v2')
-rw-r--r--v2/elements.ts76
-rw-r--r--v2/screen.ts97
-rw-r--r--v2/screen/login.ts33
-rw-r--r--v2/screenbuilder.ts30
4 files changed, 236 insertions, 0 deletions
diff --git a/v2/elements.ts b/v2/elements.ts
new file mode 100644
index 0000000..d712d65
--- /dev/null
+++ b/v2/elements.ts
@@ -0,0 +1,76 @@
+import chalk from "chalk";
+import { type Key } from 'node:readline';
+import { type Screen } from "./screen.ts";
+
+export abstract class Element {
+    override focusable: boolean = false;
+    focused: boolean = false;
+    // screen property is asigned by the addElement function of Scren
+    //@ts-ignore
+    screen: Screen;
+    abstract render(): void;
+    onkeypres(key: Key): void {};
+}
+
+export class Text extends Element {
+    text: string;
+    constructor(text: string) {
+        super();
+        this.text = text;
+    }
+    render() {
+        process.stdout.write(this.text)
+    }
+}
+
+export class Input extends Element {
+    focusable: boolean = true;
+    value: string = "";
+
+    isPassword: boolean = false;
+
+    render(): void {
+        let text = this.value
+        if (this.isPassword) text = text.replace(/[^]/g, '*');
+        if (this.focused) text += "_"
+        console.log(text)
+    }
+
+    onkeypres(key: Key): void {
+        //@ts-ignore
+        if (key.meta || key.code || ["return", "backspace"].includes(key.name)) {
+            switch (key.name) {
+                case "return":
+                    this.focused = false;
+                    const focusableIDs = Object.keys(this.screen.getFocusable());
+                    const focusedIndex = focusableIDs.indexOf(this.screen.focusedElementId);
+                    this.screen.focus(focusableIDs[(focusedIndex - 1) % focusableIDs.length]);
+                    break;
+                
+                case "backspace":
+                    const prevValue = '' + this.value
+                    // logs.push(`doing backspace : before ${prevValue}, after ${prevValue.substring(0, prevValue.length - 1)} : 0-${prevValue.length - 1}`)
+                    this.value = prevValue.substring(0, prevValue.length - 1)
+                    break;
+            }
+            return;
+        }
+        if (!key.sequence || key.sequence.length > 1 || key.name != key.sequence?.toLowerCase()) return;
+        this.value += key.sequence;
+    }
+
+    constructor(isPassword: boolean, ) {
+        super()
+        this.isPassword = isPassword
+    }
+}
+
+export class Button extends Text {
+    focusable: boolean = true;
+    constructor (text: string) {
+        super(text)
+    }
+    render(): void {
+        console.log(`(${(this.focused ? chalk.bgWhite : (a:string)=>a)(this.text)})`)
+    }
+}
diff --git a/v2/screen.ts b/v2/screen.ts
new file mode 100644
index 0000000..0ff6f98
--- /dev/null
+++ b/v2/screen.ts
@@ -0,0 +1,97 @@
+import type { Element } from "./elements.ts"
+import readline from 'node:readline';
+
+readline.emitKeypressEvents(process.stdin);
+
+const logs: string[] = [];
+
+function onexit() {
+    console.clear()
+    console.log("\nQuitting meower CL")
+    for (const log of logs) {
+        console.log(log)
+    }
+}
+
+export class Screen {
+    elements: Map<string, Element> = new Map<string, Element>();
+    name: string;
+    focusedElementId: string = '';
+
+    constructor(name: string) {
+        this.name = name;
+    }
+
+    handleKeypress(chunk: any, key: any, screen: Screen) {
+        const focusableIDs = Object.keys(screen.getFocusable());
+        const focusedIndex = focusableIDs.indexOf(screen.focusedElementId);
+        if (key && key.name == 'escape') {
+            onexit();
+            process.exit();
+        }
+        
+        if (['up', 'left'].includes(key.name)) {
+            // logs.push(`Got up key, moving focus upward ${focusedIndex} ${(focusedIndex - 1) % focusableIDs.length}`)
+            screen.focus(focusableIDs[(focusedIndex - 1) % focusableIDs.length]);
+            return screen.render()
+        }
+        if (['down', 'right'].includes(key.name)) {
+            // logs.push(`Got down key, moving focus downward ${focusedIndex} ${(focusedIndex + 1) % focusableIDs.length}`)
+            screen.focus(focusableIDs[(focusedIndex + 1) % focusableIDs.length]);
+            return screen.render()
+        }
+
+        // logs.push("pressed key, data: " + JSON.stringify(key))
+        if (!screen.focusedElementId) return;
+        const focusedElement = screen.getFocusedElement();
+        focusedElement?.onkeypres(key)
+        this.render()
+    }
+
+    getKeypressHandler(screen: Screen) {
+        return (chunk: any, key: any) => this.handleKeypress(chunk,key, screen);
+    }
+
+    ready() {
+        process.stdin.on('keypress', this.getKeypressHandler(this));
+        this.render()
+    }
+
+    off() {
+        process.stdin.off('keypress', this.getKeypressHandler(this))
+    }
+
+    addElement(name: string, element: Element) {
+        if(this.elements.has(name)) throw new Error();
+        element.screen = this;
+        this.elements.set(name, element);
+    }
+
+    render() {
+        console.clear()
+        this.elements.forEach(element => {
+            element.render()
+        });
+    }
+
+    getFocusable() {
+        return Object.fromEntries([...this.elements.entries()].filter(([k, v]) => v.focusable))
+    }
+
+    getElements() {
+        return Object.fromEntries([...this.elements.entries()])
+    }
+
+    focus(id: string) {
+        this.elements.forEach(e => e.focused = false);
+        const focusElem = this.elements.get(id) as Element
+        focusElem.focused = true;
+        this.focusedElementId = id
+    }
+
+    getFocusedElement(): Element|undefined {
+        return this.focusedElementId ? this.elements.get(this.focusedElementId) as Element : undefined
+    }
+}
+
+if (process.stdin.isTTY) process.stdin.setRawMode(true); // makes the terminal send stdin without the user pressing enter
diff --git a/v2/screen/login.ts b/v2/screen/login.ts
new file mode 100644
index 0000000..c9e4fec
--- /dev/null
+++ b/v2/screen/login.ts
@@ -0,0 +1,33 @@
+import { ElemType } from "../screenbuilder.ts";
+
+export default {
+    elements: [
+        {
+            type: ElemType.TextElem,
+            id: 'username-label',
+            data: ["Username: \n"]
+        },
+        {
+            type: ElemType.InputElem,
+            id: 'username-input',
+            data: [false]
+        },
+        {
+            type: ElemType.TextElem,
+            id: 'password-label',
+            data: ["Password: \n"]
+        },
+        {
+            type: ElemType.InputElem,
+            id: 'password-input',
+            data: [true]
+        },
+        {
+            type: ElemType.ButtonElem,
+            id: 'done-btn',
+            data: ["Done"]
+        }
+    ],
+    focus: "username-input",
+    name: 'login'
+}
\ No newline at end of file
diff --git a/v2/screenbuilder.ts b/v2/screenbuilder.ts
new file mode 100644
index 0000000..dcf1af7
--- /dev/null
+++ b/v2/screenbuilder.ts
@@ -0,0 +1,30 @@
+import { Screen } from "./screen.ts";
+import * as elements from "./elements.ts";
+
+export enum ElemType {
+    TextElem,
+    InputElem,
+    ButtonElem,
+}
+
+const types = {
+    0: elements.Text,
+    1: elements.Input,
+    2: elements.Button
+}
+
+type BuilderElem = {
+    type: ElemType,
+    id: string,
+    data: any[]
+}
+
+export function build(data: {elements: BuilderElem[],focus?:string,name:string}) {
+    const screen = new Screen(data.name);
+    for (const element of data.elements) {
+        //@ts-ignore
+        screen.addElement(element.id, new types[element.type](...element.data))
+    }
+    if (data.focus) screen.focus(data.focus);
+    screen.ready()
+}
\ No newline at end of file