« get me outta code hell

PARTY MODE - mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2019-07-07 18:49:54 -0300
committerFlorrie <towerofnix@gmail.com>2019-07-07 18:49:54 -0300
commita0d5dfcd12b27811dad61b2fea5b73c359f54cf5 (patch)
treefa52f00c74b1e8bbd6d5efe1bca51ca783591a68
parentd7f31bd2541e5d37057136bc34e3f9603803ab57 (diff)
PARTY MODE
-rwxr-xr-xindex.js1
-rw-r--r--telnet-server.js13
-rw-r--r--ui.js131
3 files changed, 134 insertions, 11 deletions
diff --git a/index.js b/index.js
index 10e0696..a36eceb 100755
--- a/index.js
+++ b/index.js
@@ -106,6 +106,7 @@ async function main() {
   if (process.argv.includes('--telnet-server')) {
     telnetServer = new TelnetServer(backend)
     await telnetServer.listen(1244)
+    appElement.attachAsServerHost(telnetServer)
   }
 
   if (process.argv.includes('--stress-test')) {
diff --git a/telnet-server.js b/telnet-server.js
index 6111d9d..d8d8e07 100644
--- a/telnet-server.js
+++ b/telnet-server.js
@@ -1,5 +1,6 @@
 'use strict'
 
+const EventEmitter = require('events')
 const net = require('net')
 const setupClient = require('./client')
 
@@ -9,8 +10,10 @@ const {
   }
 } = require('./tui-lib')
 
-class TelnetServer {
+class TelnetServer extends EventEmitter {
   constructor(backend) {
+    super()
+
     this.backend = backend
     this.server = new net.Server(socket => this.handleConnection(socket))
     this.sockets = []
@@ -28,7 +31,7 @@ class TelnetServer {
       interfacer,
       appConfig: {
         canControlPlayback: false,
-        canControlQueue: false,
+        canControlQueue: true,
         canProcessMetadata: false,
         canSuspend: false,
         showLeftPane: true,
@@ -37,6 +40,8 @@ class TelnetServer {
       }
     })
 
+    appElement.attachAsServerClient(this)
+
     let closed = false
 
     const quit = (msg = 'See you!') => {
@@ -52,16 +57,18 @@ class TelnetServer {
     appElement.on('quitRequested', quit)
 
     socket.on('close', () => {
+      clearInterval(renderInterval)
       if (!closed) {
-        clearInterval(renderInterval)
         flushable.end()
         closed = true
         this.sockets.splice(this.sockets.indexOf(socket), 1)
       }
+      this.emit('left', socket)
     })
 
     socket.quit = quit
     this.sockets.push(socket)
+    this.emit('joined', socket)
   }
 
   disconnectAllSockets(msg) {
diff --git a/ui.js b/ui.js
index 0a1cbca..378cd77 100644
--- a/ui.js
+++ b/ui.js
@@ -138,6 +138,8 @@ class AppElement extends FocusElement {
     super()
 
     this.backend = backend
+    this.telnetServer = null
+    this.isPartyHost = false
 
     this.bindListeners()
     this.attachListeners()
@@ -212,6 +214,21 @@ class AppElement extends FocusElement {
     this.playbackInfoElement.on('seek ahead', () => this.backend.seekAhead(5))
     this.playbackInfoElement.on('toggle pause', () => this.backend.togglePause())
 
+    this.partyTop = new DisplayElement()
+    this.partyBottom = new DisplayElement()
+    this.addChild(this.partyTop)
+    this.addChild(this.partyBottom)
+    this.partyTop.visible = false
+    this.partyBottom.visible = false
+
+    this.partyTopBanner = new PartyBanner(1)
+    this.partyBottomBanner = new PartyBanner(-1)
+    this.partyTop.addChild(this.partyTopBanner)
+    this.partyBottom.addChild(this.partyBottomBanner)
+
+    this.partyLabel = new Label('')
+    this.partyTop.addChild(this.partyLabel)
+
     // Dialogs
 
     this.openPlaylistDialog = new OpenPlaylistDialog()
@@ -666,12 +683,42 @@ class AppElement extends FocusElement {
 
     this.menubar.fixLayout()
 
-    const mainHeight = this.contentH - 6
+    let topY = this.contentH
+
+    if (this.partyBottom.visible) {
+      this.partyBottom.w = this.contentW
+      this.partyBottom.h = 1
+      this.partyBottom.x = 0
+      this.partyBottom.y = topY - this.partyBottom.h
+      topY = this.partyBottom.top
+      this.partyBottomBanner.w = this.partyBottom.w
+    }
+
+    this.playbackPane.w = this.contentW
+    this.playbackPane.h = 5
+    this.playbackPane.x = 0
+    this.playbackPane.y = topY - this.playbackPane.h
+    topY = this.playbackPane.top
+
+    let bottomY = 1
+
+    if (this.partyTop.visible) {
+      this.partyTop.w = this.contentW
+      this.partyTop.h = 1
+      this.partyTop.x = 0
+      this.partyTop.y = 1
+      bottomY = this.partyTop.bottom
+
+      this.partyTopBanner.w = this.partyTop.w
+      this.partyTopBanner.y = this.partyTop.contentH - 1
+
+      this.alignPartyLabel()
+    }
 
     if (this.paneLeft.visible) {
       this.paneLeft.w = Math.max(Math.floor(0.8 * this.contentW), this.contentW - 80)
-      this.paneLeft.h = mainHeight
-      this.paneLeft.y = 1
+      this.paneLeft.y = bottomY
+      this.paneLeft.h = topY - this.paneLeft.y
       this.paneRight.x = this.paneLeft.right
       this.paneRight.w = this.contentW - this.paneLeft.right
     } else {
@@ -679,11 +726,9 @@ class AppElement extends FocusElement {
       this.paneRight.w = this.contentW
     }
 
-    this.paneRight.y = 1
-    this.paneRight.h = mainHeight
-    this.playbackPane.y = mainHeight + 1
-    this.playbackPane.w = this.contentW
-    this.playbackPane.h = this.contentH - this.playbackPane.y
+    this.paneRight.y = bottomY
+    this.paneRight.h = topY - this.paneRight.y
+    topY = this.paneRight.y
 
     this.tabber.fillParent()
 
@@ -702,6 +747,42 @@ class AppElement extends FocusElement {
     this.playbackInfoElement.fillParent()
   }
 
+  alignPartyLabel() {
+    this.partyLabel.centerInParent()
+    this.partyLabel.y = 0
+  }
+
+  attachAsServerHost(telnetServer) {
+    this.isPartyHost = true
+    this.attachAsServer(telnetServer)
+  }
+
+  attachAsServerClient(telnetServer) {
+    this.isPartyHost = false
+    this.attachAsServer(telnetServer)
+  }
+
+  attachAsServer(telnetServer) {
+    this.telnetServer = telnetServer
+    this.updatePartyLabel()
+
+    this.telnetServer.on('joined', () => this.updatePartyLabel())
+    this.telnetServer.on('left', () => this.updatePartyLabel())
+
+    this.partyTop.visible = true
+    this.partyBottom.visible = true
+    this.fixLayout()
+  }
+
+  updatePartyLabel() {
+    const clients = this.telnetServer.sockets.length
+    const clientsMsg = clients === 1 ? '1-ish connection' : `${clients}-ish connections`
+    let msg = `${process.env.USER} playing for ${clientsMsg}`
+
+    this.partyLabel.text = `  ${msg}  `
+    this.alignPartyLabel()
+  }
+
   keyPressed(keyBuf) {
     if (keyBuf[0] === 0x03) { // Ctrl-C
       this.shutdown()
@@ -2787,4 +2868,38 @@ class Menubar extends ListScrollForm {
   }
 }
 
+class PartyBanner extends DisplayElement {
+  constructor(direction) {
+    super()
+
+    this.direction = direction
+  }
+
+  drawTo(writable) {
+    writable.write(ansi.moveCursor(this.absTop, this.absLeft))
+
+    const timerNum = Date.now() / 2000 * this.direction
+    let lastAttribute = ''
+    const updateAttribute = offsetNum => {
+      const attr = (Math.cos(offsetNum - timerNum) < 0 ? '\x1b[0;1m' : '\x1b[0;2m')
+      if (attr === lastAttribute) {
+        return ''
+      } else {
+        lastAttribute = attr
+        return attr
+      }
+    }
+    let str = new Array(this.w).fill('0').map((_, i) => {
+      const offsetNum = i / this.w * 2 * Math.PI
+      return (
+        updateAttribute(offsetNum) +
+        (Math.sin(offsetNum + timerNum) < 0 ? '-' : '*')
+      )
+    }).join('')
+
+    writable.write(str)
+    writable.write(ansi.resetAttributes())
+  }
+}
+
 module.exports = AppElement