From 2ec5450bcfb659daf3316d9a47825a64abf85431 Mon Sep 17 00:00:00 2001 From: Florrie Date: Tue, 3 Jul 2018 01:17:29 -0300 Subject: Ctrl-O to open a prompt for loading a playlist --- README.md | 1 + index.js | 23 ++------ package-lock.json | 5 ++ package.json | 1 + todo.txt | 5 ++ tui-lib | 2 +- ui.js | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 183 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b5bbc47..d233c74 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ playlist.json file (usually generated by http-music or downloaded from online). * Escape - stop playing the current track * Right - seek ahead * Left - seek back +* Ctrl+O - open a playlist from a source (like a /path/to/a/folder or a YouTube playlist URL) (you can also just pass this source to `mtui`) * **In the main listing:** * Enter - if the selected item is a group, enter it; otherwise play it * Backspace - leave the current group (if in one) diff --git a/index.js b/index.js index 59c91ec..694a384 100755 --- a/index.js +++ b/index.js @@ -65,31 +65,16 @@ async function main() { source: ['crawl-local', process.env.HOME + '/Music'] } - if (process.argv[2]) { - console.log('Opening playlist...') - - const crawlers = getAllCrawlersForArg(process.argv[2]) - - if (crawlers.length === 0) { - console.error(`No suitable playlist crawler for "${process.argv[2]}".`) - process.exit(1) - } - - const crawler = crawlers[0] - - if (crawlers.length > 1) { - console.warn(`More than one suitable crawler for "${process.argv[2]}" - using ${crawler.name}.`) - } - - grouplike = await crawler(process.argv[2]) - } - grouplike = await processSmartPlaylist(grouplike) appElement.grouplikeListingElement.loadGrouplike(grouplike) root.select(appElement.form) + if (process.argv[2]) { + appElement.handlePlaylistSource(process.argv[2]) + } + const flushable = new Flushable(process.stdout, true) flushable.resizeScreen(size) flushable.shouldShowCompressionStatistics = process.argv.includes('--show-ansi-stats') diff --git a/package-lock.json b/package-lock.json index 4352577..efb723d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,11 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-3.2.0.tgz", "integrity": "sha1-5WfP3LMk1OeuWSKjcAraXeh5oMo=" }, + "expand-home-dir": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/expand-home-dir/-/expand-home-dir-0.0.3.tgz", + "integrity": "sha1-ct6KBIbMKKO71wRjU5iCW1tign0=" + }, "fifo-js": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fifo-js/-/fifo-js-2.1.0.tgz", diff --git a/package.json b/package.json index fe701c3..b3855ac 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "GPL-3.0", "dependencies": { "command-exists": "^1.2.6", + "expand-home-dir": "0.0.3", "fifo-js": "^2.1.0", "js-base64": "^2.4.5", "mkdirp": "^0.5.1", diff --git a/todo.txt b/todo.txt index c1f7b51..9ad8667 100644 --- a/todo.txt +++ b/todo.txt @@ -49,3 +49,8 @@ TODO: There's some weird glitch where, if downloaderArg is missing (=== ""), 72_food (by Jake Chudnow) plays. (That's the first thing returned by readdir, I suppose.) (Done!) + +TODO: Mouse support, obviously. + +TODO: Ctrl-O'ing a playlist sets the left-pane's selected index to the second + item, for some reason. (Regardless of what you had selected before..) diff --git a/tui-lib b/tui-lib index 9743a9f..1076bd5 160000 --- a/tui-lib +++ b/tui-lib @@ -1 +1 @@ -Subproject commit 9743a9fcf7b8e5e5dd98cd1c74aefa36a3f70a94 +Subproject commit 1076bd5e65658a0e846a7892cee787ade7660bb2 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 -- cgit 1.3.0-6-gf8a5