« get me outta code hell

use ESM module syntax & minor cleanups - tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
path: root/ui
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-05-12 17:42:09 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-05-13 12:48:36 -0300
commit6ea74c268a12325296a1d2e7fc31b02030ddb8bc (patch)
tree5da94d93acb64e7ab650d240d6cb23c659ad02ca /ui
parente783bcf8522fa68e6b221afd18469c3c265b1bb7 (diff)
use ESM module syntax & minor cleanups
The biggest change here is moving various element classes under
more scope-specific directories, which helps to avoid circular
dependencies and is just cleaner to navigate and expand in the
future.

Otherwise this is a largely uncritical port to ESM module syntax!
There are probably a number of changes and other cleanups that
remain much needed.

Whenever I make changes to tui-lib it's hard to believe it's
already been <INSERT COUNTING NUMBER HERE> years since the
previous time. First commits are from January 2017, and the
code originates a month earlier in KAaRMNoD!
Diffstat (limited to 'ui')
-rw-r--r--ui/controls/Button.js (renamed from ui/form/Button.js)8
-rw-r--r--ui/controls/FocusBox.js (renamed from ui/form/FocusBox.js)6
-rw-r--r--ui/controls/Form.js (renamed from ui/form/Form.js)6
-rw-r--r--ui/controls/ListScrollForm.js (renamed from ui/form/ListScrollForm.js)13
-rw-r--r--ui/controls/TextInput.js (renamed from ui/form/TextInput.js)12
-rw-r--r--ui/controls/index.js16
-rw-r--r--ui/dialogs/CancelDialog.js (renamed from ui/form/CancelDialog.js)13
-rw-r--r--ui/dialogs/ConfirmDialog.js (renamed from ui/form/ConfirmDialog.js)13
-rw-r--r--ui/dialogs/Dialog.js (renamed from ui/Dialog.js)9
-rw-r--r--ui/dialogs/FilePickerForm.js (renamed from ui/tools/FilePickerForm.js)23
-rw-r--r--ui/dialogs/OpenFileDialog.js (renamed from ui/tools/OpenFileDialog.js)20
-rw-r--r--ui/dialogs/index.js16
-rw-r--r--ui/index.js4
-rw-r--r--ui/presentation/HorizontalBox.js (renamed from ui/HorizontalBox.js)4
-rw-r--r--ui/presentation/Label.js (renamed from ui/Label.js)6
-rw-r--r--ui/presentation/Pane.js (renamed from ui/Pane.js)10
-rw-r--r--ui/presentation/Sprite.js (renamed from ui/Sprite.js)6
-rw-r--r--ui/presentation/WrapLabel.js (renamed from ui/WrapLabel.js)9
-rw-r--r--ui/presentation/index.js15
-rw-r--r--ui/primitives/DisplayElement.js (renamed from ui/DisplayElement.js)13
-rw-r--r--ui/primitives/Element.js (renamed from ui/Element.js)4
-rw-r--r--ui/primitives/FocusElement.js (renamed from ui/form/FocusElement.js)4
-rw-r--r--ui/primitives/Root.js (renamed from ui/Root.js)26
-rw-r--r--ui/primitives/index.js11
24 files changed, 159 insertions, 108 deletions
diff --git a/ui/form/Button.js b/ui/controls/Button.js
index 46329a6..5be2b2a 100644
--- a/ui/form/Button.js
+++ b/ui/controls/Button.js
@@ -1,9 +1,9 @@
-const ansi = require('../../util/ansi')
-const telc = require('../../util/telchars')
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const FocusElement = require('./FocusElement')
+import * as ansi from 'tui-lib/util/ansi'
+import telc from 'tui-lib/util/telchars'
 
-module.exports = class Button extends FocusElement {
+export default class Button extends FocusElement {
   // A button.
 
   constructor(text) {
diff --git a/ui/form/FocusBox.js b/ui/controls/FocusBox.js
index 69b5bf5..64f84c9 100644
--- a/ui/form/FocusBox.js
+++ b/ui/controls/FocusBox.js
@@ -1,8 +1,8 @@
-const ansi = require('../../util/ansi')
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const FocusElement = require('./FocusElement')
+import * as ansi from 'tui-lib/util/ansi'
 
-module.exports = class FocusBox extends FocusElement {
+export default class FocusBox extends FocusElement {
   // A box (not to be confused with Pane!) that can be selected. When it's
   // selected, it applies an invert effect to its children. (This won't work
   // well if you have elements inside of it that have their own attributes,
diff --git a/ui/form/Form.js b/ui/controls/Form.js
index 451baa4..921096a 100644
--- a/ui/form/Form.js
+++ b/ui/controls/Form.js
@@ -1,8 +1,8 @@
-const telc = require('../../util/telchars')
+import telc from 'tui-lib/util/telchars'
 
-const FocusElement = require('./FocusElement')
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-module.exports = class Form extends FocusElement {
+export default class Form extends FocusElement {
   constructor() {
     super()
 
diff --git a/ui/form/ListScrollForm.js b/ui/controls/ListScrollForm.js
index 78c376f..3f75599 100644
--- a/ui/form/ListScrollForm.js
+++ b/ui/controls/ListScrollForm.js
@@ -1,11 +1,12 @@
-const ansi = require('../../util/ansi')
-const telc = require('../../util/telchars')
-const unic = require('../../util/unichars')
+import * as ansi from 'tui-lib/util/ansi'
+import telc from 'tui-lib/util/telchars'
+import unic from 'tui-lib/util/unichars'
 
-const DisplayElement = require('../DisplayElement')
-const Form = require('./Form')
+import {DisplayElement} from 'tui-lib/ui/primitives'
 
-module.exports = class ListScrollForm extends Form {
+import Form from './Form.js'
+
+export default class ListScrollForm extends Form {
   // A form that lets the user scroll through a list of items. It
   // automatically adjusts to always allow the selected item to be visible.
   // Unless disabled in the constructor, a scrollbar is automatically displayed
diff --git a/ui/form/TextInput.js b/ui/controls/TextInput.js
index 78d3b6d..1a32605 100644
--- a/ui/form/TextInput.js
+++ b/ui/controls/TextInput.js
@@ -1,10 +1,10 @@
-const ansi = require('../../util/ansi')
-const unic = require('../../util/unichars')
-const telc = require('../../util/telchars')
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const FocusElement = require('./FocusElement')
+import * as ansi from 'tui-lib/util/ansi'
+import telc from 'tui-lib/util/telchars'
+import unic from 'tui-lib/util/unichars'
 
-module.exports = class TextInput extends FocusElement {
+export default class TextInput extends FocusElement {
   // An element that the user can type in.
 
   constructor() {
@@ -142,4 +142,6 @@ module.exports = class TextInput extends FocusElement {
 
   get value() { return this.getDep('value') }
   set value(v) { return this.setDep('value', v) }
+  get cursorIndex() { return this.getDep('cursorIndex') }
+  set cursorIndex(v) { return this.setDep('cursorIndex', v) }
 }
diff --git a/ui/controls/index.js b/ui/controls/index.js
new file mode 100644
index 0000000..e99add1
--- /dev/null
+++ b/ui/controls/index.js
@@ -0,0 +1,16 @@
+//
+// Import mapping:
+//
+//   primitives ->
+//     Button
+//     FocusBox
+//     TextInput
+//
+//     Form -> ListScrollForm
+//
+
+export {default as Button} from './Button.js'
+export {default as FocusBox} from './FocusBox.js'
+export {default as Form} from './Form.js'
+export {default as ListScrollForm} from './ListScrollForm.js'
+export {default as TextInput} from './TextInput.js'
diff --git a/ui/form/CancelDialog.js b/ui/dialogs/CancelDialog.js
index 21ff6df..9069d43 100644
--- a/ui/form/CancelDialog.js
+++ b/ui/dialogs/CancelDialog.js
@@ -1,13 +1,10 @@
-const telc = require('../../util/telchars')
+import {Button, Form} from 'tui-lib/ui/controls'
+import {Label, Pane} from 'tui-lib/ui/presentation'
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const FocusElement = require('./FocusElement')
+import telc from 'tui-lib/util/telchars'
 
-const Button = require('./Button')
-const Form = require('./Form')
-const Label = require('../Label')
-const Pane = require('../Pane')
-
-module.exports = class ConfirmDialog extends FocusElement {
+export default class CancelDialog extends FocusElement {
   // A basic cancel dialog. Has one buttons, cancel, and a label.
   // The escape (esc) key can be used to exit the dialog (which sends a
   // 'cancelled' event, as the cancel button also does).
diff --git a/ui/form/ConfirmDialog.js b/ui/dialogs/ConfirmDialog.js
index 230230d..c0bcfae 100644
--- a/ui/form/ConfirmDialog.js
+++ b/ui/dialogs/ConfirmDialog.js
@@ -1,13 +1,10 @@
-const telc = require('../../util/telchars')
+import {Button, Form} from 'tui-lib/ui/controls'
+import {Label, Pane} from 'tui-lib/ui/presentation'
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const FocusElement = require('./FocusElement')
+import telc from 'tui-lib/util/telchars'
 
-const Button = require('./Button')
-const Form = require('./Form')
-const Label = require('../Label')
-const Pane = require('../Pane')
-
-module.exports = class ConfirmDialog extends FocusElement {
+export default class ConfirmDialog extends FocusElement {
   // A basic yes/no dialog. Has two buttons, confirm/cancel, and a label.
   // The escape (esc) key can be used to exit the dialog (which sends a
   // 'cancelled' event, as the cancel button also does).
diff --git a/ui/Dialog.js b/ui/dialogs/Dialog.js
index 0b77b12..19565f5 100644
--- a/ui/Dialog.js
+++ b/ui/dialogs/Dialog.js
@@ -1,10 +1,9 @@
-const FocusElement = require('./form/FocusElement')
+import {Pane} from 'tui-lib/ui/presentation'
+import {FocusElement} from 'tui-lib/ui/primitives'
 
-const Pane = require('./Pane')
+import telc from 'tui-lib/util/telchars'
 
-const telc = require('../util/telchars')
-
-module.exports = class Dialog extends FocusElement {
+export default class Dialog extends FocusElement {
   // A simple base dialog.
   //
   // Emits the 'cancelled' event when the cancel key (escape) is pressed,
diff --git a/ui/tools/FilePickerForm.js b/ui/dialogs/FilePickerForm.js
index 51d59a9..6414818 100644
--- a/ui/tools/FilePickerForm.js
+++ b/ui/dialogs/FilePickerForm.js
@@ -1,15 +1,11 @@
-const fs = require('fs')
-const util = require('util')
-const path = require('path')
+import {readdir, stat} from 'node:fs/promises'
+import path from 'node:path'
 
-const readdir = util.promisify(fs.readdir)
-const stat = util.promisify(fs.stat)
-const naturalSort = require('node-natural-sort')
+import {compare as naturalCompare} from 'natural-orderby'
 
-const Button = require('../form/Button')
-const ListScrollForm = require('../form/ListScrollForm')
+import {Button, ListScrollForm} from 'tui-lib/ui/controls'
 
-module.exports = class FilePickerForm extends ListScrollForm {
+export default class FilePickerForm extends ListScrollForm {
   fillItems(dirPath) {
     this.inputs = []
     this.children = []
@@ -33,14 +29,10 @@ module.exports = class FilePickerForm extends ListScrollForm {
           })
         }))
 
-        const sort = naturalSort({
-          properties: {
-            caseSensitive: false
-          }
-        })
+        const compare = naturalCompare()
         processedItems.sort((a, b) => {
           if (a.isDirectory === b.isDirectory) {
-            return sort(a.label, b.label)
+            return compare(a.label, b.label)
           } else {
             if (a.isDirectory) {
               return -1
@@ -85,4 +77,3 @@ module.exports = class FilePickerForm extends ListScrollForm {
       })
   }
 }
-
diff --git a/ui/tools/OpenFileDialog.js b/ui/dialogs/OpenFileDialog.js
index 43f2638..970e291 100644
--- a/ui/tools/OpenFileDialog.js
+++ b/ui/dialogs/OpenFileDialog.js
@@ -1,13 +1,12 @@
-const path = require('path')
+import path from 'node:path'
 
-const Button = require('../form/Button')
-const Dialog = require('../Dialog')
-const FilePickerForm = require('./FilePickerForm')
-const Form = require('../form/Form')
-const Label = require('../Label')
-const TextInput = require('../form/TextInput')
+import {Button, Form, TextInput} from 'tui-lib/ui/controls'
+import {Label} from 'tui-lib/ui/presentation'
 
-module.exports = class OpenFileDialog extends Dialog {
+import Dialog from './Dialog.js'
+import FilePickerForm from './FilePickerForm.js'
+
+export default class OpenFileDialog extends Dialog {
   constructor() {
     super()
 
@@ -42,7 +41,7 @@ module.exports = class OpenFileDialog extends Dialog {
 
     {
       const cb = append => p => {
-        this.filePathInput.setValue((path.relative(__dirname, p) || '.') + append)
+        this.filePathInput.setValue((path.relative(process.cwd(), p) || '.') + append)
       }
 
       this.filePickerForm.on('selected', cb(''))
@@ -54,7 +53,7 @@ module.exports = class OpenFileDialog extends Dialog {
     })
 
     const dir = (this.lastFilePath
-      ? path.relative(__dirname, path.dirname(this.lastFilePath)) + '/'
+      ? path.relative(process.cwd(), path.dirname(this.lastFilePath)) + '/'
       : './')
 
     this.filePathInput.setValue(dir)
@@ -107,4 +106,3 @@ module.exports = class OpenFileDialog extends Dialog {
     })
   }
 }
-
diff --git a/ui/dialogs/index.js b/ui/dialogs/index.js
new file mode 100644
index 0000000..5cb9f04
--- /dev/null
+++ b/ui/dialogs/index.js
@@ -0,0 +1,16 @@
+//
+// Import mapping:
+//
+//   controls, presentation, primitives ->
+//     CancelDialog
+//     ConfirmDialog
+//
+//     Dialog -> OpenFileDialog
+//     FilePickerForm -> OpenFileDialog
+//
+
+export {default as CancelDialog} from './CancelDialog.js'
+export {default as ConfirmDialog} from './ConfirmDialog.js'
+export {default as Dialog} from './Dialog.js'
+export {default as FilePickerForm} from './FilePickerForm.js'
+export {default as OpenFileDialog} from './OpenFileDialog.js'
diff --git a/ui/index.js b/ui/index.js
new file mode 100644
index 0000000..df6cae8
--- /dev/null
+++ b/ui/index.js
@@ -0,0 +1,4 @@
+export * as controls from './controls/index.js'
+export * as dialogs from './dialogs/index.js'
+export * as presentation from './presentation/index.js'
+export * as primitives from './primitives/index.js'
diff --git a/ui/HorizontalBox.js b/ui/presentation/HorizontalBox.js
index f92bf10..d396ec3 100644
--- a/ui/HorizontalBox.js
+++ b/ui/presentation/HorizontalBox.js
@@ -1,6 +1,6 @@
-const DisplayElement = require('./DisplayElement')
+import {DisplayElement} from 'tui-lib/ui/primitives'
 
-module.exports = class HorizontalBox extends DisplayElement {
+export default class HorizontalBox extends DisplayElement {
   // A box that will automatically lay out its children in a horizontal row.
 
   fixLayout() {
diff --git a/ui/Label.js b/ui/presentation/Label.js
index f2cd405..81223df 100644
--- a/ui/Label.js
+++ b/ui/presentation/Label.js
@@ -1,8 +1,8 @@
-const ansi = require('../util/ansi')
+import {DisplayElement} from 'tui-lib/ui/primitives'
 
-const DisplayElement = require('./DisplayElement')
+import * as ansi from 'tui-lib/util/ansi'
 
-module.exports = class Label extends DisplayElement {
+export default class Label extends DisplayElement {
   // A simple text display. Automatically adjusts size to fit text.
 
   constructor(text = '') {
diff --git a/ui/Pane.js b/ui/presentation/Pane.js
index b33a1b7..4769cf9 100644
--- a/ui/Pane.js
+++ b/ui/presentation/Pane.js
@@ -1,11 +1,11 @@
-const ansi = require('../util/ansi')
-const unic = require('../util/unichars')
+import {DisplayElement} from 'tui-lib/ui/primitives'
 
-const DisplayElement = require('./DisplayElement')
+import * as ansi from 'tui-lib/util/ansi'
+import unic from 'tui-lib/util/unichars'
 
-const Label = require('./Label')
+import Label from './Label.js'
 
-module.exports = class Pane extends DisplayElement {
+export default class Pane extends DisplayElement {
   // A simple rectangular framed pane.
 
   constructor() {
diff --git a/ui/Sprite.js b/ui/presentation/Sprite.js
index 701f1b8..49ee450 100644
--- a/ui/Sprite.js
+++ b/ui/presentation/Sprite.js
@@ -1,8 +1,8 @@
-const ansi = require('../util/ansi')
+import {DisplayElement} from 'tui-lib/ui/primitives'
 
-const DisplayElement = require('./DisplayElement')
+import * as ansi from 'tui-lib/util/ansi'
 
-module.exports = class Sprite extends DisplayElement {
+export default class Sprite extends DisplayElement {
   // "A sprite is a two-dimensional bitmap that is integrated into a larger
   // scene." - Wikipedia
   //
diff --git a/ui/WrapLabel.js b/ui/presentation/WrapLabel.js
index babf462..0ecc777 100644
--- a/ui/WrapLabel.js
+++ b/ui/presentation/WrapLabel.js
@@ -1,9 +1,10 @@
-const ansi = require('../util/ansi')
-const wrap = require('word-wrap')
+import wrap from 'word-wrap'
 
-const Label = require('./Label')
+import * as ansi from 'tui-lib/util/ansi'
 
-module.exports = class WrapLabel extends Label {
+import Label from './Label.js'
+
+export default class WrapLabel extends Label {
   // A word-wrapping text display. Given a width, wraps text to fit.
 
   constructor(...args) {
diff --git a/ui/presentation/index.js b/ui/presentation/index.js
new file mode 100644
index 0000000..9605d25
--- /dev/null
+++ b/ui/presentation/index.js
@@ -0,0 +1,15 @@
+//
+// Import mapping:
+//
+//   primitives ->
+//     HorizontalBox
+//     Sprite
+//
+//     Label -> Pane, WrapLabel
+//
+
+export {default as HorizontalBox} from './HorizontalBox.js'
+export {default as Label} from './Label.js'
+export {default as Pane} from './Pane.js'
+export {default as Sprite} from './Sprite.js'
+export {default as WrapLabel} from './WrapLabel.js'
diff --git a/ui/DisplayElement.js b/ui/primitives/DisplayElement.js
index 8720142..d2a0956 100644
--- a/ui/DisplayElement.js
+++ b/ui/primitives/DisplayElement.js
@@ -1,7 +1,6 @@
-const Element = require('./Element')
-const exception = require('../util/exception')
+import Element from './Element.js'
 
-module.exports = class DisplayElement extends Element {
+export default class DisplayElement extends Element {
   // A general class that handles dealing with screen coordinates, the tree
   // of elements, and other common stuff.
   //
@@ -11,6 +10,10 @@ module.exports = class DisplayElement extends Element {
   // It's a subclass of EventEmitter, so you can make your own events within
   // the logic of your subclass.
 
+  static drawValues = Symbol('drawValues')
+  static lastDrawValues = Symbol('lastDrawValues')
+  static scheduledDraw = Symbol('scheduledDraw')
+
   constructor() {
     super()
 
@@ -300,7 +303,3 @@ module.exports = class DisplayElement extends Element {
   get absTop()    { return this.absY }
   get absBottom() { return this.absY + this.h - 1 }
 }
-
-module.exports.drawValues = Symbol('drawValues')
-module.exports.lastDrawValues = Symbol('lastDrawValues')
-module.exports.scheduledDraw = Symbol('scheduledDraw')
diff --git a/ui/Element.js b/ui/primitives/Element.js
index c5beb59..fea8c03 100644
--- a/ui/Element.js
+++ b/ui/primitives/Element.js
@@ -1,6 +1,6 @@
-const EventEmitter = require('events')
+import EventEmitter from 'node:events'
 
-module.exports = class Element extends EventEmitter {
+export default class Element extends EventEmitter {
   // The basic class containing methods for working with an element hierarchy.
   // Generally speaking, you usually want to extend DisplayElement instead of
   // this class.
diff --git a/ui/form/FocusElement.js b/ui/primitives/FocusElement.js
index 23c2e02..2c23b1e 100644
--- a/ui/form/FocusElement.js
+++ b/ui/primitives/FocusElement.js
@@ -1,6 +1,6 @@
-const DisplayElement = require('../DisplayElement')
+import DisplayElement from './DisplayElement.js'
 
-module.exports = class FocusElement extends DisplayElement {
+export default class FocusElement extends DisplayElement {
   // A basic element that can receive cursor focus.
 
   constructor() {
diff --git a/ui/Root.js b/ui/primitives/Root.js
index 2b13203..a779637 100644
--- a/ui/Root.js
+++ b/ui/primitives/Root.js
@@ -1,19 +1,17 @@
-const ansi = require('../util/ansi')
-const telc = require('../util/telchars')
+import * as ansi from 'tui-lib/util/ansi'
+import telc from 'tui-lib/util/telchars'
 
-const DisplayElement = require('./DisplayElement')
+import DisplayElement from './DisplayElement.js'
 
-const Form = require('./form/Form')
-
-module.exports = class Root extends DisplayElement {
+export default class Root extends DisplayElement {
   // An element to be used as the root of a UI. Handles lots of UI and
   // socket stuff.
 
-  constructor(interfacer, writable = null) {
+  constructor(interfaceArg, writable = null) {
     super()
 
-    this.interfacer = interfacer
-    this.writable = writable || interfacer
+    this.interface = interfaceArg
+    this.writable = writable || interfaceArg
 
     this.selectedElement = null
 
@@ -21,7 +19,7 @@ module.exports = class Root extends DisplayElement {
 
     this.oldSelectionStates = []
 
-    interfacer.on('inputData', buf => this.handleData(buf))
+    this.interface.on('inputData', buf => this.handleData(buf))
 
     this.renderCount = 0
   }
@@ -174,11 +172,17 @@ module.exports = class Root extends DisplayElement {
 
     // If the element is part of a form, just be lazy and pass control to that
     // form...unless the form itself asked us to select the element!
+    //
     // TODO: This is so that if an element is selected, its parent form will
     // automatically see that and correctly update its curIndex... but what if
     // the element is an input of a form which is NOT its parent?
+    //
+    // XXX: We currently use a HUGE HACK instead of `instanceof` to avoid
+    // breaking the rule of import direction (controls -> primitives, never
+    // the other way around). This is bad for obvious reasons, but I haven't
+    // yet looked into what the correct approach would be.
     const parent = el.parent
-    if (!fromForm && parent instanceof Form && parent.inputs.includes(el)) {
+    if (!fromForm && parent.constructor.name === 'Form' && parent.inputs.includes(el)) {
       parent.selectInput(el)
       return
     }
diff --git a/ui/primitives/index.js b/ui/primitives/index.js
new file mode 100644
index 0000000..4e36452
--- /dev/null
+++ b/ui/primitives/index.js
@@ -0,0 +1,11 @@
+//
+// Import mapping:
+//
+//   Element ->
+//     DisplayElement -> FocusElement, Root
+//
+
+export {default as DisplayElement} from './DisplayElement.js'
+export {default as Element} from './Element.js'
+export {default as FocusElement} from './FocusElement.js'
+export {default as Root} from './Root.js'