allow playlist add/remove from frontend
authormarcelveldt <marcelvanderveldt@MacBook-Silvia.local>
Sun, 27 Oct 2019 22:29:08 +0000 (23:29 +0100)
committermarcelveldt <marcelvanderveldt@MacBook-Silvia.local>
Sun, 27 Oct 2019 22:29:08 +0000 (23:29 +0100)
music_assistant/database.py
music_assistant/music_manager.py
music_assistant/web/components/addtoplaylistdialog.vue.js [deleted file]
music_assistant/web/components/contextmenu.vue.js
music_assistant/web/components/listdialog.vue.js [deleted file]
music_assistant/web/components/listviewItem.vue.js
music_assistant/web/components/player.vue.js
music_assistant/web/index.html
music_assistant/web/lib/utils.js
music_assistant/web/pages/browse.vue.js
music_assistant/web/pages/playlistdetails.vue.js

index 7f087f1f901e5889c090d7a12ccd6a15d30f05fd..ebff6bed81d82b7be9396714818728a01f035cd2 100755 (executable)
@@ -77,6 +77,7 @@ class Database():
         if MediaType.Album in media_types:
             result["albums"] = await self.albums(sql_query, limit=limit)
         if MediaType.Track in media_types:
+            sql_query = 'SELECT * FROM tracks WHERE name LIKE "%s"' % searchquery
             result["tracks"] = await self.tracks(sql_query, limit=limit)
         if MediaType.Playlist in media_types:
             result["playlists"] = await self.playlists(sql_query, limit=limit)
@@ -101,9 +102,9 @@ class Database():
     async def library_tracks(self, provider=None, limit=100000, offset=0, orderby='name') -> List[Track]:
         ''' get all library tracks, optionally filtered by provider'''
         if provider != None:
-            sql_query = ' WHERE track_id in (SELECT item_id FROM library_items WHERE provider = "%s" AND media_type = %d)' % (provider,MediaType.Track)
+            sql_query = 'SELECT * FROM tracks WHERE track_id in (SELECT item_id FROM library_items WHERE provider = "%s" AND media_type = %d)' % (provider,MediaType.Track)
         else:
-            sql_query = ' WHERE track_id in (SELECT item_id FROM library_items WHERE media_type = %d)' % MediaType.Track
+            sql_query = 'SELECT * FROM tracks WHERE track_id in (SELECT item_id FROM library_items WHERE media_type = %d)' % MediaType.Track
         return await self.tracks(sql_query, limit=limit, offset=offset, orderby=orderby)
     
     async def playlists(self, filter_query=None, provider=None, limit=100000, offset=0, orderby='name') -> List[Playlist]:
@@ -406,47 +407,46 @@ class Database():
         LOGGER.debug('added album %s (%s) to database: %s' %(album.name, album.provider_ids, album_id))
         return album_id
 
-    async def tracks(self, filter_query=None, limit=100000, offset=0, orderby='name', fulldata=True, db=None) -> List[Track]:
+    async def tracks(self, custom_query=None, limit=100000, offset=0, orderby='name', fulldata=True) -> List[Track]:
         ''' fetch all track records from table'''
         tracks = []
         sql_query = 'SELECT * FROM tracks'
-        if filter_query:
-            sql_query += ' ' + filter_query
+        if custom_query:
+            sql_query = custom_query
         sql_query += ' ORDER BY %s' % orderby
         if limit:
             sql_query += ' LIMIT %d OFFSET %d' %(limit, offset)
-        if not db:
-            db = await aiosqlite.connect(self.dbfile)
-            should_close_db = True
-        else:
-            should_close_db = False
-        async with db.execute(sql_query) as cursor:
-            db_rows = await cursor.fetchall()
-        for db_row in db_rows:
-            track = Track()
-            track.item_id = db_row[0]
-            track.name = db_row[1]
-            track.album = await self.album(db_row[2], fulldata=False, db=db)
-            track.artists = await self.__get_track_artists(track.item_id, db, fulldata=False)
-            track.duration = db_row[3]
-            track.version = db_row[4]
-            track.disc_number = db_row[5]
-            track.track_number = db_row[6]
-            track.in_library = await self.__get_library_providers(track.item_id, MediaType.Track, db)
-            track.external_ids = await self.__get_external_ids(track.item_id, MediaType.Track, db)
-            track.provider_ids = await self.__get_prov_ids(track.item_id, MediaType.Track, db)
-            if fulldata:
-                track.metadata = await self.__get_metadata(track.item_id, MediaType.Track, db)
-                track.tags = await self.__get_tags(track.item_id, MediaType.Track, db)
-            tracks.append(track)
-        if should_close_db:
-            await db.close()
+        async with aiosqlite.connect(self.dbfile) as db:
+            db.row_factory = aiosqlite.Row
+            async with db.execute(sql_query) as cursor:
+                for db_row in await cursor.fetchall():
+                    track = Track()
+                    track.item_id = db_row["track_id"]
+                    track.name = db_row["name"]
+                    track.album = await self.album(db_row["album_id"], fulldata=False, db=db)
+                    track.artists = await self.__get_track_artists(track.item_id, db, fulldata=False)
+                    track.duration = db_row["duration"]
+                    track.version = db_row["version"]
+                    track.disc_number = db_row["disc_number"]
+                    track.track_number = db_row["track_number"]
+                    try:
+                        track.position = db_row["position"]
+                    except IndexError:
+                        pass
+                    track.in_library = await self.__get_library_providers(track.item_id, MediaType.Track, db)
+                    track.external_ids = await self.__get_external_ids(track.item_id, MediaType.Track, db)
+                    track.provider_ids = await self.__get_prov_ids(track.item_id, MediaType.Track, db)
+                    if fulldata:
+                        track.metadata = await self.__get_metadata(track.item_id, MediaType.Track, db)
+                        track.tags = await self.__get_tags(track.item_id, MediaType.Track, db)
+                    tracks.append(track)
         return tracks
 
     async def track(self, track_id:int, fulldata=True) -> Track:
         ''' get track record by id '''
         track_id = try_parse_int(track_id)
-        tracks = await self.tracks('WHERE track_id = %s' % track_id, fulldata=fulldata)
+        sql_query = "SELECT * FROM tracks WHERE track_id = %s" % track_id
+        tracks = await self.tracks(sql_query, fulldata=fulldata)
         if not tracks:
             return None
         return tracks[0]
@@ -504,7 +504,7 @@ class Database():
     async def artist_tracks(self, artist_id, limit=1000000, offset=0, orderby='name') -> List[Track]:
         ''' get all library tracks for the given artist '''
         artist_id = try_parse_int(artist_id)
-        sql_query = ' WHERE track_id in (SELECT track_id FROM track_artists WHERE artist_id = %d)' % artist_id
+        sql_query = 'SELECT * FROM tracks WHERE track_id in (SELECT track_id FROM track_artists WHERE artist_id = %d)' % artist_id
         return await self.tracks(sql_query, limit=limit, offset=offset, orderby=orderby, fulldata=False)
 
     async def artist_albums(self, artist_id, limit=1000000, offset=0, orderby='name') -> List[Album]:
@@ -514,22 +514,10 @@ class Database():
 
     async def playlist_tracks(self, playlist_id:int, limit=100000, offset=0, orderby='position') -> List[Track]:
         ''' get playlist tracks for the given playlist_id '''
-        playlist_id = try_parse_int(playlist_id)
-        playlist_tracks = []
-        sql_query = 'SELECT track_id, position FROM playlist_tracks WHERE playlist_id = ? ORDER BY track_id'
-        if limit:
-            sql_query += ' LIMIT %d OFFSET %d' %(limit, offset)
-        async with aiosqlite.connect(self.dbfile) as db:
-            async with db.execute(sql_query, (playlist_id,)) as cursor:
-                db_rows = await cursor.fetchall()
-            playlist_track_ids = [str(item[0]) for item in db_rows]
-            sql_query = 'WHERE track_id in (%s)' % ','.join(playlist_track_ids)
-            tracks = await self.tracks(sql_query, orderby='track_id', db=db, fulldata=False)
-            for index, track in enumerate(tracks):
-                track.position = db_rows[index][1]
-                playlist_tracks.append(track)
-            playlist_tracks = sorted(playlist_tracks, key=operator.attrgetter(orderby), reverse=False)
-        return playlist_tracks
+        sql_query = """SELECT *, playlist_tracks.position FROM tracks
+                    INNER JOIN playlist_tracks USING(track_id)
+                    WHERE playlist_tracks.playlist_id=%s""" % playlist_id
+        return await self.tracks(sql_query, orderby=orderby, limit=limit, offset=offset, fulldata=False)
 
     async def add_playlist_track(self, playlist_id:int, track_id, position):
         ''' add playlist track to playlist '''
index c7533542e957c88a81216b20e82da0425df48043..0dab61b658d47964a7afb41ea99d5b4bdd263486 100755 (executable)
@@ -263,7 +263,8 @@ class MusicManager():
             new_pos = len(cur_playlist_tracks) + index
             await self.mass.db.add_playlist_track(playlist.item_id, track.item_id, new_pos)
         # actually add the tracks to the playlist on the provider
-        return await self.providers[playlist_prov['provider']].add_playlist_tracks(playlist_prov['item_id'], track_ids_to_add)
+        if track_ids_to_add:
+            return await self.providers[playlist_prov['provider']].add_playlist_tracks(playlist_prov['item_id'], track_ids_to_add)
 
     async def remove_playlist_tracks(self, playlist_id, tracks:List[Track]):
         ''' remove tracks from playlist '''
@@ -287,7 +288,8 @@ class MusicManager():
             # remove track from db playlist
             await self.mass.db.remove_playlist_track(playlist.item_id, track.item_id)
         # actually remove the tracks from the playlist on the provider
-        return await self.providers[prov_playlist_provider_id].add_playlist_tracks(prov_playlist_playlist_id, track_ids_to_remove)
+        if track_ids_to_remove:
+            return await self.providers[prov_playlist_provider_id].add_playlist_tracks(prov_playlist_playlist_id, track_ids_to_remove)
 
     @run_periodic(3600)
     async def sync_music_providers(self):
@@ -400,8 +402,10 @@ class MusicManager():
                 item_provider = prov_mapping['provider']
                 prov_item_id = prov_mapping['item_id']
                 db_item = await self.providers[item_provider].track(prov_item_id, lazy=False)
-                cur_db_ids.append(db_item.item_id)
-                if not db_item.item_id in prev_db_ids:
+                if not db_item.item_id in cur_db_ids:
+                    cur_db_ids.append(db_item.item_id)
+                    # always add/update because position could be changed
+                    # note: we ignore duplicate tracks in the same playlist
                     await self.mass.db.add_playlist_track(db_playlist_id, db_item.item_id, pos)
             pos += 1
         # process playlist track deletions
diff --git a/music_assistant/web/components/addtoplaylistdialog.vue.js b/music_assistant/web/components/addtoplaylistdialog.vue.js
deleted file mode 100644 (file)
index 97eba4a..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-Vue.component("addtoplaylistdialog", {\r
-       template: `\r
-       <v-dialog :value="value" @input="$emit('input', $event)" max-width="500px">\r
-        <v-card>\r
-                       <v-list>\r
-                               <v-subheader class="title">{{ header }}</v-subheader>\r
-                               <v-subheader>{{ subheader }}</v-subheader>\r
-                               <div v-for="(item, index) in menuItems">\r
-                                       <v-list-tile avatar @click="itemCommand(item.action)">\r
-                                               <v-list-tile-avatar>\r
-                                                       <v-icon>{{item.icon}}</v-icon>\r
-                                               </v-list-tile-avatar>\r
-                                               <v-list-tile-content>\r
-                                                       <v-list-tile-title>{{ $t(item.label) }}</v-list-tile-title>\r
-                                               </v-list-tile-content>\r
-                                       </v-list-tile>\r
-                                       <v-divider></v-divider>\r
-                               </div>\r
-                       </v-list>\r
-               </v-card>\r
-      </v-dialog>\r
-`,\r
-       props: ['value', 'active_player'],\r
-       data () { \r
-               return {\r
-                       mediaPlayItems: [\r
-                               {\r
-                                       label: "play_now",\r
-                                       action: "play",\r
-                                       icon: "play_circle_outline"\r
-                               },\r
-                               {\r
-                                       label: "play_next",\r
-                                       action: "next",\r
-                                       icon: "queue_play_next"\r
-                               },\r
-                               {\r
-                                       label: "add_queue",\r
-                                       action: "add",\r
-                                       icon: "playlist_add"\r
-                               }\r
-                       ],\r
-                       showTrackInfoItem: {\r
-                                       label: "show_info",\r
-                                       action: "info",\r
-                                       icon: "info"\r
-                       },\r
-                       addToPlaylistItem: {\r
-                                       label: "add_playlist",\r
-                                       action: "add_playlist",\r
-                                       icon: "add_circle_outline"\r
-                       },\r
-                       removeFromPlaylistItem: {\r
-                                       label: "remove_playlist",\r
-                                       action: "remove_playlist",\r
-                                       icon: "remove_circle_outline"\r
-                       },\r
-                       playerQueueItems: [\r
-                       ],\r
-                       playlists: [],\r
-                       show_playlists: false\r
-               }\r
-       },\r
-       mounted() { },\r
-       created() { },\r
-       computed: {\r
-               menuItems() {\r
-                       if (!this.$globals.contextmenuitem)\r
-                               return [];\r
-                       else if (this.show_playlists)\r
-                               return this.playlists;\r
-                       else if (this.$globals.contextmenucontext == 'playerqueue')\r
-                               return this.playerQueueItems; // TODO: return queue contextmenu\r
-                       else if (this.$globals.contextmenucontext == 'trackdetails') {\r
-                               // track details\r
-                               var items = [];\r
-                               items.push(...this.mediaPlayItems);\r
-                               items.push(this.addToPlaylistItem);\r
-                               return items;\r
-                       }\r
-                       else if (this.$globals.contextmenuitem.media_type == 3) {\r
-                               // track item in list\r
-                               var items = [];\r
-                               items.push(...this.mediaPlayItems);\r
-                               items.push(this.showTrackInfoItem);\r
-                               items.push(this.addToPlaylistItem);\r
-                               if (this.$globals.contextmenucontext.is_editable)\r
-                                       items.push(this.removeFromPlaylistItem);\r
-                               return items;\r
-                       }\r
-                       else {\r
-                               // all other playable media\r
-                               return this.mediaPlayItems;\r
-                       }\r
-               },\r
-               header() {\r
-                       return !!this.$globals.contextmenuitem ? this.$globals.contextmenuitem.name : '';\r
-               },\r
-               subheader() {\r
-                       if (!!this.active_player)\r
-                               return this.$t('play_on') + this.active_player.name;\r
-                       else\r
-                               return "";\r
-               }\r
-       },\r
-       methods: { \r
-               itemCommand(cmd) {\r
-                       console.log('itemCommand: ' + cmd);\r
-               if (cmd == 'info') {\r
-                               // show track info\r
-                               this.$router.push({ path: '/tracks/' + this.$globals.contextmenuitem.item_id, query: {provider: this.$globals.contextmenuitem.provider}})\r
-                               this.$globals.showcontextmenu = false;\r
-                       }       \r
-                       else if (cmd == 'add_playlist') {\r
-                               // add to playlist\r
-                               console.log(`add ${this.$globals.contextmenuitem.name} to playlist?`);\r
-                               this.getPlaylists();\r
-                               this.show_playlists = true;\r
-                       }\r
-                       else if (cmd == 'remove_playlist') {\r
-                               // remove track from playlist\r
-                               this.playlistAddRemove(this.$globals.contextmenuitem, this.$globals.contextmenucontext.item_id, 'playlist_remove');\r
-                               this.$globals.showcontextmenu = false;\r
-                       }\r
-                       else {\r
-                               // assume play command\r
-                               this.$emit('playItem', this.$globals.contextmenuitem, cmd)\r
-                               this.$globals.showcontextmenu = false;\r
-                       }\r
-                       \r
-               },\r
-               playlistAddRemove(track, playlist_id, action='playlist_add') {\r
-                       /// add or remove track on playlist\r
-                       var url = `${this.$globals.server}api/track/${track.item_id}`;\r
-                       console.log('loading ' + url);\r
-                       axios\r
-                               .get(url, { params: { \r
-                                       provider: track.provider, \r
-                                       action: action, \r
-                                       action_details: playlist_id\r
-                               }})\r
-                               .then(result => {\r
-                                       console.log(result);\r
-                                       // reload playlist\r
-                                       if (action == 'playlist_remove')\r
-                                               this.$router.go()\r
-                                       })\r
-                               .catch(error => {\r
-                                       console.log("error", error);\r
-                               });\r
-               },\r
-               getPlaylists() {\r
-                       // get all editable playlists\r
-                       const api_url = this.$globals.apiAddress + 'playlists';\r
-                       axios\r
-                               .get(api_url, { })\r
-                               .then(result => {\r
-                                       let items = []\r
-                                       for (var item of result.data) {\r
-                                               if (item.item_id != this.$globals.contextmenucontext.item_id)\r
-                                                       if (item.is_editable)\r
-                                                               items.push(item);\r
-                                       }\r
-                                       console.log(items);\r
-                                       this.playlists = items;\r
-                               })\r
-                               .catch(error => {\r
-                                       console.log("error", error);\r
-                                       this.playlists = [];\r
-                       });\r
-               }\r
-       }\r
-  })\r
index 554987b25b05384e3f1b4884b540f16049302052..0cad9c7097925aa209e95c90ee0939b1d9e82de8 100644 (file)
@@ -1,13 +1,48 @@
 Vue.component("contextmenu", {\r
        template: `\r
-       <listdialog v-model="$globals.showcontextmenu" \r
-               v-on:onClick="itemCommand" \r
-               :items=menuItems \r
-               :header="header" \r
-               :subheader="subheader" \r
-       </listdialog>\r
+       <v-dialog :value="value" @input="$emit('input', $event)" max-width="500px">\r
+               <v-card>\r
+                       <v-list>\r
+                               <v-subheader class="title">{{ header }}</v-subheader>\r
+                               <v-subheader v-if="subheader">{{ subheader }}</v-subheader>\r
+                               <!-- normal contextmenu items -->\r
+                               <div v-if="!show_playlists" v-for="(item, index) in menuItems">\r
+                                       <v-list-tile avatar @click="itemCommand(item.action)">\r
+                                               <v-list-tile-avatar>\r
+                                                       <v-icon>{{item.icon}}</v-icon>\r
+                                               </v-list-tile-avatar>\r
+                                               <v-list-tile-content>\r
+                                                       <v-list-tile-title>{{ $t(item.label) }}</v-list-tile-title>\r
+                                               </v-list-tile-content>\r
+                                       </v-list-tile>\r
+                                       <v-divider></v-divider>\r
+                               </div>\r
+                               <listviewItem v-if="show_playlists"\r
+                                       v-for="(item, index) in playlists"\r
+                                       :key="item.item_id"\r
+                                       v-bind:item="item"\r
+                                       v-bind:totalitems="playlists.length"\r
+                                       v-bind:index="index"\r
+                                       :hideavatar="false"\r
+                                       :hidetracknum="true"\r
+                                       :hideproviders="false"\r
+                                       :hidelibrary="true"\r
+                                       :hidemenu="true"\r
+                                       :context="'selectplaylist'"\r
+                                       :onClick="playlistSelected"\r
+                                       >\r
+                               </listviewItem>\r
+                       </v-list>\r
+               </v-card>\r
+      </v-dialog>\r
 `,\r
-       props: ['active_player'],\r
+       props: ['value', 'active_player'],\r
+       watch: {\r
+               value: function (val) {\r
+                       if (!val)\r
+                               this.show_playlists = false;\r
+               }\r
+       },\r
        data () { \r
                return {\r
                        mediaPlayItems: [\r
@@ -43,7 +78,9 @@ Vue.component("contextmenu", {
                                        icon: "remove_circle_outline"\r
                        },\r
                        playerQueueItems: [\r
-                       ]\r
+                       ],\r
+                       playlists: [],\r
+                       show_playlists: false\r
                }\r
        },\r
        mounted() { },\r
@@ -52,6 +89,8 @@ Vue.component("contextmenu", {
                menuItems() {\r
                        if (!this.$globals.contextmenuitem)\r
                                return [];\r
+                       else if (this.show_playlists)\r
+                               return this.playlists;\r
                        else if (this.$globals.contextmenucontext == 'playerqueue')\r
                                return this.playerQueueItems; // TODO: return queue contextmenu\r
                        else if (this.$globals.contextmenucontext == 'trackdetails') {\r
@@ -77,18 +116,24 @@ Vue.component("contextmenu", {
                        }\r
                },\r
                header() {\r
-                       return !!this.$globals.contextmenuitem ? this.$globals.contextmenuitem.name : '';\r
+                       if (this.show_playlists)\r
+                               return this.$t('add_playlist');\r
+                       else if (!this.$globals.contextmenuitem)\r
+                               return "";\r
+                       else\r
+                               return this.$globals.contextmenuitem.name;\r
                },\r
                subheader() {\r
-                       if (!!this.active_player)\r
-                               return this.$t('play_on') + this.active_player.name;\r
-                       else\r
+                       if (this.show_playlists && !!this.$globals.contextmenuitem)\r
+                               return this.$globals.contextmenuitem.name;\r
+                       else if (!this.active_player)\r
                                return "";\r
+                       else\r
+                               return this.$t('play_on') + this.active_player.name;\r
                }\r
        },\r
        methods: { \r
                itemCommand(cmd) {\r
-                       console.log('itemCommand: ' + cmd);\r
                if (cmd == 'info') {\r
                                // show track info\r
                                this.$router.push({ path: '/tracks/' + this.$globals.contextmenuitem.item_id, query: {provider: this.$globals.contextmenuitem.provider}})\r
@@ -96,7 +141,6 @@ Vue.component("contextmenu", {
                        }       \r
                        else if (cmd == 'add_playlist') {\r
                                // add to playlist\r
-                               console.log(`add ${this.$globals.contextmenuitem.name} to playlist?`);\r
                                this.getPlaylists();\r
                                this.show_playlists = true;\r
                        }\r
@@ -112,10 +156,13 @@ Vue.component("contextmenu", {
                        }\r
                        \r
                },\r
+               playlistSelected(playlistobj) {\r
+                       this.playlistAddRemove(this.$globals.contextmenuitem, playlistobj.item_id, 'playlist_add');\r
+                       this.$globals.showcontextmenu = false;\r
+               },\r
                playlistAddRemove(track, playlist_id, action='playlist_add') {\r
                        /// add or remove track on playlist\r
                        var url = `${this.$globals.server}api/track/${track.item_id}`;\r
-                       console.log('loading ' + url);\r
                        axios\r
                                .get(url, { params: { \r
                                        provider: track.provider, \r
@@ -123,8 +170,7 @@ Vue.component("contextmenu", {
                                        action_details: playlist_id\r
                                }})\r
                                .then(result => {\r
-                                       console.log(result);\r
-                                       // reload playlist\r
+                                       // reload listing\r
                                        if (action == 'playlist_remove')\r
                                                this.$router.go()\r
                                        })\r
@@ -135,16 +181,22 @@ Vue.component("contextmenu", {
                getPlaylists() {\r
                        // get all editable playlists\r
                        const api_url = this.$globals.apiAddress + 'playlists';\r
+                       let track_provs = [];\r
+                       for (var prov of this.$globals.contextmenuitem.provider_ids)\r
+                               track_provs.push(prov.provider);\r
                        axios\r
                                .get(api_url, { })\r
                                .then(result => {\r
                                        let items = []\r
-                                       for (var item of result.data) {\r
-                                               if (item.item_id != this.$globals.contextmenucontext.item_id)\r
-                                                       if (item.is_editable)\r
-                                                               items.push(item);\r
+                                       for (var playlist of result.data) {\r
+                                               if (playlist.is_editable && playlist.item_id != this.$globals.contextmenucontext.item_id)\r
+                                                       for (var prov of playlist.provider_ids)\r
+                                                               if (track_provs.includes(prov.provider))\r
+                                                               {\r
+                                                                       items.push(playlist);\r
+                                                                       break\r
+                                                               }\r
                                        }\r
-                                       console.log(items);\r
                                        this.playlists = items;\r
                                })\r
                                .catch(error => {\r
diff --git a/music_assistant/web/components/listdialog.vue.js b/music_assistant/web/components/listdialog.vue.js
deleted file mode 100644 (file)
index 6df30bb..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-Vue.component("listdialog", {\r
-       template: `\r
-       <v-dialog :value="value" @input="$emit('input', $event)" max-width="500px">\r
-        <v-card>\r
-                       <v-list>\r
-                               <v-subheader class="title">{{ header }}</v-subheader>\r
-                               <v-subheader>{{ subheader }}</v-subheader>\r
-                               <div v-for="(item, index) in items">\r
-                                       <v-list-tile avatar @click="$emit('onClick', item)">\r
-                                               <v-list-tile-avatar>\r
-                                                       <v-icon>{{item.icon}}</v-icon>\r
-                                               </v-list-tile-avatar>\r
-                                               <v-list-tile-content>\r
-                                                       <v-list-tile-title>{{ $t(item.label) }}</v-list-tile-title>\r
-                                               </v-list-tile-content>\r
-                                       </v-list-tile>\r
-                                       <v-divider></v-divider>\r
-                               </div>\r
-                       </v-list>\r
-               </v-card>\r
-      </v-dialog>\r
-`,\r
-       props: ['value', 'items', 'header', 'subheader'],\r
-       data () { \r
-               return {}\r
-       },\r
-       mounted() { },\r
-       created() { },\r
-       computed: { },\r
-       methods: { }\r
-  })\r
index b29a326012cbec4a7c1f52a4e0c70b8d3527f1c8..79a847b72ee026c9a46e9be96f7f8121dcc0c870 100755 (executable)
@@ -4,8 +4,8 @@ Vue.component("listviewItem", {
     <v-list-tile
     avatar
     ripple
-    @click="clickItem(item, context)">
-
+    @click="onClick ? onClick(item, context) : clickItem(item, context)"
+    >
           <v-list-tile-avatar color="grey" v-if="!hideavatar">
               <img v-if="(item.media_type != 3) && item.metadata && item.metadata.image" :src="item.metadata.image"/>
               <img v-if="(item.media_type == 3) && item.album && item.album.metadata && item.album.metadata.image" :src="item.album.metadata.image"/>
@@ -22,7 +22,7 @@ Vue.component("listviewItem", {
             </v-list-tile-title>
             
             <v-list-tile-sub-title v-if="item.artists">
-                <span v-for="(artist, artistindex) in item.artists">
+                <span v-for="(artist, artistindex) in item.artists" :key="artist.item_id">
                     <a v-on:click="clickItem(artist)" @click.stop="">{{ artist.name }}</a>
                     <label v-if="artistindex + 1 < item.artists.length" :key="artistindex"> / </label>
                 </span>
@@ -61,12 +61,11 @@ Vue.component("listviewItem", {
           <!-- menu button/icon -->
           <v-icon v-if="!hidemenu" @click="showPlayMenu(item, context)" @click.stop="" color="grey lighten-1" style="margin-right:-10px;padding-left:10px">more_vert</v-icon>
           
-
         </v-list-tile>
         <v-divider v-if="index + 1 < totalitems" :key="index"></v-divider>
         </div>
      `,
-props: ['item', 'context', 'index', 'totalitems', 'hideavatar', 'hidetracknum', 'hideproviders', 'hidemenu', 'hidelibrary', 'hideduration'],
+props: ['item', 'context', 'index', 'totalitems', 'hideavatar', 'hidetracknum', 'hideproviders', 'hidemenu', 'hidelibrary', 'hideduration', 'onClick'],
 data() {
   return {}
   },
index 92f030999ba17905f39c286b92eeb0aa2a031833..278a544fdd36d9280d310883939576948c65f4f8 100755 (executable)
@@ -135,7 +135,7 @@ Vue.component("player", {
             </div>
         </v-list>
     </v-navigation-drawer>
-    <contextmenu v-on:playItem="playItem" :active_player="active_player" />
+    <contextmenu v-model="$globals.showcontextmenu" v-on:playItem="playItem" :active_player="active_player" />
   </div>
   
   `,
@@ -233,7 +233,6 @@ Vue.component("player", {
       this.ws.send(JSON.stringify({message:'player command', message_details: msg_details}));
     },
     playItem(item, queueopt) {
-      console.log('playItem: ' + item);
       this.$globals.loading = true;
       var api_url = 'api/players/' + this.active_player_id + '/play_media/' + item.media_type + '/' + item.item_id + '/' + queueopt;
       axios
@@ -243,11 +242,9 @@ Vue.component("player", {
         }
       })
       .then(result => {
-        console.log(result.data);
         this.$globals.loading = false;
       })
       .catch(error => {
-        console.log("error", error);
         this.$globals.loading = false;
       });
     },
index ac1fae03ec651812223ad2f13d1325b7275cf771..ad04ae465f5f0f562ce42741d4a1505eb0926b4b 100755 (executable)
@@ -53,9 +53,7 @@
         <script src='./components/player.vue.js'></script>
         <script src='./components/listviewItem.vue.js'></script>
         <script src='./components/readmore.vue.js'></script>
-        <script src='./components/listdialog.vue.js'></script>
         <script src='./components/contextmenu.vue.js'></script>
-        <script src='./components/addtoplaylistdialog.vue.js'></script>
         <script src='./components/volumecontrol.vue.js'></script>
         <script src='./components/infoheader.vue.js'></script>
         <script src='./components/providericons.vue.js'></script>
index 9f08215d39fe7db119261ecd44177f6708dbdbe4..de849155c9ba99a453d20aa7c4cf92d4f6c5665d 100644 (file)
@@ -3,7 +3,7 @@ const isInStandaloneMode = () => ('standalone' in window.navigator) && (window.n
 
 function showPlayMenu (item, context=null) {
     /// make the contextmenu visible
-    console.log(context);
+    console.log("showPlayMenu");
     this.$globals.contextmenuitem = item;
     this.$globals.contextmenucontext = context;
     this.$globals.showcontextmenu = !this.$globals.showcontextmenu;
index c9ba1141422670fde0e75048ac583ac6f89ae9f8..058e13fde69eb8ace63b426925ec1a90941615b7 100755 (executable)
@@ -4,14 +4,15 @@ var Browse = Vue.component('Browse', {
       <v-list two-line>
         <listviewItem 
             v-for="(item, index) in items"
-            :key="item.item_id"
+            :key="item.item_id+item.provider"
             v-bind:item="item"
             v-bind:totalitems="items.length"
             v-bind:index="index"
             :hideavatar="item.media_type == 3 ? isMobile() : false"
             :hidetracknum="true"
             :hideproviders="isMobile()"
-            :hidelibrary="isMobile() ? true : item.media_type != 3"
+            :hidelibrary="true"
+            :hidemenu="item.media_type == 3"
             :context="mediatype"
             >
         </listviewItem>
@@ -23,26 +24,36 @@ var Browse = Vue.component('Browse', {
     return {
       selected: [2],
       items: [],
-      offset: 0
+      offset: 0,
+      full_list_loaded: false
     }
   },
   created() {
-    this.showavatar = true;
-    mediatitle = 
     this.$globals.windowtitle = this.$t(this.mediatype)
     this.scroll(this.Browse);
-    this.getItems();
+    if (!this.full_list_loaded)
+      this.getItems();
   },
   methods: {
     getItems () {
+      if (this.full_list_loaded)
+        return;
       this.$globals.loading = true
       const api_url = this.$globals.apiAddress + this.mediatype;
+      const limit = 20;
       axios
-        .get(api_url, { params: { offset: this.offset, limit: 50, provider: this.provider }})
+        .get(api_url, { params: { offset: this.offset, limit: limit, provider: this.provider }})
         .then(result => {
           data = result.data;
+          if (data.length < limit)
+          {
+            this.full_list_loaded = true;
+            this.$globals.loading = false;
+            if (data.length == 0)
+              return
+          }
           this.items.push(...data);
-          this.offset += 50;
+          this.offset += limit;
           this.$globals.loading = false;
         })
         .catch(error => {
@@ -53,8 +64,7 @@ var Browse = Vue.component('Browse', {
     scroll (Browse) {
       window.onscroll = () => {
         let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
-
-        if (bottomOfWindow) {
+        if (bottomOfWindow && !this.full_list_loaded) {
           this.getItems();
         }
       };
index 0414065b458ca197a9d75cedc6b5122d07ee2c3d..0cb02bec464bc69099330d7e689dd21e38354b14 100755 (executable)
@@ -15,11 +15,12 @@ var PlaylistDetails = Vue.component('PlaylistDetails', {
                   <listviewItem 
                       v-for="(item, index) in items" 
                       v-bind:item="item"
-                      :key="item.db_id"
+                      :key="index"
                       :hideavatar="isMobile()"
                       :hidetracknum="true"
                       :hideproviders="isMobile()"
                       :hidelibrary="isMobile()"
+                      :hidemenu="true"
                       v-bind:context="info"
                       >
                   </listviewItem>
@@ -35,7 +36,8 @@ var PlaylistDetails = Vue.component('PlaylistDetails', {
       info: {},
       items: [],
       offset: 0,
-      active: 0
+      active: 0,
+      full_list_loaded: false
     }
   },
   created() {
@@ -45,7 +47,7 @@ var PlaylistDetails = Vue.component('PlaylistDetails', {
     this.scroll(this.Browse);
   },
   methods: {
-    getInfo () {
+    async getInfo () {
       const api_url = this.$globals.apiAddress + 'playlists/' + this.media_id
       axios
       .get(api_url, { params: { provider: this.provider }})
@@ -58,26 +60,36 @@ var PlaylistDetails = Vue.component('PlaylistDetails', {
           console.log("error", error);
         });
     },
-    getPlaylistTracks () {
-      this.$globals.loading = true
+    async getPlaylistTracks () {
+      if (this.full_list_loaded)
+        return;
+      this.$globals.loading = true;
       const api_url = this.$globals.apiAddress + 'playlists/' + this.media_id + '/tracks'
+      let limit = 20;
       axios
-        .get(api_url, { params: { offset: this.offset, limit: 25, provider: this.provider}})
+        .get(api_url, { params: { offset: this.offset, limit: limit, provider: this.provider}})
         .then(result => {
+          this.$globals.loading = false;
           data = result.data;
+          if (data.length < limit)
+          {
+            this.full_list_loaded = true;
+            this.$globals.loading = false;
+            if (data.length == 0)
+              return
+          }
           this.items.push(...data);
-          this.offset += 25;
-          this.$globals.loading = false;
+          this.offset += limit;
+          
         })
         .catch(error => {
           console.log("error", error);
         });
-        
     },
     scroll (Browse) {
       window.onscroll = () => {
         let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
-        if (bottomOfWindow) {
+        if (bottomOfWindow && !this.full_list_loaded) {
           this.getPlaylistTracks();
         }
       };