From 45f804fef3766e3183610b93ac3d1ffb89f08408 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 22 Apr 2021 20:06:58 -0300 Subject: "party sources" ui (no socket functionality yet) --- backend.js | 16 ++++++++++++-- index.js | 5 ++++- socket.js | 34 +++++++++++++++++++++++++++++ todo.txt | 2 ++ ui.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 5 files changed, 109 insertions(+), 21 deletions(-) diff --git a/backend.js b/backend.js index 349d7f3..ddfb000 100644 --- a/backend.js +++ b/backend.js @@ -660,8 +660,8 @@ class Backend extends EventEmitter { } = {}) { super() - this.playerName = playerName; - this.playerOptions = playerOptions; + this.playerName = playerName + this.playerOptions = playerOptions if (playerOptions.length && !playerName) { throw new Error(`Must specify playerName to specify playerOptions`); @@ -858,6 +858,18 @@ class Backend extends EventEmitter { showLogMessage(messageInfo) { this.emit('log message', messageInfo) } + + loadPartyGrouplike(partyGrouplike) { + this.emit('got party grouplike', partyGrouplike) + } + + shareWithParty(item) { + this.emit('share with party', item) + } + + partyGrouplikeUpdated(partyGrouplike) { + this.emit('party grouplike updated', partyGrouplike) + } } module.exports = Backend diff --git a/index.js b/index.js index d628a20..d212a82 100755 --- a/index.js +++ b/index.js @@ -111,7 +111,10 @@ async function main() { const { appElement, dirtyTerminal, flushable, root } = await setupClient({ backend, interfacer: new CommandLineInterfacer(), - writable: process.stdout + writable: process.stdout, + appConfig: { + showPartyControls: !!(options['socket-server'] || options['socket-client']) + } }) appElement.on('quitRequested', () => { diff --git a/socket.js b/socket.js index 19fecb9..92a48bf 100644 --- a/socket.js +++ b/socket.js @@ -24,6 +24,8 @@ // clients / the server otherwise. const DEFAULT_NICKNAME = '(Unnamed)' +const originalSymbol = Symbol('Original item') + const EventEmitter = require('events') const net = require('net') @@ -40,6 +42,14 @@ const { silenceEvents } = require('./general-util') +const { + parentSymbol, + updateGroupFormat, + updateTrackFormat, + isTrack, + isGroup +} = require('./playlist-utils') + function serializeCommandToData(command) { // Turn a command into a string/buffer that can be sent over a socket. return JSON.stringify(command) @@ -422,8 +432,11 @@ function attachBackendToSocketClient(backend, client, { // All actual logic for instances of the mtui backend interacting with each // other through commands lives here. + const partyGrouplike = {name: 'My Party Sources', isPartySources: true, items: []} + backend.setAlwaysStartPaused(true) backend.setWaitWhenDonePlaying(true) + backend.loadPartyGrouplike(partyGrouplike) function logCommand(command) { const nickToMessage = nickname => `\x1b[32;1m${nickname}\x1b[0m` @@ -744,6 +757,27 @@ function attachBackendToSocketClient(backend, client, { topItem: saveItemReference(topItem) }) }) + + backend.on('share with party', origItem => { + let newItem = { + ...origItem, + [parentSymbol]: partyGrouplike, + [originalSymbol]: origItem + } + + if (isGroup(newItem)) { + newItem = updateGroupFormat(newItem) + } else if (isTrack(newItem)) { + newItem = updateTrackFormat(newItem) + } else { + return + } + + if (partyGrouplike.items.every(x => x[originalSymbol] !== origItem)) { + partyGrouplike.items.push(newItem) + backend.partyGrouplikeUpdated(partyGrouplike) + } + }) } function attachSocketServerToBackend(server, backend) { diff --git a/todo.txt b/todo.txt index ed7c830..fd024d7 100644 --- a/todo.txt +++ b/todo.txt @@ -579,3 +579,5 @@ TODO: "Challenge 1 (Tricks)" etc in FP World 3 are "Challenge (Tricks)"! Bad. TODO: Pressing next track (shift+N) on the last track should start the first track, if the queue is being looped. + +TODO: Tabber tab list should be accessible via tab (key). diff --git a/ui.js b/ui.js index 8d89722..fb0916e 100644 --- a/ui.js +++ b/ui.js @@ -206,7 +206,8 @@ class AppElement extends FocusElement { canSuspend: true, menubarColor: 4, // blue showTabberPane: true, - stopPlayingUponQuit: true + stopPlayingUponQuit: true, + showPartyControls: false }, config) // TODO: Move edit mode stuff to the backend! @@ -456,6 +457,8 @@ class AppElement extends FocusElement { 'handleAddedQueuePlayer', 'handleRemovedQueuePlayer', 'handleLogMessage', + 'handleGotPartyGrouplike', + 'handlePartyGrouplikeUpdated', 'handleSetLoopQueueAtEnd' ]) { this[key] = this[key].bind(this) @@ -528,6 +531,8 @@ class AppElement extends FocusElement { this.backend.on('added queue player', this.handleAddedQueuePlayer) this.backend.on('removed queue player', this.handleRemovedQueuePlayer) this.backend.on('log message', this.handleLogMessage) + this.backend.on('got party grouplike', this.handleGotPartyGrouplike) + this.backend.on('party grouplike updated', this.handlePartyGrouplikeUpdated) this.backend.on('set-loop-queue-at-end', this.handleSetLoopQueueAtEnd) } @@ -536,6 +541,8 @@ class AppElement extends FocusElement { this.backend.removeListener('added queue player', this.handleAddedQueuePlayer) this.backend.removeListener('removed queue player', this.handleRemovedQueuePlayer) this.backend.removeListener('log message', this.handleLogMessage) + this.backend.removeListener('got party grouplike', this.handleGotPartyGrouplike) + this.backend.removeListener('party grouplike updated', this.handlePartyGrouplikeUpdated) this.backend.removeListener('set-loop-queue-at-end', this.handleSetLoopQueueAtEnd) } @@ -554,6 +561,18 @@ class AppElement extends FocusElement { this.log.newLogMessage(messageInfo) } + handleGotPartyGrouplike(partyGrouplike) { + this.newPartyTab(partyGrouplike) + } + + handlePartyGrouplikeUpdated(partyGrouplike) { + for (const grouplikeListing of this.tabber.tabberElements) { + if (grouplikeListing.grouplike === partyGrouplike) { + grouplikeListing.loadGrouplike(partyGrouplike, false) + } + } + } + handleSetLoopQueueAtEnd() { this.updateQueueLengthLabel() } @@ -888,6 +907,10 @@ class AppElement extends FocusElement { this.queueListingElement.selectAndShow(item) } + shareWithParty(item) { + this.backend.shareWithParty(item) + } + replaceMark(items) { this.markGrouplike.items = items.slice(0) // Don't share the array! :) this.emitMarkChanged() @@ -1124,7 +1147,9 @@ class AppElement extends FocusElement { }) } + const rootGroup = getItemPath(item)[0] const hasNotesFile = !!getCorrespondingFileForItem(item, '.txt') + if (listing.grouplike.isTheQueue && isTrack(item)) { return [ item[parentSymbol] && this.tabberPane.visible && {label: 'Reveal', action: () => this.reveal(item)}, @@ -1169,23 +1194,30 @@ class AppElement extends FocusElement { {divider: true}, */ - canControlQueue && isPlayable(item) && {element: this.whereControl}, - canControlQueue && isGroup(item) && {element: this.orderControl}, - canControlQueue && isPlayable(item) && {label: 'Play!', action: emitControls(true)}, - canControlQueue && isPlayable(item) && {label: 'Queue!', action: emitControls(false)}, - {divider: true}, - - canProcessMetadata && isGroup(item) && {label: 'Process metadata (new entries)', action: () => setTimeout(() => this.processMetadata(item, false))}, - canProcessMetadata && isGroup(item) && {label: 'Process metadata (reprocess)', action: () => setTimeout(() => this.processMetadata(item, true))}, - canProcessMetadata && isTrack(item) && {label: 'Process metadata', action: () => setTimeout(() => this.processMetadata(item, true))}, - isOpenable(item) && item.url.endsWith('.json') && {label: 'Open (JSON Playlist)', action: () => this.openSpecialOrThroughSystem(item)}, - isOpenable(item) && {label: 'Open (System)', action: () => this.openThroughSystem(item)}, - /* - !hasNotesFile && isPlayable(item) && {label: 'Create notes file', action: () => this.editNotesFile(item, true)}, - hasNotesFile && isPlayable(item) && {label: 'Edit notes file', action: () => this.editNotesFile(item, true)}, - */ - canControlQueue && isPlayable(item) && {label: 'Remove from queue', action: () => this.unqueue(item)}, - {divider: true}, + ...((this.config.showPartyControls && !rootGroup.isPartySources) + ? [ + {label: 'Share with party', action: () => this.shareWithParty(item)}, + {divider: true} + ] + : [ + canControlQueue && isPlayable(item) && {element: this.whereControl}, + canControlQueue && isGroup(item) && {element: this.orderControl}, + canControlQueue && isPlayable(item) && {label: 'Play!', action: emitControls(true)}, + canControlQueue && isPlayable(item) && {label: 'Queue!', action: emitControls(false)}, + {divider: true}, + + canProcessMetadata && isGroup(item) && {label: 'Process metadata (new entries)', action: () => setTimeout(() => this.processMetadata(item, false))}, + canProcessMetadata && isGroup(item) && {label: 'Process metadata (reprocess)', action: () => setTimeout(() => this.processMetadata(item, true))}, + canProcessMetadata && isTrack(item) && {label: 'Process metadata', action: () => setTimeout(() => this.processMetadata(item, true))}, + isOpenable(item) && item.url.endsWith('.json') && {label: 'Open (JSON Playlist)', action: () => this.openSpecialOrThroughSystem(item)}, + isOpenable(item) && {label: 'Open (System)', action: () => this.openThroughSystem(item)}, + /* + !hasNotesFile && isPlayable(item) && {label: 'Create notes file', action: () => this.editNotesFile(item, true)}, + hasNotesFile && isPlayable(item) && {label: 'Edit notes file', action: () => this.editNotesFile(item, true)}, + */ + canControlQueue && isPlayable(item) && {label: 'Remove from queue', action: () => this.unqueue(item)}, + {divider: true}, + ]), ...(item === this.markGrouplike ? [{label: 'Deselect all', action: () => this.unmarkAll()}] @@ -1547,6 +1579,11 @@ class AppElement extends FocusElement { }) } + newPartyTab(partyGrouplike) { + const listing = this.newGrouplikeListing() + listing.loadGrouplike(partyGrouplike) + } + cloneCurrentTab() { const grouplike = this.tabber.currentElement.grouplike const listing = this.newGrouplikeListing() -- cgit 1.3.0-6-gf8a5