From a0fc339cf4b326ffcb6ec7de5f4001833b71087b Mon Sep 17 00:00:00 2001 From: Florrie Date: Wed, 12 Sep 2018 14:10:49 -0300 Subject: ctrl-F, '/': jump to an item by entering its name --- README.md | 1 + todo.txt | 7 ++++++ tui-lib | 2 +- ui.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f6a10c..f245848 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 - jump to a track or group by entering (part of) its name * 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`) * t and T (shift+T) - switch between playlist tabs * Ctrl+T - open the current playlist in a new tab (so, clone the current tab) diff --git a/todo.txt b/todo.txt index 25c8369..bde9a89 100644 --- a/todo.txt +++ b/todo.txt @@ -117,3 +117,10 @@ TODO: A "remove from queue" option for tracks and groups, which removes them TODO: After the end of a shuffled queue, the next song from the group of the last track is played (and so on, until the group is empty). This seems definitely wrong. + +TODO: Show a preview of where "Jump to" will go while typing. + +TODO: Entering more than one key "at once" into a text input element will only + move the cursor right by one character, not by the length of the inputted + text. (This is an issue when pasting or spamming the keyboard.) Should be + fixed in tui-lib. diff --git a/tui-lib b/tui-lib index 68d5c75..e2f8306 160000 --- a/tui-lib +++ b/tui-lib @@ -1 +1 @@ -Subproject commit 68d5c75dce1c950468d07241261fb9f8c8e3d39b +Subproject commit e2f830680c1a6e9f28ad305b105caf5cdf092e63 diff --git a/ui.js b/ui.js index 3da7f5c..1d8d2da 100644 --- a/ui.js +++ b/ui.js @@ -708,6 +708,16 @@ class GrouplikeListingElement extends Form { } }) + this.jumpElement = new ListingJumpElement() + this.addInput(this.jumpElement) + this.jumpElement.visible = false + + this.jumpElement.on('cancel', () => { + this.hideJumpElement() + }) + + this.jumpElement.on('value', value => this.handleJumpValue(value)) + this.pathElement = new PathElement() this.addInput(this.pathElement) @@ -727,9 +737,15 @@ class GrouplikeListingElement extends Form { this.form.y = this.commentLabel.bottom this.form.h -= this.commentLabel.h this.form.h -= 1 // For the path element + if (this.jumpElement.visible) this.form.h -= 1 + + this.form.fixLayout() // Respond to being resized this.pathElement.y = this.contentH - 1 this.pathElement.w = this.contentW + + this.jumpElement.y = this.pathElement.y - 1 + this.jumpElement.w = this.contentW } selected() { @@ -744,6 +760,8 @@ class GrouplikeListingElement extends Form { keyPressed(keyBuf) { if (telc.isBackspace(keyBuf)) { this.loadParentGrouplike() + } else if (telc.isCharacter(keyBuf, '/') || keyBuf[0] === 6) { // '/', ctrl-F + this.showJumpElement() } else { return super.keyPressed(keyBuf) } @@ -837,6 +855,40 @@ class GrouplikeListingElement extends Form { this.form.selectAndShow(item) } + handleJumpValue(value) { + // Don't perform the search if the user didn't enter anything. + if (value.length) { + const lower = value.toLowerCase() + const getName = inp => (inp.item && inp.item.name) ? inp.item.name.toLowerCase().trim() : '' + // TODO: Search past the current index, for repeated searches? + const startsIndex = this.form.inputs.findIndex(inp => getName(inp).startsWith(lower)) + const includesIndex = this.form.inputs.findIndex(inp => getName(inp).includes(lower)) + const matchedIndex = startsIndex >= 0 ? startsIndex : includesIndex + + if (matchedIndex >= 0) { + this.form.curIndex = matchedIndex + this.form.scrollSelectedElementIntoView() + } else { + // TODO: Feedback that the search failed.. right now we just close the + // jump-to menu, which might not be right. + } + } + + this.hideJumpElement() + } + + showJumpElement() { + this.jumpElement.visible = true + this.root.select(this.jumpElement) + this.fixLayout() + } + + hideJumpElement() { + this.jumpElement.visible = false + this.root.select(this) + this.fixLayout() + } + get tabberLabel() { if (this.grouplike) { return this.grouplike.name || 'Unnamed group' @@ -872,7 +924,6 @@ class GrouplikeListingForm extends ListScrollForm { } selectAndShow(item) { - // TODO: Make sure this is still working. const index = this.inputs.findIndex(inp => inp.item === item) if (index >= 0) { this.curIndex = index @@ -998,6 +1049,37 @@ class GrouplikeItemElement extends Button { } } +class ListingJumpElement extends Form { + constructor() { + super() + + this.label = new Label('Jump to: ') + this.addChild(this.label) + + this.input = new TextInput() + this.addInput(this.input) + + this.input.on('value', value => { + this.emit('value', value) + }) + + this.input.on('cancel', () => { + this.emit('cancel') + }) + } + + selected() { + this.input.value = '' + this.input.keepCursorInRange() + this.root.select(this.input) + } + + fixLayout() { + this.input.x = this.label.right + this.input.w = this.contentW - this.input.x + } +} + class PathElement extends ListScrollForm { constructor() { super('horizontal') -- cgit 1.3.0-6-gf8a5