From b47f2968742b77378791f171c819957546b4fc7d 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 | 26 +++++++++++++++++++++++ todo.txt | 2 ++ ui.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 5 files changed, 100 insertions(+), 21 deletions(-) diff --git a/backend.js b/backend.js index b629424..86da76e 100644 --- a/backend.js +++ b/backend.js @@ -690,8 +690,8 @@ export default 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`); @@ -888,4 +888,16 @@ export default 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) + } } diff --git a/index.js b/index.js index efee187..21bd80a 100755 --- a/index.js +++ b/index.js @@ -127,7 +127,10 @@ async function main() { const { appElement, dirtyTerminal, flushable, root } = await setupClient({ backend, screenInterface: new CommandLineInterface(), - 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 c6c3d19..5f3ded0 100644 --- a/socket.js +++ b/socket.js @@ -40,6 +40,8 @@ import { // clients / the server otherwise. const DEFAULT_NICKNAME = '(Unnamed)' +const originalSymbol = Symbol('Original item') + function serializeCommandToData(command) { // Turn a command into a string/buffer that can be sent over a socket. return JSON.stringify(command) @@ -422,8 +424,11 @@ export 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 +749,27 @@ export 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) + } + }) } export function attachSocketServerToBackend(server, backend) { diff --git a/todo.txt b/todo.txt index bfb6e98..f2a6562 100644 --- a/todo.txt +++ b/todo.txt @@ -691,6 +691,8 @@ TODO: Apparently pressing any key while the UI is booting up will make the screen is resized. I think we're interrupting a control sequence somehow, and that isn't being handled very well? +TODO: Tabber tab list should be accessible via tab (key). + TODO: Pressing escape while you've got items selected should deselect those items, rather than stop playback! ...Or SHOULD IT??? Well, yes. But it's still handy to not be locked out of stopping playback altogether. diff --git a/ui.js b/ui.js index 65ca2d1..b9c417a 100644 --- a/ui.js +++ b/ui.js @@ -195,7 +195,8 @@ export default class AppElement extends FocusElement { themeColor: 4, // blue seekToStartThreshold: 3, showTabberPane: true, - stopPlayingUponQuit: true + stopPlayingUponQuit: true, + showPartyControls: false }, config) // TODO: Move edit mode stuff to the backend! @@ -478,7 +479,9 @@ export default class AppElement extends FocusElement { 'handleAddedQueuePlayer', 'handleRemovedQueuePlayer', 'handleSetLoopQueueAtEnd', - 'handleLogMessage' + 'handleLogMessage', + 'handleGotPartyGrouplike', + 'handlePartyGrouplikeUpdated' ]) { this[key] = this[key].bind(this) } @@ -551,6 +554,8 @@ export default class AppElement extends FocusElement { this.backend.on('removed queue player', this.handleRemovedQueuePlayer) this.backend.on('set-loop-queue-at-end', this.handleSetLoopQueueAtEnd) this.backend.on('log message', this.handleLogMessage) + this.backend.on('got party grouplike', this.handleGotPartyGrouplike) + this.backend.on('party grouplike updated', this.handlePartyGrouplikeUpdated) } removeBackendListeners() { @@ -559,6 +564,8 @@ export default class AppElement extends FocusElement { this.backend.removeListener('removed queue player', this.handleRemovedQueuePlayer) this.backend.removeListener('set-loop-queue-at-end', this.handleSetLoopQueueAtEnd) this.backend.removeListener('log message', this.handleLogMessage) + this.backend.removeListener('got party grouplike', this.handleGotPartyGrouplike) + this.backend.removeListener('party grouplike updated', this.handlePartyGrouplikeUpdated) } handleAddedQueuePlayer(queuePlayer) { @@ -580,6 +587,18 @@ export default 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) + } + } + } + async handlePlayingDetails(track, oldTrack, startTime, queuePlayer) { const PIE = this.getPlaybackInfoElementForQueuePlayer(queuePlayer) if (PIE) { @@ -942,6 +961,10 @@ export default 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() @@ -1474,6 +1497,7 @@ export default class AppElement extends FocusElement { // TODO: Implement this! :P // const isMarked = false + const rootGroup = getItemPath(item)[0] // const hasNotesFile = !!getCorrespondingFileForItem(item, '.txt') const timestampsItem = this.hasTimestampsFile(item) && (this.timestampsExpanded(item, listing) ? {label: 'Collapse saved timestamps', action: () => this.collapseTimestamps(item, listing)} @@ -1525,22 +1549,29 @@ export default class AppElement extends FocusElement { // anyMarked && !this.isReal && {label: 'Paste', action: () => this.emit('paste')}, // No "above" or "elow" in the label because the "fake" item/row will be replaced (it'll disappear, since there'll be an item in the group) // {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)}, - isTrack(item) && isQueued && {label: 'Reveal in queue', action: () => this.revealInQueue(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)}, + isTrack(item) && isQueued && {label: 'Reveal in queue', action: () => this.revealInQueue(item)}, + {divider: true}, + ]), timestampsItem, ...(item === this.markGrouplike @@ -1919,6 +1950,11 @@ export default 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