diff --git a/app/assets/icons/mute-sm.svg b/app/assets/icons/mute-sm.svg new file mode 100644 index 0000000..670071f --- /dev/null +++ b/app/assets/icons/mute-sm.svg @@ -0,0 +1 @@ +mute-sm \ No newline at end of file diff --git a/app/collections/tracks.js b/app/collections/tracks.js index 342252d..cc85d86 100644 --- a/app/collections/tracks.js +++ b/app/collections/tracks.js @@ -16,7 +16,20 @@ const Tracks = Backbone.Collection.extend({ 'reset:UpNext', this.resetUpNext ); + this.listenTo( + application.appState, + 'change:shuffle', + this.shuffleUpNext + ); + } + }, + + // UpNext : shuffle + shuffleUpNext(appState, shuffle) { + if (shuffle) { + this.reset(this.shuffle(), {silent:true}); } + this.sort(); }, // UpNext : reset @@ -39,7 +52,11 @@ const Tracks = Backbone.Collection.extend({ }, comparator(model) { - return model.get('metas').title; + if (this.type == 'upNext' && application.appState.get('shuffle')) { + return undefined; + } else { + return model.get('metas').title; + } }, sync(method, model, options) { diff --git a/app/initialize.js b/app/initialize.js index c9225e5..bb3a443 100644 --- a/app/initialize.js +++ b/app/initialize.js @@ -14,6 +14,13 @@ function initLocale() { window.t = polyglot.t.bind(polyglot) } +$(document).keydown(function(e) { + let isScrollKey = [32, 37, 38, 39, 40].indexOf(e.which) > -1; + if (isScrollKey && e.target == document.body) { + e.preventDefault(); // prevent the scroll with keyboard + } +}); + document.addEventListener('DOMContentLoaded', function () { initLocale(); application.start(); diff --git a/app/libs/file.js b/app/libs/file.js index fd10336..2c20f27 100644 --- a/app/libs/file.js +++ b/app/libs/file.js @@ -39,7 +39,7 @@ function getAllTracksFileId(musicFiles) { function deleteTrack(allTracks, musicFilesFileId) { for (let i = 0; i < allTracks.length; i++) { let t = allTracks[i]; - if (musicFilesFileId.indexOf(t.get('ressource').fileID) <= -1) { + if (!_.includes(musicFilesFileId, t.get('ressource').fileID)) { t.destroy({ success: () => { application.allTracks.get('tracks').remove(t); }}); @@ -64,7 +64,7 @@ function saveTrack(musicFiles, tracksFileId) { } }); - if (tracksFileId.indexOf(fileid) <= -1) { // does not contains fileid + if (!_.includes(tracksFileId, fileid)) { // does not contains fileid application.allTracks.get('tracks').create(t); } } diff --git a/app/models/appState.js b/app/models/appState.js index 30ec953..d91786c 100644 --- a/app/models/appState.js +++ b/app/models/appState.js @@ -6,6 +6,10 @@ const AppState = Backbone.Model.extend({ defaults: { currentTrack: '', currentPlaylist: '', + shuffle: false, + repeat: 'false', // can be 'false' / 'track' / 'playlist' + currentVolume: 0.5, + mute: false }, sync(method, model, options) { diff --git a/app/models/track.js b/app/models/track.js index d1155ab..a329646 100644 --- a/app/models/track.js +++ b/app/models/track.js @@ -60,8 +60,7 @@ const Track = Backbone.Model.extend({ let id = this.get('ressource').fileID; cozysdk.getFileURL(id, 'file', (err, res) => { if (res) { - let url = 'http://' + res.split('@')[1]; // to delete in prod - callback(url); + callback(res); } }) break; diff --git a/app/styles/content.styl b/app/styles/content.styl index 24a3ebc..0b05b82 100644 --- a/app/styles/content.styl +++ b/app/styles/content.styl @@ -5,45 +5,47 @@ div[role='contentinfo'] overflow: auto margin-bottom: 71px; - h3 - font-size: 24px - line-height: 0.8 - height: 31px - color: $text-dark - display: inline-block - - em + 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 - 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 + svg + position: absolute + height: 20px + left: 9px + width: 20px + top: 6px + + path + fill: #696d78 + stroke: 2px + + button:active + padding-top: 1px table width: 100% @@ -84,6 +86,42 @@ div[role='contentinfo'] 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/player.styl b/app/styles/player.styl index bea9649..421224e 100644 --- a/app/styles/player.styl +++ b/app/styles/player.styl @@ -119,6 +119,14 @@ path fill: #92a0b2 + .active + path + fill: #f14a53 + + #repeat-one-sm + path + fill: #f14a53 + #broadcast margin-left: 100px path diff --git a/app/views/player.js b/app/views/player.js index 4762100..50e9ae2 100644 --- a/app/views/player.js +++ b/app/views/player.js @@ -16,7 +16,10 @@ const Player = Mn.LayoutView.extend({ volume: '#volume', volumeBar: '#volume-bar', playButton: '#play', - trackname: '#trackname' + trackname: '#trackname', + shuffle: '#shuffle', + repeat: '#repeat', + speaker: '#speaker' }, events: { @@ -24,7 +27,10 @@ const Player = Mn.LayoutView.extend({ 'click #play': 'toggle', 'click #next': 'next', 'click @ui.progressBar': 'skip', - 'click @ui.volumeBar': 'changeVol' + 'click @ui.volumeBar': 'changeVol', + 'click @ui.shuffle': 'toggleShuffle', + 'click @ui.repeat': 'toggleRepeat', + 'click @ui.speaker': 'toggleVolume' }, initialize() { @@ -35,6 +41,29 @@ const Player = Mn.LayoutView.extend({ this.load(currentTrack); } }); + $(document).keyup((e) => { + e.preventDefault(); + switch (e.key) { + case ' ': + this.toggle(); + break; + case 'ArrowRight': + this.next(); + break; + case 'ArrowLeft': + this.prev(); + break; + case 'ArrowUp': + // increaseVol + break; + case 'ArrowDown': + // decreaseVol + break; + case 'm': + // mute + break; + } + }); }, onRender() { @@ -92,23 +121,94 @@ const Player = Mn.LayoutView.extend({ prev() { let upNext = application.upNext.get('tracks'); let currentTrack = application.appState.get('currentTrack'); - let index = upNext.indexOf(currentTrack); - let prev = upNext.at(index - 1) - if (prev) { + let index = upNext.indexOf(currentTrack) - 1; + let prev = upNext.at(index) + if (prev && index > -1) { application.appState.set('currentTrack', prev); + } else { + this.replayCurrent(); } }, next() { + let repeat = application.appState.get('repeat'); let upNext = application.upNext.get('tracks'); let currentTrack = application.appState.get('currentTrack'); - let index = upNext.indexOf(currentTrack); - let next = upNext.at(index + 1) - if (next) { + let index = upNext.indexOf(currentTrack) + 1; + let next = upNext.at(index) + if (repeat == 'track') { + this.replayCurrent(); + } else if (next) { application.appState.set('currentTrack', next); + } else if (repeat == 'playlist' && upNext.at(0)) { + if (upNext.length == 1) { + this.replayCurrent(); + } + application.appState.set('currentTrack', upNext.at(0)); } }, + replayCurrent() { + let audio = this.ui.player.get(0); + audio.currentTime = 0; + audio.play(); + this.ui.playButton.find('use').attr( + 'xlink:href', + require('../assets/icons/pause-lg.svg') + ); + }, + + toggleShuffle() { + let shuffle = application.appState.get('shuffle'); + application.appState.set('shuffle', !shuffle); + $('#shuffle-sm').toggleClass('active', !shuffle); + }, + + toggleRepeat() { + let repeat = application.appState.get('repeat'); + switch (repeat) { + case 'false': + application.appState.set('repeat', 'track'); + $('#repeat-sm').toggleClass('active', true); + this.ui.repeat.find('use').attr( + 'xlink:href', + require('../assets/icons/repeat-one-sm.svg') + ); + break; + case 'track': + application.appState.set('repeat', 'playlist'); + this.ui.repeat.find('use').attr( + 'xlink:href', + require('../assets/icons/repeat-sm.svg') + ); + break; + case 'playlist': + application.appState.set('repeat', 'false'); + $('#repeat-sm').toggleClass('active', false); + break; + } + }, + + toggleVolume() { + let audio = this.ui.player.get(0); + let mute = application.appState.get('mute'); + application.appState.set('mute', !mute); + if (!mute) { + this.ui.speaker.find('use').attr( + 'xlink:href', + require('../assets/icons/mute-sm.svg') + ); + audio.volume = 0; + } else { + this.ui.speaker.find('use').attr( + 'xlink:href', + require('../assets/icons/speaker-sm.svg') + ); + audio.volume = application.appState.get('currentVolume'); + } + + }, + // Go to a certain time in the track skip(e) { let audio = this.ui.player.get(0); @@ -142,6 +242,7 @@ const Player = Mn.LayoutView.extend({ let bar = this.ui.volumeBar.get(0); let volume = (e.pageX - bar.offsetLeft) / bar.clientWidth; audio.volume = volume; + application.appState.set('currentVolume', volume); } }); diff --git a/app/views/templates/track.jst b/app/views/templates/track.jst index c8605ed..13cb58c 100644 --- a/app/views/templates/track.jst +++ b/app/views/templates/track.jst @@ -1,5 +1,24 @@ <%- number %> <%- title %> <%- artist %> -<%- album %> +<%- album %> +
+ + + +
+ <%- duration %> diff --git a/app/views/toolbar.js b/app/views/toolbar.js index 207acd1..a5ad965 100644 --- a/app/views/toolbar.js +++ b/app/views/toolbar.js @@ -28,7 +28,8 @@ const Toolbar = Mn.LayoutView.extend({ 'click @ui.search': 'search', 'focusout @ui.importSC': 'focusoutImportSc', 'focusout @ui.search': 'focusoutSearch', - 'keyup @ui.importText': 'keyImportScText' + 'keyup @ui.importText': 'keyImportScText', + 'keyup @ui.searchText': 'keySearchText' }, sync() { @@ -44,6 +45,7 @@ const Toolbar = Mn.LayoutView.extend({ // Import the track when `Enter` is pressed keyImportScText(e) { + e.stopPropagation(); let url = this.ui.importText.val(); if(e.keyCode == 13) { scdl.import(url); @@ -52,6 +54,10 @@ const Toolbar = Mn.LayoutView.extend({ } }, + keySearchText(e) { + e.stopPropagation(); + }, + // Show the input importStream() { this.ui.importSC diff --git a/app/views/track.js b/app/views/track.js index 438cc68..01d239a 100644 --- a/app/views/track.js +++ b/app/views/track.js @@ -11,7 +11,10 @@ const TrackView = Mn.ItemView.extend({ events: { 'click': 'play', - 'click .delete': 'delete' + 'click .delete': 'delete', + 'click #menu': 'displayMenu', + 'click #add-to-upnext':'addToUpNext', + 'click "delete-from-upnext': 'deleteFromUpNext' }, modelEvents: { @@ -24,6 +27,23 @@ 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.togglePlayingState(); + }, + play(e) { application.appState.set('currentTrack', this.model); }, @@ -53,6 +73,4 @@ const TrackView = Mn.ItemView.extend({ }); -TrackView.prototype.onRender = TrackView.prototype.togglePlayingState; - export default TrackView; diff --git a/app/views/tracks.js b/app/views/tracks.js index 3f25044..16fc23b 100644 --- a/app/views/tracks.js +++ b/app/views/tracks.js @@ -10,6 +10,7 @@ const TracksView = Mn.CompositeView.extend({ childViewContainer: '#track-list', childView: TrackView + }); export default TracksView;