diff --git a/app/application.js b/app/application.js index 9d9e5f7..eb6bd93 100644 --- a/app/application.js +++ b/app/application.js @@ -24,13 +24,7 @@ let Application = Mn.Application.extend({ // All track initialization let allTracks = new Tracks([], { type: 'all' }); - allTracks.fetch({ - success: () => { // For now initialize upNext with all track - this.allTracks.get('tracks').each(track => { - this.upNext.get('tracks').add(track); - }); - } - }); + allTracks.fetch(); this.allTracks = new Playlist({ title: "All Songs", tracks: allTracks diff --git a/app/collections/playlists.js b/app/collections/playlists.js index 4322832..6e83466 100644 --- a/app/collections/playlists.js +++ b/app/collections/playlists.js @@ -27,14 +27,25 @@ const Playlists = Backbone.Collection.extend({ if (method == 'read') { cozysdk.run('Playlist', 'all', {}, (err, res) => { if (res) { - let playlists = JSON.parse('' + res); - for (let i = 0; i < playlists.length; i++) { - this.add(playlists[i].value); + if (options && options.success) { + options.success(res); + } + } else { + if (options && options.error) { + options.error(err); } - options.success(); } }); } + }, + + parse(resp, options) { + let playlists = JSON.parse('' + resp); + let result = []; + for (let i = 0; i < playlists.length; i++) { + result.push(playlists[i].value); + } + return result; } }); diff --git a/app/collections/tracks.js b/app/collections/tracks.js index cc85d86..cb99df1 100644 --- a/app/collections/tracks.js +++ b/app/collections/tracks.js @@ -22,6 +22,7 @@ const Tracks = Backbone.Collection.extend({ this.shuffleUpNext ); } + this.on('change:hidden', this.removeTrack, this); }, // UpNext : shuffle @@ -51,6 +52,10 @@ const Tracks = Backbone.Collection.extend({ ); }, + removeTrack(track) { + this.remove(track); + }, + comparator(model) { if (this.type == 'upNext' && application.appState.get('shuffle')) { return undefined; @@ -60,17 +65,28 @@ const Tracks = Backbone.Collection.extend({ }, sync(method, model, options) { - if (method == 'read' && this.type) { - cozysdk.run('Track', this.type, {}, (err, res) => { + if (method == 'read' && this.type == "all") { + cozysdk.run('Track', 'playable', {}, (err, res) => { if (res) { - let tracks = JSON.parse('' + res); - for (let i = 0; i < tracks.length; i++) { - this.add(tracks[i].value); + if (options && options.success) { + options.success(res); + } + } else { + if (options && options.error) { + options.error(err); } - options.success(); } }); } + }, + + parse(resp, options) { + let result = []; + let tracks = JSON.parse('' + resp); + for (let i = 0; i < tracks.length; i++) { + result.push(tracks[i].value); + } + return result; } }); diff --git a/app/libs/file.js b/app/libs/file.js index 2c20f27..0e4e61b 100644 --- a/app/libs/file.js +++ b/app/libs/file.js @@ -29,8 +29,11 @@ function getAllTracksFileId(musicFiles) { } saveTrack(musicFiles, tracksFileId); deleteTrack(allTracksFiles, musicFilesFileId); - let msg = t('all your audio files have been added'); - application.channel.request('notification', msg); + let notification = { + status: 'ok', + message: t('all your audio files have been added') + } + application.channel.request('notification', notification); } }); } diff --git a/app/libs/soundcloud.js b/app/libs/soundcloud.js index 3924ebb..554c57e 100644 --- a/app/libs/soundcloud.js +++ b/app/libs/soundcloud.js @@ -32,8 +32,11 @@ class Soundcloud { if (!exist) { this.importTrack(track); } else { - let msg = t('track is already in the database'); - application.channel.request('notification', msg); + let notification = { + status: 'ko', + message: t('track is already in the database') + } + application.channel.request('notification', notification); } } }); @@ -42,7 +45,11 @@ class Soundcloud { // Set the track's metas and save it. importTrack(track) { if (!track.streamable) { - alert('This track is not streamable'); + let notification = { + status: 'ko', + message: 'This track is not streamable' + } + application.channel.request('notification', notification); return; } let newTrack = new Track(); @@ -57,8 +64,11 @@ class Soundcloud { duration: track.duration }); application.allTracks.get('tracks').create(newTrack); - let msg = t('stream track imported'); - application.channel.request('notification', msg); + let notification = { + status: 'ok', + message: t('stream track imported') + } + application.channel.request('notification', notification); } // Add our clientID to the current url diff --git a/app/models/playlist.js b/app/models/playlist.js index 5689e0d..609daf0 100644 --- a/app/models/playlist.js +++ b/app/models/playlist.js @@ -13,7 +13,9 @@ const Playlist = Backbone.Model.extend({ }, initialize(attributes, options) { - let tracks = attributes.tracks || new Tracks([], {}); + let tracks = attributes.tracks || new Tracks(tracks, { + type: 'playlist' + }); this.set('tracks', tracks); }, @@ -56,7 +58,24 @@ const Playlist = Backbone.Model.extend({ // Add a track to the playlist addTrack(track) { - this.set('tracks', this.get('tracks').push(track)); + let tracks = this.get('tracks'); + tracks.push(track); + if (tracks.type == 'playlist') this.save(); + }, + + // Remove a track to the playlist + removeTrack(track) { + let tracks = this.get('tracks'); + tracks.remove(track); + if (tracks.type == 'playlist') this.save(); + }, + + parse(attrs, options) { + if (attrs) { + let tracks = attrs.tracks; + attrs.tracks = new Tracks(tracks, { type: 'playlist'}); + return attrs + } } }); diff --git a/app/styles/app.styl b/app/styles/app.styl index e4096de..d7b3e72 100644 --- a/app/styles/app.styl +++ b/app/styles/app.styl @@ -7,6 +7,8 @@ $text-dark = #32363f $text-light = #788195 $text = #92a0b2 $red = #ef4c57 +$svg = #696d78 +$svg-red = #f14a53 @import 'reset' global-reset() @@ -26,6 +28,8 @@ div[role='application'] ::-moz-focus-inner border: 0 +input + all: unset button all: unset cursor: pointer @@ -45,6 +49,9 @@ placeholder() &:-ms-input-placeholder {block} -@import 'content' +@import 'tracks' +@import 'header' +@import 'track-actions' +@import 'popup-menu' @import 'toolbar' @import 'player' diff --git a/app/styles/content.styl b/app/styles/content.styl deleted file mode 100644 index 0b05b82..0000000 --- a/app/styles/content.styl +++ /dev/null @@ -1,127 +0,0 @@ -div[role='contentinfo'] - background-color: white - padding: 25px 33px - flex-grow: 1 - overflow: auto - margin-bottom: 71px; - - div[role='complementary'] - - h3 - font-size: 24px - line-height: 0.8 - height: 31px - color: $text-dark - display: inline-block - - em - color: $text-light - - button - padding: 0px 12px - height: 32px - float: right - display: inline-block - cursor: pointer - font-family: 'Source Sans Pro' - font-weight: bold - color: $text-light - background-color: $background-dark - border-radius: 2px - position: relative - padding-left: 32px - text-transform: uppercase - font-size: 16px - - svg - position: absolute - height: 20px - left: 9px - width: 20px - top: 6px - - path - fill: #696d78 - stroke: 2px - - button:active - padding-top: 1px - - table - width: 100% - color: $text-light - - thead - color: $text-dark - font-family: 'Source Sans Pro' - font-weight: bold - height: 41px - box-shadow: inset 0 -2px 0 0 #d5dce1 - font-size: 13px - line-height: 1.5 - - .number - box-sizing: border-box - width: 42px - text-align: center - - svg - width: 18px - height: 18px - - tbody - - .title - color: $text-dark - - .playing * - color: $red - - tr - cursor: pointer - height: 41px - background-color: transparent - box-shadow: inset 0 -1px 0 0 #d5dce1 - - td - white-space: nowrap - overflow: hidden - position: relative - - .all - #delete-from-upnext - display: none - - .upNext - #add-to-upnext - display: none - - .actions - height: 41px - visibility: hidden - position: absolute - right: 18px - top: 0px - button - height: 100% - width: 22px - svg - height: 20px - width: 20px - path - fill: #696d78 - - .help - position: absolute - visibility: hidden - - button:hover - .help - visibility: visibile - - tr:hover - .actions - visibility: visible - - tr:hover - background-color: $background-dark diff --git a/app/styles/header.styl b/app/styles/header.styl new file mode 100644 index 0000000..8608b26 --- /dev/null +++ b/app/styles/header.styl @@ -0,0 +1,62 @@ +div[role='contentinfo'] + background-color: white + padding: 25px 33px + flex-grow: 1 + overflow: auto + margin-bottom: 71px; + + div[role='complementary'] + + h3 + font-size: 24px + line-height: 34px + height: 34px + text-align: center + color: $text-dark + display: inline-block + + span + padding: 0 8px + + span[contenteditable='true']:hover + background-color: $background-dark + + span[contenteditable='true']:focus + background-color: $background-dark + + p + font-size: 24px + line-height: 0.8 + height: 31px + display: inline-block + color: $text-light + + button + padding: 0px 12px + height: 32px + float: right + display: inline-block + cursor: pointer + font-family: 'Source Sans Pro' + font-weight: bold + color: $text-light + background-color: $background-dark + border-radius: 2px + position: relative + padding-left: 32px + text-transform: uppercase + font-size: 16px + + svg + position: absolute + height: 20px + left: 9px + width: 20px + top: 6px + + path + fill: $svg + stroke: 2px + + button:active + padding-top: 1px \ No newline at end of file diff --git a/app/styles/player.styl b/app/styles/player.styl index 421224e..0a5f9fc 100644 --- a/app/styles/player.styl +++ b/app/styles/player.styl @@ -5,8 +5,8 @@ left:0 height: 72px width: 100% - background-color: #eff1f4 - border: solid 1px #cedae4 + background-color: $background-dark + border: solid 1px $border-color audio position: absolute @@ -42,7 +42,7 @@ padding-left: 28px path - fill: #696d78 + fill: $svg .timeline margin-left: 32px @@ -63,7 +63,7 @@ color: $text-light p - color: #92a0b2 + color: $text height: 20px font-family: "Source Sans Pro" font-weight: bold @@ -77,7 +77,7 @@ #progress-bar margin-top: 8px - background-color: #cedae4 + background-color: $border-color width: 100% height: 4px border-radius: 2px @@ -87,8 +87,7 @@ width: 0% height: 4px border-radius: 2px - background-color: #0dbabe - + background-color: $blue .controls width: 280px @@ -97,7 +96,7 @@ width: 90px height: 4px border-radius: 2px - background-color: #cedae4 + background-color: $border-color display: inline-block position: absolute top: 34px @@ -107,7 +106,7 @@ width: 100% height: 4px border-radius: 2px - background-color: #696d78 + background-color: $svg svg width: 20px @@ -117,17 +116,17 @@ display: inline-block path - fill: #92a0b2 + fill: $text .active path - fill: #f14a53 + fill: $svg-red #repeat-one-sm path - fill: #f14a53 + fill: $svg-red #broadcast margin-left: 100px path - fill: #f14a53 + fill: $svg-red diff --git a/app/styles/popup-menu.styl b/app/styles/popup-menu.styl new file mode 100644 index 0000000..ab79f68 --- /dev/null +++ b/app/styles/popup-menu.styl @@ -0,0 +1,66 @@ +#tracks + .popup-menu + visibility: hidden + position: absolute + z-index: 99 + top: 37px + right: 50px + padding: 14px 0px + border-radius: 10px + background-color: white + box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 0 rgba(0, 0, 0, 0.2) + + .small + width: 14px + height: 14px + opacity: 0.3 + position: absolute + right: 14px + svg + path + fill: $svg + ul + li + font-size: 16px + padding: 0px 20px + height: 36px + box-shadow: none + color: $text-dark + align-items: center + + svg + height: 20px + width: 20px + padding-right: 14px + path + fill: $svg + + li:hover + background-color: $background-dark; + + .show + visibility: visible + + #playlist-popup-menu + z-index: 100 + right: 200px + top: 0px + min-width: 200px + + #add-playlist + border-top: 1px solid #d5dce1; + height: 40px + display: flex + align-items: center + padding: 0px 20px + + input + width: 100% + height: 100% + display:none + + #add-playlist.input + p + display:none + input + display: block diff --git a/app/styles/toolbar.styl b/app/styles/toolbar.styl index fe28315..0bb3560 100644 --- a/app/styles/toolbar.styl +++ b/app/styles/toolbar.styl @@ -6,7 +6,7 @@ div[role='toolbar'] flex-basis: 280px display: flex flex-direction: column - background-color: #eff1f4 + background-color: $background-dark border: solid 1px #cedae4 .tools @@ -35,7 +35,7 @@ div[role='toolbar'] top: 6px path - fill: white + fill: $white p position: relative @@ -45,7 +45,7 @@ div[role='toolbar'] margin-top: 9px margin-bottom: 10px height: 20px - color: white + color: $white display: inline-block text-transform: uppercase @@ -131,7 +131,7 @@ div[role='toolbar'] top: 4px path - fill: #92a0b2 + fill: $text .input border: solid 1px $blue @@ -183,7 +183,7 @@ div[role='toolbar'] cursor: pointer li.selected - color: white + color: $white border-top-right-radius: 2px border-bottom-right-radius: 2px background-color: #f14a53 @@ -198,28 +198,25 @@ div[role='toolbar'] background-color: #696d78 box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1) opacity: 0 - visibility: hidden + visibility: visible + display: flex + align-items: center p - position: absolute - top: 10px - left: 64px + padding-left: 14px padding-right: 32px - height: 42px font-family: "Source Sans Pro" font-size: 16px line-height: 1.3 - color: #ffffff + color: $white svg - position: absolute - left: 14px - top: 13px + padding-left: 14px width: 40px height: 40px path - fill: white + fill: $white .notify animation-timing-function: ease; diff --git a/app/styles/track-actions.styl b/app/styles/track-actions.styl new file mode 100644 index 0000000..2e13d9e --- /dev/null +++ b/app/styles/track-actions.styl @@ -0,0 +1,40 @@ +#tracks + .actions + height: 41px + visibility: hidden + position: absolute + right: 18px + top: 0px + + .active + path + fill: $svg-red + + button:hover:not(.active) + .help + visibility: visibile + path + fill: $text-dark + + button + height: 100% + width: 22px + svg + height: 20px + width: 20px + path + fill: $svg + -webkit-transition: fill 0.2s; /* Safari */ + transition: fill 0.2s; + + .all + #delete-from-upnext + display: none + + .upNext + #add-to-upnext + display: none + + .help + position: absolute + visibility: hidden diff --git a/app/styles/tracks.styl b/app/styles/tracks.styl new file mode 100644 index 0000000..0db194e --- /dev/null +++ b/app/styles/tracks.styl @@ -0,0 +1,68 @@ +#tracks + div[role='rowheader'] + width: 100% + color: $text-light + display: flex + color: $text-dark + font-family: 'Source Sans Pro' + font-weight: bold + height: 36px + box-shadow: inset 0 -2px 0 0 #d5dce1 + font-size: 13px + text-transform: uppercase + + [role='gridcell'] + text-align: left + display: flex; + align-items: center + + .number-column-cell + justify-content: center + width: 42px + svg + display: none + width: 18px + height: 18px + path + fill: $red + + .song-column-cell + flex-grow: 1 + + .artist-column-cell + width: 200px + + .album-column-cell + position: relative + width: 200px + + .length-column-cell + width: 62px + + ul + .playing + color: $red + .song-column-cell + color: $red + .number-column-cell + svg + display: block + li + display: flex + cursor: pointer + height: 41px + background-color: transparent + box-shadow: inset 0 -1px 0 0 #d5dce1 + font-size: 14px + color: $text-light + position: relative + + .song-column-cell + flex-grow: 1 + font-size: 16px + color: $text-dark + + li:hover + background-color: $background-dark + .actions + visibility: visible diff --git a/app/views/header.js b/app/views/header.js index 92e98aa..39d3aa4 100644 --- a/app/views/header.js +++ b/app/views/header.js @@ -6,14 +6,20 @@ const Header = Mn.ItemView.extend({ template: require('./templates/header'), + ui: { + title: '#playlist-title' + }, + events: { 'click #reset-upnext': 'resetUpNext', - 'click #delete-playlist': 'deletePlaylist' + 'click #delete-playlist': 'deletePlaylist', + 'keypress @ui.title': 'keypressPlaylistTitle', + 'blur @ui.title' : 'savePlaylistTitle' }, modelEvents: { 'change:currentPlaylist': 'changedPlaylist' - }, + }, initialize() { this.listenTo( @@ -21,13 +27,31 @@ const Header = Mn.ItemView.extend({ 'update reset', this.render ); - }, + }, + + keypressPlaylistTitle(e) { + if (e.key == 'Enter') { + this.ui.title.blur(); + return false; + } + }, + + savePlaylistTitle(e) { + if (this.ui.title.html() == '') { + setTimeout(() => { this.ui.title.focus() }, 0); + } else { + let currentPlaylist = this.model.get('currentPlaylist'); + currentPlaylist.set('title', this.ui.title.html()); + currentPlaylist.save(); + } + }, serializeData() { let currentPlaylist = this.model.get('currentPlaylist'); return { title: currentPlaylist.get('title'), - count: currentPlaylist.get('tracks').length + count: currentPlaylist.get('tracks').length, + type: currentPlaylist.get('tracks').type } }, diff --git a/app/views/notification.js b/app/views/notification.js index 128ad6a..f7ae800 100644 --- a/app/views/notification.js +++ b/app/views/notification.js @@ -9,17 +9,22 @@ const Notification = Mn.ItemView.extend({ notification: '.notification', }, - initialize(options) { - this.message = options.message; + initialize(notification) { + this.message = notification.message; + this.status = notification.status; }, onRender() { this.ui.notification.addClass('notify'); + setTimeout(() => { // Prevent animation to replay after reopening the app + this.ui.notification.removeClass('notify'); + }, 4000); }, serializeData() { return { - message: this.message + message: this.message, + status: this.status } } }); diff --git a/app/views/player.js b/app/views/player.js index 50e9ae2..a68a1bc 100644 --- a/app/views/player.js +++ b/app/views/player.js @@ -26,15 +26,18 @@ const Player = Mn.LayoutView.extend({ 'click #prev': 'prev', 'click #play': 'toggle', 'click #next': 'next', - 'click @ui.progressBar': 'skip', - 'click @ui.volumeBar': 'changeVol', + 'mousedown @ui.progressBar': 'skip', + 'mousedown @ui.volumeBar': 'changeVol', 'click @ui.shuffle': 'toggleShuffle', 'click @ui.repeat': 'toggleRepeat', 'click @ui.speaker': 'toggleVolume' }, initialize() { - this.listenTo(application.channel,'reset:UpNext', this.render); + this.listenTo(application.channel, { + 'reset:UpNext': this.render, + 'player:next': this.next + }); this.listenTo(application.appState, 'change:currentTrack', function(appState, currentTrack) { if (currentTrack) { @@ -64,14 +67,25 @@ const Player = Mn.LayoutView.extend({ break; } }); + $(document).mousemove((e) => { + if (this.volumeDown) { + this.changeVol(e); + } else if (this.progressDown) { + this.skip(e); + } + }); + $(document).mouseup((e) => { + this.volumeDown = false; + this.progressDown = false; + }); }, onRender() { let audio = this.ui.player.get(0); audio.ontimeupdate = this.onTimeUpdate; - audio.onended = this.next; + audio.onended = () => { this.next() }; audio.onvolumechange = this.onVolumeChange; - audio.volume = 0.5; + audio.volume = application.appState.get('currentVolume'); }, load(track) { @@ -145,6 +159,9 @@ const Player = Mn.LayoutView.extend({ this.replayCurrent(); } application.appState.set('currentTrack', upNext.at(0)); + } else { + application.appState.set('currentTrack', undefined); + this.render(); } }, @@ -211,6 +228,7 @@ const Player = Mn.LayoutView.extend({ // Go to a certain time in the track skip(e) { + this.progressDown = true; let audio = this.ui.player.get(0); let bar = this.ui.progressBar.get(0); let newTime = audio.duration * ((e.pageX - bar.offsetLeft) / bar.clientWidth); @@ -238,6 +256,7 @@ const Player = Mn.LayoutView.extend({ // Change the volume changeVol(e) { + this.volumeDown = true; let audio = this.ui.player.get(0); let bar = this.ui.volumeBar.get(0); let volume = (e.pageX - bar.offsetLeft) / bar.clientWidth; diff --git a/app/views/playlist.js b/app/views/playlist.js index 9396b3e..ae1ca2c 100644 --- a/app/views/playlist.js +++ b/app/views/playlist.js @@ -10,7 +10,11 @@ const PlaylistView = Mn.ItemView.extend({ className: 'playlist', - modelEvents: { change: 'render' } + modelEvents: { change: 'render' }, + + onRender() { + this.$el.attr('data-id', this.model.get('_id')); + } }); export default PlaylistView; \ No newline at end of file diff --git a/app/views/playlists.js b/app/views/playlists.js index 766fb23..db12fdc 100644 --- a/app/views/playlists.js +++ b/app/views/playlists.js @@ -65,19 +65,16 @@ const Playlists = Mn.CompositeView.extend({ }, changePlaylist(e) { - let playlist = $(e.currentTarget); - let playlists = this.$('.playlist'); - let index = playlists.index(playlist) - 2; - playlists.removeClass('selected'); - playlist.addClass('selected'); + this.$('.playlist').removeClass('selected'); + $(e.currentTarget).addClass('selected'); - if (playlist.attr('id') == "up-next") { + if ($(e.currentTarget).attr('id') == "up-next") { application.appState.set('currentPlaylist', application.upNext); - } else if (playlist.attr('id') == "all-song") { + } else if ($(e.currentTarget).attr('id') == "all-song") { application.appState.set('currentPlaylist', application.allTracks); } else { - this.currentIndex = index; - let playlist = application.allPlaylists.at(index); + let id = e.currentTarget.dataset.id; + let playlist = application.allPlaylists.get(id); application.appState.set('currentPlaylist', playlist); } } diff --git a/app/views/popupPlaylists.js b/app/views/popupPlaylists.js new file mode 100644 index 0000000..2915050 --- /dev/null +++ b/app/views/popupPlaylists.js @@ -0,0 +1,79 @@ +import Mn from 'backbone.marionette'; +import PlaylistView from './playlist'; +import application from '../application'; +import Playlist from '../models/playlist'; + + +const PopupPlaylists = Mn.CompositeView.extend({ + + template: require('./templates/popupPlaylists'), + + childViewContainer: '#playlist-list', + + childView: PlaylistView, + + ui: { + addPlaylist: '#add-playlist', + playlistText: '#playlist-text', + playlistPopup: '#playlist-popup-menu' + }, + + events: { + 'click li': 'addToPlaylist', + 'click @ui.addPlaylist': 'showInput', + 'keyup @ui.playlistText': 'keyupPlaylistText', + }, + + initialize() { + this.listenTo(application.channel, { + 'playlistPopup:show': this.showPopup, + 'playlistPopup:hide': this.hidePopup + }); + }, + + showPopup(model) { + if (this.model == model) { + this.ui.playlistPopup.addClass('show'); + } + }, + + hidePopup(model) { + if (this.model == model) { + this.ui.playlistPopup.removeClass('show'); + } + }, + + addToPlaylist(e) { + e.stopPropagation(); + let id = e.currentTarget.dataset.id; + let playlist = application.allPlaylists.get(id); + playlist.addTrack(this.model); + let notification = { + status: 'ok', + message: 'Added to ' + playlist.get('title') + } + application.channel.request('notification', notification); + }, + + showInput(e) { + e.stopPropagation(); + this.ui.addPlaylist.addClass('input'); + this.ui.playlistText.focus(); + }, + + // Create the playlist when `Enter` is pressed + keyupPlaylistText(e) { + let title = this.ui.playlistText.val(); + if(e.keyCode == 13) { + let newPlaylist = new Playlist({ title: title }); + newPlaylist.addTrack(this.model); + application.allPlaylists.add(newPlaylist); + this.ui.playlistText.val(''); + } + }, + + +}); + +export default PopupPlaylists; + diff --git a/app/views/templates/header.jst b/app/views/templates/header.jst index 264f15d..116d436 100644 --- a/app/views/templates/header.jst +++ b/app/views/templates/header.jst @@ -1,12 +1,18 @@ -

<%= title %> (<%= count %>)

-<% if (title == t('up next')) { %> +<% if (type == 'playlist') { %> +

<%= title %>

+<% } else { %> +

<%= title %>

+<% } %> +

(<%= count %>)

+ +<% if (type == 'upNext') { %> -<% } else if (title != t('all songs')) { %> +<% } else if (type == 'playlist') { %> - - - -<%- duration %> + +
<%- duration %>
+ diff --git a/app/views/templates/tracks.jst b/app/views/templates/tracks.jst index eb050c7..4a9602e 100644 --- a/app/views/templates/tracks.jst +++ b/app/views/templates/tracks.jst @@ -1,10 +1,8 @@ - - - - - - - - - -
#SONGARTISTALBUMLENGTH
+
+
#
+
SONG
+
ARTIST
+
ALBUM
+
LENGTH
+
+ \ No newline at end of file diff --git a/app/views/toolbar.js b/app/views/toolbar.js index a5ad965..dd375d2 100644 --- a/app/views/toolbar.js +++ b/app/views/toolbar.js @@ -87,10 +87,10 @@ const Toolbar = Mn.LayoutView.extend({ } }, - showNotification(msg) { + showNotification(notification) { this.showChildView( 'notification', - new NotificationView({ message: msg }) + new NotificationView(notification) ); } }); diff --git a/app/views/track.js b/app/views/track.js index 01d239a..2f3c0be 100644 --- a/app/views/track.js +++ b/app/views/track.js @@ -1,20 +1,35 @@ import Mn from 'backbone.marionette'; import application from '../application'; import { timeToString } from '../libs/utils'; +import PopPlaylistView from './popupPlaylists' - -const TrackView = Mn.ItemView.extend({ +const TrackView = Mn.LayoutView.extend({ template: require('./templates/track'), - tagName: 'tr', + tagName: 'li', + + ui: { + 'menu': '.menu', + 'popupMenu': '#popup-menu', + }, + + regions: { + playlistPopup: '.playlist-popup-container', + }, events: { 'click': 'play', - 'click .delete': 'delete', - 'click #menu': 'displayMenu', - 'click #add-to-upnext':'addToUpNext', - 'click "delete-from-upnext': 'deleteFromUpNext' + 'click @ui.menu': 'toggleMenu', + 'click .add-to-upnext':'addToUpNext', + 'mouseenter .add-to-playlist':'showPlaylist', + 'mouseleave .add-to-playlist':'hidePlaylist', + 'click .album-to-upnext':'albumToUpNext', + 'click .edit-details':'editDetails', + 'click .delete':'delete', + 'click .delete-from-upnext': 'deleteFromUpNext', + 'click .remove-from-playlist': 'removeFromPlaylist', + 'mouseleave #popup-menu': 'hidePopupMenu', }, modelEvents: { @@ -28,26 +43,77 @@ const TrackView = Mn.ItemView.extend({ }, onRender() { - let currentPlaylist = application.currentPlaylist; - let type = currentPlaylist ? currentPlaylist.get('tracks').type : 'all' - switch (type) { // Can be refactored ? - case 'upNext': - this.$('.actions').addClass('upNext'); - break; - case 'all': - this.$('.actions').addClass('all'); - break; - case 'playlist': - this.$('.actions').addClass('playlist'); - break; - } + this.showChildView('playlistPopup', new PopPlaylistView({ + model: this.model, + collection: application.allPlaylists + })); this.togglePlayingState(); }, play(e) { + // Add current playlist to upNext if no track in UpNext + let tracks = application.appState.get('currentPlaylist').get('tracks'); + if (application.upNext.get('tracks').length == 0) { + tracks.each(track => { + application.upNext.get('tracks').add(track); + }); + } application.appState.set('currentTrack', this.model); }, + toggleMenu(e) { + e.stopPropagation(); + this.ui.popupMenu.toggleClass('show'); + this.ui.menu.toggleClass('active'); + }, + + hidePopupMenu(e) { + this.ui.popupMenu.removeClass('show'); + this.ui.menu.removeClass('active'); + }, + + showPlaylist(e) { + application.channel.trigger('playlistPopup:show', this.model); + }, + + hidePlaylist(e) { + application.channel.trigger('playlistPopup:hide', this.model); + }, + + addToUpNext(e) { + e.stopPropagation(); + application.upNext.addTrack(this.model); + + let notification = { + status: 'ok', + message: 'Added to Up Next' + } + application.channel.request('notification', notification); + }, + + deleteFromUpNext(e) { + e.stopPropagation(); + if (this.model == application.appState.get('currentTrack')) { + application.channel.trigger('player:next'); + } + application.upNext.removeTrack(this.model); + }, + + removeFromPlaylist(e) { + e.stopPropagation(); + application.appState.get('currentPlaylist').removeTrack(this.model); + }, + + // TO DO + albumToUpNext(e) { + e.stopPropagation(); + }, + + // TO DO + editDetails(e) { + e.stopPropagation(); + }, + delete(e) { let item = this.model; item.set('hidden', true); @@ -57,10 +123,12 @@ const TrackView = Mn.ItemView.extend({ serializeData() { let metas = this.model.get('metas'); + let currentPlaylist = application.appState.get('currentPlaylist'); return _.extend( _.defaults({}, metas, { artist: '', album: '', - number: '' + number: '', + type: currentPlaylist.get('tracks').type }), { duration: metas.duration? timeToString(metas.duration/1000):'--:--' }); diff --git a/app/views/tracks.js b/app/views/tracks.js index 16fc23b..e973923 100644 --- a/app/views/tracks.js +++ b/app/views/tracks.js @@ -9,7 +9,18 @@ const TracksView = Mn.CompositeView.extend({ childViewContainer: '#track-list', - childView: TrackView + childView: TrackView, + + // Override + remove() { + this._removeElement(); + try { + this.stopListening(); // Weird Error + } catch (e) { + //console.log('views/tracks.js', e); + } + return this; + }, });