« get me outta code hell

tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ui/Root.js7
-rw-r--r--ui/form/Form.js12
-rw-r--r--ui/form/ListScrollForm.js4
-rw-r--r--ui/tools/FilePickerForm.js88
-rw-r--r--ui/tools/OpenFileDialog.js110
5 files changed, 214 insertions, 7 deletions
diff --git a/ui/Root.js b/ui/Root.js
index 22c2c97..d306a41 100644
--- a/ui/Root.js
+++ b/ui/Root.js
@@ -90,4 +90,11 @@ module.exports = class Root extends DisplayElement {
 
     this.cursorMoved()
   }
+
+  isChildOrSelfSelected(el) {
+    if (!this.selected) return false
+    if (this.selected === el) return true
+    if (this.selected.directAncestors.includes(el)) return true
+    return false
+  }
 }
diff --git a/ui/form/Form.js b/ui/form/Form.js
index 708de1f..ce064aa 100644
--- a/ui/form/Form.js
+++ b/ui/form/Form.js
@@ -80,15 +80,17 @@ module.exports = class Form extends FocusElement {
     this.updateSelectedElement()
   }
 
-  firstInput() {
+  firstInput(selectForm = true) {
     this.curIndex = 0
 
-    this.updateSelectedElement()
+    if (selectForm || (
+      this.root.isChildOrSelfSelected && this.root.isChildOrSelfSelected(this)
+    )) {
+      this.updateSelectedElement()
+    }
   }
 
   focused() {
-    if (this.root.select) {
-      this.root.select(this.inputs[this.curIndex])
-    }
+    this.updateSelectedElement()
   }
 }
diff --git a/ui/form/ListScrollForm.js b/ui/form/ListScrollForm.js
index daa640a..a06efda 100644
--- a/ui/form/ListScrollForm.js
+++ b/ui/form/ListScrollForm.js
@@ -106,10 +106,10 @@ module.exports = class ListScrollForm extends Form {
     this.fixLayout()
   }
 
-  firstInput() {
+  firstInput(...args) {
     this.scrollItems = 0
 
-    super.firstInput()
+    super.firstInput(...args)
   }
 
   getItemPos(item) {
diff --git a/ui/tools/FilePickerForm.js b/ui/tools/FilePickerForm.js
new file mode 100644
index 0000000..51d59a9
--- /dev/null
+++ b/ui/tools/FilePickerForm.js
@@ -0,0 +1,88 @@
+const fs = require('fs')
+const util = require('util')
+const path = require('path')
+
+const readdir = util.promisify(fs.readdir)
+const stat = util.promisify(fs.stat)
+const naturalSort = require('node-natural-sort')
+
+const Button = require('../form/Button')
+const ListScrollForm = require('../form/ListScrollForm')
+
+module.exports = class FilePickerForm extends ListScrollForm {
+  fillItems(dirPath) {
+    this.inputs = []
+    this.children = []
+
+    const button = new Button('..Loading..')
+    this.addInput(button)
+    this.firstInput(false)
+
+    readdir(dirPath).then(
+      async items => {
+        this.removeInput(button)
+
+        const processedItems = await Promise.all(items.map(item => {
+          const itemPath = path.resolve(dirPath, item)
+          return stat(itemPath).then(s => {
+            return {
+              path: itemPath,
+              label: item + (s.isDirectory() ? '/' : ''),
+              isDirectory: s.isDirectory()
+            }
+          })
+        }))
+
+        const sort = naturalSort({
+          properties: {
+            caseSensitive: false
+          }
+        })
+        processedItems.sort((a, b) => {
+          if (a.isDirectory === b.isDirectory) {
+            return sort(a.label, b.label)
+          } else {
+            if (a.isDirectory) {
+              return -1
+            } else {
+              return +1
+            }
+          }
+        })
+
+        processedItems.unshift({
+          path: path.resolve(dirPath, '..'),
+          label: '../',
+          isDirectory: true
+        })
+
+        let y = 0
+        for (const item of processedItems) {
+          const itemButton = new Button(item.label)
+          itemButton.y = y
+          y++
+          this.addInput(itemButton)
+
+          itemButton.on('pressed', () => {
+            if (item.isDirectory) {
+              this.emit('browsingDirectory', item.path)
+              this.fillItems(item.path)
+            } else {
+              this.emit('selected', item.path)
+            }
+          })
+        }
+
+        console.log('HALLO.', false)
+        this.firstInput(false)
+        this.fixLayout()
+      },
+      () => {
+        button.text = 'Failed to read path! (Cancel)'
+        button.on('pressed', () => {
+          this.emit('canceled')
+        })
+      })
+  }
+}
+
diff --git a/ui/tools/OpenFileDialog.js b/ui/tools/OpenFileDialog.js
new file mode 100644
index 0000000..92bd4af
--- /dev/null
+++ b/ui/tools/OpenFileDialog.js
@@ -0,0 +1,110 @@
+const path = require('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')
+
+module.exports = class OpenFileDialog extends Dialog {
+  constructor() {
+    super()
+
+    this.visible = false
+
+    this.form = new Form()
+    this.pane.addChild(this.form)
+
+    this.filePathLabel = new Label('Enter file path:')
+    this.filePathInput = new TextInput()
+    this.openButton = new Button('Open')
+    this.cancelButton = new Button('Cancel')
+
+    this.filePickerForm = new FilePickerForm()
+    this.filePickerForm.captureTab = false
+
+    this.form.addChild(this.filePathLabel)
+    this.form.addInput(this.filePathInput)
+    this.form.addInput(this.filePickerForm)
+    this.form.addInput(this.openButton)
+    this.form.addInput(this.cancelButton)
+
+    this._resolve = null
+
+    this.openButton.on('pressed', () => {
+      this._resolve(this.filePathInput.value)
+    })
+
+    this.filePathInput.on('value', () => {
+      this._resolve(this.filePathInput.value)
+    })
+
+    {
+      const cb = append => p => {
+        this.filePathInput.setValue((path.relative(__dirname, p) || '.') + append)
+      }
+
+      this.filePickerForm.on('selected', cb(''))
+      this.filePickerForm.on('browsingDirectory', cb('/'))
+    }
+
+    this.cancelButton.on('pressed', () => {
+      this._resolve(null)
+    })
+
+    const dir = (this.lastFilePath
+      ? path.relative(__dirname, path.dirname(this.lastFilePath)) + '/'
+      : './')
+
+    this.filePathInput.setValue(dir)
+    this.filePickerForm.fillItems(dir)
+  }
+
+  fixLayout() {
+    super.fixLayout()
+
+    this.pane.w = Math.min(this.contentW, 40)
+    this.pane.h = Math.min(this.contentH, 20)
+    this.pane.centerInParent()
+
+    this.form.w = this.pane.contentW
+    this.form.h = this.pane.contentH
+
+    this.filePathLabel.x = 0
+    this.filePathLabel.y = 0
+
+    this.filePathInput.x = this.filePathLabel.right + 2
+    this.filePathInput.y = this.filePathLabel.y
+    this.filePathInput.w = this.form.contentW - this.filePathInput.x
+
+    this.filePickerForm.x = 0
+    this.filePickerForm.y = this.filePathInput.y + 2
+    this.filePickerForm.w = this.form.contentW
+    this.filePickerForm.h = this.form.contentH - this.filePickerForm.y - 2
+
+    this.openButton.x = 0
+    this.openButton.y = this.form.contentH - 1
+
+    this.cancelButton.x = this.openButton.right + 2
+    this.cancelButton.y = this.openButton.y
+  }
+
+  focused() {
+    this.form.firstInput()
+  }
+
+  go() {
+    this.visible = true
+    this.root.select(this)
+
+    return new Promise(resolve => {
+      this._resolve = resolve
+    }).then(filePath => {
+      this.visible = false
+      this.lastFilePath = filePath
+      return filePath
+    })
+  }
+}
+