diff options
-rw-r--r-- | ui.js | 48 | ||||
-rw-r--r-- | undo-manager.js | 42 |
2 files changed, 87 insertions, 3 deletions
diff --git a/ui.js b/ui.js index 4b64017..c4c2307 100644 --- a/ui.js +++ b/ui.js @@ -4,6 +4,7 @@ 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 UndoManager = require('./undo-manager') const ansi = require('./tui-lib/util/ansi') const Button = require('./tui-lib/ui/form/Button') @@ -30,7 +31,9 @@ class AppElement extends FocusElement { this.player = null this.recordStore = new RecordStore() + this.undoManager = new UndoManager() this.queueGrouplike = {isTheQueue: true, items: []} + this.editMode = false this.rootDirectory = process.env.HOME + '/.mtui' @@ -57,7 +60,8 @@ class AppElement extends FocusElement { this.queueListingElement.on('select (enter)', item => this.playGrouplikeItem(item, false)) this.queueListingElement.on('select (space)', item => this.handleSpacePressed( () => this.playGrouplikeItem(item, false))) - this.queueListingElement.on('remove', item => this.unqueueGrouplikeItem(item)) + this.queueListingElement.on('remove (backspace)', item => this.unqueueGrouplikeItem(item)) + this.queueListingElement.on('remove (x)', item => this.unqueueGrouplikeItem(item)) this.queueListingElement.on('shuffle', () => this.shuffleQueue()) this.queueListingElement.on('clear', () => this.clearQueue()) this.queueListingElement.on('select main listing', @@ -109,6 +113,36 @@ class AppElement extends FocusElement { grouplikeListing.on('queue (shuffled)', item => this.shuffleQueueGrouplikeItem(item)) grouplikeListing.on('queue (play next)', item => this.queueGrouplikeItem(item, true, this.playingTrack)) + grouplikeListing.on('remove (x)', item => { + if (this.editMode) { + const parent = item[parentSymbol] + const index = parent.items.indexOf(item) + + const updateDisplay = () => { + for (const grouplikeListing of this.tabber.tabberElements) { + if (grouplikeListing.grouplike === parent) { + grouplikeListing.loadGrouplike(parent) + } else if (grouplikeListing.grouplike === item) { + grouplikeListing.loadGrouplike(item) + } + } + } + + this.undoManager.pushAction({ + activate: () => { + parent.items.splice(index, 1) + delete item[parentSymbol] + updateDisplay() + }, + undo: () => { + parent.items.splice(index, 0, item) + item[parentSymbol] = parent + updateDisplay() + } + }) + } + }) + const handleSelectFromPathElement = item => { this.form.selectInput(grouplikeListing) if (isGroup(item)) { @@ -241,12 +275,18 @@ 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([5]))) { // Ctrl-E + this.editMode = !this.editMode } else if (keyBuf.equals(Buffer.from([15]))) { // ctrl-O this.openPlaylistDialog.open() } else if (keyBuf.equals(Buffer.from([20]))) { // ctrl-T this.cloneCurrentTab() } else if (keyBuf.equals(Buffer.from([23]))) { // ctrl-W this.tabber.closeTab(this.tabber.currentElement) + } else if (keyBuf.equals(Buffer.from(['j'.charCodeAt(0)]))) { + this.undoManager.undoLastAction() + } else if (keyBuf.equals(Buffer.from(['J'.charCodeAt(0)]))) { + this.undoManager.redoLastUndoneAction() } else if (this.tabber.isSelected && keyBuf.equals(Buffer.from(['t'.charCodeAt(0)]))) { this.tabber.nextTab() } else if (this.tabber.isSelected && keyBuf.equals(Buffer.from(['T'.charCodeAt(0)]))) { @@ -634,7 +674,7 @@ class GrouplikeListingElement extends FocusElement { if (this.grouplike.items.length) { for (const item of this.grouplike.items) { const itemElement = new GrouplikeItemElement(item, this.recordStore) - for (const evtName of ['download', 'remove', 'select (space)', 'select (enter)', 'queue', 'queue (shuffled)', 'queue (play next)']) { + for (const evtName of ['download', 'remove (backspace)', 'remove (x)', 'select (space)', 'select (enter)', 'queue', 'queue (shuffled)', 'queue (play next)']) { itemElement.on(evtName, () => this.emit(evtName, item)) } form.addInput(itemElement) @@ -796,7 +836,9 @@ class GrouplikeItemElement extends Button { } } } else if (telc.isBackspace(keyBuf)) { - this.emit('remove') + this.emit('remove (backspace)') + } else if (telc.isCharacter(keyBuf, 'x')) { + this.emit('remove (x)') } else if (telc.isSpace(keyBuf)) { this.emit('select (space)') } else if (telc.isEnter(keyBuf)) { diff --git a/undo-manager.js b/undo-manager.js new file mode 100644 index 0000000..4a042ad --- /dev/null +++ b/undo-manager.js @@ -0,0 +1,42 @@ +class UndoManager { + constructor() { + this.actionStack = [] + this.undoneStack = [] + } + + pushAction(action) { + this.undoneStack = [] + this.actionStack.push(action) + action.activate() + } + + undoLastAction() { + if (this.actionStack.length === 0) { + return + } + + const action = this.actionStack.pop() + this.undoneStack.push(action) + + action.undo() + } + + redoLastUndoneAction() { + if (this.undoneStack.length === 0) { + return + } + + const action = this.undoneStack.pop() + this.actionStack.push(action) + action.activate() + } + + get safeToPushAction() { + // Is it safe to push a new action? That is, since pushing a new action + // clears the undone actions stack, will any undone actions be lost? + + return this.undoStack.length === 0 + } +} + +module.exports = UndoManager |