resonatecoop / stream

Monorepo for Resonate Coop player
https://stream.resonate.coop
GNU General Public License v3.0
171 stars 24 forks source link

Re-order tracks on playlists #133

Open peterklingelhofer opened 2 years ago

peterklingelhofer commented 2 years ago

Operating system

macOS 11.6.2

Browser name and version

Firefox 95.02

Expected behavior

Should be able to drag and drop to re-arrange songs in playlists created by me.

Actual behavior

Unable to drag and drop to re-arrange songs in playlists created by me.

Steps to reproduce behavior

Create a new playlist, add a few songs, go to your playlist, attempt to click and drag a song, no changes occur.

whatSocks commented 2 years ago

this functionality exists in the (private) uploader repo. If you (or someone) is open to copy+pasting it over maybe @hakanto can bless you with access?

peterklingelhofer commented 2 years ago

this functionality exists in the (private) uploader repo. If you (or someone) is open to copy+pasting it over maybe @hakanto can bless you with access?

That's great news. I'll close this issue out then!

hakanto commented 2 years ago

I'd keep this issue open! Sure it's available on the Dashboard, but the goal is definitely to make this a feature on the Player interface itself.

I'm pretty sure anyone can log in with their listener account at dash.resonate.coop to reorder playlists in the meantime. Try it out!

peterklingelhofer commented 2 years ago

I'd keep this issue open! Sure it's available on the Dashboard, but the goal is definitely to make this a feature on the Player interface itself.

I'm pretty sure anyone can log in with their listener account at dash.resonate.coop to reorder playlists in the meantime. Try it out!

Ah, now I understand better. Well I'd be happy to tackle this, the functionality/response does look really smooth on the Dashboard site. Since it seems that repo is private, it would be really handy if someone merely sent me a code snippet of the drag and drop functionality going on in that repo so I can emulate that here. Cheers!

hakanto commented 2 years ago

@auggod @jackhajb Could one of y'all pass this code snippet along to @peterklingelhofer or grant access to the uploader repo?

Adding this functionality to the Player would be a big quality of life improvement and power-up our active listeners to bring on more users. And it seems like a pretty easy change! :sun_behind_large_cloud:

auggod commented 2 years ago

@peterklingelhofer I have granted you access to the uploader repo. The SortableList component is located here: https://github.com/resonatecoop/uploader/blob/develop/app/components/sortable-list/index.js

It uses SortableJS (https://sortablejs.github.io/Sortable/)

To update the tracks order within the playlist, you'll use the [PUT] /{id}/items request from API v2 service. See: https://api.resonate.coop/v2/docs?urls.primaryName=User%20Trackgroups%20API%20Service

hakanto commented 2 years ago

I'm super super super excited about this

peterklingelhofer commented 2 years ago

Ran into this error, and implemented the solution, but window is undefined. Tried installing window and jsdom but received wild errors from SortableJS thinking they weren't legit HTML elements. Posting my diff below in case anyone has any ideas (ignore the import changes aside from adding sortablejs all I did was alphabetize them):

diff --git a/package.json b/package.json
index 82c7398..1e80410 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
     "build": "run-s build:beta",
     "build:beta": "npm run build:ts && npm run build --workspace beta-player",
     "build:embed": "npm run build:ts && npm run build --workspace embed",
-       "build:ts": "tsc -b --force"
+    "build:ts": "tsc -b --force"
   },
   "workspaces": [
     "packages/*",
@@ -36,6 +36,7 @@
     "postcss-nested": "^4.1.2",
     "postcss-preset-env": "^6.6.0",
     "postcss-reporter": "^6.0.1",
+    "sortablejs": "^1.14.0",
     "tape": "^5.3.1",
     "tape-run": "^9.0.0"
   },
diff --git a/packages/playlist-component/index.js b/packages/playlist-component/index.js
index f8d76c9..37d4161 100644
--- a/packages/playlist-component/index.js
+++ b/packages/playlist-component/index.js
@@ -1,15 +1,16 @@
+const icon = require('@resonate/icon-element')
+const Loader = require('@resonate/play-count-component')
+const { iconFill } = require('@resonate/theme-skins')
+const Track = require('@resonate/track-component')
 const assert = require('assert')
-const html = require('nanohtml')
+const { isNode } = require('browser-or-node')
 const Component = require('nanocomponent')
 const compare = require('nanocomponent/compare')
+const html = require('nanohtml')
 const nanostate = require('nanostate')
-const clone = require('shallow-clone')
-const Loader = require('@resonate/play-count-component')
-const { isNode } = require('browser-or-node')
-const Track = require('@resonate/track-component')
 const ResponsiveContainer = require('resize-observer-component')
-const icon = require('@resonate/icon-element')
-const { iconFill } = require('@resonate/theme-skins')
+const clone = require('shallow-clone')
+const Sortable = require('sortablejs')

 /*
  * Component for listing tracks (generally 50 tracks max)
@@ -107,31 +108,69 @@ class Playlist extends Component {
         `
       },
       data: () => {
-        const container = new ResponsiveContainer()
+        if (window === undefined) {
+          return html`<div></div>`
+        }

-        return container.render(html`
-          <ul class="playlist flex flex-auto flex-column list ma0 pa0">
-            ${this.local.playlist.map((item, index) => {
-              const cid = `${this._name}-track-item-${item.track.id}`
-              const trackItem = new Track(cid, this.state, this.emit)
-
-              return trackItem.render({
-                type: this.local.type,
-                showArtist: this.local.type !== 'album' ? true : !!this.local.various,
-                hideMenu: this.local.hideMenu,
-                hideCount: this.local.hideCount,
-                count: item.count,
-                fav: item.fav,
-                favorite: item.favorite,
-                index: index + 1,
-                src: item.url,
-                track: item.track,
-                trackGroup: item.track_group,
-                playlist: this.local.playlist
+        const container = new ResponsiveContainer()
+        const list = html`<div id="sortableContainer"></div>`
+        document.getElementById('sortableContainer').innerHTML = html`
+        <ul class="playlist flex flex-auto flex-column list ma0 pa0">
+        ${this.local.playlist.map((item, index) => {
+          const cid = `${this._name}-track-item-${item.track.id}`
+          const trackItem = new Track(cid, this.state, this.emit)
+
+          return trackItem.render({
+            type: this.local.type,
+            showArtist: this.local.type !== 'album' ? true : !!this.local.various,
+            hideMenu: this.local.hideMenu,
+            hideCount: this.local.hideCount,
+            count: item.count,
+            fav: item.fav,
+            favorite: item.favorite,
+            index: index + 1,
+            src: item.url,
+            track: item.track,
+            trackGroup: item.track_group,
+            playlist: this.local.playlist
+          })
+        })}
+        </ul>`
+
+        const sortable = Sortable.create(list, {
+          handle: '.handle',
+          onEnd: (e) => {
+            // get new order as array of numbers (item.index)
+            const order = sortable
+              .toArray()
+              .map(s => Number(s))
+
+            // sort original array based on new array
+            // then update item.index
+            this.local.items = this.local.items
+              .sort((a, b) => {
+                return order.indexOf(a.index) < order.indexOf(b.index) ? -1 : 1
+              })
+              .map((item, index) => {
+                item.index = index + 1
+                return item
               })
-            })}
-          </ul>
-        `)
+
+            // soft render template
+            this._update()
+
+            const tracks = this.local.items.map((item) => {
+              return {
+                index: item.index,
+                track_id: item.track.id
+              }
+            })
+
+            this.emit('trackgroup:items:update', { tracks })
+          }
+        })
+
+        return container.render(sortable)
       }
     }[this.local.machine.state]