« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
path: root/ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui.js')
-rw-r--r--ui.js166
1 files changed, 166 insertions, 0 deletions
diff --git a/ui.js b/ui.js
index 4fe1d8a..8d912a2 100644
--- a/ui.js
+++ b/ui.js
@@ -1,9 +1,13 @@
+const { getAllCrawlersForArg } = require('./crawlers')
 const { getDownloaderFor } = require('./downloaders')
 const { getPlayer } = require('./players')
 const { parentSymbol, isGroup, isTrack, getItemPath, getItemPathString, flattenGrouplike } = require('./playlist-utils')
 const { shuffleArray } = require('./general-util')
+const processSmartPlaylist = require('./smart-playlist')
+
 const ansi = require('./tui-lib/util/ansi')
 const Button = require('./tui-lib/ui/form/Button')
+const Dialog = require('./tui-lib/ui/Dialog')
 const DisplayElement = require('./tui-lib/ui/DisplayElement')
 const FocusElement = require('./tui-lib/ui/form/FocusElement')
 const Form = require('./tui-lib/ui/form/Form')
@@ -11,6 +15,7 @@ const Label = require('./tui-lib/ui/Label')
 const ListScrollForm = require('./tui-lib/ui/form/ListScrollForm')
 const Pane = require('./tui-lib/ui/Pane')
 const RecordStore = require('./record-store')
+const TextInput = require('./tui-lib/ui/form/TextInput')
 const WrapLabel = require('./tui-lib/ui/WrapLabel')
 const telc = require('./tui-lib/util/telchars')
 const unic = require('./tui-lib/util/unichars')
@@ -92,6 +97,64 @@ class AppElement extends FocusElement {
 
     this.playbackInfoElement = new PlaybackInfoElement()
     this.playbackPane.addChild(this.playbackInfoElement)
+
+    // Dialogs
+
+    this.openPlaylistDialog = new OpenPlaylistDialog()
+    this.setupDialog(this.openPlaylistDialog)
+
+    this.openPlaylistDialog.on('source selected', source => this.handlePlaylistSource(source))
+
+    this.alertDialog = new AlertDialog()
+    this.setupDialog(this.alertDialog)
+  }
+
+  async handlePlaylistSource(source) {
+    this.openPlaylistDialog.close()
+    this.alertDialog.showMessage('Opening playlist...', false)
+
+    let grouplike
+    try {
+      grouplike = await this.openPlaylist(source)
+    } catch (error) {
+      if (error === 'unknown argument') {
+        this.alertDialog.showMessage('Could not figure out how to load a playlist from: ' + source)
+      } else if (typeof error === 'string') {
+        this.alertDialog.showMessage(error)
+      } else {
+        throw error
+      }
+
+      return
+    }
+
+    this.alertDialog.close()
+    this.root.select(this.form)
+
+    grouplike = await processSmartPlaylist(grouplike)
+    this.grouplikeListingElement.loadGrouplike(grouplike)
+  }
+
+  openPlaylist(arg) {
+    const crawlers = getAllCrawlersForArg(arg)
+
+    if (crawlers.length === 0) {
+      throw 'unknown argument'
+    }
+
+    const crawler = crawlers[0]
+
+    return crawler(arg)
+  }
+
+  setupDialog(dialog) {
+    dialog.visible = false
+    this.addChild(dialog)
+
+    dialog.on('cancelled', () => {
+      dialog.close()
+      this.root.select(this.form)
+    })
   }
 
   async setup() {
@@ -155,6 +218,8 @@ class AppElement extends FocusElement {
     } else if (telc.isCharacter(keyBuf, '2') && this.queueListingElement.selectable) {
       this.form.curIndex = this.form.inputs.indexOf(this.queueListingElement)
       this.form.updateSelectedElement()
+    } else if (keyBuf.equals(Buffer.from([15]))) { // ctrl-O
+      this.openPlaylistDialog.open()
     } else {
       super.keyPressed(keyBuf)
     }
@@ -843,4 +908,105 @@ class PlaybackInfoElement extends DisplayElement {
   }
 }
 
+class OpenPlaylistDialog extends Dialog {
+  constructor() {
+    super()
+
+    this.label = new Label('Enter a playlist source:')
+    this.pane.addChild(this.label)
+
+    this.form = new Form()
+    this.pane.addChild(this.form)
+
+    this.input = new TextInput()
+    this.form.addInput(this.input)
+
+    this.button = new Button('Open')
+    this.form.addInput(this.button)
+
+    this.button.on('pressed', () => {
+      if (this.input.value) {
+        this.emit('source selected', this.input.value)
+      }
+    })
+  }
+
+  opened() {
+    this.input.setValue('')
+    this.form.curIndex = 0
+    this.form.updateSelectedElement()
+  }
+
+  fixLayout() {
+    super.fixLayout()
+
+    this.pane.w = Math.min(60, this.contentW)
+    this.pane.h = 5
+    this.pane.centerInParent()
+
+    this.label.centerInParent()
+    this.label.y = 0
+
+    this.form.w = this.pane.contentW
+    this.form.h = 2
+    this.form.y = 1
+
+    this.input.w = this.form.contentW
+
+    this.button.centerInParent()
+    this.button.y = 1
+  }
+
+  focused() {
+    this.root.select(this.form)
+  }
+}
+
+class AlertDialog extends Dialog {
+  constructor() {
+    super()
+
+    this.label = new Label()
+    this.pane.addChild(this.label)
+
+    this.button = new Button('Close')
+    this.button.on('pressed', () => {
+      if (this.canClose) {
+        this.emit('cancelled')
+      }
+    })
+    this.pane.addChild(this.button)
+  }
+
+  focused() {
+    this.root.select(this.button)
+  }
+
+  showMessage(message, canClose = true) {
+    this.canClose = canClose
+    this.label.text = message
+    this.button.text = canClose ? 'Close' : '(Hold on...)'
+    this.open()
+  }
+
+  fixLayout() {
+    super.fixLayout()
+
+    this.pane.w = Math.min(this.label.w + 4, this.contentW)
+    this.pane.h = 4
+    this.pane.centerInParent()
+
+    this.label.centerInParent()
+    this.label.y = 0
+
+    this.button.fixLayout()
+    this.button.centerInParent()
+    this.button.y = 1
+  }
+
+  keyPressed() {
+    // Don't handle the escape key.
+  }
+}
+
 module.exports.AppElement = AppElement