« get me outta code hell

"party sources" ui (no socket functionality yet) - mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <towerofnix@gmail.com>2021-04-22 20:06:58 -0300
committer(quasar) nebula <towerofnix@gmail.com>2021-04-22 20:06:58 -0300
commit45f804fef3766e3183610b93ac3d1ffb89f08408 (patch)
treea8591c4886ee9109ae21a5ee18a9c282a7435998
parent1ca458abc3503ba951c5e19362a54208a285833c (diff)
"party sources" ui (no socket functionality yet)
-rw-r--r--backend.js16
-rwxr-xr-xindex.js5
-rw-r--r--socket.js34
-rw-r--r--todo.txt2
-rw-r--r--ui.js73
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()