mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Lint everything
This commit is contained in:
parent
449fac97fc
commit
adfebd0167
104 changed files with 2770 additions and 2764 deletions
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
libs
|
||||
tests
|
6
.eslintrc
Normal file
6
.eslintrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "vue",
|
||||
"rules": {
|
||||
"no-multi-str": "off"
|
||||
}
|
||||
}
|
|
@ -50,13 +50,16 @@
|
|||
"browserify-hmr": "^0.3.1",
|
||||
"chai": "^3.4.1",
|
||||
"chalk": "^1.1.3",
|
||||
"eslint": "^3.10.2",
|
||||
"eslint-config-vue": "^2.0.1",
|
||||
"eslint-plugin-vue": "^1.0.0",
|
||||
"jsdom": "^9.2.1",
|
||||
"mocha": "^2.3.4",
|
||||
"sinon": "^1.17.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "cross-env NODE_ENV=production && gulp --production",
|
||||
"test": "mocha --compilers js:babel-register --require resources/assets/js/tests/helper.js resources/assets/js/tests/**/*Test.js",
|
||||
"test": "eslint resources/assets/js --ext=js,vue && mocha --compilers js:babel-register --require resources/assets/js/tests/helper.js resources/assets/js/tests/**/*Test.js",
|
||||
"e2e": "gulp e2e"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,77 +23,79 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import $ from 'jquery';
|
||||
import Vue from 'vue'
|
||||
import $ from 'jquery'
|
||||
|
||||
import siteHeader from './components/site-header/index.vue';
|
||||
import siteFooter from './components/site-footer/index.vue';
|
||||
import mainWrapper from './components/main-wrapper/index.vue';
|
||||
import overlay from './components/shared/overlay.vue';
|
||||
import loginForm from './components/auth/login-form.vue';
|
||||
import editSongsForm from './components/modals/edit-songs-form.vue';
|
||||
import siteHeader from './components/site-header/index.vue'
|
||||
import siteFooter from './components/site-footer/index.vue'
|
||||
import mainWrapper from './components/main-wrapper/index.vue'
|
||||
import overlay from './components/shared/overlay.vue'
|
||||
import loginForm from './components/auth/login-form.vue'
|
||||
import editSongsForm from './components/modals/edit-songs-form.vue'
|
||||
|
||||
import { event, showOverlay, hideOverlay, forceReloadWindow, url } from './utils';
|
||||
import { sharedStore, songStore, userStore, preferenceStore as preferences } from './stores';
|
||||
import { playback, ls } from './services';
|
||||
import { focusDirective, clickawayDirective } from './directives';
|
||||
import router from './router';
|
||||
import { event, showOverlay, hideOverlay, forceReloadWindow } from './utils'
|
||||
import { sharedStore, userStore, preferenceStore as preferences } from './stores'
|
||||
import { playback, ls } from './services'
|
||||
import { focusDirective, clickawayDirective } from './directives'
|
||||
import router from './router'
|
||||
|
||||
export default {
|
||||
components: { siteHeader, siteFooter, mainWrapper, overlay, loginForm, editSongsForm },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
authenticated: false,
|
||||
};
|
||||
authenticated: false
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
mounted () {
|
||||
// The app has just been initialized, check if we can get the user data with an already existing token
|
||||
const token = ls.get('jwt-token');
|
||||
const token = ls.get('jwt-token')
|
||||
if (token) {
|
||||
this.authenticated = true;
|
||||
this.init();
|
||||
this.authenticated = true
|
||||
this.init()
|
||||
}
|
||||
|
||||
// Create the element to be the ghost drag image.
|
||||
$('<div id="dragGhost"></div>').appendTo('body');
|
||||
$('<div id="dragGhost"></div>').appendTo('body')
|
||||
|
||||
// And the textarea to copy stuff
|
||||
$('<textarea id="copyArea"></textarea>').appendTo('body');
|
||||
$('<textarea id="copyArea"></textarea>').appendTo('body')
|
||||
|
||||
// Add an ugly mac/non-mac class for OS-targeting styles.
|
||||
// I'm crying inside.
|
||||
$('html').addClass(navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac');
|
||||
$('html').addClass(navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac')
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
showOverlay();
|
||||
init () {
|
||||
showOverlay()
|
||||
|
||||
// Make the most important HTTP request to get all necessary data from the server.
|
||||
// Afterwards, init all mandatory stores and services.
|
||||
sharedStore.init().then(() => {
|
||||
playback.init();
|
||||
hideOverlay();
|
||||
playback.init()
|
||||
hideOverlay()
|
||||
|
||||
// Ask for user's notification permission.
|
||||
this.requestNotifPermission();
|
||||
this.requestNotifPermission()
|
||||
|
||||
// To confirm or not to confirm closing, it's a question.
|
||||
window.onbeforeunload = e => {
|
||||
if (!preferences.confirmClosing) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Notice that a custom message like this has ceased to be supported
|
||||
// starting from Chrome 51.
|
||||
return 'You asked Koel to confirm before closing, so here it is.';
|
||||
};
|
||||
return 'You asked Koel to confirm before closing, so here it is.'
|
||||
}
|
||||
|
||||
// Let all other components know we're ready.
|
||||
event.emit('koel:ready');
|
||||
}).catch(() => this.authenticated = false);
|
||||
event.emit('koel:ready')
|
||||
}).catch(() => {
|
||||
this.authenticated = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,14 +103,14 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event
|
||||
*/
|
||||
togglePlayback(e) {
|
||||
togglePlayback (e) {
|
||||
if ($(e.target).is('input,textarea,button,select')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
// Ah... Good ol' jQuery. Whatever play/pause control is there, we blindly click it.
|
||||
$('#mainFooter .play:visible, #mainFooter .pause:visible').click();
|
||||
e.preventDefault();
|
||||
$('#mainFooter .play:visible, #mainFooter .pause:visible').click()
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -116,13 +118,13 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event
|
||||
*/
|
||||
playPrev(e) {
|
||||
playPrev (e) {
|
||||
if ($(e.target).is('input,textarea')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
playback.playPrev();
|
||||
e.preventDefault();
|
||||
playback.playPrev()
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -130,13 +132,13 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event
|
||||
*/
|
||||
playNext(e) {
|
||||
playNext (e) {
|
||||
if ($(e.target).is('input,textarea')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
playback.playNext();
|
||||
e.preventDefault();
|
||||
playback.playNext()
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -144,37 +146,37 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event
|
||||
*/
|
||||
search(e) {
|
||||
search (e) {
|
||||
if ($(e.target).is('input,textarea') || e.metaKey || e.ctrlKey) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
$('#searchForm input[type="search"]').focus().select();
|
||||
e.preventDefault();
|
||||
$('#searchForm input[type="search"]').focus().select()
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
/**
|
||||
* Request for notification permission if it's not provided and the user is OK with notifs.
|
||||
*/
|
||||
requestNotifPermission() {
|
||||
if (window.Notification && preferences.notify && Notification.permission !== 'granted') {
|
||||
Notification.requestPermission(result => {
|
||||
requestNotifPermission () {
|
||||
if (window.Notification && preferences.notify && window.Notification.permission !== 'granted') {
|
||||
window.Notification.requestPermission(result => {
|
||||
if (result === 'denied') {
|
||||
preferences.notify = false;
|
||||
preferences.notify = false
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
/**
|
||||
* When the user logs in, set the whole app to be "authenticated" and initialize it.
|
||||
*/
|
||||
'user:loggedin': () => {
|
||||
this.authenticated = true;
|
||||
this.init();
|
||||
'user:loggedin': () => {
|
||||
this.authenticated = true
|
||||
this.init()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -187,22 +189,22 @@ export default {
|
|||
/**
|
||||
* Log the current user out and reset the application state.
|
||||
*/
|
||||
logout() {
|
||||
logout () {
|
||||
userStore.logout().then((r) => {
|
||||
ls.remove('jwt-token');
|
||||
forceReloadWindow();
|
||||
});
|
||||
ls.remove('jwt-token')
|
||||
forceReloadWindow()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Init our basic, custom router on ready to determine app state.
|
||||
*/
|
||||
'koel:ready': () => {
|
||||
router.init();
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
router.init()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Register our custom key codes
|
||||
Vue.config.keyCodes = {
|
||||
|
@ -213,11 +215,11 @@ Vue.config.keyCodes = {
|
|||
mediaNext: 176,
|
||||
mediaPrev: 177,
|
||||
mediaToggle: 179
|
||||
};
|
||||
}
|
||||
|
||||
// …and the global directives
|
||||
Vue.directive('koel-focus', focusDirective);
|
||||
Vue.directive('koel-clickaway',clickawayDirective);
|
||||
Vue.directive('koel-focus', focusDirective)
|
||||
Vue.directive('koel-clickaway', clickawayDirective)
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -7,33 +7,35 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { userStore } from '../../stores';
|
||||
import { event } from '../../utils';
|
||||
import { userStore } from '../../stores'
|
||||
import { event } from '../../utils'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
email: '',
|
||||
password: '',
|
||||
failed: false,
|
||||
};
|
||||
failed: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
login() {
|
||||
this.failed = false;
|
||||
login () {
|
||||
this.failed = false
|
||||
|
||||
userStore.login(this.email, this.password).then(() => {
|
||||
this.failed = false;
|
||||
this.failed = false
|
||||
|
||||
// Reset the password so that the next login will have this field empty.
|
||||
this.password = '';
|
||||
this.password = ''
|
||||
|
||||
event.emit('user:loggedin');
|
||||
}).catch(() => this.failed = true);
|
||||
},
|
||||
},
|
||||
};
|
||||
event.emit('user:loggedin')
|
||||
}).catch(() => {
|
||||
this.failed = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -37,42 +37,42 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { playback } from '../../../services';
|
||||
import { playback } from '../../../services'
|
||||
|
||||
export default {
|
||||
props: ['album', 'mode'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
showingFullWiki: false,
|
||||
};
|
||||
showingFullWiki: false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
album() {
|
||||
this.showingFullWiki = false;
|
||||
},
|
||||
album () {
|
||||
this.showingFullWiki = false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
showSummary() {
|
||||
return this.mode !== 'full' && !this.showingFullWiki;
|
||||
showSummary () {
|
||||
return this.mode !== 'full' && !this.showingFullWiki
|
||||
},
|
||||
|
||||
showFull() {
|
||||
return this.mode === 'full' || this.showingFullWiki;
|
||||
},
|
||||
showFull () {
|
||||
return this.mode === 'full' || this.showingFullWiki
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Shuffle all songs in the current album.
|
||||
*/
|
||||
shuffleAll() {
|
||||
playback.playAllInAlbum(this.album);
|
||||
},
|
||||
},
|
||||
};
|
||||
shuffleAll () {
|
||||
playback.playAllInAlbum(this.album)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -29,30 +29,30 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { playback } from '../../../services';
|
||||
import { playback } from '../../../services'
|
||||
|
||||
export default {
|
||||
props: ['artist', 'mode'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
showingFullBio: false,
|
||||
};
|
||||
showingFullBio: false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
artist() {
|
||||
this.showingFullBio = false;
|
||||
},
|
||||
artist () {
|
||||
this.showingFullBio = false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
showSummary() {
|
||||
return this.mode !== 'full' && !this.showingFullBio;
|
||||
showSummary () {
|
||||
return this.mode !== 'full' && !this.showingFullBio
|
||||
},
|
||||
|
||||
showFull() {
|
||||
return this.mode === 'full' || this.showingFullBio;
|
||||
showFull () {
|
||||
return this.mode === 'full' || this.showingFullBio
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -60,11 +60,11 @@ export default {
|
|||
/**
|
||||
* Shuffle all songs performed by the current song's artist.
|
||||
*/
|
||||
shuffleAll() {
|
||||
playback.playAllByArtist(this.artist);
|
||||
},
|
||||
},
|
||||
};
|
||||
shuffleAll () {
|
||||
playback.playAllByArtist(this.artist)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -35,30 +35,29 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import { invokeMap } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils';
|
||||
import { sharedStore, songStore, preferenceStore as preferences } from '../../../stores';
|
||||
import { songInfo } from '../../../services';
|
||||
import { event } from '../../../utils'
|
||||
import { sharedStore, songStore, preferenceStore as preferences } from '../../../stores'
|
||||
import { songInfo } from '../../../services'
|
||||
|
||||
import lyrics from './lyrics.vue';
|
||||
import artistInfo from './artist-info.vue';
|
||||
import albumInfo from './album-info.vue';
|
||||
import youtube from './youtube.vue';
|
||||
import lyrics from './lyrics.vue'
|
||||
import artistInfo from './artist-info.vue'
|
||||
import albumInfo from './album-info.vue'
|
||||
import youtube from './youtube.vue'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--extra--index',
|
||||
components: { lyrics, artistInfo, albumInfo, youtube },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
song: songStore.stub,
|
||||
state: preferences.state,
|
||||
sharedState: sharedStore.state,
|
||||
currentView: 'lyrics',
|
||||
};
|
||||
currentView: 'lyrics'
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -67,25 +66,25 @@ export default {
|
|||
* to/from the html tag.
|
||||
* Some element's CSS can then be controlled based on this class.
|
||||
*/
|
||||
'state.showExtraPanel': function (newVal) {
|
||||
'state.showExtraPanel' (newVal) {
|
||||
if (newVal && !isMobile.any) {
|
||||
$('html').addClass('with-extra-panel');
|
||||
$('html').addClass('with-extra-panel')
|
||||
} else {
|
||||
$('html').removeClass('with-extra-panel');
|
||||
$('html').removeClass('with-extra-panel')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
mounted () {
|
||||
// On ready, add 'with-extra-panel' class.
|
||||
if (!isMobile.any) {
|
||||
$('html').addClass('with-extra-panel');
|
||||
$('html').addClass('with-extra-panel')
|
||||
}
|
||||
|
||||
if (isMobile.phone) {
|
||||
// On a mobile device, we always hide the panel initially regardless of
|
||||
// the saved preference.
|
||||
preferences.showExtraPanel = false;
|
||||
preferences.showExtraPanel = false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -93,31 +92,31 @@ export default {
|
|||
/**
|
||||
* Reset all self and applicable child components' states.
|
||||
*/
|
||||
resetState() {
|
||||
this.currentView = 'lyrics';
|
||||
this.song = songStore.stub;
|
||||
},
|
||||
resetState () {
|
||||
this.currentView = 'lyrics'
|
||||
this.song = songStore.stub
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
'main-content-view:load': view => {
|
||||
// Hide the panel away if a main view is triggered on mobile.
|
||||
if (isMobile.phone) {
|
||||
preferences.showExtraPanel = false;
|
||||
preferences.showExtraPanel = false
|
||||
}
|
||||
},
|
||||
|
||||
'song:played': song => {
|
||||
songInfo.fetch(song).then(song => {
|
||||
this.song = song;
|
||||
});
|
||||
this.song = song
|
||||
})
|
||||
},
|
||||
|
||||
'koel:teardown': () => this.resetState(),
|
||||
});
|
||||
},
|
||||
};
|
||||
'koel:teardown': () => this.resetState()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['song'],
|
||||
};
|
||||
props: ['song']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -18,43 +18,42 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { event } from '../../../utils';
|
||||
import { youtube as youtubeService } from '../../../services';
|
||||
import { youtube as youtubeService } from '../../../services'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--extra--youtube',
|
||||
props: ['song'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
videos: [],
|
||||
};
|
||||
videos: []
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
song(val) {
|
||||
this.videos = val.youtube ? val.youtube.items : [];
|
||||
},
|
||||
song (val) {
|
||||
this.videos = val.youtube ? val.youtube.items : []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
playYouTube(id) {
|
||||
youtubeService.play(id);
|
||||
playYouTube (id) {
|
||||
youtubeService.play(id)
|
||||
},
|
||||
|
||||
/**
|
||||
* Load more videos.
|
||||
*/
|
||||
loadMore() {
|
||||
this.loading = true;
|
||||
loadMore () {
|
||||
this.loading = true
|
||||
youtubeService.searchVideosRelatedToSong(this.song, () => {
|
||||
this.videos = this.song.youtube.items;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
this.videos = this.song.youtube.items
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import sidebar from './sidebar/index.vue';
|
||||
import mainContent from './main-content/index.vue';
|
||||
import extra from './extra/index.vue';
|
||||
import sidebar from './sidebar/index.vue'
|
||||
import mainContent from './main-content/index.vue'
|
||||
import extra from './extra/index.vue'
|
||||
|
||||
export default {
|
||||
components: { sidebar, mainContent, extra },
|
||||
};
|
||||
components: { sidebar, mainContent, extra }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -46,15 +46,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
|
||||
import { pluralize, event } from '../../../utils';
|
||||
import { albumStore, artistStore, sharedStore } from '../../../stores';
|
||||
import { playback, download, albumInfo as albumInfoService } from '../../../services';
|
||||
import router from '../../../router';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import albumInfo from '../extra/album-info.vue';
|
||||
import soundBar from '../../shared/sound-bar.vue';
|
||||
import { pluralize, event } from '../../../utils'
|
||||
import { albumStore, artistStore, sharedStore } from '../../../stores'
|
||||
import { playback, download, albumInfo as albumInfoService } from '../../../services'
|
||||
import router from '../../../router'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
import albumInfo from '../extra/album-info.vue'
|
||||
import soundBar from '../../shared/sound-bar.vue'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--album',
|
||||
|
@ -62,22 +60,22 @@ export default {
|
|||
components: { albumInfo, soundBar },
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
sharedState: sharedStore.state,
|
||||
album: albumStore.stub,
|
||||
info: {
|
||||
showing: false,
|
||||
loading: true,
|
||||
},
|
||||
};
|
||||
loading: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isNormalArtist() {
|
||||
return !artistStore.isVariousArtists(this.album.artist)
|
||||
&& !artistStore.isUnknownArtist(this.album.artist);
|
||||
},
|
||||
isNormalArtist () {
|
||||
return !artistStore.isVariousArtists(this.album.artist) &&
|
||||
!artistStore.isUnknownArtist(this.album.artist)
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -89,12 +87,12 @@ export default {
|
|||
*/
|
||||
'album.songs.length' (newVal) {
|
||||
if (!newVal) {
|
||||
router.go('albums');
|
||||
router.go('albums')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
/**
|
||||
* Listen to 'main-content-view:load' event to load the requested album
|
||||
* into view if applicable.
|
||||
|
@ -104,38 +102,40 @@ export default {
|
|||
*/
|
||||
event.on('main-content-view:load', (view, album) => {
|
||||
if (view === 'album') {
|
||||
this.info.showing = false;
|
||||
this.album = album;
|
||||
this.info.showing = false
|
||||
this.album = album
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Shuffle the songs in the current album.
|
||||
*/
|
||||
shuffle() {
|
||||
playback.queueAndPlay(this.album.songs, true);
|
||||
shuffle () {
|
||||
playback.queueAndPlay(this.album.songs, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all songs from the album.
|
||||
*/
|
||||
download() {
|
||||
download.fromAlbum(this.album);
|
||||
download () {
|
||||
download.fromAlbum(this.album)
|
||||
},
|
||||
|
||||
showInfo() {
|
||||
this.info.showing = true;
|
||||
showInfo () {
|
||||
this.info.showing = true
|
||||
if (!this.album.info) {
|
||||
this.info.loading = true;
|
||||
albumInfoService.fetch(this.album).then(() => this.info.loading = false);
|
||||
this.info.loading = true
|
||||
albumInfoService.fetch(this.album).then(() => {
|
||||
this.info.loading = false
|
||||
})
|
||||
} else {
|
||||
this.info.loading = false;
|
||||
this.info.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -14,41 +14,41 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { filterBy, limitBy, event } from '../../../utils';
|
||||
import { albumStore } from '../../../stores';
|
||||
import albumItem from '../../shared/album-item.vue';
|
||||
import viewModeSwitch from '../../shared/view-mode-switch.vue';
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll';
|
||||
import { filterBy, limitBy, event } from '../../../utils'
|
||||
import { albumStore } from '../../../stores'
|
||||
import albumItem from '../../shared/album-item.vue'
|
||||
import viewModeSwitch from '../../shared/view-mode-switch.vue'
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll'
|
||||
|
||||
export default {
|
||||
mixins: [infiniteScroll],
|
||||
components: { albumItem, viewModeSwitch },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
perPage: 9,
|
||||
numOfItems: 9,
|
||||
q: '',
|
||||
viewMode: null,
|
||||
};
|
||||
viewMode: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayedItems() {
|
||||
displayedItems () {
|
||||
return limitBy(
|
||||
filterBy(albumStore.all, this.q, 'name', 'artist.name'),
|
||||
this.numOfItems
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeViewMode(mode) {
|
||||
this.viewMode = mode;
|
||||
},
|
||||
changeViewMode (mode) {
|
||||
this.viewMode = mode
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
/**
|
||||
* When the application is ready, load the first batch of items.
|
||||
|
@ -56,14 +56,16 @@ export default {
|
|||
'koel:ready': () => this.displayMore(),
|
||||
|
||||
'koel:teardown': () => {
|
||||
this.q = '';
|
||||
this.numOfItems = 9;
|
||||
this.q = ''
|
||||
this.numOfItems = 9
|
||||
},
|
||||
|
||||
'filter:changed': q => this.q = q,
|
||||
});
|
||||
},
|
||||
};
|
||||
'filter:changed': q => {
|
||||
this.q = q
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -45,15 +45,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
|
||||
import { pluralize, event } from '../../../utils';
|
||||
import { sharedStore, artistStore } from '../../../stores';
|
||||
import { playback, download, artistInfo as artistInfoService } from '../../../services';
|
||||
import router from '../../../router';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import artistInfo from '../extra/artist-info.vue';
|
||||
import soundBar from '../../shared/sound-bar.vue';
|
||||
import { pluralize, event } from '../../../utils'
|
||||
import { sharedStore, artistStore } from '../../../stores'
|
||||
import { playback, download, artistInfo as artistInfoService } from '../../../services'
|
||||
import router from '../../../router'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
import artistInfo from '../extra/artist-info.vue'
|
||||
import soundBar from '../../shared/sound-bar.vue'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--artist',
|
||||
|
@ -61,15 +59,15 @@ export default {
|
|||
components: { artistInfo, soundBar },
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
sharedState: sharedStore.state,
|
||||
artist: artistStore.stub,
|
||||
info: {
|
||||
showing: false,
|
||||
loading: true,
|
||||
},
|
||||
};
|
||||
loading: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -81,12 +79,12 @@ export default {
|
|||
*/
|
||||
'artist.albums.length' (newVal) {
|
||||
if (!newVal) {
|
||||
router.go('artists');
|
||||
router.go('artists')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
/**
|
||||
* Listen to 'main-content-view:load' event to load the requested artist
|
||||
* into view if applicable.
|
||||
|
@ -96,38 +94,40 @@ export default {
|
|||
*/
|
||||
event.on('main-content-view:load', (view, artist) => {
|
||||
if (view === 'artist') {
|
||||
this.info.showing = false;
|
||||
this.artist = artist;
|
||||
this.info.showing = false
|
||||
this.artist = artist
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Shuffle the songs by the current artist.
|
||||
*/
|
||||
shuffle() {
|
||||
playback.queueAndPlay(this.artist.songs, true);
|
||||
shuffle () {
|
||||
playback.queueAndPlay(this.artist.songs, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all songs by the artist.
|
||||
*/
|
||||
download() {
|
||||
download.fromArtist(this.artist);
|
||||
download () {
|
||||
download.fromArtist(this.artist)
|
||||
},
|
||||
|
||||
showInfo() {
|
||||
this.info.showing = true;
|
||||
showInfo () {
|
||||
this.info.showing = true
|
||||
if (!this.artist.info) {
|
||||
this.info.loading = true;
|
||||
artistInfoService.fetch(this.artist).then(() => this.info.loading = false);
|
||||
this.info.loading = true
|
||||
artistInfoService.fetch(this.artist).then(() => {
|
||||
this.info.loading = false
|
||||
})
|
||||
} else {
|
||||
this.info.loading = false;
|
||||
this.info.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -14,43 +14,43 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { filterBy, limitBy, event } from '../../../utils';
|
||||
import { artistStore } from '../../../stores';
|
||||
import { filterBy, limitBy, event } from '../../../utils'
|
||||
import { artistStore } from '../../../stores'
|
||||
|
||||
import artistItem from '../../shared/artist-item.vue';
|
||||
import viewModeSwitch from '../../shared/view-mode-switch.vue';
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll';
|
||||
import artistItem from '../../shared/artist-item.vue'
|
||||
import viewModeSwitch from '../../shared/view-mode-switch.vue'
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll'
|
||||
|
||||
export default {
|
||||
mixins: [infiniteScroll],
|
||||
|
||||
components: { artistItem, viewModeSwitch },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
perPage: 9,
|
||||
numOfItems: 9,
|
||||
q: '',
|
||||
viewMode: null,
|
||||
};
|
||||
viewMode: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayedItems() {
|
||||
displayedItems () {
|
||||
return limitBy(
|
||||
filterBy(artistStore.all, this.q, 'name'),
|
||||
this.numOfItems
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeViewMode(mode) {
|
||||
this.viewMode = mode;
|
||||
},
|
||||
changeViewMode (mode) {
|
||||
this.viewMode = mode
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
/**
|
||||
* When the application is ready, load the first batch of items.
|
||||
|
@ -58,14 +58,16 @@ export default {
|
|||
'koel:ready': () => this.displayMore(),
|
||||
|
||||
'koel:teardown': () => {
|
||||
this.q = '';
|
||||
this.numOfItems = 9;
|
||||
this.q = ''
|
||||
this.numOfItems = 9
|
||||
},
|
||||
|
||||
'filter:changed': q => this.q = q,
|
||||
});
|
||||
},
|
||||
};
|
||||
'filter:changed': q => {
|
||||
this.q = q
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -35,12 +35,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
|
||||
import { pluralize } from '../../../utils';
|
||||
import { favoriteStore, sharedStore } from '../../../stores';
|
||||
import { playback, download } from '../../../services';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import { pluralize } from '../../../utils'
|
||||
import { favoriteStore, sharedStore } from '../../../stores'
|
||||
import { download } from '../../../services'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--favorites',
|
||||
|
@ -50,19 +48,19 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
state: favoriteStore.state,
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Download all favorite songs.
|
||||
*/
|
||||
download() {
|
||||
download.fromFavorites();
|
||||
},
|
||||
},
|
||||
};
|
||||
download () {
|
||||
download.fromFavorites()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -74,15 +74,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { sample } from 'lodash';
|
||||
import { sample } from 'lodash'
|
||||
|
||||
import { event } from '../../../utils';
|
||||
import { songStore, albumStore, artistStore, userStore, preferenceStore } from '../../../stores';
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll';
|
||||
import { event } from '../../../utils'
|
||||
import { songStore, albumStore, artistStore, userStore, preferenceStore } from '../../../stores'
|
||||
import infiniteScroll from '../../../mixins/infinite-scroll'
|
||||
|
||||
import albumItem from '../../shared/album-item.vue';
|
||||
import artistItem from '../../shared/artist-item.vue';
|
||||
import songItem from '../../shared/home-song-item.vue';
|
||||
import albumItem from '../../shared/album-item.vue'
|
||||
import artistItem from '../../shared/artist-item.vue'
|
||||
import songItem from '../../shared/home-song-item.vue'
|
||||
|
||||
export default {
|
||||
components: { albumItem, artistItem, songItem },
|
||||
|
@ -103,55 +103,55 @@ export default {
|
|||
'Sup, %s?',
|
||||
'How’s life, %s?',
|
||||
'How’s your day, %s?',
|
||||
'How have you been, %s?',
|
||||
'How have you been, %s?'
|
||||
],
|
||||
recentSongs: [],
|
||||
top: {
|
||||
songs: [],
|
||||
albums: [],
|
||||
artists: [],
|
||||
artists: []
|
||||
},
|
||||
recentlyAdded: {
|
||||
albums: [],
|
||||
songs: [],
|
||||
songs: []
|
||||
},
|
||||
|
||||
preferences: preferenceStore.state,
|
||||
};
|
||||
preferences: preferenceStore.state
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
greeting() {
|
||||
return sample(this.greetings).replace('%s', userStore.current.name);
|
||||
greeting () {
|
||||
return sample(this.greetings).replace('%s', userStore.current.name)
|
||||
},
|
||||
|
||||
showRecentlyAddedSection() {
|
||||
return this.recentlyAdded.albums.length || this.recentlyAdded.songs.length;
|
||||
},
|
||||
showRecentlyAddedSection () {
|
||||
return this.recentlyAdded.albums.length || this.recentlyAdded.songs.length
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Refresh the dashboard with latest data.
|
||||
*/
|
||||
refreshDashboard() {
|
||||
this.top.songs = songStore.getMostPlayed(7);
|
||||
this.top.albums = albumStore.getMostPlayed(6);
|
||||
this.top.artists = artistStore.getMostPlayed(6);
|
||||
this.recentlyAdded.albums = albumStore.getRecentlyAdded(6);
|
||||
this.recentlyAdded.songs = songStore.getRecentlyAdded(10);
|
||||
this.recentSongs = songStore.getRecent(7);
|
||||
},
|
||||
refreshDashboard () {
|
||||
this.top.songs = songStore.getMostPlayed(7)
|
||||
this.top.albums = albumStore.getMostPlayed(6)
|
||||
this.top.artists = artistStore.getMostPlayed(6)
|
||||
this.recentlyAdded.albums = albumStore.getRecentlyAdded(6)
|
||||
this.recentlyAdded.songs = songStore.getRecentlyAdded(10)
|
||||
this.recentSongs = songStore.getRecent(7)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
'koel:ready': () => this.refreshDashboard(),
|
||||
|
||||
'song:played': () => this.refreshDashboard(),
|
||||
});
|
||||
},
|
||||
};
|
||||
'song:played': () => this.refreshDashboard()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -18,38 +18,40 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { event } from '../../../utils';
|
||||
import { albumStore, sharedStore } from '../../../stores';
|
||||
import { event } from '../../../utils'
|
||||
import { albumStore, sharedStore } from '../../../stores'
|
||||
|
||||
import albums from './albums.vue';
|
||||
import album from './album.vue';
|
||||
import artists from './artists.vue';
|
||||
import artist from './artist.vue';
|
||||
import songs from './songs.vue';
|
||||
import settings from './settings.vue';
|
||||
import users from './users.vue';
|
||||
import queue from './queue.vue';
|
||||
import home from './home.vue';
|
||||
import playlist from './playlist.vue';
|
||||
import favorites from './favorites.vue';
|
||||
import profile from './profile.vue';
|
||||
import youtubePlayer from './youtube-player.vue';
|
||||
import albums from './albums.vue'
|
||||
import album from './album.vue'
|
||||
import artists from './artists.vue'
|
||||
import artist from './artist.vue'
|
||||
import songs from './songs.vue'
|
||||
import settings from './settings.vue'
|
||||
import users from './users.vue'
|
||||
import queue from './queue.vue'
|
||||
import home from './home.vue'
|
||||
import playlist from './playlist.vue'
|
||||
import favorites from './favorites.vue'
|
||||
import profile from './profile.vue'
|
||||
import youtubePlayer from './youtube-player.vue'
|
||||
|
||||
export default {
|
||||
components: { albums, album, artists, artist, songs, settings,
|
||||
users, home, queue, playlist, favorites, profile, youtubePlayer },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
view: 'home', // The default view
|
||||
albumCover: null,
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
'main-content-view:load': view => this.view = view,
|
||||
'main-content-view:load': view => {
|
||||
this.view = view
|
||||
},
|
||||
|
||||
/**
|
||||
* When a new song is played, find its cover for the translucent effect.
|
||||
|
@ -59,11 +61,11 @@ export default {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
'song:played': song => {
|
||||
this.albumCover = song.album.cover === albumStore.stub.cover ? null : song.album.cover;
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
this.albumCover = song.album.cover === albumStore.stub.cover ? null : song.album.cover
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -37,31 +37,30 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import swal from 'sweetalert';
|
||||
import swal from 'sweetalert'
|
||||
|
||||
import { pluralize, event } from '../../../utils';
|
||||
import { playlistStore, sharedStore } from '../../../stores';
|
||||
import { playback, download } from '../../../services';
|
||||
import router from '../../../router';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import { pluralize, event } from '../../../utils'
|
||||
import { playlistStore, sharedStore } from '../../../stores'
|
||||
import { playback, download } from '../../../services'
|
||||
import router from '../../../router'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--playlist',
|
||||
mixins: [hasSongList],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
playlist: playlistStore.stub,
|
||||
sharedState: sharedStore.state,
|
||||
songListControlConfig: {
|
||||
deletePlaylist: true,
|
||||
},
|
||||
};
|
||||
deletePlaylist: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
/**
|
||||
* Listen to 'main-content-view:load' event to load the requested
|
||||
* playlist into view if applicable.
|
||||
|
@ -71,9 +70,9 @@ export default {
|
|||
*/
|
||||
event.on('main-content-view:load', (view, playlist) => {
|
||||
if (view === 'playlist') {
|
||||
this.playlist = playlist;
|
||||
this.playlist = playlist
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -81,19 +80,18 @@ export default {
|
|||
* Shuffle the songs in the current playlist.
|
||||
* Overriding the mixin.
|
||||
*/
|
||||
shuffleAll() {
|
||||
playback.queueAndPlay(this.playlist.songs, true);
|
||||
shuffleAll () {
|
||||
playback.queueAndPlay(this.playlist.songs, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm deleting the playlist.
|
||||
*/
|
||||
confirmDelete() {
|
||||
confirmDelete () {
|
||||
// If the playlist is empty, just go ahead and delete it.
|
||||
if (!this.playlist.songs.length) {
|
||||
this.del();
|
||||
|
||||
return;
|
||||
this.del()
|
||||
return
|
||||
}
|
||||
|
||||
swal({
|
||||
|
@ -101,32 +99,32 @@ export default {
|
|||
text: 'Once it’s gone, it’s gone, and there’s no turning back.',
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Yes, go ahead',
|
||||
}, this.del);
|
||||
confirmButtonText: 'Yes, go ahead'
|
||||
}, this.del)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the current playlist.
|
||||
*/
|
||||
del() {
|
||||
del () {
|
||||
playlistStore.delete(this.playlist).then(() => {
|
||||
// Reset the current playlist to our stub, so that we don't encounter
|
||||
// any property reference error.
|
||||
this.playlist = playlistStore.stub;
|
||||
this.playlist = playlistStore.stub
|
||||
|
||||
// Switch back to Home screen
|
||||
router.go('home');
|
||||
});
|
||||
router.go('home')
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all songs in the current playlist.
|
||||
*/
|
||||
download() {
|
||||
return download.fromPlaylist(this.playlist);
|
||||
},
|
||||
},
|
||||
};
|
||||
download () {
|
||||
return download.fromPlaylist(this.playlist)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -104,57 +104,56 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import swal from 'sweetalert';
|
||||
import $ from 'jquery'
|
||||
import swal from 'sweetalert'
|
||||
|
||||
import { userStore, preferenceStore, sharedStore } from '../../../stores';
|
||||
import { forceReloadWindow } from '../../../utils';
|
||||
import { http, ls } from '../../../services';
|
||||
import { userStore, preferenceStore, sharedStore } from '../../../stores'
|
||||
import { forceReloadWindow } from '../../../utils'
|
||||
import { http, ls } from '../../../services'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: userStore.state,
|
||||
cache: userStore.stub,
|
||||
pwd: '',
|
||||
confirmPwd: '',
|
||||
prefs: preferenceStore.state,
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Update the current user's profile.
|
||||
*/
|
||||
update() {
|
||||
update () {
|
||||
// A little validation put in a small place.
|
||||
if ((this.pwd || this.confirmPwd) && this.pwd !== this.confirmPwd) {
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').addClass('error');
|
||||
|
||||
return;
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').addClass('error')
|
||||
return
|
||||
}
|
||||
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').removeClass('error');
|
||||
$('#inputProfilePassword, #inputProfileConfirmPassword').removeClass('error')
|
||||
|
||||
userStore.updateProfile(this.pwd).then(() => {
|
||||
this.pwd = '';
|
||||
this.confirmPwd = '';
|
||||
this.pwd = ''
|
||||
this.confirmPwd = ''
|
||||
|
||||
swal({
|
||||
title: 'Done!',
|
||||
text: 'Profile saved.',
|
||||
type: 'success',
|
||||
allowOutsideClick: true,
|
||||
});
|
||||
});
|
||||
allowOutsideClick: true
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current user's preference.
|
||||
*/
|
||||
savePreference() {
|
||||
this.$nextTick(() => preferenceStore.save());
|
||||
savePreference () {
|
||||
this.$nextTick(() => preferenceStore.save())
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -162,29 +161,29 @@ export default {
|
|||
* This method opens a new window.
|
||||
* Koel will reload once the connection is successful.
|
||||
*/
|
||||
connectToLastfm() {
|
||||
connectToLastfm () {
|
||||
window.open(
|
||||
`/api/lastfm/connect?jwt-token=${ls.get('jwt-token')}`,
|
||||
'_blank',
|
||||
'toolbar=no,titlebar=no,location=no,width=1024,height=640'
|
||||
);
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect the current user from Last.fm.
|
||||
* Oh God why.
|
||||
*/
|
||||
disconnectFromLastfm() {
|
||||
disconnectFromLastfm () {
|
||||
// Should we use userStore?
|
||||
// - We shouldn't. This doesn't have anything to do with stores.
|
||||
// Should we confirm the user?
|
||||
// - Nope. Users should be grown-ass adults who take responsibilty of their actions.
|
||||
// But one of my users is my new born kid!
|
||||
// - Then? Kids will fuck things up anyway.
|
||||
http.delete('lastfm/disconnect', {}, forceReloadWindow);
|
||||
},
|
||||
},
|
||||
};
|
||||
http.delete('lastfm/disconnect', {}, forceReloadWindow)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -34,32 +34,32 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { pluralize } from '../../../utils';
|
||||
import { queueStore, songStore } from '../../../stores';
|
||||
import { playback } from '../../../services';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import { pluralize } from '../../../utils'
|
||||
import { queueStore, songStore } from '../../../stores'
|
||||
import { playback } from '../../../services'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--queue',
|
||||
mixins: [hasSongList],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: queueStore.state,
|
||||
songListControlConfig: {
|
||||
clearQueue: true,
|
||||
clearQueue: true
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if we should display a "Shuffle All" link.
|
||||
*/
|
||||
showShufflingAllOption() {
|
||||
return songStore.all.length;
|
||||
},
|
||||
showShufflingAllOption () {
|
||||
return songStore.all.length
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -67,18 +67,18 @@ export default {
|
|||
* Shuffle all songs we have.
|
||||
* Overriding the mixin.
|
||||
*/
|
||||
shuffleAll() {
|
||||
playback.queueAndPlay(this.state.songs.length ? this.state.songs : songStore.all, true);
|
||||
shuffleAll () {
|
||||
playback.queueAndPlay(this.state.songs.length ? this.state.songs : songStore.all, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the queue.
|
||||
*/
|
||||
clearQueue() {
|
||||
queueStore.clear();
|
||||
},
|
||||
},
|
||||
};
|
||||
clearQueue () {
|
||||
queueStore.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -24,18 +24,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import swal from 'sweetalert';
|
||||
import swal from 'sweetalert'
|
||||
|
||||
import { settingStore, sharedStore } from '../../../stores';
|
||||
import { parseValidationError, forceReloadWindow, event, showOverlay, hideOverlay } from '../../../utils';
|
||||
import router from '../../../router';
|
||||
import { settingStore, sharedStore } from '../../../stores'
|
||||
import { parseValidationError, forceReloadWindow, showOverlay, hideOverlay } from '../../../utils'
|
||||
import router from '../../../router'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: settingStore.state,
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -44,15 +44,15 @@ export default {
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
shouldWarn() {
|
||||
shouldWarn () {
|
||||
// Warn the user if the media path is not empty and about to change.
|
||||
return this.sharedState.originalMediaPath &&
|
||||
this.sharedState.originalMediaPath !== this.state.settings.media_path.trim();
|
||||
},
|
||||
this.sharedState.originalMediaPath !== this.state.settings.media_path.trim()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirmThenSave() {
|
||||
confirmThenSave () {
|
||||
if (this.shouldWarn) {
|
||||
swal({
|
||||
title: 'Be careful!',
|
||||
|
@ -61,42 +61,42 @@ export default {
|
|||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'I know. Go ahead.',
|
||||
confirmButtonColor: '#c34848',
|
||||
}, this.save);
|
||||
confirmButtonColor: '#c34848'
|
||||
}, this.save)
|
||||
} else {
|
||||
this.save();
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the settings.
|
||||
*/
|
||||
save() {
|
||||
showOverlay();
|
||||
save () {
|
||||
showOverlay()
|
||||
|
||||
settingStore.update().then(() => {
|
||||
// Make sure we're back to home first.
|
||||
router.go('home');
|
||||
forceReloadWindow();
|
||||
router.go('home')
|
||||
forceReloadWindow()
|
||||
}).catch(r => {
|
||||
let msg = 'Unknown error.';
|
||||
let msg = 'Unknown error.'
|
||||
|
||||
if (r.status === 422) {
|
||||
msg = parseValidationError(r.responseJSON)[0];
|
||||
msg = parseValidationError(r.responseJSON)[0]
|
||||
}
|
||||
|
||||
hideOverlay();
|
||||
hideOverlay()
|
||||
|
||||
swal({
|
||||
title: 'Something went wrong',
|
||||
text: msg,
|
||||
type: 'error',
|
||||
allowOutsideClick: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
allowOutsideClick: true
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -25,24 +25,21 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
|
||||
import { pluralize } from '../../../utils';
|
||||
import { songStore } from '../../../stores';
|
||||
import { playback } from '../../../services';
|
||||
import hasSongList from '../../../mixins/has-song-list';
|
||||
import { pluralize } from '../../../utils'
|
||||
import { songStore } from '../../../stores'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--songs',
|
||||
mixins: [hasSongList],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: songStore.state
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -44,37 +44,37 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { clone } from 'lodash';
|
||||
import isMobile from 'ismobilejs';
|
||||
import { clone } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { userStore } from '../../../stores';
|
||||
import userItem from '../../shared/user-item.vue';
|
||||
import { userStore } from '../../../stores'
|
||||
import userItem from '../../shared/user-item.vue'
|
||||
|
||||
export default {
|
||||
components: { userItem },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: userStore.state,
|
||||
isPhone: isMobile.phone,
|
||||
showingControls: false,
|
||||
creating: false,
|
||||
newUser: {},
|
||||
};
|
||||
newUser: {}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Store the newly created user.
|
||||
*/
|
||||
store() {
|
||||
store () {
|
||||
userStore.store(this.newUser.name, this.newUser.email, this.newUser.password).then(u => {
|
||||
this.newUser = clone(userStore.stub);
|
||||
this.creating = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
this.newUser = clone(userStore.stub)
|
||||
this.creating = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { event } from '../../../utils';
|
||||
import { playback } from '../../../services';
|
||||
import YouTubePlayer from 'youtube-player';
|
||||
import { event } from '../../../utils'
|
||||
import { playback } from '../../../services'
|
||||
import YouTubePlayer from 'youtube-player'
|
||||
|
||||
let player;
|
||||
let player
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--youtube-player',
|
||||
|
@ -24,36 +24,36 @@ export default {
|
|||
/**
|
||||
* Initialize the YouTube player. This should only be called once.
|
||||
*/
|
||||
initPlayer() {
|
||||
initPlayer () {
|
||||
if (!player) {
|
||||
player = YouTubePlayer('player', {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
||||
height: '100%'
|
||||
})
|
||||
|
||||
player.on('stateChange', event => {
|
||||
// Pause song playback when video is played
|
||||
event.data === 1 && playback.pause();
|
||||
});
|
||||
event.data === 1 && playback.pause()
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
'youtube:play': id => {
|
||||
this.initPlayer();
|
||||
player.loadVideoById(id);
|
||||
player.playVideo();
|
||||
this.initPlayer()
|
||||
player.loadVideoById(id)
|
||||
player.playVideo()
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop video playback when a song is played/resumed.
|
||||
*/
|
||||
'song:played': () => player && player.pauseVideo(),
|
||||
});
|
||||
},
|
||||
};
|
||||
'song:played': () => player && player.pauseVideo()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -55,29 +55,29 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import $ from 'jquery';
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils';
|
||||
import { sharedStore, userStore, songStore, queueStore } from '../../../stores';
|
||||
import playlists from './playlists.vue';
|
||||
import { event } from '../../../utils'
|
||||
import { sharedStore, userStore, songStore, queueStore } from '../../../stores'
|
||||
import playlists from './playlists.vue'
|
||||
|
||||
export default {
|
||||
components: { playlists },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
currentView: 'home',
|
||||
user: userStore.state,
|
||||
showing: !isMobile.phone,
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
latestVersionUrl() {
|
||||
return 'https://github.com/phanan/koel/releases/tag/' + this.sharedState.latestVersion;
|
||||
},
|
||||
latestVersionUrl () {
|
||||
return 'https://github.com/phanan/koel/releases/tag/' + this.sharedState.latestVersion
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -86,8 +86,8 @@ export default {
|
|||
*
|
||||
* @param {Object} e The dragleave event.
|
||||
*/
|
||||
removeDroppableState(e) {
|
||||
$(e.target).removeClass('droppable');
|
||||
removeDroppableState (e) {
|
||||
$(e.target).removeClass('droppable')
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -95,11 +95,11 @@ export default {
|
|||
*
|
||||
* @param {Object} e The dragover event.
|
||||
*/
|
||||
allowDrop(e) {
|
||||
$(e.target).addClass('droppable');
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
allowDrop (e) {
|
||||
$(e.target).addClass('droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false;
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -109,42 +109,44 @@ export default {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
handleDrop(e) {
|
||||
this.removeDroppableState(e);
|
||||
handleDrop (e) {
|
||||
this.removeDroppableState(e)
|
||||
|
||||
if (!e.dataTransfer.getData('application/x-koel.text+plain')) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
const songs = songStore.byIds(e.dataTransfer.getData('application/x-koel.text+plain').split(','));
|
||||
const songs = songStore.byIds(e.dataTransfer.getData('application/x-koel.text+plain').split(','))
|
||||
|
||||
if (!songs.length) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
queueStore.queue(songs);
|
||||
queueStore.queue(songs)
|
||||
|
||||
return false;
|
||||
},
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on('main-content-view:load', view => {
|
||||
this.currentView = view;
|
||||
this.currentView = view
|
||||
|
||||
// Hide the sidebar if on mobile
|
||||
if (isMobile.phone) {
|
||||
this.showing = false;
|
||||
this.showing = false
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* Listen to sidebar:toggle event to show or hide the sidebar.
|
||||
* This should only be triggered on a mobile device.
|
||||
*/
|
||||
event.on('sidebar:toggle', () => this.showing = !this.showing);
|
||||
},
|
||||
};
|
||||
event.on('sidebar:toggle', () => {
|
||||
this.showing = !this.showing
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -20,20 +20,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../../../utils';
|
||||
import { songStore, playlistStore, favoriteStore } from '../../../stores';
|
||||
import { event } from '../../../utils'
|
||||
import { songStore, playlistStore, favoriteStore } from '../../../stores'
|
||||
|
||||
export default {
|
||||
props: ['playlist', 'type'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
newName: '',
|
||||
editing: false,
|
||||
active: false,
|
||||
};
|
||||
active: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -42,53 +42,53 @@ export default {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isFavorites() {
|
||||
return this.type === 'favorites';
|
||||
isFavorites () {
|
||||
return this.type === 'favorites'
|
||||
},
|
||||
|
||||
playlistUrl() {
|
||||
return this.isFavorites ? '/#!/favorites' : `/#!/playlist/${this.playlist.id}`;
|
||||
},
|
||||
playlistUrl () {
|
||||
return this.isFavorites ? '/#!/favorites' : `/#!/playlist/${this.playlist.id}`
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Show the form to edit the playlist.
|
||||
*/
|
||||
edit() {
|
||||
edit () {
|
||||
if (this.isFavorites) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.beforeEditCache = this.playlist.name;
|
||||
this.editing = true;
|
||||
this.beforeEditCache = this.playlist.name
|
||||
this.editing = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the playlist's name.
|
||||
*/
|
||||
update() {
|
||||
update () {
|
||||
if (this.isFavorites || !this.editing) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.editing = false;
|
||||
this.editing = false
|
||||
|
||||
this.playlist.name = this.playlist.name.trim();
|
||||
this.playlist.name = this.playlist.name.trim()
|
||||
if (!this.playlist.name) {
|
||||
this.playlist.name = this.beforeEditCache;
|
||||
return;
|
||||
this.playlist.name = this.beforeEditCache
|
||||
return
|
||||
}
|
||||
|
||||
playlistStore.update(this.playlist);
|
||||
playlistStore.update(this.playlist)
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel editing.
|
||||
*/
|
||||
cancelEdit() {
|
||||
this.editing = false;
|
||||
this.playlist.name = this.beforeEditCache;
|
||||
cancelEdit () {
|
||||
this.editing = false
|
||||
this.playlist.name = this.beforeEditCache
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -96,8 +96,8 @@ export default {
|
|||
*
|
||||
* @param {Object} e The dragleave event.
|
||||
*/
|
||||
removeDroppableState(e) {
|
||||
$(e.target).removeClass('droppable');
|
||||
removeDroppableState (e) {
|
||||
$(e.target).removeClass('droppable')
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -106,11 +106,11 @@ export default {
|
|||
*
|
||||
* @param {Object} e The dragover event.
|
||||
*/
|
||||
allowDrop(e) {
|
||||
$(e.target).addClass('droppable');
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
allowDrop (e) {
|
||||
$(e.target).addClass('droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false;
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -120,41 +120,41 @@ export default {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
handleDrop(e) {
|
||||
this.removeDroppableState(e);
|
||||
handleDrop (e) {
|
||||
this.removeDroppableState(e)
|
||||
|
||||
if (!e.dataTransfer.getData('application/x-koel.text+plain')) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
const songs = songStore.byIds(e.dataTransfer.getData('application/x-koel.text+plain').split(','));
|
||||
const songs = songStore.byIds(e.dataTransfer.getData('application/x-koel.text+plain').split(','))
|
||||
|
||||
if (!songs.length) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.type === 'favorites') {
|
||||
favoriteStore.like(songs);
|
||||
favoriteStore.like(songs)
|
||||
} else {
|
||||
playlistStore.addSongs(this.playlist, songs);
|
||||
playlistStore.addSongs(this.playlist, songs)
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on('main-content-view:load', (view, playlist) => {
|
||||
if (view === 'favorites') {
|
||||
this.active = this.isFavorites;
|
||||
this.active = this.isFavorites
|
||||
} else if (view === 'playlist') {
|
||||
this.active = this.playlist === playlist;
|
||||
this.active = this.playlist === playlist
|
||||
} else {
|
||||
this.active = false;
|
||||
this.active = false
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -22,40 +22,40 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { playlistStore, favoriteStore } from '../../../stores';
|
||||
import router from '../../../router';
|
||||
import { playlistStore, favoriteStore } from '../../../stores'
|
||||
import router from '../../../router'
|
||||
|
||||
import playlistItem from './playlist-item.vue';
|
||||
import playlistItem from './playlist-item.vue'
|
||||
|
||||
export default {
|
||||
name: 'sidebar--playlists',
|
||||
props: ['currentView'],
|
||||
components: { playlistItem },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
playlistState: playlistStore.state,
|
||||
favoriteState: favoriteStore.state,
|
||||
creating: false,
|
||||
newName: '',
|
||||
};
|
||||
newName: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Store/create a new playlist.
|
||||
*/
|
||||
store() {
|
||||
this.creating = false;
|
||||
store () {
|
||||
this.creating = false
|
||||
|
||||
playlistStore.store(this.newName).then(p => {
|
||||
this.newName = '';
|
||||
this.newName = ''
|
||||
// Activate the new playlist right away
|
||||
this.$nextTick(() => router.go(`playlist/${p.id}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
this.$nextTick(() => router.go(`playlist/${p.id}`))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -76,25 +76,25 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { every, filter } from 'lodash';
|
||||
import { every, filter } from 'lodash'
|
||||
|
||||
import { br2nl } from '../../utils';
|
||||
import { songInfo } from '../../services/info';
|
||||
import { artistStore, albumStore, songStore } from '../../stores';
|
||||
import { br2nl, forceReloadWindow } from '../../utils'
|
||||
import { songInfo } from '../../services/info'
|
||||
import { artistStore, albumStore, songStore } from '../../stores'
|
||||
|
||||
import soundBar from '../shared/sound-bar.vue';
|
||||
import typeahead from '../shared/typeahead.vue';
|
||||
import soundBar from '../shared/sound-bar.vue'
|
||||
import typeahead from '../shared/typeahead.vue'
|
||||
|
||||
const COMPILATION_STATES = {
|
||||
NONE: 0, // No songs belong to a compilation album
|
||||
ALL: 1, // All songs belong to compilation album(s)
|
||||
SOME: 2, // Some of the songs belong to compilation album(s)
|
||||
};
|
||||
SOME: 2 // Some of the songs belong to compilation album(s)
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { soundBar, typeahead },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
shown: false,
|
||||
songs: [],
|
||||
|
@ -105,13 +105,13 @@
|
|||
artistState: artistStore.state,
|
||||
artistTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
|
||||
albumState: albumStore.state,
|
||||
albumTypeaheadOptions: {
|
||||
displayKey: 'name',
|
||||
filterKey: 'name',
|
||||
filterKey: 'name'
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -126,9 +126,9 @@
|
|||
artistName: '',
|
||||
lyrics: '',
|
||||
track: '',
|
||||
compilationState: null,
|
||||
},
|
||||
};
|
||||
compilationState: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -137,8 +137,8 @@
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
editSingle() {
|
||||
return this.songs.length === 1;
|
||||
editSingle () {
|
||||
return this.songs.length === 1
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -146,8 +146,8 @@
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
bySameArtist() {
|
||||
return every(this.songs, song => song.artist.id === this.songs[0].artist.id);
|
||||
bySameArtist () {
|
||||
return every(this.songs, song => song.artist.id === this.songs[0].artist.id)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -155,8 +155,8 @@
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
inSameAlbum() {
|
||||
return every(this.songs, song => song.album.id === this.songs[0].album.id);
|
||||
inSameAlbum () {
|
||||
return every(this.songs, song => song.album.id === this.songs[0].album.id)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -164,8 +164,8 @@
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
coverUrl() {
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : '/public/img/covers/unknown-album.png';
|
||||
coverUrl () {
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -173,18 +173,18 @@
|
|||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
compilationState() {
|
||||
compilationState () {
|
||||
const contributedSongs = filter(this.songs, song => song.contributing_artist_id)
|
||||
|
||||
if (!contributedSongs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.NONE
|
||||
} else if (contributedSongs.length === this.songs.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.ALL;
|
||||
this.formData.compilationState = COMPILATION_STATES.ALL
|
||||
} else {
|
||||
this.formData.compilationState = COMPILATION_STATES.SOME;
|
||||
this.formData.compilationState = COMPILATION_STATES.SOME
|
||||
}
|
||||
|
||||
return this.formData.compilationState;
|
||||
return this.formData.compilationState
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -192,8 +192,8 @@
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedTitle() {
|
||||
return this.editSingle ? this.formData.title : `${this.songs.length} songs selected`;
|
||||
displayedTitle () {
|
||||
return this.editSingle ? this.formData.title : `${this.songs.length} songs selected`
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -201,11 +201,11 @@
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedAlbum() {
|
||||
displayedAlbum () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.albumName;
|
||||
return this.formData.albumName
|
||||
} else {
|
||||
return this.formData.albumName ? this.formData.albumName : 'Mixed Albums';
|
||||
return this.formData.albumName ? this.formData.albumName : 'Mixed Albums'
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -214,75 +214,75 @@
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
displayedArtist() {
|
||||
displayedArtist () {
|
||||
if (this.editSingle) {
|
||||
return this.formData.artistName;
|
||||
return this.formData.artistName
|
||||
} else {
|
||||
return this.formData.artistName ? this.formData.artistName : 'Mixed Artists';
|
||||
return this.formData.artistName ? this.formData.artistName : 'Mixed Artists'
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open(songs) {
|
||||
this.shown = true;
|
||||
this.songs = songs;
|
||||
this.currentView = 'details';
|
||||
this.needsReload = false;
|
||||
open (songs) {
|
||||
this.shown = true
|
||||
this.songs = songs
|
||||
this.currentView = 'details'
|
||||
this.needsReload = false
|
||||
|
||||
if (this.editSingle) {
|
||||
this.formData.title = this.songs[0].title;
|
||||
this.formData.albumName = this.songs[0].album.name;
|
||||
this.formData.artistName = this.songs[0].artist.name;
|
||||
this.formData.title = this.songs[0].title
|
||||
this.formData.albumName = this.songs[0].album.name
|
||||
this.formData.artistName = this.songs[0].artist.name
|
||||
|
||||
// If we're editing only one song and the song's info (including lyrics)
|
||||
// hasn't been loaded, load it now.
|
||||
if (!this.songs[0].infoRetrieved) {
|
||||
this.loading = true;
|
||||
this.loading = true
|
||||
|
||||
songInfo.fetch(this.songs[0]).then(r => {
|
||||
this.loading = false;
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics);
|
||||
this.formData.track = this.songs[0].track;
|
||||
this.initCompilationStateCheckbox();
|
||||
});
|
||||
this.loading = false
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics)
|
||||
this.formData.track = this.songs[0].track
|
||||
this.initCompilationStateCheckbox()
|
||||
})
|
||||
} else {
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics);
|
||||
this.formData.track = this.songs[0].track;
|
||||
this.initCompilationStateCheckbox();
|
||||
this.formData.lyrics = br2nl(this.songs[0].lyrics)
|
||||
this.formData.track = this.songs[0].track
|
||||
this.initCompilationStateCheckbox()
|
||||
}
|
||||
} else {
|
||||
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : '';
|
||||
this.formData.artistName = this.bySameArtist ? this.songs[0].artist.name : '';
|
||||
this.loading = false;
|
||||
this.initCompilationStateCheckbox();
|
||||
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : ''
|
||||
this.formData.artistName = this.bySameArtist ? this.songs[0].artist.name : ''
|
||||
this.loading = false
|
||||
this.initCompilationStateCheckbox()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the compilation state's checkbox of the editing songs' album(s).
|
||||
*/
|
||||
initCompilationStateCheckbox() {
|
||||
initCompilationStateCheckbox () {
|
||||
// This must be wrapped in a $nextTick callback, because the form is dynamically
|
||||
// attached into DOM in conjunction with `this.loading` data binding.
|
||||
this.$nextTick(() => {
|
||||
const chk = this.$refs.compilationStateChk;
|
||||
const chk = this.$refs.compilationStateChk
|
||||
|
||||
switch (this.compilationState) {
|
||||
case COMPILATION_STATES.ALL:
|
||||
chk.checked = true;
|
||||
chk.indeterminate = false;
|
||||
break;
|
||||
chk.checked = true
|
||||
chk.indeterminate = false
|
||||
break
|
||||
case COMPILATION_STATES.NONE:
|
||||
chk.checked = false;
|
||||
chk.indeterminate = false;
|
||||
break;
|
||||
chk.checked = false
|
||||
chk.indeterminate = false
|
||||
break
|
||||
default:
|
||||
chk.checked = false;
|
||||
chk.indeterminate = true;
|
||||
break;
|
||||
chk.checked = false
|
||||
chk.indeterminate = true
|
||||
break
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -291,34 +291,34 @@
|
|||
* Also, following iTunes style, we don't support circular switching of the states -
|
||||
* once the user clicks the checkbox, there's no going back to indeterminate state.
|
||||
*/
|
||||
changeCompilationState(e) {
|
||||
this.formData.compilationState = e.target.checked ? COMPILATION_STATES.ALL : COMPILATION_STATES.NONE;
|
||||
this.needsReload = true;
|
||||
changeCompilationState (e) {
|
||||
this.formData.compilationState = e.target.checked ? COMPILATION_STATES.ALL : COMPILATION_STATES.NONE
|
||||
this.needsReload = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the modal.
|
||||
*/
|
||||
close() {
|
||||
this.shown = false;
|
||||
close () {
|
||||
this.shown = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit the form.
|
||||
*/
|
||||
submit() {
|
||||
this.loading = true;
|
||||
submit () {
|
||||
this.loading = true
|
||||
|
||||
songStore.update(this.songs, this.formData).then(r => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
if (this.needsReload) {
|
||||
forceReloadWindow();
|
||||
}
|
||||
}).catch(r => this.loading = false);
|
||||
},
|
||||
},
|
||||
};
|
||||
this.loading = false
|
||||
this.close()
|
||||
this.needsReload && forceReloadWindow()
|
||||
}).catch(r => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -27,12 +27,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { assign } from 'lodash';
|
||||
|
||||
import { pluralize, event } from '../../utils';
|
||||
import { playlistStore } from '../../stores';
|
||||
import router from '../../router';
|
||||
import songMenuMethods from '../../mixins/song-menu-methods';
|
||||
import { pluralize } from '../../utils'
|
||||
import { playlistStore } from '../../stores'
|
||||
import router from '../../router'
|
||||
import songMenuMethods from '../../mixins/song-menu-methods'
|
||||
|
||||
export default {
|
||||
name: 'shared--add-to-menu',
|
||||
|
@ -40,19 +38,19 @@ export default {
|
|||
mixins: [songMenuMethods],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
newPlaylistName: '',
|
||||
playlistState: playlistStore.state,
|
||||
};
|
||||
playlistState: playlistStore.state
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
songs() {
|
||||
songs () {
|
||||
if (!this.songs.length) {
|
||||
this.close();
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -60,27 +58,27 @@ export default {
|
|||
* Save the selected songs as a playlist.
|
||||
* As of current we don't have selective save.
|
||||
*/
|
||||
createNewPlaylistFromSongs() {
|
||||
this.newPlaylistName = this.newPlaylistName.trim();
|
||||
createNewPlaylistFromSongs () {
|
||||
this.newPlaylistName = this.newPlaylistName.trim()
|
||||
|
||||
if (!this.newPlaylistName) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
playlistStore.store(this.newPlaylistName, this.songs).then(p => {
|
||||
this.newPlaylistName = '';
|
||||
this.newPlaylistName = ''
|
||||
// Activate the new playlist right away
|
||||
this.$nextTick(() => router.go(`playlist/${p.id}`));
|
||||
});
|
||||
this.$nextTick(() => router.go(`playlist/${p.id}`))
|
||||
})
|
||||
|
||||
this.close();
|
||||
this.close()
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$parent.closeAddToMenu();
|
||||
},
|
||||
},
|
||||
};
|
||||
close () {
|
||||
this.$parent.closeAddToMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -36,29 +36,29 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { map } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { map } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { pluralize } from '../../utils';
|
||||
import { queueStore, artistStore, sharedStore } from '../../stores';
|
||||
import { playback, download } from '../../services';
|
||||
import { pluralize } from '../../utils'
|
||||
import { queueStore, artistStore, sharedStore } from '../../stores'
|
||||
import { playback, download } from '../../services'
|
||||
|
||||
export default {
|
||||
name: 'shared--album-item',
|
||||
props: ['album'],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isNormalArtist() {
|
||||
return !artistStore.isVariousArtists(this.album.artist)
|
||||
&& !artistStore.isUnknownArtist(this.album.artist);
|
||||
},
|
||||
isNormalArtist () {
|
||||
return !artistStore.isVariousArtists(this.album.artist) &&
|
||||
!artistStore.isUnknownArtist(this.album.artist)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -66,42 +66,42 @@ export default {
|
|||
* Play all songs in the current album in track order,
|
||||
* or queue them up if Ctrl/Cmd key is pressed.
|
||||
*/
|
||||
play(e) {
|
||||
play (e) {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
queueStore.queue(this.album.songs);
|
||||
queueStore.queue(this.album.songs)
|
||||
} else {
|
||||
playback.playAllInAlbum(this.album, false);
|
||||
playback.playAllInAlbum(this.album, false)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shuffle all songs in album.
|
||||
*/
|
||||
shuffle() {
|
||||
playback.playAllInAlbum(this.album, true);
|
||||
shuffle () {
|
||||
playback.playAllInAlbum(this.album, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all songs in album.
|
||||
*/
|
||||
download() {
|
||||
download.fromAlbum(this.album);
|
||||
download () {
|
||||
download.fromAlbum(this.album)
|
||||
},
|
||||
|
||||
/**
|
||||
* Allow dragging the album (actually, its songs).
|
||||
*/
|
||||
dragStart(e) {
|
||||
const songIds = map(this.album.songs, 'id');
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
dragStart (e) {
|
||||
const songIds = map(this.album.songs, 'id')
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`All ${songIds.length} song${songIds.length === 1 ? '' : 's'} in ${this.album.name}`);
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
const $ghost = $('#dragGhost').text(`All ${songIds.length} song${songIds.length === 1 ? '' : 's'} in ${this.album.name}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -28,22 +28,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { map } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { map } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { pluralize } from '../../utils';
|
||||
import { artistStore, queueStore, sharedStore } from '../../stores';
|
||||
import { playback, download } from '../../services';
|
||||
import { pluralize } from '../../utils'
|
||||
import { artistStore, queueStore, sharedStore } from '../../stores'
|
||||
import { playback, download } from '../../services'
|
||||
|
||||
export default {
|
||||
name: 'shared--artist-item',
|
||||
props: ['artist'],
|
||||
filters: { pluralize },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
sharedState: sharedStore.state,
|
||||
};
|
||||
sharedState: sharedStore.state
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -53,8 +53,8 @@ export default {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
showing() {
|
||||
return this.artist.songCount && !artistStore.isVariousArtists(this.artist);
|
||||
showing () {
|
||||
return this.artist.songCount && !artistStore.isVariousArtists(this.artist)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -62,35 +62,35 @@ export default {
|
|||
/**
|
||||
* Play all songs by the current artist, or queue them up if Ctrl/Cmd key is pressed.
|
||||
*/
|
||||
play(e) {
|
||||
play (e) {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
queueStore.queue(this.artist.songs);
|
||||
queueStore.queue(this.artist.songs)
|
||||
} else {
|
||||
playback.playAllByArtist(this.artist);
|
||||
playback.playAllByArtist(this.artist)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all songs by artist.
|
||||
*/
|
||||
download() {
|
||||
download.fromArtist(this.artist);
|
||||
download () {
|
||||
download.fromArtist(this.artist)
|
||||
},
|
||||
|
||||
/**
|
||||
* Allow dragging the artist (actually, their songs).
|
||||
*/
|
||||
dragStart(e) {
|
||||
const songIds = map(this.artist.songs, 'id');
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
dragStart (e) {
|
||||
const songIds = map(this.artist.songs, 'id')
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`All ${songIds.length} song${songIds.length === 1 ? '' : 's'} by ${this.artist.name}`);
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
const $ghost = $('#dragGhost').text(`All ${songIds.length} song${songIds.length === 1 ? '' : 's'} by ${this.artist.name}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { pluralize } from '../../utils';
|
||||
import { queueStore } from '../../stores';
|
||||
import { playback } from '../../services';
|
||||
import { pluralize } from '../../utils'
|
||||
import { queueStore } from '../../stores'
|
||||
import { playback } from '../../services'
|
||||
|
||||
export default {
|
||||
name: 'shared--home-song-item',
|
||||
|
@ -31,31 +31,31 @@ export default {
|
|||
filters: { pluralize },
|
||||
|
||||
computed: {
|
||||
showPlayCount() {
|
||||
return this.topPlayCount && this.song.playCount;
|
||||
},
|
||||
showPlayCount () {
|
||||
return this.topPlayCount && this.song.playCount
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
play() {
|
||||
play () {
|
||||
if (!queueStore.contains(this.song)) {
|
||||
queueStore.queueAfterCurrent(this.song);
|
||||
queueStore.queueAfterCurrent(this.song)
|
||||
}
|
||||
|
||||
playback.play(this.song);
|
||||
playback.play(this.song)
|
||||
},
|
||||
|
||||
changeSongState() {
|
||||
changeSongState () {
|
||||
if (this.song.playbackState === 'stopped') {
|
||||
this.play(this.song);
|
||||
this.play(this.song)
|
||||
} else if (this.song.playbackState === 'paused') {
|
||||
playback.resume();
|
||||
playback.resume()
|
||||
} else {
|
||||
playback.pause();
|
||||
playback.pause()
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { assign } from 'lodash';
|
||||
import { assign } from 'lodash'
|
||||
|
||||
import { event } from '../../utils';
|
||||
import soundBar from './sound-bar.vue';
|
||||
import { event } from '../../utils'
|
||||
import soundBar from './sound-bar.vue'
|
||||
|
||||
export default {
|
||||
components: { soundBar },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: {
|
||||
showing: true,
|
||||
|
@ -35,9 +35,9 @@ export default {
|
|||
* @type {String}
|
||||
*/
|
||||
type: 'loading',
|
||||
message: '',
|
||||
},
|
||||
};
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -48,16 +48,16 @@ export default {
|
|||
* @param {String} type (loading|success|info|warning|error)
|
||||
* @param {Boolean} dismissable Whether to show the Close button
|
||||
*/
|
||||
show(options) {
|
||||
assign(this.state, options);
|
||||
this.state.showing = true;
|
||||
show (options) {
|
||||
assign(this.state, options)
|
||||
this.state.showing = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the overlay.
|
||||
*/
|
||||
hide() {
|
||||
this.state.showing = false;
|
||||
hide () {
|
||||
this.state.showing = false
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,18 +66,18 @@ export default {
|
|||
*
|
||||
* @param {Boolean} dismissable
|
||||
*/
|
||||
setDimissable(dismissable = true) {
|
||||
this.state.dismissable = dismissable;
|
||||
},
|
||||
setDimissable (dismissable = true) {
|
||||
this.state.dismissable = dismissable
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
'overlay:show': options => this.show(options),
|
||||
'overlay:hide': () => this.hide(),
|
||||
});
|
||||
},
|
||||
};
|
||||
'overlay:hide': () => this.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -25,73 +25,73 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { playback } from '../../services';
|
||||
import { queueStore } from '../../stores';
|
||||
import { playback } from '../../services'
|
||||
import { queueStore } from '../../stores'
|
||||
|
||||
export default {
|
||||
props: ['song'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
selected: false,
|
||||
};
|
||||
selected: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
playing() {
|
||||
return this.song.playbackState === 'playing' || this.song.playbackState === 'paused';
|
||||
},
|
||||
playing () {
|
||||
return this.song.playbackState === 'playing' || this.song.playbackState === 'paused'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Play the song right away.
|
||||
*/
|
||||
playRightAwayyyyyyy() {
|
||||
playRightAwayyyyyyy () {
|
||||
if (!queueStore.contains(this.song)) {
|
||||
queueStore.queueAfterCurrent(this.song);
|
||||
queueStore.queueAfterCurrent(this.song)
|
||||
}
|
||||
|
||||
playback.play(this.song);
|
||||
playback.play(this.song)
|
||||
},
|
||||
|
||||
/**
|
||||
* Take the right playback action based on the current playback state.
|
||||
*/
|
||||
doPlayback() {
|
||||
doPlayback () {
|
||||
switch (this.song.playbackState) {
|
||||
case 'playing':
|
||||
playback.pause();
|
||||
break;
|
||||
playback.pause()
|
||||
break
|
||||
case 'paused':
|
||||
playback.resume();
|
||||
break;
|
||||
playback.resume()
|
||||
break
|
||||
default:
|
||||
this.playRightAwayyyyyyy();
|
||||
break;
|
||||
this.playRightAwayyyyyyy()
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
clicked($e) {
|
||||
this.$emit('itemClicked', this.song.id, $e);
|
||||
clicked ($e) {
|
||||
this.$emit('itemClicked', this.song.id, $e)
|
||||
},
|
||||
|
||||
select() {
|
||||
this.selected = true;
|
||||
select () {
|
||||
this.selected = true
|
||||
},
|
||||
|
||||
deselect() {
|
||||
this.selected = false;
|
||||
deselect () {
|
||||
this.selected = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the "selected" state of the current component.
|
||||
*/
|
||||
toggleSelectedState() {
|
||||
this.selected = !this.selected;
|
||||
},
|
||||
},
|
||||
};
|
||||
toggleSelectedState () {
|
||||
this.selected = !this.selected
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -6,21 +6,21 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
export default {
|
||||
name: 'shared--song-list-controls-toggler',
|
||||
props: ['showingControls'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
isPhone: isMobile.phone
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleControls() {
|
||||
this.$emit('toggleControls');
|
||||
toggleControls () {
|
||||
this.$emit('toggleControls')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { assign } from 'lodash';
|
||||
import { queueStore } from '../../stores';
|
||||
import addToMenu from './add-to-menu.vue';
|
||||
import { assign } from 'lodash'
|
||||
import addToMenu from './add-to-menu.vue'
|
||||
|
||||
export default {
|
||||
name: 'shared--song-list-controls',
|
||||
|
@ -49,7 +48,7 @@ export default {
|
|||
|
||||
components: { addToMenu },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
fullConfig: {
|
||||
shuffle: true,
|
||||
|
@ -57,52 +56,52 @@ export default {
|
|||
queue: true,
|
||||
favorites: true,
|
||||
playlists: true,
|
||||
newPlaylist: true,
|
||||
newPlaylist: true
|
||||
},
|
||||
clearQueue: false,
|
||||
deletePlaylist: false
|
||||
},
|
||||
showingAddToMenu: false,
|
||||
numberOfQueuedSongs: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
showClearQueueButton() {
|
||||
return this.fullConfig.clearQueue;
|
||||
showClearQueueButton () {
|
||||
return this.fullConfig.clearQueue
|
||||
},
|
||||
|
||||
showDeletePlaylistButton() {
|
||||
return this.fullConfig.deletePlaylist;
|
||||
},
|
||||
showDeletePlaylistButton () {
|
||||
return this.fullConfig.deletePlaylist
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
assign(this.fullConfig, this.config);
|
||||
mounted () {
|
||||
assign(this.fullConfig, this.config)
|
||||
},
|
||||
|
||||
methods: {
|
||||
shuffle() {
|
||||
this.$emit('shuffleAll');
|
||||
shuffle () {
|
||||
this.$emit('shuffleAll')
|
||||
},
|
||||
|
||||
shuffleSelected() {
|
||||
this.$emit('shuffleSelected');
|
||||
shuffleSelected () {
|
||||
this.$emit('shuffleSelected')
|
||||
},
|
||||
|
||||
clearQueue() {
|
||||
this.$emit('clearQueue');
|
||||
clearQueue () {
|
||||
this.$emit('clearQueue')
|
||||
},
|
||||
|
||||
deletePlaylist() {
|
||||
this.$emit('deletePlaylist');
|
||||
deletePlaylist () {
|
||||
this.$emit('deletePlaylist')
|
||||
},
|
||||
|
||||
closeAddToMenu() {
|
||||
this.showingAddToMenu = false;
|
||||
closeAddToMenu () {
|
||||
this.showingAddToMenu = false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
||||
|
|
|
@ -50,17 +50,17 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { find, invokeMap, filter, map } from 'lodash';
|
||||
import isMobile from 'ismobilejs';
|
||||
import $ from 'jquery';
|
||||
import { find, invokeMap, filter, map } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { filterBy, orderBy, limitBy, event } from '../../utils';
|
||||
import { playlistStore, queueStore, songStore, favoriteStore } from '../../stores';
|
||||
import { playback } from '../../services';
|
||||
import router from '../../router';
|
||||
import songItem from './song-item.vue';
|
||||
import songMenu from './song-menu.vue';
|
||||
import infiniteScroll from '../../mixins/infinite-scroll';
|
||||
import { filterBy, orderBy, limitBy, event } from '../../utils'
|
||||
import { playlistStore, queueStore, songStore, favoriteStore } from '../../stores'
|
||||
import { playback } from '../../services'
|
||||
import router from '../../router'
|
||||
import songItem from './song-item.vue'
|
||||
import songMenu from './song-menu.vue'
|
||||
import infiniteScroll from '../../mixins/infinite-scroll'
|
||||
|
||||
export default {
|
||||
name: 'song-list',
|
||||
|
@ -68,7 +68,7 @@ export default {
|
|||
mixins: [infiniteScroll],
|
||||
components: { songItem, songMenu },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
lastSelectedRow: null,
|
||||
q: '', // The filter query
|
||||
|
@ -77,35 +77,35 @@ export default {
|
|||
sortingByAlbum: false,
|
||||
sortingByArtist: false,
|
||||
selectedSongs: [],
|
||||
mutatedItems: [],
|
||||
};
|
||||
mutatedItems: []
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
/**
|
||||
* Watch the items.
|
||||
*/
|
||||
items() {
|
||||
items () {
|
||||
if (this.sortable === false) {
|
||||
this.sortKey = '';
|
||||
this.sortKey = ''
|
||||
}
|
||||
|
||||
this.mutatedItems = this.items;
|
||||
this.mutatedItems = this.items
|
||||
|
||||
// Update the song count and duration status on parent.
|
||||
this.$parent.updateMeta({
|
||||
songCount: this.items.length,
|
||||
totalLength: songStore.getLength(this.items, true),
|
||||
});
|
||||
totalLength: songStore.getLength(this.items, true)
|
||||
})
|
||||
},
|
||||
|
||||
selectedSongs(val) {
|
||||
this.$parent.setSelectedSongs(val);
|
||||
},
|
||||
selectedSongs (val) {
|
||||
this.$parent.setSelectedSongs(val)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayedItems() {
|
||||
displayedItems () {
|
||||
return limitBy(
|
||||
filterBy(
|
||||
this.mutatedItems,
|
||||
|
@ -113,8 +113,8 @@ export default {
|
|||
'title', 'album.name', 'artist.name'
|
||||
),
|
||||
this.numOfItems
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -123,43 +123,43 @@ export default {
|
|||
*
|
||||
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'length'
|
||||
*/
|
||||
sort(key) {
|
||||
sort (key) {
|
||||
if (this.sortable === false) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.sortKey = key;
|
||||
this.order = 0 - this.order;
|
||||
this.sortingByAlbum = Array.isArray(this.sortKey) && this.sortKey[0] === 'album.name';
|
||||
this.sortingByArtist = Array.isArray(this.sortKey) && this.sortKey[0] === 'album.artist.name';
|
||||
this.mutatedItems = orderBy(this.items, this.sortKey, this.order);
|
||||
this.sortKey = key
|
||||
this.order = 0 - this.order
|
||||
this.sortingByAlbum = Array.isArray(this.sortKey) && this.sortKey[0] === 'album.name'
|
||||
this.sortingByArtist = Array.isArray(this.sortKey) && this.sortKey[0] === 'album.artist.name'
|
||||
this.mutatedItems = orderBy(this.items, this.sortKey, this.order)
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the corresponding reaction(s) when the user presses Delete.
|
||||
*/
|
||||
handleDelete() {
|
||||
const songs = this.selectedSongs;
|
||||
handleDelete () {
|
||||
const songs = this.selectedSongs
|
||||
|
||||
if (!songs.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case 'queue':
|
||||
queueStore.unqueue(songs);
|
||||
break;
|
||||
queueStore.unqueue(songs)
|
||||
break
|
||||
case 'favorites':
|
||||
favoriteStore.unlike(songs);
|
||||
break;
|
||||
favoriteStore.unlike(songs)
|
||||
break
|
||||
case 'playlist':
|
||||
playlistStore.removeSongs(this.playlist, songs);
|
||||
break;
|
||||
playlistStore.removeSongs(this.playlist, songs)
|
||||
break
|
||||
default:
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
this.clearSelection();
|
||||
this.clearSelection()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -167,25 +167,25 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event.
|
||||
*/
|
||||
handleEnter(e) {
|
||||
const songs = this.selectedSongs;
|
||||
handleEnter (e) {
|
||||
const songs = this.selectedSongs
|
||||
|
||||
if (!songs.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (songs.length === 1) {
|
||||
// Just play the song
|
||||
playback.play(songs[0]);
|
||||
playback.play(songs[0])
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case 'queue':
|
||||
// Play the first song selected if we're in Queue screen.
|
||||
playback.play(songs[0]);
|
||||
break;
|
||||
playback.play(songs[0])
|
||||
break
|
||||
case 'favorites':
|
||||
case 'playlist':
|
||||
default:
|
||||
|
@ -201,20 +201,20 @@ export default {
|
|||
// Also, if there's only one song selected, play it right away.
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
queueStore.queue(songs, false, e.shiftKey);
|
||||
queueStore.queue(songs, false, e.shiftKey)
|
||||
|
||||
this.$nextTick(() => {
|
||||
router.go('queue');
|
||||
router.go('queue')
|
||||
|
||||
if (e.ctrlKey || e.metaKey || songs.length === 1) {
|
||||
playback.play(songs[0]);
|
||||
playback.play(songs[0])
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
this.clearSelection();
|
||||
this.clearSelection()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -224,8 +224,8 @@ export default {
|
|||
*
|
||||
* @return {Object} The Vue compoenent
|
||||
*/
|
||||
getComponentBySongId(id) {
|
||||
return find(this.$refs.rows, { song: { id } });
|
||||
getComponentBySongId (id) {
|
||||
return find(this.$refs.rows, { song: { id }})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -233,13 +233,13 @@ export default {
|
|||
*
|
||||
* @param {Object} e The keydown event.
|
||||
*/
|
||||
handleA(e) {
|
||||
handleA (e) {
|
||||
if (!e.metaKey && !e.ctrlKey) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
invokeMap(this.$refs.rows, 'select');
|
||||
this.gatherSelected();
|
||||
invokeMap(this.$refs.rows, 'select')
|
||||
this.gatherSelected()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -247,11 +247,11 @@ export default {
|
|||
*
|
||||
* @return {Array.<Object>} An array of Song objects
|
||||
*/
|
||||
gatherSelected() {
|
||||
const selectedRows = filter(this.$refs.rows, { selected: true });
|
||||
const ids = map(selectedRows, row => row.song.id);
|
||||
gatherSelected () {
|
||||
const selectedRows = filter(this.$refs.rows, { selected: true })
|
||||
const ids = map(selectedRows, row => row.song.id)
|
||||
|
||||
this.selectedSongs = songStore.byIds(ids);
|
||||
this.selectedSongs = songStore.byIds(ids)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -268,33 +268,33 @@ export default {
|
|||
* @param {String} songId
|
||||
* @param {Object} e
|
||||
*/
|
||||
itemClicked(songId, e) {
|
||||
const row = this.getComponentBySongId(songId);
|
||||
itemClicked (songId, e) {
|
||||
const row = this.getComponentBySongId(songId)
|
||||
|
||||
// If we're on a touch device, or if Ctrl/Cmd key is pressed, just toggle selection.
|
||||
if (isMobile.any) {
|
||||
this.toggleRow(row);
|
||||
this.gatherSelected();
|
||||
this.toggleRow(row)
|
||||
this.gatherSelected()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this.toggleRow(row);
|
||||
this.toggleRow(row)
|
||||
}
|
||||
|
||||
if (e.button === 0) {
|
||||
if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
||||
this.clearSelection();
|
||||
this.toggleRow(row);
|
||||
this.clearSelection()
|
||||
this.toggleRow(row)
|
||||
}
|
||||
|
||||
if (e.shiftKey && this.lastSelectedRow && this.lastSelectedRow.$el) {
|
||||
this.selectRowsBetweenIndexes([this.lastSelectedRow.$el.rowIndex, row.$el.rowIndex]);
|
||||
this.selectRowsBetweenIndexes([this.lastSelectedRow.$el.rowIndex, row.$el.rowIndex])
|
||||
}
|
||||
}
|
||||
|
||||
this.gatherSelected();
|
||||
this.gatherSelected()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -302,27 +302,27 @@ export default {
|
|||
*
|
||||
* @param {Object} row The song-item component
|
||||
*/
|
||||
toggleRow(row) {
|
||||
row.toggleSelectedState();
|
||||
this.lastSelectedRow = row;
|
||||
toggleRow (row) {
|
||||
row.toggleSelectedState()
|
||||
this.lastSelectedRow = row
|
||||
},
|
||||
|
||||
selectRowsBetweenIndexes(indexes) {
|
||||
indexes.sort((a, b) => a - b);
|
||||
selectRowsBetweenIndexes (indexes) {
|
||||
indexes.sort((a, b) => a - b)
|
||||
|
||||
const rows = $(this.$refs.wrapper).find('tbody tr');
|
||||
const rows = $(this.$refs.wrapper).find('tbody tr')
|
||||
|
||||
for (let i = indexes[0]; i <= indexes[1]; ++i) {
|
||||
this.getComponentBySongId($(rows[i - 1]).data('song-id')).select();
|
||||
this.getComponentBySongId($(rows[i - 1]).data('song-id')).select()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the current selection on this song list.
|
||||
*/
|
||||
clearSelection() {
|
||||
invokeMap(this.$refs.rows, 'deselect');
|
||||
this.gatherSelected();
|
||||
clearSelection () {
|
||||
invokeMap(this.$refs.rows, 'deselect')
|
||||
this.gatherSelected()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -332,24 +332,24 @@ export default {
|
|||
*
|
||||
* @param {Object} e The event.
|
||||
*/
|
||||
dragStart(songId, e) {
|
||||
dragStart (songId, e) {
|
||||
// If the user is dragging an unselected row, clear the current selection.
|
||||
const currentRow = this.getComponentBySongId(songId);
|
||||
const currentRow = this.getComponentBySongId(songId)
|
||||
if (!currentRow.selected) {
|
||||
this.clearSelection();
|
||||
currentRow.select();
|
||||
this.gatherSelected();
|
||||
this.clearSelection()
|
||||
currentRow.select()
|
||||
this.gatherSelected()
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const songIds = map(this.selectedSongs, 'id');
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
const songIds = map(this.selectedSongs, 'id')
|
||||
e.dataTransfer.setData('application/x-koel.text+plain', songIds)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
// Set a fancy drop image using our ghost element.
|
||||
const $ghost = $('#dragGhost').text(`${songIds.length} song${songIds.length === 1 ? '' : 's'}`);
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0);
|
||||
});
|
||||
const $ghost = $('#dragGhost').text(`${songIds.length} song${songIds.length === 1 ? '' : 's'}`)
|
||||
e.dataTransfer.setDragImage($ghost[0], 0, 0)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -358,15 +358,15 @@ export default {
|
|||
* @param {String} songId
|
||||
* @param {Object} e The dragover event.
|
||||
*/
|
||||
allowDrop(songId, e) {
|
||||
allowDrop (songId, e) {
|
||||
if (this.type !== 'queue') {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
$(e.target).parents('tr').addClass('droppable');
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
$(e.target).parents('tr').addClass('droppable')
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
return false;
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -375,26 +375,24 @@ export default {
|
|||
* @param {String} songId
|
||||
* @param {Object} e
|
||||
*/
|
||||
handleDrop(songId, e) {
|
||||
console.log('dropping into', songId);
|
||||
handleDrop (songId, e) {
|
||||
if (this.type !== 'queue') {
|
||||
return this.removeDroppableState(e) && false;
|
||||
return this.removeDroppableState(e) && false
|
||||
}
|
||||
|
||||
if (!e.dataTransfer.getData('application/x-koel.text+plain')) {
|
||||
return this.removeDroppableState(e) && false;
|
||||
return this.removeDroppableState(e) && false
|
||||
}
|
||||
|
||||
const songs = this.selectedSongs;
|
||||
console.log('selected songs after drop:', songs);
|
||||
const songs = this.selectedSongs
|
||||
|
||||
if (!songs.length) {
|
||||
return this.removeDroppableState(e) && false;
|
||||
return this.removeDroppableState(e) && false
|
||||
}
|
||||
|
||||
queueStore.move(songs, songStore.byId(songId));
|
||||
queueStore.move(songs, songStore.byId(songId))
|
||||
|
||||
return this.removeDroppableState(e) && false;
|
||||
return this.removeDroppableState(e) && false
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -402,25 +400,25 @@ export default {
|
|||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
removeDroppableState(e) {
|
||||
return $(e.target).parents('tr').removeClass('droppable');
|
||||
removeDroppableState (e) {
|
||||
return $(e.target).parents('tr').removeClass('droppable')
|
||||
},
|
||||
|
||||
openContextMenu(songId, e) {
|
||||
openContextMenu (songId, e) {
|
||||
// If the user is right-clicking an unselected row,
|
||||
// clear the current selection and select it instead.
|
||||
const currentRow = this.getComponentBySongId(songId);
|
||||
const currentRow = this.getComponentBySongId(songId)
|
||||
if (!currentRow.selected) {
|
||||
this.clearSelection();
|
||||
currentRow.select();
|
||||
this.gatherSelected();
|
||||
this.clearSelection()
|
||||
currentRow.select()
|
||||
this.gatherSelected()
|
||||
}
|
||||
|
||||
this.$nextTick(() => this.$refs.contextMenu.open(e.pageY, e.pageX));
|
||||
},
|
||||
this.$nextTick(() => this.$refs.contextMenu.open(e.pageY, e.pageX))
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
/**
|
||||
* Listen to song:played event to do some logic.
|
||||
|
@ -430,21 +428,21 @@ export default {
|
|||
'song:played': song => {
|
||||
// If the song is at the end of the current displayed items, load more.
|
||||
if (this.type === 'queue' && this.items.indexOf(song) >= this.numOfItems) {
|
||||
this.displayMore();
|
||||
this.displayMore()
|
||||
}
|
||||
|
||||
// Scroll the item into view if it's lost into oblivion.
|
||||
if (this.type === 'queue') {
|
||||
const $wrapper = $(this.$refs.wrapper);
|
||||
const $row = $wrapper.find(`.song-item[data-song-id="${song.id}"]`);
|
||||
const $wrapper = $(this.$refs.wrapper)
|
||||
const $row = $wrapper.find(`.song-item[data-song-id="${song.id}"]`)
|
||||
|
||||
if (!$row.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if ($wrapper[0].getBoundingClientRect().top + $wrapper[0].getBoundingClientRect().height <
|
||||
$row[0].getBoundingClientRect().top) {
|
||||
$wrapper.scrollTop($wrapper.scrollTop() + $row.position().top);
|
||||
$wrapper.scrollTop($wrapper.scrollTop() + $row.position().top)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -452,7 +450,9 @@ export default {
|
|||
/**
|
||||
* Listen to 'filter:changed' event to filter the current list.
|
||||
*/
|
||||
'filter:changed': q => this.q = q,
|
||||
'filter:changed': q => {
|
||||
this.q = q
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the current list's selection if the user has switched to another view.
|
||||
|
@ -463,10 +463,10 @@ export default {
|
|||
* Listen to 'song:selection-clear' (often broadcasted from the direct parent)
|
||||
* to clear the selected songs.
|
||||
*/
|
||||
'song:selection-clear': () => this.clearSelection(),
|
||||
});
|
||||
},
|
||||
};
|
||||
'song:selection-clear': () => this.clearSelection()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -30,128 +30,128 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import $ from 'jquery'
|
||||
|
||||
import songMenuMethods from '../../mixins/song-menu-methods';
|
||||
import songMenuMethods from '../../mixins/song-menu-methods'
|
||||
|
||||
import { event, isClipboardSupported, copyText } from '../../utils';
|
||||
import { sharedStore, songStore, queueStore, userStore, playlistStore } from '../../stores';
|
||||
import { playback, download } from '../../services';
|
||||
import router from '../../router';
|
||||
import { event, isClipboardSupported, copyText } from '../../utils'
|
||||
import { sharedStore, songStore, queueStore, userStore, playlistStore } from '../../stores'
|
||||
import { playback, download } from '../../services'
|
||||
import router from '../../router'
|
||||
|
||||
export default {
|
||||
name: 'song-menu',
|
||||
props: ['songs'],
|
||||
mixins: [songMenuMethods],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
playlistState: playlistStore.state,
|
||||
sharedState: sharedStore.state,
|
||||
copyable: isClipboardSupported(),
|
||||
};
|
||||
copyable: isClipboardSupported()
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
onlyOneSongSelected() {
|
||||
return this.songs.length === 1;
|
||||
onlyOneSongSelected () {
|
||||
return this.songs.length === 1
|
||||
},
|
||||
|
||||
firstSongPlaying() {
|
||||
return this.songs[0] ? this.songs[0].playbackState === 'playing' : false;
|
||||
firstSongPlaying () {
|
||||
return this.songs[0] ? this.songs[0].playbackState === 'playing' : false
|
||||
},
|
||||
|
||||
isAdmin() {
|
||||
return userStore.current.is_admin;
|
||||
},
|
||||
isAdmin () {
|
||||
return userStore.current.is_admin
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open(top = 0, left = 0) {
|
||||
open (top = 0, left = 0) {
|
||||
if (!this.songs.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.top = top;
|
||||
this.left = left;
|
||||
this.shown = true;
|
||||
this.top = top
|
||||
this.left = left
|
||||
this.shown = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
// Make sure the menu isn't off-screen
|
||||
if (this.$el.getBoundingClientRect().bottom > window.innerHeight) {
|
||||
$(this.$el).css({
|
||||
top: 'auto',
|
||||
bottom: 0,
|
||||
});
|
||||
bottom: 0
|
||||
})
|
||||
} else {
|
||||
$(this.$el).css({
|
||||
top: this.top,
|
||||
bottom: 'auto',
|
||||
});
|
||||
bottom: 'auto'
|
||||
})
|
||||
}
|
||||
|
||||
this.$refs.menu.focus();
|
||||
});
|
||||
this.$refs.menu.focus()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Take the right playback action based on the current playback state.
|
||||
*/
|
||||
doPlayback() {
|
||||
doPlayback () {
|
||||
switch (this.songs[0].playbackState) {
|
||||
case 'playing':
|
||||
playback.pause();
|
||||
break;
|
||||
playback.pause()
|
||||
break
|
||||
case 'paused':
|
||||
playback.resume();
|
||||
break;
|
||||
playback.resume()
|
||||
break
|
||||
default:
|
||||
if (!queueStore.contains(this.songs[0])) {
|
||||
queueStore.queueAfterCurrent(this.songs[0]);
|
||||
queueStore.queueAfterCurrent(this.songs[0])
|
||||
}
|
||||
|
||||
playback.play(this.songs[0]);
|
||||
break;
|
||||
playback.play(this.songs[0])
|
||||
break
|
||||
}
|
||||
|
||||
this.close();
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger opening the "Edit Song" form/overlay.
|
||||
*/
|
||||
openEditForm() {
|
||||
openEditForm () {
|
||||
if (this.songs.length) {
|
||||
event.emit('songs:edit', this.songs);
|
||||
event.emit('songs:edit', this.songs)
|
||||
}
|
||||
|
||||
this.close();
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the album details screen.
|
||||
*/
|
||||
viewAlbumDetails(album) {
|
||||
router.go(`album/${album.id}`);
|
||||
this.close();
|
||||
viewAlbumDetails (album) {
|
||||
router.go(`album/${album.id}`)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the artist details screen.
|
||||
*/
|
||||
viewArtistDetails(artist) {
|
||||
router.go(`artist/${artist.id}`);
|
||||
this.close();
|
||||
viewArtistDetails (artist) {
|
||||
router.go(`artist/${artist.id}`)
|
||||
this.close()
|
||||
},
|
||||
|
||||
download() {
|
||||
download.fromSongs(this.songs);
|
||||
this.close();
|
||||
download () {
|
||||
download.fromSongs(this.songs)
|
||||
this.close()
|
||||
},
|
||||
|
||||
copyUrl() {
|
||||
copyText(songStore.getShareableUrl(this.songs[0]));
|
||||
},
|
||||
copyUrl () {
|
||||
copyText(songStore.getShareableUrl(this.songs[0]))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -159,30 +159,30 @@ export default {
|
|||
* With this, we can catch when the submenus shown or hidden, and can make sure
|
||||
* they don't appear off-screen.
|
||||
*/
|
||||
mounted() {
|
||||
mounted () {
|
||||
$(this.$el).find('.has-sub').hover(e => {
|
||||
const $submenu = $(e.target).find('.submenu:first');
|
||||
const $submenu = $(e.target).find('.submenu:first')
|
||||
if (!$submenu.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
$submenu.show();
|
||||
$submenu.show()
|
||||
|
||||
// Make sure the submenu isn't off-screen
|
||||
if ($submenu[0].getBoundingClientRect().bottom > window.innerHeight) {
|
||||
$submenu.css({
|
||||
top: 'auto',
|
||||
bottom: 0,
|
||||
});
|
||||
bottom: 0
|
||||
})
|
||||
}
|
||||
}, e => {
|
||||
$(e.target).find('.submenu:first').hide().css({
|
||||
top: 0,
|
||||
bottom: 'auto',
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
bottom: 'auto'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -42,7 +42,7 @@ export default {
|
|||
// They call the Rising Sun
|
||||
// And it's been the ruin of many a poor boy
|
||||
// And God, I know I'm one.
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['showing'],
|
||||
};
|
||||
props: ['showing']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -21,104 +21,104 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import { filterBy } from '../../utils';
|
||||
import $ from 'jquery'
|
||||
import { filterBy } from '../../utils'
|
||||
|
||||
export default {
|
||||
props: ['options', 'value', 'items'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
filter: '',
|
||||
showingResult: false,
|
||||
mutatedValue: this.value,
|
||||
};
|
||||
mutatedValue: this.value
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayedItems() {
|
||||
return filterBy(this.items, this.filter, this.options.filterKey);
|
||||
},
|
||||
displayedItems () {
|
||||
return filterBy(this.items, this.filter, this.options.filterKey)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Navigate down the result list.
|
||||
*/
|
||||
down(e) {
|
||||
const selected = $(this.$el).find('.result li.selected');
|
||||
down (e) {
|
||||
const selected = $(this.$el).find('.result li.selected')
|
||||
|
||||
if (!selected.length || !selected.removeClass('selected').next('li').addClass('selected').length) {
|
||||
$(this.$el).find('.result li:first').addClass('selected');
|
||||
$(this.$el).find('.result li:first').addClass('selected')
|
||||
}
|
||||
|
||||
this.scrollSelectedIntoView(false);
|
||||
this.apply();
|
||||
this.scrollSelectedIntoView(false)
|
||||
this.apply()
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate up the result list.
|
||||
*/
|
||||
up(e) {
|
||||
const selected = $(this.$el).find('.result li.selected');
|
||||
up (e) {
|
||||
const selected = $(this.$el).find('.result li.selected')
|
||||
|
||||
if (!selected.length || !selected.removeClass('selected').prev('li').addClass('selected').length) {
|
||||
$(this.$el).find('.result li:last').addClass('selected');
|
||||
$(this.$el).find('.result li:last').addClass('selected')
|
||||
}
|
||||
|
||||
this.scrollSelectedIntoView(true);
|
||||
this.apply();
|
||||
this.scrollSelectedIntoView(true)
|
||||
this.apply()
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle ENTER or TAB keydown events.
|
||||
*/
|
||||
enter() {
|
||||
this.apply();
|
||||
this.showingResult = false;
|
||||
enter () {
|
||||
this.apply()
|
||||
this.showingResult = false
|
||||
},
|
||||
|
||||
keyup(e) {
|
||||
keyup (e) {
|
||||
/**
|
||||
* If it's an UP or DOWN arrow key, stop event bubbling.
|
||||
* The actually result navigation is handled by this.up() and this.down().
|
||||
*/
|
||||
if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// If it's an ENTER or TAB key, don't do anything.
|
||||
// We've handled ENTER & TAB on keydown.
|
||||
if (e.keyCode === 13 || e.keyCode === 9) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Hide the typeahead results and reset the value if ESC is pressed.
|
||||
if (e.keyCode === 27) {
|
||||
this.showingResult = false;
|
||||
this.showingResult = false
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.filter = this.mutatedValue;
|
||||
this.showingResult = true;
|
||||
this.filter = this.mutatedValue
|
||||
this.showingResult = true
|
||||
},
|
||||
|
||||
resultClick(e) {
|
||||
$(this.$el).find('.result li.selected').removeClass('selected');
|
||||
$(e.target).addClass('selected');
|
||||
resultClick (e) {
|
||||
$(this.$el).find('.result li.selected').removeClass('selected')
|
||||
$(e.target).addClass('selected')
|
||||
|
||||
this.apply();
|
||||
this.showingResult = false;
|
||||
this.apply()
|
||||
this.showingResult = false
|
||||
},
|
||||
|
||||
apply() {
|
||||
this.mutatedValue = $(this.$el).find('.result li.selected').text().trim() || this.mutatedValue;
|
||||
apply () {
|
||||
this.mutatedValue = $(this.$el).find('.result li.selected').text().trim() || this.mutatedValue
|
||||
// In Vue 2.0, we can use v-model on custom components like this.
|
||||
this.$emit('input', this.mutatedValue);
|
||||
this.$emit('input', this.mutatedValue)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -126,25 +126,25 @@ export default {
|
|||
*
|
||||
* @param {boolean} alignTop Whether the item should be aligned to top of its container.
|
||||
*/
|
||||
scrollSelectedIntoView(alignTop) {
|
||||
const elem = $(this.$el).find('.result li.selected')[0];
|
||||
scrollSelectedIntoView (alignTop) {
|
||||
const elem = $(this.$el).find('.result li.selected')[0]
|
||||
if (!elem) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const elemRect = elem.getBoundingClientRect();
|
||||
const containerRect = elem.offsetParent.getBoundingClientRect();
|
||||
const elemRect = elem.getBoundingClientRect()
|
||||
const containerRect = elem.offsetParent.getBoundingClientRect()
|
||||
|
||||
if (elemRect.bottom > containerRect.bottom || elemRect.top < containerRect.top) {
|
||||
elem.scrollIntoView(alignTop);
|
||||
elem.scrollIntoView(alignTop)
|
||||
}
|
||||
},
|
||||
|
||||
hideResults() {
|
||||
this.showingResult = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
hideResults () {
|
||||
this.showingResult = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -43,21 +43,21 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { clone, assign } from 'lodash';
|
||||
import swal from 'sweetalert';
|
||||
import { clone, assign } from 'lodash'
|
||||
import swal from 'sweetalert'
|
||||
|
||||
import { userStore } from '../../stores';
|
||||
import router from '../../router';
|
||||
import { userStore } from '../../stores'
|
||||
import router from '../../router'
|
||||
|
||||
export default {
|
||||
props: ['user'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
editing: false,
|
||||
confirmingDelete: false,
|
||||
cached: {},
|
||||
};
|
||||
cached: {}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -66,9 +66,9 @@ export default {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isCurrentUser() {
|
||||
return this.user.id === userStore.current.id;
|
||||
},
|
||||
isCurrentUser () {
|
||||
return this.user.id === userStore.current.id
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -76,55 +76,55 @@ export default {
|
|||
* Trigger editing a user.
|
||||
* If the user is the current logged-in user, redirect to the profile screen instead.
|
||||
*/
|
||||
edit() {
|
||||
edit () {
|
||||
if (this.isCurrentUser) {
|
||||
router.go('profile');
|
||||
router.go('profile')
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Keep a cached version of the user for rolling back.
|
||||
this.cached = clone(this.user);
|
||||
this.editing = true;
|
||||
this.cached = clone(this.user)
|
||||
this.editing = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel editing a user.
|
||||
*/
|
||||
cancelEdit() {
|
||||
cancelEdit () {
|
||||
// Restore the original user's properties
|
||||
assign(this.user, this.cached);
|
||||
this.editing = false;
|
||||
assign(this.user, this.cached)
|
||||
this.editing = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the edited user.
|
||||
*/
|
||||
update() {
|
||||
userStore.update(this.user, this.user.name, this.user.email, this.user.password). then(u => {
|
||||
this.editing = false;
|
||||
});
|
||||
update () {
|
||||
userStore.update(this.user, this.user.name, this.user.email, this.user.password).then(u => {
|
||||
this.editing = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Kill off the freaking user.
|
||||
*/
|
||||
del() {
|
||||
del () {
|
||||
swal({
|
||||
title: 'Hey…',
|
||||
text: `You’re about to unperson ${this.user.name}. Are you sure?`,
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Certainly',
|
||||
cancelButtonText: 'Oops',
|
||||
cancelButtonText: 'Oops'
|
||||
}, () => {
|
||||
userStore.destroy(this.user).then(() => {
|
||||
this.$destroy(true);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
this.$destroy(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -10,18 +10,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { event } from '../../utils';
|
||||
import { preferenceStore as preferences } from '../../stores';
|
||||
import { event } from '../../utils'
|
||||
import { preferenceStore as preferences } from '../../stores'
|
||||
|
||||
export default {
|
||||
props: ['mode', 'for'],
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
mutatedMode: this.mode,
|
||||
};
|
||||
mutatedMode: this.mode
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -30,33 +30,33 @@ export default {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
preferenceKey() {
|
||||
return `${this.for}ViewMode`;
|
||||
},
|
||||
preferenceKey () {
|
||||
return `${this.for}ViewMode`
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setMode(mode) {
|
||||
preferences[this.preferenceKey] = this.mutatedMode = mode;
|
||||
this.$parent.changeViewMode(mode);
|
||||
},
|
||||
setMode (mode) {
|
||||
preferences[this.preferenceKey] = this.mutatedMode = mode
|
||||
this.$parent.changeViewMode(mode)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on('koel:ready', () => {
|
||||
this.mutatedMode = preferences[this.preferenceKey];
|
||||
this.mutatedMode = preferences[this.preferenceKey]
|
||||
|
||||
// If the value is empty, we set a default mode.
|
||||
// On mobile, the mode should be 'listing'.
|
||||
// For desktop, 'thumbnails'.
|
||||
if (!this.mutatedMode) {
|
||||
this.mutatedMode = isMobile.phone ? 'list' : 'thumbnails';
|
||||
this.mutatedMode = isMobile.phone ? 'list' : 'thumbnails'
|
||||
}
|
||||
|
||||
this.setMode(this.mutatedMode);
|
||||
});
|
||||
},
|
||||
};
|
||||
this.setMode(this.mutatedMode)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
|
|
@ -40,33 +40,34 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { map, cloneDeep } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import rangeslider from 'rangeslider.js';
|
||||
import { map, cloneDeep } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import rangeslider from 'rangeslider.js'
|
||||
|
||||
import { isAudioContextSupported, event } from '../../utils';
|
||||
import { equalizerStore, preferenceStore as preferences } from '../../stores';
|
||||
import { isAudioContextSupported, event } from '../../utils'
|
||||
import { equalizerStore, preferenceStore as preferences } from '../../stores'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
idx: 0,
|
||||
bands: [],
|
||||
preampGainValue: 0,
|
||||
selectedPresetIndex: -1,
|
||||
};
|
||||
selectedPresetIndex: -1
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
presets() {
|
||||
let clonedPreset = cloneDeep(equalizerStore.presets);
|
||||
presets () {
|
||||
const clonedPreset = cloneDeep(equalizerStore.presets)
|
||||
// Prepend an empty option for instruction purpose.
|
||||
clonedPreset.unshift({
|
||||
id: -1,
|
||||
name: 'Preset',
|
||||
});
|
||||
return clonedPreset;
|
||||
},
|
||||
name: 'Preset'
|
||||
})
|
||||
return clonedPreset
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -74,16 +75,16 @@ export default {
|
|||
* Watch selectedPresetIndex and trigger our logic.
|
||||
* @param {Number} val
|
||||
*/
|
||||
selectedPresetIndex(val) {
|
||||
selectedPresetIndex (val) {
|
||||
/**
|
||||
* Save the selected preset (index) into local storage every time the value's changed.
|
||||
*/
|
||||
preferences.selectedPreset = val;
|
||||
preferences.selectedPreset = val
|
||||
|
||||
if (~~val !== -1) {
|
||||
this.loadPreset(equalizerStore.getPresetById(val));
|
||||
this.loadPreset(equalizerStore.getPresetById(val))
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -92,62 +93,63 @@ export default {
|
|||
*
|
||||
* @param {Element} player The audio player's DOM.
|
||||
*/
|
||||
init(player) {
|
||||
const settings = equalizerStore.get();
|
||||
init (player) {
|
||||
const settings = equalizerStore.get()
|
||||
|
||||
const AudioContext = window.AudioContext ||
|
||||
window.webkitAudioContext ||
|
||||
window.mozAudioContext ||
|
||||
window.oAudioContext ||
|
||||
window.msAudioContext;
|
||||
window.msAudioContext
|
||||
|
||||
const context = new AudioContext();
|
||||
const context = new AudioContext()
|
||||
|
||||
this.preampGainNode = context.createGain();
|
||||
this.changePreampGain(settings.preamp);
|
||||
this.preampGainNode = context.createGain()
|
||||
this.changePreampGain(settings.preamp)
|
||||
|
||||
const source = context.createMediaElementSource(player);
|
||||
source.connect(this.preampGainNode);
|
||||
const source = context.createMediaElementSource(player)
|
||||
source.connect(this.preampGainNode)
|
||||
|
||||
let prevFilter = null;
|
||||
let prevFilter = null
|
||||
|
||||
// Create 10 bands with the frequencies similar to those of Winamp and connect them together.
|
||||
[60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000].forEach((f, i) => {
|
||||
const filter = context.createBiquadFilter();
|
||||
const freqs = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]
|
||||
freqs.forEach((f, i) => {
|
||||
const filter = context.createBiquadFilter()
|
||||
|
||||
if (i === 0) {
|
||||
filter.type = 'lowshelf';
|
||||
filter.type = 'lowshelf'
|
||||
} else if (i === 9) {
|
||||
filter.type = 'highshelf';
|
||||
filter.type = 'highshelf'
|
||||
} else {
|
||||
filter.type = 'peaking';
|
||||
filter.type = 'peaking'
|
||||
}
|
||||
|
||||
filter.gain.value = settings.gains[i] ? settings.gains[i] : 0;
|
||||
filter.Q.value = 1;
|
||||
filter.frequency.value = f;
|
||||
filter.gain.value = settings.gains[i] ? settings.gains[i] : 0
|
||||
filter.Q.value = 1
|
||||
filter.frequency.value = f
|
||||
|
||||
prevFilter ? prevFilter.connect(filter) : this.preampGainNode.connect(filter);
|
||||
prevFilter = filter;
|
||||
prevFilter ? prevFilter.connect(filter) : this.preampGainNode.connect(filter)
|
||||
prevFilter = filter
|
||||
|
||||
this.bands.push({
|
||||
filter,
|
||||
label: (f + '').replace('000', 'K'),
|
||||
});
|
||||
});
|
||||
label: (f + '').replace('000', 'K')
|
||||
})
|
||||
})
|
||||
|
||||
prevFilter.connect(context.destination);
|
||||
prevFilter.connect(context.destination)
|
||||
|
||||
this.$nextTick(this.createRangeSliders);
|
||||
this.$nextTick(this.createRangeSliders)
|
||||
|
||||
// Now we set this value to trigger the audio processing.
|
||||
this.selectedPresetIndex = preferences.selectedPreset;
|
||||
this.selectedPresetIndex = preferences.selectedPreset
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the UI slider for both the preamp and the normal bands using rangeslider.js.
|
||||
*/
|
||||
createRangeSliders() {
|
||||
createRangeSliders () {
|
||||
$('#equalizer input[type="range"]').each((i, el) => {
|
||||
$(el).rangeslider({
|
||||
/**
|
||||
|
@ -165,9 +167,9 @@ export default {
|
|||
*/
|
||||
onSlide: (position, value) => {
|
||||
if ($(el).parents('.band').is('.preamp')) {
|
||||
this.changePreampGain(value);
|
||||
this.changePreampGain(value)
|
||||
} else {
|
||||
this.changeFilterGain(this.bands[i - 1].filter, value);
|
||||
this.changeFilterGain(this.bands[i - 1].filter, value)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -175,11 +177,11 @@ export default {
|
|||
* Save the settings and set the preset index to -1 (which is None) on slideEnd.
|
||||
*/
|
||||
onSlideEnd: () => {
|
||||
this.selectedPresetIndex = -1;
|
||||
this.save();
|
||||
},
|
||||
});
|
||||
});
|
||||
this.selectedPresetIndex = -1
|
||||
this.save()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -187,9 +189,9 @@ export default {
|
|||
*
|
||||
* @param {Number} dbValue The value of the gain, in dB.
|
||||
*/
|
||||
changePreampGain(dbValue) {
|
||||
this.preampGainValue = dbValue;
|
||||
this.preampGainNode.gain.value = Math.pow(10, dbValue / 20);
|
||||
changePreampGain (dbValue) {
|
||||
this.preampGainValue = dbValue
|
||||
this.preampGainNode.gain.value = Math.pow(10, dbValue / 20)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -198,48 +200,48 @@ export default {
|
|||
* @param {Object} filter The filter object
|
||||
* @param {Object} value Value of the gain, in dB.
|
||||
*/
|
||||
changeFilterGain(filter, value) {
|
||||
filter.gain.value = value;
|
||||
changeFilterGain (filter, value) {
|
||||
filter.gain.value = value
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a preset when the user select it from the dropdown.
|
||||
*/
|
||||
loadPreset(preset) {
|
||||
loadPreset (preset) {
|
||||
$('#equalizer input[type=range]').each((i, input) => {
|
||||
// We treat our preamp slider differently.
|
||||
if ($(input).parents('.band').is('.preamp')) {
|
||||
this.changePreampGain(preset.preamp);
|
||||
this.changePreampGain(preset.preamp)
|
||||
} else {
|
||||
this.changeFilterGain(this.bands[i - 1].filter, preset.gains[i - 1]);
|
||||
input.value = preset.gains[i - 1];
|
||||
this.changeFilterGain(this.bands[i - 1].filter, preset.gains[i - 1])
|
||||
input.value = preset.gains[i - 1]
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
this.$nextTick(() => {
|
||||
// Update the slider values into GUI.
|
||||
$('#equalizer input[type="range"]').rangeslider('update', true);
|
||||
});
|
||||
$('#equalizer input[type="range"]').rangeslider('update', true)
|
||||
})
|
||||
|
||||
this.save();
|
||||
this.save()
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current user's equalizer preferences into local storage.
|
||||
*/
|
||||
save() {
|
||||
equalizerStore.set(this.preampGainValue, map(this.bands, 'filter.gain.value'));
|
||||
},
|
||||
save () {
|
||||
equalizerStore.set(this.preampGainValue, map(this.bands, 'filter.gain.value'))
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
mounted () {
|
||||
event.on('equalizer:init', player => {
|
||||
if (isAudioContextSupported()) {
|
||||
this.init(player);
|
||||
this.init(player)
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -56,17 +56,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import config from '../../config';
|
||||
import { playback } from '../../services';
|
||||
import { isAudioContextSupported, event } from '../../utils';
|
||||
import { songStore, favoriteStore, preferenceStore } from '../../stores';
|
||||
import { playback } from '../../services'
|
||||
import { isAudioContextSupported, event } from '../../utils'
|
||||
import { songStore, favoriteStore, preferenceStore } from '../../stores'
|
||||
|
||||
import soundBar from '../shared/sound-bar.vue';
|
||||
import equalizer from './equalizer.vue';
|
||||
import volume from './volume.vue';
|
||||
import soundBar from '../shared/sound-bar.vue'
|
||||
import equalizer from './equalizer.vue'
|
||||
import volume from './volume.vue'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
song: songStore.stub,
|
||||
viewingQueue: false,
|
||||
|
@ -80,8 +79,8 @@ export default {
|
|||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
useEqualizer: isAudioContextSupported(),
|
||||
};
|
||||
useEqualizer: isAudioContextSupported()
|
||||
}
|
||||
},
|
||||
|
||||
components: { soundBar, equalizer, volume },
|
||||
|
@ -92,8 +91,8 @@ export default {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
prev() {
|
||||
return playback.previous;
|
||||
prev () {
|
||||
return playback.previous
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,76 +100,76 @@ export default {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
next() {
|
||||
return playback.next;
|
||||
},
|
||||
next () {
|
||||
return playback.next
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Play the previous song in queue.
|
||||
*/
|
||||
playPrev() {
|
||||
return playback.playPrev();
|
||||
playPrev () {
|
||||
return playback.playPrev()
|
||||
},
|
||||
|
||||
/**
|
||||
* Play the next song in queue.
|
||||
*/
|
||||
playNext() {
|
||||
return playback.playNext();
|
||||
playNext () {
|
||||
return playback.playNext()
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume the current song.
|
||||
* If the current song is the stub, just play the first song in the queue.
|
||||
*/
|
||||
resume() {
|
||||
resume () {
|
||||
if (!this.song.id) {
|
||||
return playback.playFirstInQueue();
|
||||
return playback.playFirstInQueue()
|
||||
}
|
||||
|
||||
playback.resume();
|
||||
playback.resume()
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause the playback.
|
||||
*/
|
||||
pause() {
|
||||
playback.pause();
|
||||
pause () {
|
||||
playback.pause()
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the repeat mode.
|
||||
*/
|
||||
changeRepeatMode() {
|
||||
return playback.changeRepeatMode();
|
||||
changeRepeatMode () {
|
||||
return playback.changeRepeatMode()
|
||||
},
|
||||
|
||||
/**
|
||||
* Like the current song.
|
||||
*/
|
||||
like() {
|
||||
like () {
|
||||
if (!this.song.id) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
favoriteStore.toggleOne(this.song);
|
||||
favoriteStore.toggleOne(this.song)
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle hide or show the extra panel.
|
||||
*/
|
||||
toggleExtraPanel() {
|
||||
preferenceStore.set('showExtraPanel', !this.prefs.showExtraPanel);
|
||||
toggleExtraPanel () {
|
||||
preferenceStore.set('showExtraPanel', !this.prefs.showExtraPanel)
|
||||
},
|
||||
|
||||
closeEqualizer() {
|
||||
this.showEqualizer = false;
|
||||
},
|
||||
closeEqualizer () {
|
||||
this.showEqualizer = false
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on({
|
||||
/**
|
||||
* Listen to song:played event to set the current playing song and the cover image.
|
||||
|
@ -180,20 +179,24 @@ export default {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
'song:played': song => {
|
||||
this.song = song;
|
||||
this.cover = this.song.album.cover;
|
||||
this.song = song
|
||||
this.cover = this.song.album.cover
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen to main-content-view:load event and highlight the Queue icon if
|
||||
* the Queue screen is being loaded.
|
||||
*/
|
||||
'main-content-view:load': view => this.viewingQueue = view === 'queue',
|
||||
'main-content-view:load': view => {
|
||||
this.viewingQueue = view === 'queue'
|
||||
},
|
||||
|
||||
'koel:teardown': () => this.song = songStore.stub,
|
||||
});
|
||||
},
|
||||
};
|
||||
'koel:teardown': () => {
|
||||
this.song = songStore.stub
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -7,35 +7,33 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { playback } from '../../services';
|
||||
import { playback } from '../../services'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
muted: false,
|
||||
};
|
||||
muted: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Mute the volume.
|
||||
*/
|
||||
mute() {
|
||||
this.muted = true;
|
||||
|
||||
return playback.mute();
|
||||
mute () {
|
||||
this.muted = true
|
||||
return playback.mute()
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmute the volume.
|
||||
*/
|
||||
unmute() {
|
||||
this.muted = false;
|
||||
|
||||
return playback.unmute();
|
||||
},
|
||||
},
|
||||
};
|
||||
unmute () {
|
||||
this.muted = false
|
||||
return playback.unmute()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -14,36 +14,36 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import config from '../../config';
|
||||
import { event } from '../../utils';
|
||||
import searchForm from './search-form.vue';
|
||||
import userBadge from './user-badge.vue';
|
||||
import config from '../../config'
|
||||
import { event } from '../../utils'
|
||||
import searchForm from './search-form.vue'
|
||||
import userBadge from './user-badge.vue'
|
||||
|
||||
export default {
|
||||
components: { searchForm, userBadge },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
appTitle: config.appTitle,
|
||||
};
|
||||
appTitle: config.appTitle
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* No I'm not documenting this.
|
||||
*/
|
||||
toggleSidebar() {
|
||||
event.emit('sidebar:toggle');
|
||||
toggleSidebar () {
|
||||
event.emit('sidebar:toggle')
|
||||
},
|
||||
|
||||
/**
|
||||
* or this.
|
||||
*/
|
||||
toggleSearchForm() {
|
||||
event.emit('search:toggle');
|
||||
},
|
||||
},
|
||||
};
|
||||
toggleSearchForm () {
|
||||
event.emit('search:toggle')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -10,19 +10,19 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import isMobile from 'ismobilejs';
|
||||
import { debounce } from 'lodash';
|
||||
import isMobile from 'ismobilejs'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
import { event } from '../../utils';
|
||||
import { event } from '../../utils'
|
||||
|
||||
export default {
|
||||
name: 'site-header--search-form',
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
q: '',
|
||||
showing: !isMobile.phone,
|
||||
};
|
||||
showing: !isMobile.phone
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -30,21 +30,21 @@ export default {
|
|||
* Limit the filter's execution rate using lodash's debounce.
|
||||
*/
|
||||
filter: debounce(function () {
|
||||
event.emit('filter:changed', this.q);
|
||||
}, 200),
|
||||
event.emit('filter:changed', this.q)
|
||||
}, 200)
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on('search:toggle', () => {
|
||||
this.showing = !this.showing;
|
||||
});
|
||||
this.showing = !this.showing
|
||||
})
|
||||
|
||||
event.on('koel:teardown', () => {
|
||||
this.q = '';
|
||||
this.filter();
|
||||
this.q = ''
|
||||
this.filter()
|
||||
})
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -10,24 +10,24 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { userStore } from '../../stores';
|
||||
import { event } from '../../utils';
|
||||
import { userStore } from '../../stores'
|
||||
import { event } from '../../utils'
|
||||
|
||||
export default {
|
||||
name: 'site-header--user-badge',
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: userStore.state,
|
||||
};
|
||||
state: userStore.state
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
logout() {
|
||||
event.emit('logout');
|
||||
},
|
||||
},
|
||||
};
|
||||
logout () {
|
||||
event.emit('logout')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
unknownCover: (typeof window !== 'undefined' ? window.location.href : '/') + 'public/img/covers/unknown-album.png',
|
||||
appTitle: 'Koel',
|
||||
};
|
||||
appTitle: 'Koel'
|
||||
}
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
* @type {Object}
|
||||
*/
|
||||
export const clickawayDirective = {
|
||||
bind(el, { value }) {
|
||||
bind (el, { value }) {
|
||||
if (typeof value !== 'function') {
|
||||
console.warn(`Expect a function, got ${value}`);
|
||||
return;
|
||||
console.warn(`Expect a function, got ${value}`)
|
||||
return
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
if (!el.contains(e.target)) {
|
||||
value();
|
||||
value()
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
/**
|
||||
* A simple directive to set focus into an input field when it's shown.
|
||||
*/
|
||||
export const focusDirective = {
|
||||
inserted(el) {
|
||||
el.focus();
|
||||
},
|
||||
};
|
||||
inserted (el) {
|
||||
el.focus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './focus';
|
||||
export * from './clickaway';
|
||||
export * from './focus'
|
||||
export * from './clickaway'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import Vue from 'vue'
|
||||
|
||||
import { event } from './utils';
|
||||
import { http } from './services';
|
||||
import { event } from './utils'
|
||||
import { http } from './services'
|
||||
/**
|
||||
* For Ancelot, the ancient cross of war
|
||||
* for the holy town of Gods
|
||||
|
@ -11,8 +11,8 @@ import { http } from './services';
|
|||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(require('./app.vue')),
|
||||
created() {
|
||||
event.init();
|
||||
http.init();
|
||||
},
|
||||
});
|
||||
created () {
|
||||
event.init()
|
||||
http.init()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -2,50 +2,50 @@
|
|||
* Add necessary functionalities into a view that contains a song-list component.
|
||||
*/
|
||||
|
||||
import { assign } from 'lodash';
|
||||
import isMobile from 'ismobilejs';
|
||||
import { assign } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { playback } from '../services';
|
||||
import songList from '../components/shared/song-list.vue';
|
||||
import songListControls from '../components/shared/song-list-controls.vue';
|
||||
import controlsToggler from '../components/shared/song-list-controls-toggler.vue';
|
||||
import { playback } from '../services'
|
||||
import songList from '../components/shared/song-list.vue'
|
||||
import songListControls from '../components/shared/song-list-controls.vue'
|
||||
import controlsToggler from '../components/shared/song-list-controls-toggler.vue'
|
||||
|
||||
export default {
|
||||
components: { songList, songListControls, controlsToggler },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
state: null,
|
||||
meta: {
|
||||
songCount: 0,
|
||||
totalLength: '00:00',
|
||||
totalLength: '00:00'
|
||||
},
|
||||
selectedSongs: [],
|
||||
showingControls: false,
|
||||
songListControlConfig: {},
|
||||
isPhone: isMobile.phone,
|
||||
};
|
||||
isPhone: isMobile.phone
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setSelectedSongs(songs) {
|
||||
this.selectedSongs = songs;
|
||||
setSelectedSongs (songs) {
|
||||
this.selectedSongs = songs
|
||||
},
|
||||
|
||||
updateMeta(meta) {
|
||||
this.meta = assign(this.meta, meta);
|
||||
updateMeta (meta) {
|
||||
this.meta = assign(this.meta, meta)
|
||||
},
|
||||
|
||||
shuffleAll() {
|
||||
playback.queueAndPlay(this.state.songs, true);
|
||||
shuffleAll () {
|
||||
playback.queueAndPlay(this.state.songs, true)
|
||||
},
|
||||
|
||||
shuffleSelected() {
|
||||
playback.queueAndPlay(this.selectedSongs, true);
|
||||
shuffleSelected () {
|
||||
playback.queueAndPlay(this.selectedSongs, true)
|
||||
},
|
||||
|
||||
toggleControls() {
|
||||
this.showingControls = !this.showingControls;
|
||||
toggleControls () {
|
||||
this.showingControls = !this.showingControls
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import $ from 'jquery'
|
||||
|
||||
import { event } from '../utils';
|
||||
import toTopButton from '../components/shared/to-top-button.vue';
|
||||
import { event } from '../utils'
|
||||
import toTopButton from '../components/shared/to-top-button.vue'
|
||||
|
||||
/**
|
||||
* Add a "infinite scroll" functionality to any component using this mixin.
|
||||
|
@ -11,45 +11,45 @@ import toTopButton from '../components/shared/to-top-button.vue';
|
|||
export default {
|
||||
components: { toTopButton },
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
numOfItems: 30, // Number of currently loaded and displayed items
|
||||
perPage: 30, // Number of items to be loaded per "page"
|
||||
showBackToTop: false,
|
||||
};
|
||||
showBackToTop: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrolling(e) {
|
||||
scrolling (e) {
|
||||
// Here we check if the user has scrolled to the end of the wrapper (or 32px to the end).
|
||||
// If that's true, load more items.
|
||||
if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - 32) {
|
||||
this.displayMore();
|
||||
this.displayMore()
|
||||
}
|
||||
|
||||
this.showBackToTop = e.target.scrollTop > 64;
|
||||
this.showBackToTop = e.target.scrollTop > 64
|
||||
},
|
||||
|
||||
/**
|
||||
* Load and display more items into the scrollable area.
|
||||
*/
|
||||
displayMore() {
|
||||
this.numOfItems += this.perPage;
|
||||
displayMore () {
|
||||
this.numOfItems += this.perPage
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll to top of the wrapper.
|
||||
*/
|
||||
scrollToTop() {
|
||||
$(this.$refs.wrapper).animate({ scrollTop: 0 }, 500);
|
||||
this.showBackToTop = false;
|
||||
},
|
||||
scrollToTop () {
|
||||
$(this.$refs.wrapper).animate({ scrollTop: 0 }, 500)
|
||||
this.showBackToTop = false
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
created () {
|
||||
event.on('koel:teardown', () => {
|
||||
this.numOfItems = 30;
|
||||
this.showBackToTop = false;
|
||||
});
|
||||
},
|
||||
};
|
||||
this.numOfItems = 30
|
||||
this.showBackToTop = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import $ from 'jquery'
|
||||
|
||||
import { queueStore, playlistStore, favoriteStore } from '../stores';
|
||||
import { queueStore, playlistStore, favoriteStore } from '../stores'
|
||||
|
||||
/**
|
||||
* Includes the methods triggerable on a song (context) menu.
|
||||
|
@ -9,56 +9,55 @@ import { queueStore, playlistStore, favoriteStore } from '../stores';
|
|||
* for example close() and open().
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
shown: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {},
|
||||
open () {},
|
||||
|
||||
/**
|
||||
* Close all submenus.
|
||||
*/
|
||||
close() {
|
||||
$(this.$el).find('.submenu').hide();
|
||||
|
||||
this.shown = false;
|
||||
close () {
|
||||
$(this.$el).find('.submenu').hide()
|
||||
this.shown = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Queue select songs after the current song.
|
||||
*/
|
||||
queueSongsAfterCurrent() {
|
||||
queueStore.queueAfterCurrent(this.songs);
|
||||
this.close();
|
||||
queueSongsAfterCurrent () {
|
||||
queueStore.queueAfterCurrent(this.songs)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Queue selected songs to bottom of queue.
|
||||
*/
|
||||
queueSongsToBottom() {
|
||||
queueStore.queue(this.songs);
|
||||
this.close();
|
||||
queueSongsToBottom () {
|
||||
queueStore.queue(this.songs)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Queue selected songs to top of queue.
|
||||
*/
|
||||
queueSongsToTop() {
|
||||
queueStore.queue(this.songs, false, true);
|
||||
this.close();
|
||||
queueSongsToTop () {
|
||||
queueStore.queue(this.songs, false, true)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the selected songs into Favorites.
|
||||
*/
|
||||
addSongsToFavorite() {
|
||||
favoriteStore.like(this.songs);
|
||||
this.close();
|
||||
addSongsToFavorite () {
|
||||
favoriteStore.like(this.songs)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,9 +65,9 @@ export default {
|
|||
*
|
||||
* @param {Object} playlist The playlist.
|
||||
*/
|
||||
addSongsToExistingPlaylist(playlist) {
|
||||
playlistStore.addSongs(playlist, this.songs);
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
addSongsToExistingPlaylist (playlist) {
|
||||
playlistStore.addSongs(playlist, this.songs)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,108 +1,108 @@
|
|||
import isMobile from 'ismobilejs';
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { loadMainView } from './utils';
|
||||
import { artistStore, albumStore, songStore, queueStore, playlistStore, userStore } from './stores';
|
||||
import { playback } from './services';
|
||||
import { loadMainView } from './utils'
|
||||
import { artistStore, albumStore, songStore, queueStore, playlistStore, userStore } from './stores'
|
||||
import { playback } from './services'
|
||||
|
||||
export default {
|
||||
routes: {
|
||||
'/home'() {
|
||||
loadMainView('home');
|
||||
'/home' () {
|
||||
loadMainView('home')
|
||||
},
|
||||
|
||||
'/queue'() {
|
||||
loadMainView('queue');
|
||||
'/queue' () {
|
||||
loadMainView('queue')
|
||||
},
|
||||
|
||||
'/songs'() {
|
||||
loadMainView('songs');
|
||||
'/songs' () {
|
||||
loadMainView('songs')
|
||||
},
|
||||
|
||||
'/albums'() {
|
||||
loadMainView('albums');
|
||||
'/albums' () {
|
||||
loadMainView('albums')
|
||||
},
|
||||
|
||||
'/album/(\\d+)'(id) {
|
||||
const album = albumStore.byId(~~id);
|
||||
'/album/(\\d+)' (id) {
|
||||
const album = albumStore.byId(~~id)
|
||||
if (album) {
|
||||
loadMainView('album', album);
|
||||
loadMainView('album', album)
|
||||
}
|
||||
},
|
||||
|
||||
'/artists'() {
|
||||
loadMainView('artists');
|
||||
'/artists' () {
|
||||
loadMainView('artists')
|
||||
},
|
||||
|
||||
'/artist/(\\d+)'(id) {
|
||||
const artist = artistStore.byId(~~id);
|
||||
'/artist/(\\d+)' (id) {
|
||||
const artist = artistStore.byId(~~id)
|
||||
if (artist) {
|
||||
loadMainView('artist', artist);
|
||||
loadMainView('artist', artist)
|
||||
}
|
||||
},
|
||||
|
||||
'/favorites'() {
|
||||
loadMainView('favorites');
|
||||
'/favorites' () {
|
||||
loadMainView('favorites')
|
||||
},
|
||||
|
||||
'/playlist/(\\d+)'(id) {
|
||||
const playlist = playlistStore.byId(~~id);
|
||||
'/playlist/(\\d+)' (id) {
|
||||
const playlist = playlistStore.byId(~~id)
|
||||
if (playlist) {
|
||||
loadMainView('playlist', playlist);
|
||||
loadMainView('playlist', playlist)
|
||||
}
|
||||
},
|
||||
|
||||
'/settings'() {
|
||||
userStore.current.is_admin && loadMainView('settings');
|
||||
'/settings' () {
|
||||
userStore.current.is_admin && loadMainView('settings')
|
||||
},
|
||||
|
||||
'/users'() {
|
||||
userStore.current.is_admin && loadMainView('users');
|
||||
'/users' () {
|
||||
userStore.current.is_admin && loadMainView('users')
|
||||
},
|
||||
|
||||
'/profile'() {
|
||||
loadMainView('profile');
|
||||
'/profile' () {
|
||||
loadMainView('profile')
|
||||
},
|
||||
|
||||
'/login'() {
|
||||
'/login' () {
|
||||
|
||||
},
|
||||
|
||||
'/song/([a-z0-9]{32})'(id) {
|
||||
const song = songStore.byId(id);
|
||||
if (!song) return;
|
||||
'/song/([a-z0-9]{32})' (id) {
|
||||
const song = songStore.byId(id)
|
||||
if (!song) return
|
||||
|
||||
if (isMobile.apple.device) {
|
||||
// Mobile Safari doesn't allow autoplay, so we just queue.
|
||||
queueStore.queue(song);
|
||||
loadMainView('queue');
|
||||
queueStore.queue(song)
|
||||
loadMainView('queue')
|
||||
} else {
|
||||
playback.queueAndPlay(song);
|
||||
playback.queueAndPlay(song)
|
||||
}
|
||||
},
|
||||
|
||||
'/youtube'() {
|
||||
loadMainView('youtubePlayer');
|
||||
},
|
||||
'/youtube' () {
|
||||
loadMainView('youtubePlayer')
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
this.loadState();
|
||||
window.addEventListener('popstate', () => this.loadState(), true);
|
||||
init () {
|
||||
this.loadState()
|
||||
window.addEventListener('popstate', () => this.loadState(), true)
|
||||
},
|
||||
|
||||
loadState() {
|
||||
loadState () {
|
||||
if (!window.location.hash) {
|
||||
return this.go('home');
|
||||
return this.go('home')
|
||||
}
|
||||
|
||||
Object.keys(this.routes).forEach(route => {
|
||||
const matches = window.location.hash.match(new RegExp(`^#!${route}$`));
|
||||
const matches = window.location.hash.match(new RegExp(`^#!${route}$`))
|
||||
if (matches) {
|
||||
const [, ...params] = matches;
|
||||
this.routes[route](...params);
|
||||
return false;
|
||||
const [, ...params] = matches
|
||||
this.routes[route](...params)
|
||||
return false
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -110,15 +110,15 @@ export default {
|
|||
*
|
||||
* @param {String} path
|
||||
*/
|
||||
go(path) {
|
||||
go (path) {
|
||||
if (path[0] !== '/') {
|
||||
path = `/${path}`;
|
||||
path = `/${path}`
|
||||
}
|
||||
|
||||
if (path.indexOf('/#!') !== 0) {
|
||||
path = `/#!${path}`;
|
||||
path = `/#!${path}`
|
||||
}
|
||||
|
||||
document.location.href = `${document.location.origin}${path}`;
|
||||
},
|
||||
};
|
||||
document.location.href = `${document.location.origin}${path}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import $ from 'jquery';
|
||||
import { map } from 'lodash';
|
||||
import $ from 'jquery'
|
||||
import { map } from 'lodash'
|
||||
|
||||
import { playlistStore, favoriteStore } from '../stores';
|
||||
import { ls } from '.';
|
||||
import { playlistStore, favoriteStore } from '../stores'
|
||||
import { ls } from '.'
|
||||
|
||||
export const download = {
|
||||
/**
|
||||
|
@ -10,12 +10,12 @@ export const download = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
fromSongs(songs) {
|
||||
songs = [].concat(songs);
|
||||
const ids = map(songs, 'id');
|
||||
const params = $.param({ songs: ids });
|
||||
fromSongs (songs) {
|
||||
songs = [].concat(songs)
|
||||
const ids = map(songs, 'id')
|
||||
const params = $.param({ songs: ids })
|
||||
|
||||
return this.trigger(`songs?${params}`);
|
||||
return this.trigger(`songs?${params}`)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -23,8 +23,8 @@ export const download = {
|
|||
*
|
||||
* @param {Object} album
|
||||
*/
|
||||
fromAlbum(album) {
|
||||
return this.trigger(`album/${album.id}`);
|
||||
fromAlbum (album) {
|
||||
return this.trigger(`album/${album.id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -32,11 +32,11 @@ export const download = {
|
|||
*
|
||||
* @param {Object} artist
|
||||
*/
|
||||
fromArtist(artist) {
|
||||
fromArtist (artist) {
|
||||
// It's safe to assume an artist always has songs.
|
||||
// After all, what's an artist without her songs?
|
||||
// (See what I did there? Yes, I'm advocating for women's rights).
|
||||
return this.trigger(`artist/${artist.id}`);
|
||||
return this.trigger(`artist/${artist.id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -44,25 +44,25 @@ export const download = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
fromPlaylist(playlist) {
|
||||
fromPlaylist (playlist) {
|
||||
if (!playlistStore.getSongs(playlist).length) {
|
||||
console.warn('Empty playlist.');
|
||||
return;
|
||||
console.warn('Empty playlist.')
|
||||
return
|
||||
}
|
||||
|
||||
return this.trigger(`playlist/${playlist.id}`);
|
||||
return this.trigger(`playlist/${playlist.id}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* Download all favorite songs.
|
||||
*/
|
||||
fromFavorites() {
|
||||
fromFavorites () {
|
||||
if (!favoriteStore.all.length) {
|
||||
console.warn("You don't like any song? Come on, don't be that grumpy.");
|
||||
return;
|
||||
console.warn("You don't like any song? Come on, don't be that grumpy.")
|
||||
return
|
||||
}
|
||||
|
||||
return this.trigger('favorites');
|
||||
return this.trigger('favorites')
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -71,10 +71,10 @@ export const download = {
|
|||
* @param {string} uri The uri segment, corresponding to the song(s),
|
||||
* artist, playlist, or album.
|
||||
*/
|
||||
trigger(uri) {
|
||||
const sep = uri.indexOf('?') === -1 ? '?' : '&';
|
||||
const frameId = `downloader${Date.now()}`;
|
||||
$(`<iframe id="${frameId}" style="display:none"></iframe`).appendTo('body');
|
||||
document.getElementById(frameId).src = `/api/download/${uri}${sep}jwt-token=${ls.get('jwt-token')}`;
|
||||
},
|
||||
trigger (uri) {
|
||||
const sep = uri.indexOf('?') === -1 ? '?' : '&'
|
||||
const frameId = `downloader${Date.now()}`
|
||||
$(`<iframe id="${frameId}" style="display:none"></iframe`).appendTo('body')
|
||||
document.getElementById(frameId).src = `/api/download/${uri}${sep}jwt-token=${ls.get('jwt-token')}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
import $ from 'jquery';
|
||||
import NProgress from 'nprogress';
|
||||
import $ from 'jquery'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
import { event } from '../utils';
|
||||
import { ls } from '../services';
|
||||
import { event } from '../utils'
|
||||
import { ls } from '../services'
|
||||
|
||||
/**
|
||||
* Responsible for all HTTP requests.
|
||||
*/
|
||||
export const http = {
|
||||
request(method, url, data, successCb = null, errorCb = null) {
|
||||
request (method, url, data, successCb = null, errorCb = null) {
|
||||
return $.ajax({
|
||||
data,
|
||||
dataType: 'json',
|
||||
url: `/api/${url}`,
|
||||
method: method.toUpperCase(),
|
||||
headers: {
|
||||
Authorization: `Bearer ${ls.get('jwt-token')}`,
|
||||
Authorization: `Bearer ${ls.get('jwt-token')}`
|
||||
}
|
||||
}).done(successCb).fail(errorCb);
|
||||
}).done(successCb).fail(errorCb)
|
||||
},
|
||||
|
||||
get(url, successCb = null, errorCb = null) {
|
||||
return this.request('get', url, {}, successCb, errorCb);
|
||||
get (url, successCb = null, errorCb = null) {
|
||||
return this.request('get', url, {}, successCb, errorCb)
|
||||
},
|
||||
|
||||
post(url, data, successCb = null, errorCb = null) {
|
||||
return this.request('post', url, data, successCb, errorCb);
|
||||
post (url, data, successCb = null, errorCb = null) {
|
||||
return this.request('post', url, data, successCb, errorCb)
|
||||
},
|
||||
|
||||
put(url, data, successCb = null, errorCb = null) {
|
||||
return this.request('put', url, data, successCb, errorCb);
|
||||
put (url, data, successCb = null, errorCb = null) {
|
||||
return this.request('put', url, data, successCb, errorCb)
|
||||
},
|
||||
|
||||
delete(url, data = {}, successCb = null, errorCb = null) {
|
||||
return this.request('delete', url, data, successCb, errorCb);
|
||||
delete (url, data = {}, successCb = null, errorCb = null) {
|
||||
return this.request('delete', url, data, successCb, errorCb)
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the service.
|
||||
*/
|
||||
init() {
|
||||
init () {
|
||||
$(document).ajaxComplete((e, r, settings) => {
|
||||
NProgress.done();
|
||||
NProgress.done()
|
||||
|
||||
if (r.status === 400 || r.status === 401) {
|
||||
if (!(settings.method === 'POST' && /\/api\/me\/?$/.test(settings.url))) {
|
||||
// This is not a failed login. Log out then.
|
||||
event.emit('logout');
|
||||
return;
|
||||
event.emit('logout')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const token = r.getResponseHeader('Authorization');
|
||||
const token = r.getResponseHeader('Authorization')
|
||||
if (token) {
|
||||
ls.set('jwt-token', token);
|
||||
ls.set('jwt-token', token)
|
||||
}
|
||||
|
||||
if (r.responseJSON && r.responseJSON.token && r.responseJSON.token.length > 10) {
|
||||
ls.set('jwt-token', r.responseJSON.token);
|
||||
ls.set('jwt-token', r.responseJSON.token)
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from './info';
|
||||
export * from './download';
|
||||
export * from './http';
|
||||
export * from './ls';
|
||||
export * from './playback';
|
||||
export * from './youtube';
|
||||
export * from './info'
|
||||
export * from './download'
|
||||
export * from './http'
|
||||
export * from './ls'
|
||||
export * from './playback'
|
||||
export * from './youtube'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { each } from 'lodash';
|
||||
import { each } from 'lodash'
|
||||
|
||||
import { secondsToHis } from '../../utils';
|
||||
import { http } from '..';
|
||||
import { secondsToHis } from '../../utils'
|
||||
import { http } from '..'
|
||||
|
||||
export const albumInfo = {
|
||||
/**
|
||||
|
@ -9,18 +9,18 @@ export const albumInfo = {
|
|||
*
|
||||
* @param {Object} album
|
||||
*/
|
||||
fetch(album) {
|
||||
fetch (album) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (album.info) {
|
||||
resolve(album);
|
||||
return;
|
||||
resolve(album)
|
||||
return
|
||||
}
|
||||
|
||||
http.get(`album/${album.id}/info`, data => {
|
||||
data && this.merge(album, data);
|
||||
resolve(album);
|
||||
}, r => reject(r));
|
||||
});
|
||||
data && this.merge(album, data)
|
||||
resolve(album)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -29,20 +29,22 @@ export const albumInfo = {
|
|||
* @param {Object} album
|
||||
* @param {Object} info
|
||||
*/
|
||||
merge(album, info) {
|
||||
merge (album, info) {
|
||||
// Convert the duration into i:s
|
||||
info.tracks && each(info.tracks, track => track.fmtLength = secondsToHis(track.length));
|
||||
info.tracks && each(info.tracks, track => {
|
||||
track.fmtLength = secondsToHis(track.length)
|
||||
})
|
||||
|
||||
// If the album cover is not in a nice form, discard.
|
||||
if (typeof info.image !== 'string') {
|
||||
info.image = null;
|
||||
info.image = null
|
||||
}
|
||||
|
||||
// Set the album cover on the client side to the retrieved image from server.
|
||||
if (info.cover) {
|
||||
album.cover = info.cover;
|
||||
album.cover = info.cover
|
||||
}
|
||||
|
||||
album.info = info;
|
||||
},
|
||||
};
|
||||
album.info = info
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { http } from '..';
|
||||
import { http } from '..'
|
||||
|
||||
export const artistInfo = {
|
||||
/**
|
||||
|
@ -6,18 +6,18 @@ export const artistInfo = {
|
|||
*
|
||||
* @param {Object} artist
|
||||
*/
|
||||
fetch(artist) {
|
||||
fetch (artist) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (artist.info) {
|
||||
resolve(artist);
|
||||
return;
|
||||
resolve(artist)
|
||||
return
|
||||
}
|
||||
|
||||
http.get(`artist/${artist.id}/info`, data => {
|
||||
data && this.merge(artist, data);
|
||||
resolve(artist);
|
||||
}, r => reject(r));
|
||||
});
|
||||
data && this.merge(artist, data)
|
||||
resolve(artist)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -26,17 +26,17 @@ export const artistInfo = {
|
|||
* @param {Object} artist
|
||||
* @param {Object} info
|
||||
*/
|
||||
merge(artist, info) {
|
||||
merge (artist, info) {
|
||||
// If the artist image is not in a nice form, discard.
|
||||
if (typeof info.image !== 'string') {
|
||||
info.image = null;
|
||||
info.image = null
|
||||
}
|
||||
|
||||
// Set the artist image on the client side to the retrieved image from server.
|
||||
if (info.image) {
|
||||
artist.image = info.image;
|
||||
artist.image = info.image
|
||||
}
|
||||
|
||||
artist.info = info;
|
||||
},
|
||||
};
|
||||
artist.info = info
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from './album';
|
||||
export * from './artist';
|
||||
export * from './song';
|
||||
export * from './album'
|
||||
export * from './artist'
|
||||
export * from './song'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { http, albumInfo, artistInfo } from '..';
|
||||
import { http, albumInfo, artistInfo } from '..'
|
||||
|
||||
export const songInfo = {
|
||||
/**
|
||||
|
@ -6,22 +6,22 @@ export const songInfo = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
fetch(song) {
|
||||
fetch (song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if the song's info has been retrieved before.
|
||||
if (song.infoRetrieved) {
|
||||
resolve(song);
|
||||
return;
|
||||
resolve(song)
|
||||
return
|
||||
}
|
||||
|
||||
http.get(`${song.id}/info`, data => {
|
||||
song.lyrics = data.lyrics;
|
||||
data.artist_info && artistInfo.merge(song.artist, data.artist_info);
|
||||
data.album_info && albumInfo.merge(song.album, data.album_info);
|
||||
song.youtube = data.youtube;
|
||||
song.infoRetrieved = true;
|
||||
resolve(song);
|
||||
}, r => reject(r));
|
||||
});
|
||||
},
|
||||
};
|
||||
song.lyrics = data.lyrics
|
||||
data.artist_info && artistInfo.merge(song.artist, data.artist_info)
|
||||
data.album_info && albumInfo.merge(song.album, data.album_info)
|
||||
song.youtube = data.youtube
|
||||
song.infoRetrieved = true
|
||||
resolve(song)
|
||||
}, r => reject(r))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import localStore from 'local-storage';
|
||||
import localStore from 'local-storage'
|
||||
|
||||
export const ls = {
|
||||
get(key, defaultVal = null) {
|
||||
const val = localStore(key);
|
||||
get (key, defaultVal = null) {
|
||||
const val = localStore(key)
|
||||
|
||||
return val ? val : defaultVal;
|
||||
return val || defaultVal
|
||||
},
|
||||
|
||||
set(key, val) {
|
||||
return localStore(key, val);
|
||||
set (key, val) {
|
||||
return localStore(key, val)
|
||||
},
|
||||
|
||||
remove(key) {
|
||||
return localStore.remove(key);
|
||||
},
|
||||
};
|
||||
remove (key) {
|
||||
return localStore.remove(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { shuffle, orderBy } from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import plyr from 'plyr';
|
||||
import Vue from 'vue';
|
||||
import { shuffle, orderBy } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import plyr from 'plyr'
|
||||
import Vue from 'vue'
|
||||
|
||||
import { event } from '../utils';
|
||||
import { queueStore, sharedStore, userStore, songStore, artistStore, preferenceStore as preferences } from '../stores';
|
||||
import config from '../config';
|
||||
import router from '../router';
|
||||
import { event } from '../utils'
|
||||
import { queueStore, sharedStore, userStore, songStore, preferenceStore as preferences } from '../stores'
|
||||
import config from '../config'
|
||||
import router from '../router'
|
||||
|
||||
export const playback = {
|
||||
player: null,
|
||||
|
@ -17,63 +17,63 @@ export const playback = {
|
|||
/**
|
||||
* Initialize the playback service for this whole Koel app.
|
||||
*/
|
||||
init() {
|
||||
init () {
|
||||
// We don't need to init this service twice, or the media events will be duplicated.
|
||||
if (this.initialized) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.player = plyr.setup({
|
||||
controls: [],
|
||||
})[0];
|
||||
controls: []
|
||||
})[0]
|
||||
|
||||
this.audio = $('audio');
|
||||
this.audio = $('audio')
|
||||
|
||||
this.$volumeInput = $('#volumeRange');
|
||||
this.$volumeInput = $('#volumeRange')
|
||||
|
||||
/**
|
||||
* Listen to 'error' event on the audio player and play the next song if any.
|
||||
*/
|
||||
document.querySelector('.plyr').addEventListener('error', e => {
|
||||
this.playNext();
|
||||
}, true);
|
||||
this.playNext()
|
||||
}, true)
|
||||
|
||||
/**
|
||||
* Listen to 'ended' event on the audio player and play the next song in the queue.
|
||||
*/
|
||||
document.querySelector('.plyr').addEventListener('ended', e => {
|
||||
if (sharedStore.state.useLastfm && userStore.current.preferences.lastfm_session_key) {
|
||||
songStore.scrobble(queueStore.current);
|
||||
songStore.scrobble(queueStore.current)
|
||||
}
|
||||
|
||||
if (preferences.repeatMode === 'REPEAT_ONE') {
|
||||
this.restart();
|
||||
this.restart()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.playNext();
|
||||
});
|
||||
this.playNext()
|
||||
})
|
||||
|
||||
/**
|
||||
* Attempt to preload the next song if the current song is about to end.
|
||||
*/
|
||||
document.querySelector('.plyr').addEventListener('timeupdate', e => {
|
||||
if (!this.player.media.duration || this.player.media.currentTime + 10 < this.player.media.duration) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// The current song has only 10 seconds left to play.
|
||||
const nextSong = queueStore.next;
|
||||
const nextSong = queueStore.next
|
||||
if (!nextSong || nextSong.preloaded) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const $preloader = $('<audio>');
|
||||
$preloader.attr('src', songStore.getSourceUrl(nextSong));
|
||||
const $preloader = $('<audio>')
|
||||
$preloader.attr('src', songStore.getSourceUrl(nextSong))
|
||||
|
||||
nextSong.preloaded = true;
|
||||
});
|
||||
nextSong.preloaded = true
|
||||
})
|
||||
|
||||
/**
|
||||
* Listen to 'input' event on the volume range control.
|
||||
|
@ -81,16 +81,16 @@ export const playback = {
|
|||
* update the volume on the plyr object.
|
||||
*/
|
||||
this.$volumeInput.on('input', e => {
|
||||
this.setVolume($(e.target).val());
|
||||
});
|
||||
this.setVolume($(e.target).val())
|
||||
})
|
||||
|
||||
// On init, set the volume to the value found in the local storage.
|
||||
this.setVolume(preferences.volume);
|
||||
this.setVolume(preferences.volume)
|
||||
|
||||
// Init the equalizer if supported.
|
||||
event.emit('equalizer:init', this.player.media);
|
||||
event.emit('equalizer:init', this.player.media)
|
||||
|
||||
this.initialized = true;
|
||||
this.initialized = true
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -103,66 +103,66 @@ export const playback = {
|
|||
*
|
||||
* @param {Object} song The song to play
|
||||
*/
|
||||
play(song) {
|
||||
play (song) {
|
||||
if (!song) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (queueStore.current) {
|
||||
queueStore.current.playbackState = 'stopped';
|
||||
queueStore.current.playbackState = 'stopped'
|
||||
}
|
||||
|
||||
song.playbackState = 'playing';
|
||||
song.playbackState = 'playing'
|
||||
|
||||
// Set the song as the current song
|
||||
queueStore.current = song;
|
||||
queueStore.current = song
|
||||
|
||||
// Add it into the "recent" list
|
||||
songStore.addRecent(song);
|
||||
songStore.addRecent(song)
|
||||
|
||||
// Manually set the `src` attribute of the audio to prevent plyr from resetting
|
||||
// the audio media object and cause our equalizer to malfunction.
|
||||
this.player.media.src = songStore.getSourceUrl(song);
|
||||
this.player.media.src = songStore.getSourceUrl(song)
|
||||
|
||||
$('title').text(`${song.title} ♫ ${config.appTitle}`);
|
||||
$('.plyr audio').attr('title', `${song.artist.name} - ${song.title}`);
|
||||
$('title').text(`${song.title} ♫ ${config.appTitle}`)
|
||||
$('.plyr audio').attr('title', `${song.artist.name} - ${song.title}`)
|
||||
|
||||
// We'll just "restart" playing the song, which will handle notification, scrobbling etc.
|
||||
this.restart();
|
||||
this.restart()
|
||||
},
|
||||
|
||||
/**
|
||||
* Restart playing a song.
|
||||
*/
|
||||
restart() {
|
||||
const song = queueStore.current;
|
||||
restart () {
|
||||
const song = queueStore.current
|
||||
|
||||
// Record the UNIX timestamp the song start playing, for scrobbling purpose
|
||||
song.playStartTime = Math.floor(Date.now() / 1000);
|
||||
song.playStartTime = Math.floor(Date.now() / 1000)
|
||||
|
||||
event.emit('song:played', song);
|
||||
event.emit('song:played', song)
|
||||
|
||||
this.player.restart();
|
||||
this.player.play();
|
||||
this.player.restart()
|
||||
this.player.play()
|
||||
|
||||
// Register the play to the server
|
||||
songStore.registerPlay(song);
|
||||
songStore.registerPlay(song)
|
||||
|
||||
// Show the notification if we're allowed to
|
||||
if (!window.Notification || !preferences.notify) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const notification = new Notification(`♫ ${song.title}`, {
|
||||
const notif = new window.Notification(`♫ ${song.title}`, {
|
||||
icon: song.album.cover,
|
||||
body: `${song.album.name} – ${song.artist.name}`
|
||||
});
|
||||
})
|
||||
|
||||
notification.onclick = () => window.focus();
|
||||
notif.onclick = () => window.focus()
|
||||
|
||||
// Close the notif after 5 secs.
|
||||
window.setTimeout(() => notification.close(), 5000);
|
||||
window.setTimeout(() => notif.close(), 5000)
|
||||
} catch (e) {
|
||||
// Notification fails.
|
||||
// @link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
|
||||
|
@ -175,15 +175,15 @@ export const playback = {
|
|||
*
|
||||
* @return {Object} The song
|
||||
*/
|
||||
get next() {
|
||||
const next = queueStore.next;
|
||||
get next () {
|
||||
const next = queueStore.next
|
||||
|
||||
if (next) {
|
||||
return next;
|
||||
return next
|
||||
}
|
||||
|
||||
if (preferences.repeatMode === 'REPEAT_ALL') {
|
||||
return queueStore.first;
|
||||
return queueStore.first
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -193,15 +193,15 @@ export const playback = {
|
|||
*
|
||||
* @return {Object} The song
|
||||
*/
|
||||
get previous() {
|
||||
const prev = queueStore.previous;
|
||||
get previous () {
|
||||
const prev = queueStore.previous
|
||||
|
||||
if (prev) {
|
||||
return prev;
|
||||
return prev
|
||||
}
|
||||
|
||||
if (preferences.repeatMode === 'REPEAT_ALL') {
|
||||
return queueStore.last;
|
||||
return queueStore.last
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -209,55 +209,55 @@ export const playback = {
|
|||
* Circle through the repeat mode.
|
||||
* The selected mode will be stored into local storage as well.
|
||||
*/
|
||||
changeRepeatMode() {
|
||||
let idx = this.repeatModes.indexOf(preferences.repeatMode) + 1;
|
||||
changeRepeatMode () {
|
||||
let idx = this.repeatModes.indexOf(preferences.repeatMode) + 1
|
||||
|
||||
if (idx >= this.repeatModes.length) {
|
||||
idx = 0;
|
||||
idx = 0
|
||||
}
|
||||
|
||||
preferences.repeatMode = this.repeatModes[idx];
|
||||
preferences.repeatMode = this.repeatModes[idx]
|
||||
},
|
||||
|
||||
/**
|
||||
* Play the prev song in the queue, if one is found.
|
||||
* If the prev song is not found and the current mode is NO_REPEAT, we stop completely.
|
||||
*/
|
||||
playPrev() {
|
||||
playPrev () {
|
||||
// If the song's duration is greater than 5 seconds and we've passed 5 seconds into it,
|
||||
// restart playing instead.
|
||||
if (this.player.media.currentTime > 5 && queueStore.current.length > 5) {
|
||||
this.player.restart();
|
||||
this.player.restart()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const prev = this.previous;
|
||||
const prev = this.previous
|
||||
|
||||
if (!prev && preferences.repeatMode === 'NO_REPEAT') {
|
||||
this.stop();
|
||||
this.stop()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.play(prev);
|
||||
this.play(prev)
|
||||
},
|
||||
|
||||
/**
|
||||
* Play the next song in the queue, if one is found.
|
||||
* If the next song is not found and the current mode is NO_REPEAT, we stop completely.
|
||||
*/
|
||||
playNext() {
|
||||
const next = this.next;
|
||||
playNext () {
|
||||
const next = this.next
|
||||
|
||||
if (!next && preferences.repeatMode === 'NO_REPEAT') {
|
||||
// Nothing lasts forever, even cold November rain.
|
||||
this.stop();
|
||||
this.stop()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.play(next);
|
||||
this.play(next)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -266,63 +266,63 @@ export const playback = {
|
|||
* @param {Number} volume 0-10
|
||||
* @param {Boolean=true} persist Whether the volume should be saved into local storage
|
||||
*/
|
||||
setVolume(volume, persist = true) {
|
||||
this.player.setVolume(volume);
|
||||
setVolume (volume, persist = true) {
|
||||
this.player.setVolume(volume)
|
||||
|
||||
if (persist) {
|
||||
preferences.volume = volume;
|
||||
preferences.volume = volume
|
||||
}
|
||||
|
||||
this.$volumeInput.val(volume);
|
||||
this.$volumeInput.val(volume)
|
||||
},
|
||||
|
||||
/**
|
||||
* Mute playback.
|
||||
*/
|
||||
mute() {
|
||||
this.setVolume(0, false);
|
||||
mute () {
|
||||
this.setVolume(0, false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmute playback.
|
||||
*/
|
||||
unmute() {
|
||||
unmute () {
|
||||
// If the saved volume is 0, we unmute to the default level (7).
|
||||
if (preferences.volume === '0' || preferences.volume === 0) {
|
||||
preferences.volume = 7;
|
||||
preferences.volume = 7
|
||||
}
|
||||
|
||||
this.setVolume(preferences.volume);
|
||||
this.setVolume(preferences.volume)
|
||||
},
|
||||
|
||||
/**
|
||||
* Completely stop playback.
|
||||
*/
|
||||
stop() {
|
||||
$('title').text(config.appTitle);
|
||||
this.player.pause();
|
||||
this.player.seek(0);
|
||||
stop () {
|
||||
$('title').text(config.appTitle)
|
||||
this.player.pause()
|
||||
this.player.seek(0)
|
||||
|
||||
if (queueStore.current) {
|
||||
queueStore.current.playbackState = 'stopped';
|
||||
queueStore.current.playbackState = 'stopped'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause playback.
|
||||
*/
|
||||
pause() {
|
||||
this.player.pause();
|
||||
queueStore.current.playbackState = 'paused';
|
||||
pause () {
|
||||
this.player.pause()
|
||||
queueStore.current.playbackState = 'paused'
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume playback.
|
||||
*/
|
||||
resume() {
|
||||
this.player.play();
|
||||
queueStore.current.playbackState = 'playing';
|
||||
event.emit('song:played', queueStore.current);
|
||||
resume () {
|
||||
this.player.play()
|
||||
queueStore.current.playbackState = 'playing'
|
||||
event.emit('song:played', queueStore.current)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -331,41 +331,41 @@ export const playback = {
|
|||
* @param {?Array.<Object>} songs An array of song objects. Defaults to all songs if null.
|
||||
* @param {Boolean=false} shuffled Whether to shuffle the songs before playing.
|
||||
*/
|
||||
queueAndPlay(songs = null, shuffled = false) {
|
||||
queueAndPlay (songs = null, shuffled = false) {
|
||||
if (!songs) {
|
||||
songs = songStore.all;
|
||||
songs = songStore.all
|
||||
}
|
||||
|
||||
if (!songs.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (shuffled) {
|
||||
songs = shuffle(songs);
|
||||
songs = shuffle(songs)
|
||||
}
|
||||
|
||||
queueStore.queue(songs, true);
|
||||
queueStore.queue(songs, true)
|
||||
|
||||
// Wrap this inside a nextTick() to wait for the DOM to complete updating
|
||||
// and then play the first song in the queue.
|
||||
Vue.nextTick(() => {
|
||||
router.go('queue');
|
||||
this.play(queueStore.first);
|
||||
});
|
||||
router.go('queue')
|
||||
this.play(queueStore.first)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Play the first song in the queue.
|
||||
* If the current queue is empty, try creating it by shuffling all songs.
|
||||
*/
|
||||
playFirstInQueue() {
|
||||
playFirstInQueue () {
|
||||
if (!queueStore.all.length) {
|
||||
this.queueAndPlay();
|
||||
this.queueAndPlay()
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
this.play(queueStore.first);
|
||||
this.play(queueStore.first)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -374,8 +374,8 @@ export const playback = {
|
|||
* @param {Object} artist The artist object
|
||||
* @param {Boolean=true} shuffled Whether to shuffle the songs
|
||||
*/
|
||||
playAllByArtist(artist, shuffled = true) {
|
||||
this.queueAndPlay(artist.songs, shuffled);
|
||||
playAllByArtist (artist, shuffled = true) {
|
||||
this.queueAndPlay(artist.songs, shuffled)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -384,13 +384,12 @@ export const playback = {
|
|||
* @param {Object} album The album object
|
||||
* @param {Boolean=true} shuffled Whether to shuffle the songs
|
||||
*/
|
||||
playAllInAlbum(album, shuffled = true) {
|
||||
playAllInAlbum (album, shuffled = true) {
|
||||
if (!shuffled) {
|
||||
this.queueAndPlay(orderBy(album.songs, 'track'));
|
||||
|
||||
return;
|
||||
this.queueAndPlay(orderBy(album.songs, 'track'))
|
||||
return
|
||||
}
|
||||
|
||||
this.queueAndPlay(album.songs, true);
|
||||
},
|
||||
};
|
||||
this.queueAndPlay(album.songs, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { http, playback } from '.';
|
||||
import { assign } from 'lodash';
|
||||
import { event, loadMainView } from '../utils';
|
||||
import router from '../router';
|
||||
import { http } from '.'
|
||||
import { event } from '../utils'
|
||||
import router from '../router'
|
||||
|
||||
export const youtube = {
|
||||
/**
|
||||
|
@ -10,17 +9,17 @@ export const youtube = {
|
|||
* @param {Object} song
|
||||
* @param {Function} cb
|
||||
*/
|
||||
searchVideosRelatedToSong(song, cb = null) {
|
||||
searchVideosRelatedToSong (song, cb = null) {
|
||||
if (!song.youtube) {
|
||||
song.youtube = {};
|
||||
song.youtube = {}
|
||||
}
|
||||
|
||||
const pageToken = song.youtube.nextPageToken || '';
|
||||
const pageToken = song.youtube.nextPageToken || ''
|
||||
http.get(`youtube/search/song/${song.id}?pageToken=${pageToken}`).then(data => {
|
||||
song.youtube.nextPageToken = data.nextPageToken;
|
||||
song.youtube.items.push(...data.items);
|
||||
cb && cb();
|
||||
});
|
||||
song.youtube.nextPageToken = data.nextPageToken
|
||||
song.youtube.items.push(...data.items)
|
||||
cb && cb()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -28,8 +27,8 @@ export const youtube = {
|
|||
*
|
||||
* @param {string} id The video ID
|
||||
*/
|
||||
play(id) {
|
||||
event.emit('youtube:play', id);
|
||||
router.go('youtube');
|
||||
},
|
||||
};
|
||||
play (id) {
|
||||
event.emit('youtube:play', id)
|
||||
router.go('youtube')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import { reduce, each, find, union, difference, take, filter, orderBy } from 'lodash';
|
||||
import Vue from 'vue'
|
||||
import { reduce, each, find, union, difference, take, filter, orderBy } from 'lodash'
|
||||
|
||||
import { secondsToHis } from '../utils';
|
||||
import stub from '../stubs/album';
|
||||
import { songStore, artistStore } from '.';
|
||||
import { secondsToHis } from '../utils'
|
||||
import stub from '../stubs/album'
|
||||
import { songStore, artistStore } from '.'
|
||||
|
||||
export const albumStore = {
|
||||
stub,
|
||||
|
||||
state: {
|
||||
albums: [stub],
|
||||
albums: [stub]
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -17,29 +17,29 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>} artists The array of artists to extract album data from.
|
||||
*/
|
||||
init(artists) {
|
||||
init (artists) {
|
||||
// Traverse through the artists array and add their albums into our master album list.
|
||||
this.all = reduce(artists, (albums, artist) => {
|
||||
// While we're doing so, for each album, we get its length
|
||||
// and keep a back reference to the artist too.
|
||||
each(artist.albums, album => {
|
||||
this.setupAlbum(album, artist);
|
||||
});
|
||||
this.setupAlbum(album, artist)
|
||||
})
|
||||
|
||||
return albums.concat(artist.albums);
|
||||
}, []);
|
||||
return albums.concat(artist.albums)
|
||||
}, [])
|
||||
|
||||
// Then we init the song store.
|
||||
songStore.init(this.all);
|
||||
songStore.init(this.all)
|
||||
},
|
||||
|
||||
setupAlbum(album, artist) {
|
||||
Vue.set(album, 'playCount', 0);
|
||||
Vue.set(album, 'artist', artist);
|
||||
Vue.set(album, 'info', null);
|
||||
this.getLength(album);
|
||||
setupAlbum (album, artist) {
|
||||
Vue.set(album, 'playCount', 0)
|
||||
Vue.set(album, 'artist', artist)
|
||||
Vue.set(album, 'info', null)
|
||||
this.getLength(album)
|
||||
|
||||
return album;
|
||||
return album
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -47,8 +47,8 @@ export const albumStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.albums;
|
||||
get all () {
|
||||
return this.state.albums
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -56,12 +56,12 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.albums = value;
|
||||
set all (value) {
|
||||
this.state.albums = value
|
||||
},
|
||||
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -72,11 +72,11 @@ export const albumStore = {
|
|||
*
|
||||
* @return {String} The H:i:s format of the album length.
|
||||
*/
|
||||
getLength(album) {
|
||||
Vue.set(album, 'length', reduce(album.songs, (length, song) => length + song.length, 0));
|
||||
Vue.set(album, 'fmtLength', secondsToHis(album.length));
|
||||
getLength (album) {
|
||||
Vue.set(album, 'length', reduce(album.songs, (length, song) => length + song.length, 0))
|
||||
Vue.set(album, 'fmtLength', secondsToHis(album.length))
|
||||
|
||||
return album.fmtLength;
|
||||
return album.fmtLength
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -84,14 +84,14 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
add(albums) {
|
||||
albums = [].concat(albums);
|
||||
add (albums) {
|
||||
albums = [].concat(albums)
|
||||
each(albums, a => {
|
||||
this.setupAlbum(a, a.artist)
|
||||
a.playCount = reduce(a.songs, (count, song) => count + song.playCount, 0);
|
||||
});
|
||||
a.playCount = reduce(a.songs, (count, song) => count + song.playCount, 0)
|
||||
})
|
||||
|
||||
this.all = union(this.all, albums);
|
||||
this.all = union(this.all, albums)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -100,18 +100,18 @@ export const albumStore = {
|
|||
* @param {Object} album
|
||||
* @param {Array.<Object>|Object} song
|
||||
*/
|
||||
addSongsIntoAlbum(album, songs) {
|
||||
songs = [].concat(songs);
|
||||
addSongsIntoAlbum (album, songs) {
|
||||
songs = [].concat(songs)
|
||||
|
||||
album.songs = union(album.songs ? album.songs : [], songs);
|
||||
album.songs = union(album.songs ? album.songs : [], songs)
|
||||
|
||||
each(songs, song => {
|
||||
song.album_id = album.id;
|
||||
song.album = album;
|
||||
});
|
||||
song.album_id = album.id
|
||||
song.album = album
|
||||
})
|
||||
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0);
|
||||
this.getLength(album);
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0)
|
||||
this.getLength(album)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -120,10 +120,10 @@ export const albumStore = {
|
|||
* @param {Object} album
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
removeSongsFromAlbum(album, songs) {
|
||||
album.songs = difference(album.songs, [].concat(songs));
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0);
|
||||
this.getLength(album);
|
||||
removeSongsFromAlbum (album, songs) {
|
||||
album.songs = difference(album.songs, [].concat(songs))
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0)
|
||||
this.getLength(album)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -133,8 +133,8 @@ export const albumStore = {
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAlbumEmpty(album) {
|
||||
return !album.songs.length;
|
||||
isAlbumEmpty (album) {
|
||||
return !album.songs.length
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -142,14 +142,14 @@ export const albumStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
remove(albums) {
|
||||
albums = [].concat(albums);
|
||||
this.all = difference(this.all, albums);
|
||||
remove (albums) {
|
||||
albums = [].concat(albums)
|
||||
this.all = difference(this.all, albums)
|
||||
|
||||
// Remove from the artist as well
|
||||
each(albums, album => {
|
||||
artistStore.removeAlbumsFromArtist(album.artist, album);
|
||||
});
|
||||
artistStore.removeAlbumsFromArtist(album.artist, album)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -159,13 +159,13 @@ export const albumStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getMostPlayed(n = 6) {
|
||||
getMostPlayed (n = 6) {
|
||||
// Only non-unknown albums with actually play count are applicable.
|
||||
const applicable = filter(this.all, album => {
|
||||
return album.playCount && album.id !== 1;
|
||||
});
|
||||
return album.playCount && album.id !== 1
|
||||
})
|
||||
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n);
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -175,9 +175,9 @@ export const albumStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getRecentlyAdded(n = 6) {
|
||||
const applicable = filter(this.all, album => album.id !== 1);
|
||||
getRecentlyAdded (n = 6) {
|
||||
const applicable = filter(this.all, album => album.id !== 1)
|
||||
|
||||
return take(orderBy(applicable, 'created_at', 'desc'), n);
|
||||
},
|
||||
};
|
||||
return take(orderBy(applicable, 'created_at', 'desc'), n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import Vue from 'vue';
|
||||
import { reduce, each, find, union, difference, take, filter, orderBy } from 'lodash';
|
||||
import Vue from 'vue'
|
||||
import { reduce, each, find, union, difference, take, filter, orderBy } from 'lodash'
|
||||
|
||||
import config from '../config';
|
||||
import stub from '../stubs/artist';
|
||||
import { albumStore } from '.';
|
||||
import config from '../config'
|
||||
import stub from '../stubs/artist'
|
||||
import { albumStore } from '.'
|
||||
|
||||
const UNKNOWN_ARTIST_ID = 1;
|
||||
const VARIOUS_ARTISTS_ID = 2;
|
||||
const UNKNOWN_ARTIST_ID = 1
|
||||
const VARIOUS_ARTISTS_ID = 2
|
||||
|
||||
export const artistStore = {
|
||||
stub,
|
||||
|
||||
state: {
|
||||
artists: [],
|
||||
artists: []
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -20,15 +20,15 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>} artists The array of artists we got from the server.
|
||||
*/
|
||||
init(artists) {
|
||||
this.all = artists;
|
||||
init (artists) {
|
||||
this.all = artists
|
||||
|
||||
albumStore.init(this.all);
|
||||
albumStore.init(this.all)
|
||||
|
||||
// Traverse through artists array to get the cover and number of songs for each.
|
||||
each(this.all, artist => {
|
||||
this.setupArtist(artist);
|
||||
});
|
||||
this.setupArtist(artist)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -36,9 +36,9 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Object} artist
|
||||
*/
|
||||
setupArtist(artist) {
|
||||
this.getImage(artist);
|
||||
Vue.set(artist, 'playCount', 0);
|
||||
setupArtist (artist) {
|
||||
this.getImage(artist)
|
||||
Vue.set(artist, 'playCount', 0)
|
||||
|
||||
// Here we build a list of songs performed by the artist, so that we don't need to traverse
|
||||
// down the "artist > albums > items" route later.
|
||||
|
@ -46,18 +46,18 @@ export const artistStore = {
|
|||
Vue.set(artist, 'songs', reduce(artist.albums, (songs, album) => {
|
||||
// If the album is compilation, we cater for the songs contributed by this artist only.
|
||||
if (album.is_compilation) {
|
||||
return songs.concat(filter(album.songs, { contributing_artist_id: artist.id }));
|
||||
return songs.concat(filter(album.songs, { contributing_artist_id: artist.id }))
|
||||
}
|
||||
|
||||
// Otherwise, just use all songs in the album.
|
||||
return songs.concat(album.songs);
|
||||
}, []));
|
||||
return songs.concat(album.songs)
|
||||
}, []))
|
||||
|
||||
Vue.set(artist, 'songCount', artist.songs.length);
|
||||
Vue.set(artist, 'songCount', artist.songs.length)
|
||||
|
||||
Vue.set(artist, 'info', null);
|
||||
Vue.set(artist, 'info', null)
|
||||
|
||||
return artist;
|
||||
return artist
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -65,8 +65,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.artists;
|
||||
get all () {
|
||||
return this.state.artists
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -74,8 +74,8 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.artists = value;
|
||||
set all (value) {
|
||||
this.state.artists = value
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -83,8 +83,8 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Number} id
|
||||
*/
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -92,11 +92,11 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} artists
|
||||
*/
|
||||
add(artists) {
|
||||
artists = [].concat(artists);
|
||||
each(artists, a => this.setupArtist(a));
|
||||
add (artists) {
|
||||
artists = [].concat(artists)
|
||||
each(artists, a => this.setupArtist(a))
|
||||
|
||||
this.all = union(this.all, artists);
|
||||
this.all = union(this.all, artists)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -104,8 +104,8 @@ export const artistStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} artists
|
||||
*/
|
||||
remove(artists) {
|
||||
this.all = difference(this.all, [].concat(artists));
|
||||
remove (artists) {
|
||||
this.all = difference(this.all, [].concat(artists))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -115,16 +115,16 @@ export const artistStore = {
|
|||
* @param {Array.<Object>|Object} albums
|
||||
*
|
||||
*/
|
||||
addAlbumsIntoArtist(artist, albums) {
|
||||
albums = [].concat(albums);
|
||||
addAlbumsIntoArtist (artist, albums) {
|
||||
albums = [].concat(albums)
|
||||
|
||||
artist.albums = union(artist.albums ? artist.albums : [], albums);
|
||||
artist.albums = union(artist.albums ? artist.albums : [], albums)
|
||||
|
||||
each(albums, album => {
|
||||
album.artist_id = artist.id;
|
||||
album.artist = artist;
|
||||
artist.playCount += album.playCount;
|
||||
});
|
||||
album.artist_id = artist.id
|
||||
album.artist = artist
|
||||
artist.playCount += album.playCount
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -133,10 +133,12 @@ export const artistStore = {
|
|||
* @param {Object} artist
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
removeAlbumsFromArtist(artist, albums) {
|
||||
albums = [].concat(albums);
|
||||
artist.albums = difference(artist.albums, albums);
|
||||
each(albums, album => artist.playCount -= album.playCount);
|
||||
removeAlbumsFromArtist (artist, albums) {
|
||||
albums = [].concat(albums)
|
||||
artist.albums = difference(artist.albums, albums)
|
||||
each(albums, album => {
|
||||
artist.playCount -= album.playCount
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -146,8 +148,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isArtistEmpty(artist) {
|
||||
return !artist.albums.length;
|
||||
isArtistEmpty (artist) {
|
||||
return !artist.albums.length
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -157,8 +159,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isVariousArtists(artist) {
|
||||
return artist.id === VARIOUS_ARTISTS_ID;
|
||||
isVariousArtists (artist) {
|
||||
return artist.id === VARIOUS_ARTISTS_ID
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -168,8 +170,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isUnknownArtist(artist) {
|
||||
return artist.id === UNKNOWN_ARTIST_ID;
|
||||
isUnknownArtist (artist) {
|
||||
return artist.id === UNKNOWN_ARTIST_ID
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -179,8 +181,8 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getSongsByArtist(artist) {
|
||||
return artist.songs;
|
||||
getSongsByArtist (artist) {
|
||||
return artist.songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -190,23 +192,23 @@ export const artistStore = {
|
|||
*
|
||||
* @return {String}
|
||||
*/
|
||||
getImage(artist) {
|
||||
getImage (artist) {
|
||||
if (!artist.image) {
|
||||
// Try to get an image from one of the albums.
|
||||
artist.image = config.unknownCover;
|
||||
artist.image = config.unknownCover
|
||||
|
||||
artist.albums.every(album => {
|
||||
// If there's a "real" cover, use it.
|
||||
if (album.image !== config.unknownCover) {
|
||||
artist.image = album.cover;
|
||||
artist.image = album.cover
|
||||
|
||||
// I want to break free.
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return artist.image;
|
||||
return artist.image
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -216,15 +218,15 @@ export const artistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getMostPlayed(n = 6) {
|
||||
getMostPlayed (n = 6) {
|
||||
// Only non-unknown artists with actually play count are applicable.
|
||||
// Also, "Various Artists" doesn't count.
|
||||
const applicable = filter(this.all, artist => {
|
||||
return artist.playCount
|
||||
&& !this.isUnknownArtist(artist)
|
||||
&& !this.isVariousArtists(artist);
|
||||
});
|
||||
return artist.playCount &&
|
||||
!this.isUnknownArtist(artist) &&
|
||||
!this.isVariousArtists(artist)
|
||||
})
|
||||
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n);
|
||||
},
|
||||
};
|
||||
return take(orderBy(applicable, 'playCount', 'desc'), n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { find } from 'lodash';
|
||||
import { find } from 'lodash'
|
||||
|
||||
import { preferenceStore } from '.';
|
||||
import { preferenceStore } from '.'
|
||||
|
||||
export const equalizerStore = {
|
||||
presets: [
|
||||
|
@ -8,25 +8,25 @@ export const equalizerStore = {
|
|||
id: 0,
|
||||
name: 'Default',
|
||||
preamp: 0,
|
||||
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Classical',
|
||||
preamp: -1,
|
||||
gains: [-1, -1, -1, -1, -1, -1, -7, -7, -7, -9],
|
||||
gains: [-1, -1, -1, -1, -1, -1, -7, -7, -7, -9]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Club',
|
||||
preamp: -6.7,
|
||||
gains: [-1, -1, 8, 5, 5, 5, 3, -1, -1, -1],
|
||||
gains: [-1, -1, 8, 5, 5, 5, 3, -1, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Dance',
|
||||
preamp: -4.3,
|
||||
gains: [9, 7, 2, -1, -1, -5, -7, -7, -1, -1],
|
||||
gains: [9, 7, 2, -1, -1, -5, -7, -7, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
|
@ -50,48 +50,48 @@ export const equalizerStore = {
|
|||
id: 7,
|
||||
name: 'Large Hall',
|
||||
preamp: -7.2,
|
||||
gains: [10, 10, 5, 5, -1, -4, -4, -4, -1, -1],
|
||||
gains: [10, 10, 5, 5, -1, -4, -4, -4, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Live',
|
||||
preamp: -5.3,
|
||||
gains: [-4, -1, 4, 5, 5, 5, 4, 2, 2, 2],
|
||||
gains: [-4, -1, 4, 5, 5, 5, 4, 2, 2, 2]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'Pop',
|
||||
preamp: -6.2,
|
||||
gains: [-1, 4, 7, 8, 5, -1, -2, -2, -1, -1],
|
||||
gains: [-1, 4, 7, 8, 5, -1, -2, -2, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Reggae',
|
||||
preamp: -8.2,
|
||||
gains: [-1, -1, -1, -5, -1, 6, 6, -1, -1, -1],
|
||||
gains: [-1, -1, -1, -5, -1, 6, 6, -1, -1, -1]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Rock',
|
||||
preamp: -10,
|
||||
gains: [8, 4, -5, -8, -3, 4, 8, 11, 11, 11],
|
||||
gains: [8, 4, -5, -8, -3, 4, 8, 11, 11, 11]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'Soft Rock',
|
||||
preamp: -5.3,
|
||||
gains: [4, 4, 2, -1, -4, -5, -3, -1, 2, 8],
|
||||
gains: [4, 4, 2, -1, -4, -5, -3, -1, 2, 8]
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'Techno',
|
||||
preamp: -7.7,
|
||||
gains: [8, 5, -1, -5, -4, -1, 8, 9, 9, 8],
|
||||
},
|
||||
gains: [8, 5, -1, -5, -4, -1, 8, 9, 9, 8]
|
||||
}
|
||||
],
|
||||
|
||||
getPresetById(id) {
|
||||
return find(this.presets, { id });
|
||||
getPresetById (id) {
|
||||
return find(this.presets, { id })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -99,9 +99,9 @@ export const equalizerStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get() {
|
||||
get () {
|
||||
if (!this.presets[preferenceStore.selectedPreset]) {
|
||||
return preferenceStore.equalizer;
|
||||
return preferenceStore.equalizer
|
||||
}
|
||||
|
||||
// If the user chose a preset (instead of customizing one), just return it.
|
||||
|
@ -114,7 +114,7 @@ export const equalizerStore = {
|
|||
* @param {Number} preamp The preamp value (dB)
|
||||
* @param {Array.<Number>} gains The band's gain value (dB)
|
||||
*/
|
||||
set(preamp, gains) {
|
||||
preferenceStore.equalizer = { preamp, gains };
|
||||
},
|
||||
};
|
||||
set (preamp, gains) {
|
||||
preferenceStore.equalizer = { preamp, gains }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { each, map, difference, union } from 'lodash';
|
||||
import { each, map, difference, union } from 'lodash'
|
||||
|
||||
import { http } from '../services';
|
||||
import { http } from '../services'
|
||||
|
||||
export const favoriteStore = {
|
||||
state: {
|
||||
songs: [],
|
||||
length: 0,
|
||||
fmtLength: '',
|
||||
fmtLength: ''
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,8 @@ export const favoriteStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
get all () {
|
||||
return this.state.songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -23,8 +23,8 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.songs = value;
|
||||
set all (value) {
|
||||
this.state.songs = value
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -33,15 +33,15 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
toggleOne(song) {
|
||||
toggleOne (song) {
|
||||
// Don't wait for the HTTP response to update the status, just toggle right away.
|
||||
// This may cause a minor problem if the request fails somehow, but do we care?
|
||||
song.liked = !song.liked;
|
||||
song.liked ? this.add(song) : this.remove(song);
|
||||
song.liked = !song.liked
|
||||
song.liked ? this.add(song) : this.remove(song)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/like', { song: song.id }, data => resolve(data), r => reject(r));
|
||||
});
|
||||
http.post('interaction/like', { song: song.id }, data => resolve(data), r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -49,8 +49,8 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
add(songs) {
|
||||
this.all = union(this.all, [].concat(songs));
|
||||
add (songs) {
|
||||
this.all = union(this.all, [].concat(songs))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -58,15 +58,15 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
remove(songs) {
|
||||
this.all = difference(this.all, [].concat(songs));
|
||||
remove (songs) {
|
||||
this.all = difference(this.all, [].concat(songs))
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all favorites.
|
||||
*/
|
||||
clear() {
|
||||
this.all = [];
|
||||
clear () {
|
||||
this.all = []
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -74,15 +74,17 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
like(songs) {
|
||||
like (songs) {
|
||||
// Don't wait for the HTTP response to update the status, just set them to Liked right away.
|
||||
// This may cause a minor problem if the request fails somehow, but do we care?
|
||||
each(songs, song => song.liked = true);
|
||||
this.add(songs);
|
||||
each(songs, song => {
|
||||
song.liked = true
|
||||
})
|
||||
this.add(songs)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/like', { songs: map(songs, 'id') }, data => resolve(data), r => reject(r));
|
||||
});
|
||||
http.post('interaction/batch/like', { songs: map(songs, 'id') }, data => resolve(data), r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -90,12 +92,14 @@ export const favoriteStore = {
|
|||
*
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
unlike(songs) {
|
||||
each(songs, song => song.liked = false);
|
||||
this.remove(songs);
|
||||
unlike (songs) {
|
||||
each(songs, song => {
|
||||
song.liked = false
|
||||
})
|
||||
this.remove(songs)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, data => resolve(data), r => reject(r));
|
||||
});
|
||||
},
|
||||
};
|
||||
http.post('interaction/batch/unlike', { songs: map(songs, 'id') }, data => resolve(data), r => reject(r))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
export * from './album';
|
||||
export * from './artist';
|
||||
export * from './equalizer';
|
||||
export * from './favorite';
|
||||
export * from './playlist';
|
||||
export * from './preference';
|
||||
export * from './queue';
|
||||
export * from './setting';
|
||||
export * from './shared';
|
||||
export * from './song';
|
||||
export * from './user';
|
||||
export * from './album'
|
||||
export * from './artist'
|
||||
export * from './equalizer'
|
||||
export * from './favorite'
|
||||
export * from './playlist'
|
||||
export * from './preference'
|
||||
export * from './queue'
|
||||
export * from './setting'
|
||||
export * from './shared'
|
||||
export * from './song'
|
||||
export * from './user'
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { each, find, map, difference, union, without } from 'lodash';
|
||||
import NProgress from 'nprogress';
|
||||
import { each, find, map, difference, union } from 'lodash'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
import stub from '../stubs/playlist';
|
||||
import { http } from '../services';
|
||||
import { songStore } from '.';
|
||||
import stub from '../stubs/playlist'
|
||||
import { http } from '../services'
|
||||
import { songStore } from '.'
|
||||
|
||||
export const playlistStore = {
|
||||
stub,
|
||||
|
||||
state: {
|
||||
playlists: [],
|
||||
playlists: []
|
||||
},
|
||||
|
||||
init(playlists) {
|
||||
this.all = playlists;
|
||||
each(this.all, this.objectifySongs);
|
||||
init (playlists) {
|
||||
this.all = playlists
|
||||
each(this.all, this.objectifySongs)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -22,8 +22,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.playlists;
|
||||
get all () {
|
||||
return this.state.playlists
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -31,8 +31,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.playlists = value;
|
||||
set all (value) {
|
||||
this.state.playlists = value
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -42,8 +42,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -52,8 +52,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
objectifySongs(playlist) {
|
||||
playlist.songs = songStore.byIds(playlist.songs);
|
||||
objectifySongs (playlist) {
|
||||
playlist.songs = songStore.byIds(playlist.songs)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -63,8 +63,8 @@ export const playlistStore = {
|
|||
*
|
||||
* return {Array.<Object>}
|
||||
*/
|
||||
getSongs(playlist) {
|
||||
return playlist.songs;
|
||||
getSongs (playlist) {
|
||||
return playlist.songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -72,8 +72,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} playlists
|
||||
*/
|
||||
add(playlists) {
|
||||
this.all = union(this.all, [].concat(playlists));
|
||||
add (playlists) {
|
||||
this.all = union(this.all, [].concat(playlists))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -81,8 +81,8 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} playlist
|
||||
*/
|
||||
remove(playlists) {
|
||||
this.all = difference(this.all, [].concat(playlists));
|
||||
remove (playlists) {
|
||||
this.all = difference(this.all, [].concat(playlists))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -91,22 +91,22 @@ export const playlistStore = {
|
|||
* @param {String} name Name of the playlist
|
||||
* @param {Array.<Object>} songs An array of song objects
|
||||
*/
|
||||
store(name, songs = []) {
|
||||
store (name, songs = []) {
|
||||
if (songs.length) {
|
||||
// Extract the IDs from the song objects.
|
||||
songs = map(songs, 'id');
|
||||
songs = map(songs, 'id')
|
||||
}
|
||||
|
||||
NProgress.start();
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('playlist', { name, songs }, playlist => {
|
||||
playlist.songs = songs;
|
||||
this.objectifySongs(playlist);
|
||||
this.add(playlist);
|
||||
resolve(playlist);
|
||||
}, r => reject(r));
|
||||
});
|
||||
playlist.songs = songs
|
||||
this.objectifySongs(playlist)
|
||||
this.add(playlist)
|
||||
resolve(playlist)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -114,15 +114,15 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
delete(playlist) {
|
||||
NProgress.start();
|
||||
delete (playlist) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(`playlist/${playlist.id}`, {}, data => {
|
||||
this.remove(playlist);
|
||||
resolve(data);
|
||||
}, r => reject(r));
|
||||
});
|
||||
this.remove(playlist)
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -131,20 +131,20 @@ export const playlistStore = {
|
|||
* @param {Object} playlist
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
addSongs(playlist, songs) {
|
||||
addSongs (playlist, songs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const count = playlist.songs.length;
|
||||
playlist.songs = union(playlist.songs, songs);
|
||||
const count = playlist.songs.length
|
||||
playlist.songs = union(playlist.songs, songs)
|
||||
|
||||
if (count === playlist.songs.length) {
|
||||
resolve(playlist);
|
||||
return;
|
||||
resolve(playlist)
|
||||
return
|
||||
}
|
||||
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') },
|
||||
data => resolve(playlist),
|
||||
r => reject(r)
|
||||
);
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -154,14 +154,14 @@ export const playlistStore = {
|
|||
* @param {Object} playlist
|
||||
* @param {Array.<Object>} songs
|
||||
*/
|
||||
removeSongs(playlist, songs) {
|
||||
playlist.songs = difference(playlist.songs, songs);
|
||||
removeSongs (playlist, songs) {
|
||||
playlist.songs = difference(playlist.songs, songs)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`playlist/${playlist.id}/sync`, { songs: map(playlist.songs, 'id') },
|
||||
data => resolve(playlist),
|
||||
r => reject(r)
|
||||
);
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -170,11 +170,11 @@ export const playlistStore = {
|
|||
*
|
||||
* @param {Object} playlist
|
||||
*/
|
||||
update(playlist) {
|
||||
NProgress.start();
|
||||
update (playlist) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`playlist/${playlist.id}`, { name: playlist.name }, data => resolve(playlist), r => reject(r));
|
||||
});
|
||||
},
|
||||
};
|
||||
http.put(`playlist/${playlist.id}`, { name: playlist.name }, data => resolve(playlist), r => reject(r))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { extend, has, each } from 'lodash';
|
||||
import { extend, has, each } from 'lodash'
|
||||
|
||||
import { userStore } from '.';
|
||||
import { ls } from '../services';
|
||||
import { userStore } from '.'
|
||||
import { ls } from '../services'
|
||||
|
||||
export const preferenceStore = {
|
||||
storeKey: '',
|
||||
|
@ -14,11 +14,11 @@ export const preferenceStore = {
|
|||
confirmClosing: false,
|
||||
equalizer: {
|
||||
preamp: 0,
|
||||
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
artistsViewMode: null,
|
||||
albumsViewMode: null,
|
||||
selectedPreset: -1,
|
||||
selectedPreset: -1
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -26,42 +26,42 @@ export const preferenceStore = {
|
|||
*
|
||||
* @param {Object} user The user whose preferences we are managing.
|
||||
*/
|
||||
init(user = null) {
|
||||
init (user = null) {
|
||||
if (!user) {
|
||||
user = userStore.current;
|
||||
user = userStore.current
|
||||
}
|
||||
|
||||
this.storeKey = `preferences_${user.id}`;
|
||||
extend(this.state, ls.get(this.storeKey, this.state));
|
||||
this.setupProxy();
|
||||
this.storeKey = `preferences_${user.id}`
|
||||
extend(this.state, ls.get(this.storeKey, this.state))
|
||||
this.setupProxy()
|
||||
},
|
||||
|
||||
/**
|
||||
* Proxy the state properties, so that each can be directly accessed using the key.
|
||||
*/
|
||||
setupProxy() {
|
||||
setupProxy () {
|
||||
each(Object.keys(this.state), key => {
|
||||
Object.defineProperty(this, key, {
|
||||
get: () => this.state[key],
|
||||
set: (value) => {
|
||||
this.state[key] = value;
|
||||
this.save();
|
||||
this.state[key] = value
|
||||
this.save()
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
set(key, val) {
|
||||
this.state[key] = val;
|
||||
this.save();
|
||||
set (key, val) {
|
||||
this.state[key] = val
|
||||
this.save()
|
||||
},
|
||||
|
||||
get(key) {
|
||||
return has(this.state, key) ? this.state[key] : null;
|
||||
get (key) {
|
||||
return has(this.state, key) ? this.state[key] : null
|
||||
},
|
||||
|
||||
save() {
|
||||
ls.set(this.storeKey, this.state);
|
||||
},
|
||||
};
|
||||
save () {
|
||||
ls.set(this.storeKey, this.state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { head, last, each, includes, union, difference, map, shuffle as _shuffle, first } from 'lodash';
|
||||
import { head, last, each, includes, union, difference, map, shuffle as _shuffle, first } from 'lodash'
|
||||
|
||||
export const queueStore = {
|
||||
state: {
|
||||
songs: [],
|
||||
current: null,
|
||||
current: null
|
||||
},
|
||||
|
||||
init() {
|
||||
init () {
|
||||
// We don't have anything to do here yet.
|
||||
// How about another song then?
|
||||
//
|
||||
|
@ -36,8 +36,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
get all () {
|
||||
return this.state.songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -45,8 +45,8 @@ export const queueStore = {
|
|||
*
|
||||
* @param {Array.<Object>}
|
||||
*/
|
||||
set all(songs) {
|
||||
this.state.songs = songs;
|
||||
set all (songs) {
|
||||
this.state.songs = songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -54,8 +54,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get first() {
|
||||
return head(this.all);
|
||||
get first () {
|
||||
return head(this.all)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -63,8 +63,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get last() {
|
||||
return last(this.all);
|
||||
get last () {
|
||||
return last(this.all)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -74,8 +74,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
contains(song) {
|
||||
return includes(this.all, song);
|
||||
contains (song) {
|
||||
return includes(this.all, song)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -86,13 +86,13 @@ export const queueStore = {
|
|||
* @param {Boolean} replace Whether to replace the current queue
|
||||
* @param {Boolean} toTop Whether to prepend or append to the queue
|
||||
*/
|
||||
queue(songs, replace = false, toTop = false) {
|
||||
songs = [].concat(songs);
|
||||
queue (songs, replace = false, toTop = false) {
|
||||
songs = [].concat(songs)
|
||||
|
||||
if (replace) {
|
||||
this.all = songs;
|
||||
this.all = songs
|
||||
} else {
|
||||
this.all = toTop ? union(songs, this.all) : union(this.all, songs);
|
||||
this.all = toTop ? union(songs, this.all) : union(this.all, songs)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -101,18 +101,18 @@ export const queueStore = {
|
|||
*
|
||||
* @param {Array.<Object>|Object} songs
|
||||
*/
|
||||
queueAfterCurrent(songs) {
|
||||
songs = [].concat(songs);
|
||||
queueAfterCurrent (songs) {
|
||||
songs = [].concat(songs)
|
||||
|
||||
if (!this.current || !this.all.length) {
|
||||
return this.queue(songs);
|
||||
return this.queue(songs)
|
||||
}
|
||||
|
||||
// First we unqueue the songs to make sure there are no duplicates.
|
||||
this.unqueue(songs);
|
||||
this.unqueue(songs)
|
||||
|
||||
const head = this.all.splice(0, this.indexOf(this.current) + 1);
|
||||
this.all = head.concat(songs, this.all);
|
||||
const head = this.all.splice(0, this.indexOf(this.current) + 1)
|
||||
this.all = head.concat(songs, this.all)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -120,8 +120,8 @@ export const queueStore = {
|
|||
*
|
||||
* @param {Object|String|Array.<Object>} songs The song(s) to unqueue
|
||||
*/
|
||||
unqueue(songs) {
|
||||
this.all = difference(this.all, [].concat(songs));
|
||||
unqueue (songs) {
|
||||
this.all = difference(this.all, [].concat(songs))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -130,21 +130,21 @@ export const queueStore = {
|
|||
* @param {Array.<Object>} songs Songs to move
|
||||
* @param {Object} target The target song object
|
||||
*/
|
||||
move(songs, target) {
|
||||
const $targetIndex = this.indexOf(target);
|
||||
move (songs, target) {
|
||||
const $targetIndex = this.indexOf(target)
|
||||
|
||||
each(songs, song => {
|
||||
this.all.splice(this.indexOf(song), 1);
|
||||
this.all.splice($targetIndex, 0, song);
|
||||
});
|
||||
this.all.splice(this.indexOf(song), 1)
|
||||
this.all.splice($targetIndex, 0, song)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the current queue.
|
||||
*/
|
||||
clear() {
|
||||
this.all = [];
|
||||
this.current = null;
|
||||
clear () {
|
||||
this.all = []
|
||||
this.current = null
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -154,8 +154,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Integer}
|
||||
*/
|
||||
indexOf(song) {
|
||||
return this.all.indexOf(song);
|
||||
indexOf (song) {
|
||||
return this.all.indexOf(song)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -163,14 +163,14 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get next() {
|
||||
get next () {
|
||||
if (!this.current) {
|
||||
return first(this.all);
|
||||
return first(this.all)
|
||||
}
|
||||
|
||||
const idx = map(this.all, 'id').indexOf(this.current.id) + 1;
|
||||
const idx = map(this.all, 'id').indexOf(this.current.id) + 1
|
||||
|
||||
return idx >= this.all.length ? null : this.all[idx];
|
||||
return idx >= this.all.length ? null : this.all[idx]
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -178,14 +178,14 @@ export const queueStore = {
|
|||
*
|
||||
* @return {?Object}
|
||||
*/
|
||||
get previous() {
|
||||
get previous () {
|
||||
if (!this.current) {
|
||||
return last(this.all);
|
||||
return last(this.all)
|
||||
}
|
||||
|
||||
const idx = map(this.all, 'id').indexOf(this.current.id) - 1;
|
||||
const idx = map(this.all, 'id').indexOf(this.current.id) - 1
|
||||
|
||||
return idx < 0 ? null : this.all[idx];
|
||||
return idx < 0 ? null : this.all[idx]
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -193,8 +193,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get current() {
|
||||
return this.state.current;
|
||||
get current () {
|
||||
return this.state.current
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -204,8 +204,9 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Object} The queued song.
|
||||
*/
|
||||
set current(song) {
|
||||
return this.state.current = song;
|
||||
set current (song) {
|
||||
this.state.current = song
|
||||
return this.state.current
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -213,7 +214,8 @@ export const queueStore = {
|
|||
*
|
||||
* @return {Array.<Object>} The shuffled array of song objects
|
||||
*/
|
||||
shuffle() {
|
||||
return this.all = _shuffle(this.all);
|
||||
},
|
||||
};
|
||||
shuffle () {
|
||||
this.all = _shuffle(this.all)
|
||||
return this.all
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { http } from '../services';
|
||||
import stub from '../stubs/settings';
|
||||
import { http } from '../services'
|
||||
import stub from '../stubs/settings'
|
||||
|
||||
export const settingStore = {
|
||||
stub,
|
||||
|
||||
state: {
|
||||
settings: [],
|
||||
settings: []
|
||||
},
|
||||
|
||||
init(settings) {
|
||||
this.state.settings = settings;
|
||||
init (settings) {
|
||||
this.state.settings = settings
|
||||
},
|
||||
|
||||
get all() {
|
||||
return this.state.settings;
|
||||
get all () {
|
||||
return this.state.settings
|
||||
},
|
||||
|
||||
update() {
|
||||
update () {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('settings', this.all, data => resolve(data), r => reject(r));
|
||||
});
|
||||
},
|
||||
};
|
||||
http.post('settings', this.all, data => resolve(data), r => reject(r))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { assign } from 'lodash';
|
||||
import isMobile from 'ismobilejs';
|
||||
import { assign } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { http } from '../services';
|
||||
import { userStore, preferenceStore, artistStore, songStore, playlistStore, queueStore, settingStore } from '.';
|
||||
import { http } from '../services'
|
||||
import { userStore, preferenceStore, artistStore, songStore, playlistStore, queueStore, settingStore } from '.'
|
||||
|
||||
export const sharedStore = {
|
||||
state: {
|
||||
|
@ -22,58 +22,58 @@ export const sharedStore = {
|
|||
currentVersion: '',
|
||||
latestVersion: '',
|
||||
cdnUrl: '',
|
||||
originalMediaPath: '',
|
||||
originalMediaPath: ''
|
||||
},
|
||||
|
||||
init() {
|
||||
this.reset();
|
||||
init () {
|
||||
this.reset()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('data', data => {
|
||||
// Don't allow downloading on mobile devices
|
||||
data.allowDownload = data.allowDownload && !isMobile.any;
|
||||
data.allowDownload = data.allowDownload && !isMobile.any
|
||||
|
||||
assign(this.state, data);
|
||||
assign(this.state, data)
|
||||
|
||||
// Always disable YouTube integration on mobile.
|
||||
this.state.useYouTube = this.state.useYouTube && !isMobile.phone;
|
||||
this.state.useYouTube = this.state.useYouTube && !isMobile.phone
|
||||
|
||||
// If this is a new user, initialize his preferences to be an empty object.
|
||||
if (!this.state.currentUser.preferences) {
|
||||
this.state.currentUser.preferences = {};
|
||||
this.state.currentUser.preferences = {}
|
||||
}
|
||||
|
||||
userStore.init(this.state.users, this.state.currentUser);
|
||||
preferenceStore.init(this.state.preferences);
|
||||
artistStore.init(this.state.artists); // This will init album and song stores as well.
|
||||
songStore.initInteractions(this.state.interactions);
|
||||
playlistStore.init(this.state.playlists);
|
||||
queueStore.init();
|
||||
settingStore.init(this.state.settings);
|
||||
userStore.init(this.state.users, this.state.currentUser)
|
||||
preferenceStore.init(this.state.preferences)
|
||||
artistStore.init(this.state.artists) // This will init album and song stores as well.
|
||||
songStore.initInteractions(this.state.interactions)
|
||||
playlistStore.init(this.state.playlists)
|
||||
queueStore.init()
|
||||
settingStore.init(this.state.settings)
|
||||
|
||||
// Keep a copy of the media path. We'll need this to properly warn the user later.
|
||||
this.state.originalMediaPath = this.state.settings.media_path;
|
||||
this.state.originalMediaPath = this.state.settings.media_path
|
||||
|
||||
resolve(data)
|
||||
}, r => reject(r));
|
||||
});
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.state.songs = [];
|
||||
this.state.albums = [];
|
||||
this.state.artists = [];
|
||||
this.state.favorites = [];
|
||||
this.state.queued = [];
|
||||
this.state.interactions = [];
|
||||
this.state.users = [];
|
||||
this.state.settings = [];
|
||||
this.state.currentUser = null;
|
||||
this.state.playlists = [];
|
||||
this.state.useLastfm = false;
|
||||
this.state.allowDownload = false;
|
||||
this.state.currentVersion = '';
|
||||
this.state.latestVersion = '';
|
||||
this.state.cdnUrl = '';
|
||||
},
|
||||
};
|
||||
reset () {
|
||||
this.state.songs = []
|
||||
this.state.albums = []
|
||||
this.state.artists = []
|
||||
this.state.favorites = []
|
||||
this.state.queued = []
|
||||
this.state.interactions = []
|
||||
this.state.users = []
|
||||
this.state.settings = []
|
||||
this.state.currentUser = null
|
||||
this.state.playlists = []
|
||||
this.state.useLastfm = false
|
||||
this.state.allowDownload = false
|
||||
this.state.currentVersion = ''
|
||||
this.state.latestVersion = ''
|
||||
this.state.cdnUrl = ''
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import { without, map, take, remove, orderBy, each, union } from 'lodash';
|
||||
import Vue from 'vue'
|
||||
import { without, map, take, remove, orderBy, each, union } from 'lodash'
|
||||
|
||||
import { secondsToHis } from '../utils';
|
||||
import { http, ls } from '../services';
|
||||
import { sharedStore, favoriteStore, userStore, albumStore, artistStore } from '.';
|
||||
import stub from '../stubs/song';
|
||||
import { secondsToHis } from '../utils'
|
||||
import { http, ls } from '../services'
|
||||
import { sharedStore, favoriteStore, albumStore, artistStore } from '.'
|
||||
import stub from '../stubs/song'
|
||||
|
||||
export const songStore = {
|
||||
stub,
|
||||
|
@ -24,7 +24,7 @@ export const songStore = {
|
|||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
recent: [],
|
||||
recent: []
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -32,39 +32,39 @@ export const songStore = {
|
|||
*
|
||||
* @param {Array.<Object>} albums The array of albums to extract our songs from
|
||||
*/
|
||||
init(albums) {
|
||||
init (albums) {
|
||||
// Iterate through the albums. With each, add its songs into our master song list.
|
||||
// While doing so, we populate some other information into the songs as well.
|
||||
this.all = albums.reduce((songs, album) => {
|
||||
each(album.songs, song => {
|
||||
this.setupSong(song, album);
|
||||
});
|
||||
this.setupSong(song, album)
|
||||
})
|
||||
|
||||
return songs.concat(album.songs);
|
||||
}, []);
|
||||
return songs.concat(album.songs)
|
||||
}, [])
|
||||
},
|
||||
|
||||
setupSong(song, album) {
|
||||
song.fmtLength = secondsToHis(song.length);
|
||||
setupSong (song, album) {
|
||||
song.fmtLength = secondsToHis(song.length)
|
||||
|
||||
// Manually set these additional properties to be reactive
|
||||
Vue.set(song, 'playCount', 0);
|
||||
Vue.set(song, 'album', album);
|
||||
Vue.set(song, 'liked', false);
|
||||
Vue.set(song, 'lyrics', null);
|
||||
Vue.set(song, 'playbackState', 'stopped');
|
||||
Vue.set(song, 'playCount', 0)
|
||||
Vue.set(song, 'album', album)
|
||||
Vue.set(song, 'liked', false)
|
||||
Vue.set(song, 'lyrics', null)
|
||||
Vue.set(song, 'playbackState', 'stopped')
|
||||
|
||||
if (song.contributing_artist_id) {
|
||||
const artist = artistStore.byId(song.contributing_artist_id);
|
||||
artist.albums = union(artist.albums, [album]);
|
||||
artistStore.setupArtist(artist);
|
||||
Vue.set(song, 'artist', artist);
|
||||
const artist = artistStore.byId(song.contributing_artist_id)
|
||||
artist.albums = union(artist.albums, [album])
|
||||
artistStore.setupArtist(artist)
|
||||
Vue.set(song, 'artist', artist)
|
||||
} else {
|
||||
Vue.set(song, 'artist', artistStore.byId(song.album.artist.id));
|
||||
Vue.set(song, 'artist', artistStore.byId(song.album.artist.id))
|
||||
}
|
||||
|
||||
// Cache the song, so that byId() is faster
|
||||
this.cache[song.id] = song;
|
||||
this.cache[song.id] = song
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -72,25 +72,25 @@ export const songStore = {
|
|||
*
|
||||
* @param {Array.<Object>} interactions The array of interactions of the current user
|
||||
*/
|
||||
initInteractions(interactions) {
|
||||
favoriteStore.clear();
|
||||
initInteractions (interactions) {
|
||||
favoriteStore.clear()
|
||||
|
||||
each(interactions, interaction => {
|
||||
const song = this.byId(interaction.song_id);
|
||||
const song = this.byId(interaction.song_id)
|
||||
|
||||
if (!song) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
song.liked = interaction.liked;
|
||||
song.playCount = interaction.play_count;
|
||||
song.album.playCount += song.playCount;
|
||||
song.artist.playCount += song.playCount;
|
||||
song.liked = interaction.liked
|
||||
song.playCount = interaction.play_count
|
||||
song.album.playCount += song.playCount
|
||||
song.artist.playCount += song.playCount
|
||||
|
||||
if (song.liked) {
|
||||
favoriteStore.add(song);
|
||||
favoriteStore.add(song)
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,10 +101,10 @@ export const songStore = {
|
|||
*
|
||||
* @return {Float|String}
|
||||
*/
|
||||
getLength(songs, toHis) {
|
||||
const duration = songs.reduce((length, song) => length + song.length, 0);
|
||||
getLength (songs, toHis) {
|
||||
const duration = songs.reduce((length, song) => length + song.length, 0)
|
||||
|
||||
return toHis ? secondsToHis(duration) : duration;
|
||||
return toHis ? secondsToHis(duration) : duration
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -112,8 +112,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.songs;
|
||||
get all () {
|
||||
return this.state.songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -121,8 +121,8 @@ export const songStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.songs = value;
|
||||
set all (value) {
|
||||
this.state.songs = value
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -132,8 +132,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId(id) {
|
||||
return this.cache[id];
|
||||
byId (id) {
|
||||
return this.cache[id]
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -143,8 +143,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
byIds(ids) {
|
||||
return ids.map(id => this.byId(id));
|
||||
byIds (ids) {
|
||||
return ids.map(id => this.byId(id))
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -152,19 +152,19 @@ export const songStore = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
registerPlay(song) {
|
||||
registerPlay (song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const oldCount = song.playCount;
|
||||
const oldCount = song.playCount
|
||||
|
||||
http.post('interaction/play', { song: song.id }, data => {
|
||||
// Use the data from the server to make sure we don't miss a play from another device.
|
||||
song.playCount = data.play_count;
|
||||
song.album.playCount += song.playCount - oldCount;
|
||||
song.artist.playCount += song.playCount - oldCount;
|
||||
song.playCount = data.play_count
|
||||
song.album.playCount += song.playCount - oldCount
|
||||
song.artist.playCount += song.playCount - oldCount
|
||||
|
||||
resolve(data);
|
||||
}, r => reject(r));
|
||||
});
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -172,12 +172,12 @@ export const songStore = {
|
|||
*
|
||||
* @param {Object}
|
||||
*/
|
||||
addRecent(song) {
|
||||
addRecent (song) {
|
||||
// First we make sure that there's no duplicate.
|
||||
this.state.recent = without(this.state.recent, song);
|
||||
this.state.recent = without(this.state.recent, song)
|
||||
|
||||
// Then we prepend the song into the list.
|
||||
this.state.recent.unshift(song);
|
||||
this.state.recent.unshift(song)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -185,9 +185,9 @@ export const songStore = {
|
|||
*
|
||||
* @param {Object} song
|
||||
*/
|
||||
scrobble(song) {
|
||||
scrobble (song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post(`${song.id}/scrobble/${song.playStartTime}`, {}, data => resolve(data), r => reject(r));
|
||||
http.post(`${song.id}/scrobble/${song.playStartTime}`, {}, data => resolve(data), r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -197,16 +197,16 @@ export const songStore = {
|
|||
* @param {Array.<Object>} songs An array of song
|
||||
* @param {Object} data
|
||||
*/
|
||||
update(songs, data) {
|
||||
update (songs, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put('songs', {
|
||||
data,
|
||||
songs: map(songs, 'id'),
|
||||
songs: map(songs, 'id')
|
||||
}, songs => {
|
||||
each(songs, song => this.syncUpdatedSong(song));
|
||||
resolve(songs);
|
||||
}, r => reject(r));
|
||||
});
|
||||
each(songs, song => this.syncUpdatedSong(song))
|
||||
resolve(songs)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -221,7 +221,7 @@ export const songStore = {
|
|||
*
|
||||
* @return {?Object} The updated song.
|
||||
*/
|
||||
syncUpdatedSong(updatedSong) {
|
||||
syncUpdatedSong (updatedSong) {
|
||||
// Cases:
|
||||
// 1. Album doesn't change (and then, artist doesn't either)
|
||||
// 2. Album changes (note that a new album might have been created) and
|
||||
|
@ -229,77 +229,77 @@ export const songStore = {
|
|||
// 2.b. Artist changes as well. Note that an artist might have been created.
|
||||
|
||||
// Find the original song,
|
||||
const originalSong = this.byId(updatedSong.id);
|
||||
const originalSong = this.byId(updatedSong.id)
|
||||
|
||||
if (!originalSong) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// and keep track of original album/artist.
|
||||
const originalAlbumId = originalSong.album.id;
|
||||
const originalArtistId = originalSong.artist.id;
|
||||
const originalAlbumId = originalSong.album.id
|
||||
const originalArtistId = originalSong.artist.id
|
||||
|
||||
// First, we update the title, lyrics, and track #
|
||||
originalSong.title = updatedSong.title;
|
||||
originalSong.lyrics = updatedSong.lyrics;
|
||||
originalSong.track = updatedSong.track;
|
||||
originalSong.title = updatedSong.title
|
||||
originalSong.lyrics = updatedSong.lyrics
|
||||
originalSong.track = updatedSong.track
|
||||
|
||||
if (updatedSong.album.id === originalAlbumId) { // case 1
|
||||
// Nothing to do
|
||||
} else { // case 2
|
||||
// First, remove it from its old album
|
||||
albumStore.removeSongsFromAlbum(originalSong.album, originalSong);
|
||||
albumStore.removeSongsFromAlbum(originalSong.album, originalSong)
|
||||
|
||||
const existingAlbum = albumStore.byId(updatedSong.album.id);
|
||||
const newAlbumCreated = !existingAlbum;
|
||||
const existingAlbum = albumStore.byId(updatedSong.album.id)
|
||||
const newAlbumCreated = !existingAlbum
|
||||
|
||||
if (!newAlbumCreated) {
|
||||
// The song changed to an existing album. We now add it to such album.
|
||||
albumStore.addSongsIntoAlbum(existingAlbum, originalSong);
|
||||
albumStore.addSongsIntoAlbum(existingAlbum, originalSong)
|
||||
} else {
|
||||
// A new album was created. We:
|
||||
// - Add the new album into our collection
|
||||
// - Add the song into it
|
||||
albumStore.addSongsIntoAlbum(updatedSong.album, originalSong);
|
||||
albumStore.add(updatedSong.album);
|
||||
albumStore.addSongsIntoAlbum(updatedSong.album, originalSong)
|
||||
albumStore.add(updatedSong.album)
|
||||
}
|
||||
|
||||
if (updatedSong.album.artist.id === originalArtistId) { // case 2.a
|
||||
// Same artist, but what if the album is new?
|
||||
if (newAlbumCreated) {
|
||||
artistStore.addAlbumsIntoArtist(artistStore.byId(originalArtistId), updatedSong.album);
|
||||
artistStore.addAlbumsIntoArtist(artistStore.byId(originalArtistId), updatedSong.album)
|
||||
}
|
||||
} else { // case 2.b
|
||||
// The artist changes.
|
||||
const existingArtist = artistStore.byId(updatedSong.album.artist.id);
|
||||
const existingArtist = artistStore.byId(updatedSong.album.artist.id)
|
||||
|
||||
if (existingArtist) {
|
||||
originalSong.artist = existingArtist;
|
||||
originalSong.artist = existingArtist
|
||||
} else {
|
||||
// New artist created. We:
|
||||
// - Add the album into it, because now it MUST BE a new album
|
||||
// (there's no "new artist with existing album" in our system).
|
||||
// - Add the new artist into our collection
|
||||
artistStore.addAlbumsIntoArtist(updatedSong.album.artist, updatedSong.album);
|
||||
artistStore.add(updatedSong.album.artist);
|
||||
originalSong.artist = updatedSong.album.artist;
|
||||
artistStore.addAlbumsIntoArtist(updatedSong.album.artist, updatedSong.album)
|
||||
artistStore.add(updatedSong.album.artist)
|
||||
originalSong.artist = updatedSong.album.artist
|
||||
}
|
||||
}
|
||||
|
||||
// As a last step, we purify our library of empty albums/artists.
|
||||
if (albumStore.isAlbumEmpty(albumStore.byId(originalAlbumId))) {
|
||||
albumStore.remove(albumStore.byId(originalAlbumId));
|
||||
albumStore.remove(albumStore.byId(originalAlbumId))
|
||||
}
|
||||
|
||||
if (artistStore.isArtistEmpty(artistStore.byId(originalArtistId))) {
|
||||
artistStore.remove(artistStore.byId(originalArtistId));
|
||||
artistStore.remove(artistStore.byId(originalArtistId))
|
||||
}
|
||||
|
||||
// Now we make sure the next call to info() get the refreshed, correct info.
|
||||
originalSong.infoRetrieved = false;
|
||||
originalSong.infoRetrieved = false
|
||||
}
|
||||
|
||||
return originalSong;
|
||||
return originalSong
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -309,8 +309,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {string} The source URL, with JWT token appended.
|
||||
*/
|
||||
getSourceUrl(song) {
|
||||
return `${sharedStore.state.cdnUrl}api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`;
|
||||
getSourceUrl (song) {
|
||||
return `${sharedStore.state.cdnUrl}api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -321,8 +321,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getShareableUrl(song) {
|
||||
return `${window.location.origin}/#!/song/${song.id}`;
|
||||
getShareableUrl (song) {
|
||||
return `${window.location.origin}/#!/song/${song.id}`
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -332,8 +332,8 @@ export const songStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getRecent(n = 10) {
|
||||
return take(this.state.recent, n);
|
||||
getRecent (n = 10) {
|
||||
return take(this.state.recent, n)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -343,13 +343,13 @@ export const songStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getMostPlayed(n = 10) {
|
||||
const songs = take(orderBy(this.all, 'playCount', 'desc'), n);
|
||||
getMostPlayed (n = 10) {
|
||||
const songs = take(orderBy(this.all, 'playCount', 'desc'), n)
|
||||
|
||||
// Remove those with playCount=0
|
||||
remove(songs, song => !song.playCount);
|
||||
remove(songs, song => !song.playCount)
|
||||
|
||||
return songs;
|
||||
return songs
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -357,15 +357,15 @@ export const songStore = {
|
|||
* @param {Number} n
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
getRecentlyAdded(n = 10) {
|
||||
return take(orderBy(this.all, 'created_at', 'desc'), n);
|
||||
getRecentlyAdded (n = 10) {
|
||||
return take(orderBy(this.all, 'created_at', 'desc'), n)
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the application is torn down.
|
||||
* Reset stuff.
|
||||
*/
|
||||
teardown() {
|
||||
this.state.recent = [];
|
||||
},
|
||||
};
|
||||
teardown () {
|
||||
this.state.recent = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { each, find, without } from 'lodash';
|
||||
import md5 from 'blueimp-md5';
|
||||
import Vue from 'vue';
|
||||
import NProgress from 'nprogress';
|
||||
import { each, find, without } from 'lodash'
|
||||
import md5 from 'blueimp-md5'
|
||||
import Vue from 'vue'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
import { http } from '../services';
|
||||
import stub from '../stubs/user';
|
||||
import { http } from '../services'
|
||||
import stub from '../stubs/user'
|
||||
|
||||
export const userStore = {
|
||||
stub,
|
||||
|
||||
state: {
|
||||
users: [],
|
||||
current: stub,
|
||||
current: stub
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -20,15 +20,15 @@ export const userStore = {
|
|||
* @param {Array.<Object>} users The users in the system. Empty array if current user is not an admin.
|
||||
* @param {Object} currentUser The current user.
|
||||
*/
|
||||
init(users, currentUser) {
|
||||
this.all = users;
|
||||
this.current = currentUser;
|
||||
init (users, currentUser) {
|
||||
this.all = users
|
||||
this.current = currentUser
|
||||
|
||||
// Set the avatar for each of the users…
|
||||
each(this.all, this.setAvatar);
|
||||
each(this.all, this.setAvatar)
|
||||
|
||||
// …and the current user as well.
|
||||
this.setAvatar();
|
||||
this.setAvatar()
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -36,8 +36,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Array.<Object>}
|
||||
*/
|
||||
get all() {
|
||||
return this.state.users;
|
||||
get all () {
|
||||
return this.state.users
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -45,8 +45,8 @@ export const userStore = {
|
|||
*
|
||||
* @param {Array.<Object>} value
|
||||
*/
|
||||
set all(value) {
|
||||
this.state.users = value;
|
||||
set all (value) {
|
||||
this.state.users = value
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -56,8 +56,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
byId(id) {
|
||||
return find(this.all, { id });
|
||||
byId (id) {
|
||||
return find(this.all, { id })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -65,8 +65,8 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get current() {
|
||||
return this.state.current;
|
||||
get current () {
|
||||
return this.state.current
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -76,8 +76,9 @@ export const userStore = {
|
|||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
set current(user) {
|
||||
return this.state.current = user;
|
||||
set current (user) {
|
||||
this.state.current = user
|
||||
return this.state.current
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -85,12 +86,12 @@ export const userStore = {
|
|||
*
|
||||
* @param {?Object} user The user. If null, the current user.
|
||||
*/
|
||||
setAvatar(user = null) {
|
||||
setAvatar (user = null) {
|
||||
if (!user) {
|
||||
user = this.current;
|
||||
user = this.current
|
||||
}
|
||||
|
||||
Vue.set(user, 'avatar', `https://www.gravatar.com/avatar/${md5(user.email)}?s=256`);
|
||||
Vue.set(user, 'avatar', `https://www.gravatar.com/avatar/${md5(user.email)}?s=256`)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -99,21 +100,21 @@ export const userStore = {
|
|||
* @param {String} email
|
||||
* @param {String} password
|
||||
*/
|
||||
login(email, password) {
|
||||
NProgress.start();
|
||||
login (email, password) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('me', { email, password }, data => resolve(data), r => reject(r));
|
||||
});
|
||||
http.post('me', { email, password }, data => resolve(data), r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Log the current user out.
|
||||
*/
|
||||
logout() {
|
||||
logout () {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete('me', {}, data => resolve(data), r => reject(r));
|
||||
});
|
||||
http.delete('me', {}, data => resolve(data), r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -121,20 +122,20 @@ export const userStore = {
|
|||
*
|
||||
* @param {string} password Can be an empty string if the user is not changing his password.
|
||||
*/
|
||||
updateProfile(password) {
|
||||
NProgress.start();
|
||||
updateProfile (password) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put('me', {
|
||||
password,
|
||||
name: this.current.name,
|
||||
email: this.current.email
|
||||
}, () => {
|
||||
this.setAvatar();
|
||||
resolve(this.current)
|
||||
}, r => reject(r)
|
||||
);
|
||||
});
|
||||
password,
|
||||
name: this.current.name,
|
||||
email: this.current.email
|
||||
}, () => {
|
||||
this.setAvatar()
|
||||
resolve(this.current)
|
||||
},
|
||||
r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -144,16 +145,16 @@ export const userStore = {
|
|||
* @param {string} email
|
||||
* @param {string} password
|
||||
*/
|
||||
store(name, email, password) {
|
||||
NProgress.start();
|
||||
store (name, email, password) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('user', { name, email, password }, user => {
|
||||
this.setAvatar(user);
|
||||
this.all.unshift(user);
|
||||
resolve(user);
|
||||
}, r => reject(r));
|
||||
});
|
||||
this.setAvatar(user)
|
||||
this.all.unshift(user)
|
||||
resolve(user)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -164,16 +165,16 @@ export const userStore = {
|
|||
* @param {String} email
|
||||
* @param {String} password
|
||||
*/
|
||||
update(user, name, email, password) {
|
||||
NProgress.start();
|
||||
update (user, name, email, password) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.put(`user/${user.id}`, { name, email, password }, () => {
|
||||
this.setAvatar(user);
|
||||
user.password = '';
|
||||
resolve(user);
|
||||
}, r => reject(r));
|
||||
});
|
||||
this.setAvatar(user)
|
||||
user.password = ''
|
||||
resolve(user)
|
||||
}, r => reject(r))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -181,12 +182,12 @@ export const userStore = {
|
|||
*
|
||||
* @param {Object} user
|
||||
*/
|
||||
destroy(user) {
|
||||
NProgress.start();
|
||||
destroy (user) {
|
||||
NProgress.start()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(`user/${user.id}`, {}, data => {
|
||||
this.all = without(this.all, user);
|
||||
this.all = without(this.all, user)
|
||||
|
||||
// Mama, just killed a man
|
||||
// Put a gun against his head
|
||||
|
@ -210,8 +211,8 @@ export const userStore = {
|
|||
/**
|
||||
* Brian May enters the stage.
|
||||
*/
|
||||
resolve(data);
|
||||
}, r => reject(r));
|
||||
});
|
||||
},
|
||||
};
|
||||
resolve(data)
|
||||
}, r => reject(r))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import config from '../config';
|
||||
import artist from './artist';
|
||||
import config from '../config'
|
||||
import artist from './artist'
|
||||
|
||||
export default {
|
||||
artist,
|
||||
|
@ -10,5 +10,5 @@ export default {
|
|||
playCount: 0,
|
||||
length: 0,
|
||||
fmtLength: '00:00',
|
||||
songs: [],
|
||||
};
|
||||
songs: []
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ export default {
|
|||
image: null,
|
||||
playCount: 0,
|
||||
albums: [],
|
||||
songs: [],
|
||||
};
|
||||
songs: []
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
name: '',
|
||||
songs: [],
|
||||
};
|
||||
songs: []
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default {
|
||||
media_path: '',
|
||||
};
|
||||
media_path: ''
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import album from './album';
|
||||
import artist from './artist';
|
||||
import album from './album'
|
||||
import artist from './artist'
|
||||
|
||||
export default {
|
||||
album,
|
||||
|
@ -12,5 +12,5 @@ export default {
|
|||
lyrics: '',
|
||||
liked: false,
|
||||
playCount: 0,
|
||||
playbackState: 'stopped',
|
||||
};
|
||||
playbackState: 'stopped'
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@ export default {
|
|||
name: '',
|
||||
email: '',
|
||||
avatar: '',
|
||||
is_admin: false,
|
||||
};
|
||||
is_admin: false
|
||||
}
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
export default [
|
||||
{
|
||||
id: 1,
|
||||
song_id: "7900ab518f51775fe6cf06092c074ee5",
|
||||
song_id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
song_id: "95c0ffc33c08c8c14ea5de0a44d5df3c",
|
||||
song_id: '95c0ffc33c08c8c14ea5de0a44d5df3c',
|
||||
liked: false,
|
||||
play_count: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
song_id: "c83b201502eb36f1084f207761fa195c",
|
||||
song_id: 'c83b201502eb36f1084f207761fa195c',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
song_id: "cb7edeac1f097143e65b1b2cde102482",
|
||||
song_id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
liked: true,
|
||||
play_count: 3
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
song_id: "ccc38cc14bb95aefdf6da4b34adcf548",
|
||||
song_id: 'ccc38cc14bb95aefdf6da4b34adcf548',
|
||||
liked: false,
|
||||
play_count: 4
|
||||
}
|
||||
];
|
||||
]
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
export default [
|
||||
{
|
||||
id: 1,
|
||||
name: "All-4-One",
|
||||
name: 'All-4-One',
|
||||
albums: [
|
||||
{
|
||||
id: 1193,
|
||||
artist_id: 1,
|
||||
name: "All-4-One",
|
||||
cover: "/public/img/covers/565c0f7067425.jpeg",
|
||||
name: 'All-4-One',
|
||||
cover: '/public/img/covers/565c0f7067425.jpeg',
|
||||
songs: [
|
||||
{
|
||||
id: "39189f4545f9d5671fb3dc964f0080a0",
|
||||
id: '39189f4545f9d5671fb3dc964f0080a0',
|
||||
album_id: 1193,
|
||||
title: "I Swear",
|
||||
title: 'I Swear',
|
||||
length: 259.92,
|
||||
playCount: 4
|
||||
}
|
||||
|
@ -21,13 +21,13 @@ export default [
|
|||
{
|
||||
id: 1194,
|
||||
artist_id: 1,
|
||||
name: "And The Music Speaks",
|
||||
cover: "/public/img/covers/unknown-album.png",
|
||||
name: 'And The Music Speaks',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: "a6a550f7d950d2a2520f9bf1a60f025a",
|
||||
id: 'a6a550f7d950d2a2520f9bf1a60f025a',
|
||||
album_id: 1194,
|
||||
title: "I can love you like that",
|
||||
title: 'I can love you like that',
|
||||
length: 262.61,
|
||||
playCount: 2
|
||||
}
|
||||
|
@ -36,13 +36,13 @@ export default [
|
|||
{
|
||||
id: 1195,
|
||||
artist_id: 1,
|
||||
name: "Space Jam",
|
||||
cover: "/public/img/covers/565c0f7115e0f.png",
|
||||
name: 'Space Jam',
|
||||
cover: '/public/img/covers/565c0f7115e0f.png',
|
||||
songs: [
|
||||
{
|
||||
id: "d86c30fd34f13c1aff8db59b7fc9c610",
|
||||
id: 'd86c30fd34f13c1aff8db59b7fc9c610',
|
||||
album_id: 1195,
|
||||
title: "I turn to you",
|
||||
title: 'I turn to you',
|
||||
length: 293.04
|
||||
}
|
||||
]
|
||||
|
@ -51,18 +51,18 @@ export default [
|
|||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Bob Dylan",
|
||||
name: 'Bob Dylan',
|
||||
albums: [
|
||||
{
|
||||
id: 1217,
|
||||
artist_id: 2,
|
||||
name: "Highway 61 Revisited",
|
||||
cover: "/public/img/covers/565c0f76dc6e8.jpeg",
|
||||
name: 'Highway 61 Revisited',
|
||||
cover: '/public/img/covers/565c0f76dc6e8.jpeg',
|
||||
songs: [
|
||||
{
|
||||
id: "e6d3977f3ffa147801ca5d1fdf6fa55e",
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55e',
|
||||
album_id: 1217,
|
||||
title: "Like a rolling stone",
|
||||
title: 'Like a rolling stone',
|
||||
length: 373.63
|
||||
}
|
||||
]
|
||||
|
@ -70,13 +70,13 @@ export default [
|
|||
{
|
||||
id: 1218,
|
||||
artist_id: 2,
|
||||
name: "Pat Garrett & Billy the Kid",
|
||||
cover: "/public/img/covers/unknown-album.png",
|
||||
name: 'Pat Garrett & Billy the Kid',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: "aa16bbef6a9710eb9a0f41ecc534fad5",
|
||||
id: 'aa16bbef6a9710eb9a0f41ecc534fad5',
|
||||
album_id: 1218,
|
||||
title: "Knockin' on heaven's door",
|
||||
title: 'Knockin\' on heaven\'s door',
|
||||
length: 151.9
|
||||
}
|
||||
]
|
||||
|
@ -84,13 +84,13 @@ export default [
|
|||
{
|
||||
id: 1219,
|
||||
artist_id: 2,
|
||||
name: "The Times They Are A-Changin'",
|
||||
cover: "/public/img/covers/unknown-album.png",
|
||||
name: 'The Times They Are A-Changin\'',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: "cb7edeac1f097143e65b1b2cde102482",
|
||||
id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
album_id: 1219,
|
||||
title: "The times they are a-changin'",
|
||||
title: 'The times they are a-changin\'',
|
||||
length: 196
|
||||
}
|
||||
]
|
||||
|
@ -99,116 +99,116 @@ export default [
|
|||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "James Blunt",
|
||||
name: 'James Blunt',
|
||||
albums: [
|
||||
{
|
||||
id: 1268,
|
||||
artist_id: 3,
|
||||
name: "Back To Bedlam",
|
||||
cover: "/public/img/covers/unknown-album.png",
|
||||
name: 'Back To Bedlam',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: "0ba9fb128427b32683b9eb9140912a70",
|
||||
id: '0ba9fb128427b32683b9eb9140912a70',
|
||||
album_id: 1268,
|
||||
title: "No bravery",
|
||||
title: 'No bravery',
|
||||
length: 243.12
|
||||
},
|
||||
{
|
||||
id: "123fd1ad32240ecab28a4e86ed5173",
|
||||
id: '123fd1ad32240ecab28a4e86ed5173',
|
||||
album_id: 1268,
|
||||
title: "So long, Jimmy",
|
||||
title: 'So long, Jimmy',
|
||||
length: 265.04
|
||||
},
|
||||
{
|
||||
id: "6a54c674d8b16732f26df73f59c63e21",
|
||||
id: '6a54c674d8b16732f26df73f59c63e21',
|
||||
album_id: 1268,
|
||||
title: "Wisemen",
|
||||
title: 'Wisemen',
|
||||
length: 223.14
|
||||
},
|
||||
{
|
||||
id: "6df7d82a9a8701e40d1c291cf14a16bc",
|
||||
id: '6df7d82a9a8701e40d1c291cf14a16bc',
|
||||
album_id: 1268,
|
||||
title: "Goodbye my lover",
|
||||
title: 'Goodbye my lover',
|
||||
length: 258.61
|
||||
},
|
||||
{
|
||||
id: "74a2000d343e4587273d3ad14e2fd741",
|
||||
id: '74a2000d343e4587273d3ad14e2fd741',
|
||||
album_id: 1268,
|
||||
title: "High",
|
||||
title: 'High',
|
||||
length: 245.86
|
||||
},
|
||||
{
|
||||
id: "7900ab518f51775fe6cf06092c074ee5",
|
||||
id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
album_id: 1268,
|
||||
title: "You're beautiful",
|
||||
title: 'You\'re beautiful',
|
||||
length: 213.29
|
||||
},
|
||||
{
|
||||
id: "803910a51f9893347e087af851e38777",
|
||||
id: '803910a51f9893347e087af851e38777',
|
||||
album_id: 1268,
|
||||
title: "Cry",
|
||||
title: 'Cry',
|
||||
length: 246.91
|
||||
},
|
||||
{
|
||||
id: "d82b0d4d4803ebbcb61000a5b6a868f5",
|
||||
id: 'd82b0d4d4803ebbcb61000a5b6a868f5',
|
||||
album_id: 1268,
|
||||
title: "Tears and rain",
|
||||
title: 'Tears and rain',
|
||||
length: 244.45
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
export const singleAlbum = {
|
||||
id: 9999,
|
||||
artist_id: 99,
|
||||
name: "Foo bar",
|
||||
cover: "/foo.jpg",
|
||||
name: 'Foo bar',
|
||||
cover: '/foo.jpg',
|
||||
songs: [
|
||||
{
|
||||
id: "39189f4545f0d5671fc3dc964f0080a0",
|
||||
id: '39189f4545f0d5671fc3dc964f0080a0',
|
||||
album_id: 9999,
|
||||
title: "A Foo Song",
|
||||
title: 'A Foo Song',
|
||||
length: 100,
|
||||
playCount: 4
|
||||
}, {
|
||||
id: "39189f4545f9d5671fc3dc96cf1080a0",
|
||||
id: '39189f4545f9d5671fc3dc96cf1080a0',
|
||||
album_id: 9999,
|
||||
title: "A Bar Song",
|
||||
title: 'A Bar Song',
|
||||
length: 200,
|
||||
playCount: 7
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export const singleArtist = {
|
||||
id: 999,
|
||||
name: "John Cena",
|
||||
name: 'John Cena',
|
||||
albums: [
|
||||
{
|
||||
id: 9991,
|
||||
artist_id: 999,
|
||||
name: "It's John Cena!!!!",
|
||||
cover: "/tmp/john.jpg",
|
||||
name: 'It\'s John Cena!!!!',
|
||||
cover: '/tmp/john.jpg',
|
||||
songs: [
|
||||
{
|
||||
id: "e6d3977f3ffa147801ca5d1fdf6fa55f",
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55f',
|
||||
album_id: 9991,
|
||||
title: "John Cena to the Rescue",
|
||||
title: 'John Cena to the Rescue',
|
||||
length: 300
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export const singleSong = {
|
||||
id: "dccb0d4d4803ebbcb61000a5b6a868f5",
|
||||
id: 'dccb0d4d4803ebbcb61000a5b6a868f5',
|
||||
album_id: 1193,
|
||||
title: "Foo and Bar",
|
||||
title: 'Foo and Bar',
|
||||
length: 100,
|
||||
playCount: 4,
|
||||
lyrics: ''
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ export default {
|
|||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true,
|
||||
is_admin: true
|
||||
},
|
||||
|
||||
users: [
|
||||
|
@ -11,13 +11,13 @@ export default {
|
|||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true,
|
||||
is_admin: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'John Doe',
|
||||
email: 'john@doe.tld',
|
||||
is_admin: false,
|
||||
},
|
||||
is_admin: false
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { jsdom } from 'jsdom';
|
||||
import { jsdom } from 'jsdom'
|
||||
|
||||
const doc = jsdom('<!doctype html><html><body></body></html>');
|
||||
const win = doc.defaultView;
|
||||
const doc = jsdom('<!doctype html><html><body></body></html>')
|
||||
const win = doc.defaultView
|
||||
|
||||
global.document = doc;
|
||||
global.window = win;
|
||||
global.document = doc
|
||||
global.window = win
|
||||
|
||||
Object.keys(window).forEach((key) => {
|
||||
if (!(key in global)) {
|
||||
global[key] = window[key];
|
||||
global[key] = window[key]
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1,34 +1,35 @@
|
|||
require('chai').should();
|
||||
require('chai').should()
|
||||
|
||||
import localStorage from 'local-storage';
|
||||
import { ls } from '../../services';
|
||||
import localStorage from 'local-storage'
|
||||
import { ls } from '../../services'
|
||||
|
||||
describe('services/ls', () => {
|
||||
beforeEach(() => localStorage.remove('foo'));
|
||||
beforeEach(() => localStorage.remove('foo'))
|
||||
|
||||
describe('#get', () => {
|
||||
it('correctly gets an existing item from local storage', () => {
|
||||
localStorage('foo', 'bar');
|
||||
ls.get('foo').should.equal('bar');
|
||||
});
|
||||
localStorage('foo', 'bar')
|
||||
ls.get('foo').should.equal('bar')
|
||||
})
|
||||
|
||||
it('correctly returns the default value for a non exising item', () => {
|
||||
ls.get('baz', 'qux').should.equal('qux');
|
||||
});
|
||||
});
|
||||
ls.get('baz', 'qux').should.equal('qux')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#set', () => {
|
||||
it('correctly sets an item into local storage', () => {
|
||||
ls.set('foo', 'bar');
|
||||
localStorage('foo').should.equal('bar');
|
||||
});
|
||||
});
|
||||
ls.set('foo', 'bar')
|
||||
localStorage('foo').should.equal('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove', () => {
|
||||
it('correctly removes an item from local storage', () => {
|
||||
localStorage('foo', 'bar');
|
||||
ls.remove('foo');
|
||||
(localStorage('foo') === null).should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
localStorage('foo', 'bar')
|
||||
ls.remove('foo')
|
||||
var result = localStorage('foo') === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,102 +1,102 @@
|
|||
require('chai').should();
|
||||
import { cloneDeep, last } from 'lodash';
|
||||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { albumStore, artistStore } from '../../stores';
|
||||
import { default as artists, singleAlbum, singleSong } from '../blobs/media';
|
||||
import { albumStore, artistStore } from '../../stores'
|
||||
import { default as artists, singleAlbum, singleSong } from '../blobs/media'
|
||||
|
||||
describe('stores/album', () => {
|
||||
beforeEach(() => albumStore.init(cloneDeep(artists)));
|
||||
beforeEach(() => albumStore.init(cloneDeep(artists)))
|
||||
|
||||
afterEach(() => albumStore.state.albums = []);
|
||||
afterEach(() => albumStore.state.albums = [])
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers albums', () => {
|
||||
albumStore.state.albums.length.should.equal(7);
|
||||
});
|
||||
albumStore.state.albums.length.should.equal(7)
|
||||
})
|
||||
|
||||
it('correctly sets albums length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(259.92);
|
||||
});
|
||||
albumStore.state.albums[0].length.should.equal(259.92)
|
||||
})
|
||||
|
||||
it('correctly sets album artists', () => {
|
||||
albumStore.state.albums[0].artist.id.should.equal(1);
|
||||
});
|
||||
});
|
||||
albumStore.state.albums[0].artist.id.should.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all songs', () => {
|
||||
albumStore.all.length.should.equal(7);
|
||||
});
|
||||
});
|
||||
albumStore.all.length.should.equal(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getLength', () => {
|
||||
it('correctly calculates an album’s length', () => {
|
||||
albumStore.getLength(albumStore.state.albums[6]);
|
||||
albumStore.getLength(albumStore.state.albums[6])
|
||||
albumStore.state.albums[6].length.should.equal(1940.42); // I'm sorry…
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('#add', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.add(cloneDeep(singleAlbum));
|
||||
});
|
||||
albumStore.add(cloneDeep(singleAlbum))
|
||||
})
|
||||
|
||||
it('correctly adds a new album into the state', () => {
|
||||
last(albumStore.state.albums).id.should.equal(9999);
|
||||
});
|
||||
last(albumStore.state.albums).id.should.equal(9999)
|
||||
})
|
||||
|
||||
it('correctly recalculates the length', () => {
|
||||
last(albumStore.state.albums).length.should.equal(300);
|
||||
});
|
||||
last(albumStore.state.albums).length.should.equal(300)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
last(albumStore.state.albums).playCount.should.equal(11);
|
||||
});
|
||||
});
|
||||
last(albumStore.state.albums).playCount.should.equal(11)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.remove(albumStore.state.albums[0]); // ID 1193
|
||||
});
|
||||
})
|
||||
|
||||
it('correctly removes an album', () => {
|
||||
albumStore.state.albums.length.should.equal(6);
|
||||
});
|
||||
});
|
||||
albumStore.state.albums.length.should.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addSongsIntoAlbum', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.addSongsIntoAlbum(albumStore.state.albums[0], cloneDeep(singleSong));
|
||||
});
|
||||
albumStore.addSongsIntoAlbum(albumStore.state.albums[0], cloneDeep(singleSong))
|
||||
})
|
||||
|
||||
it('correctly adds a song into an album', () => {
|
||||
albumStore.state.albums[0].songs.length.should.equal(2);
|
||||
});
|
||||
albumStore.state.albums[0].songs.length.should.equal(2)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
albumStore.state.albums[0].playCount.should.equal(4);
|
||||
});
|
||||
albumStore.state.albums[0].playCount.should.equal(4)
|
||||
})
|
||||
|
||||
it ('correctly recalculates album length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(359.92);
|
||||
});
|
||||
});
|
||||
albumStore.state.albums[0].length.should.equal(359.92)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#removeSongsFromAlbum', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.removeSongsFromAlbum(albumStore.state.albums[0], albumStore.state.albums[0].songs[0]);
|
||||
});
|
||||
albumStore.removeSongsFromAlbum(albumStore.state.albums[0], albumStore.state.albums[0].songs[0])
|
||||
})
|
||||
|
||||
it('correctly removes a song from an album', () => {
|
||||
albumStore.state.albums[0].songs.length.should.equal(0);
|
||||
});
|
||||
albumStore.state.albums[0].songs.length.should.equal(0)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
albumStore.state.albums[0].playCount.should.equal(0);
|
||||
});
|
||||
albumStore.state.albums[0].playCount.should.equal(0)
|
||||
})
|
||||
|
||||
it('correctly recalculates the length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
albumStore.state.albums[0].length.should.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,73 +1,73 @@
|
|||
require('chai').should();
|
||||
import { cloneDeep, last } from 'lodash';
|
||||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { artistStore } from '../../stores';
|
||||
import { default as artists, singleAlbum, singleArtist } from '../blobs/media';
|
||||
import { artistStore } from '../../stores'
|
||||
import { default as artists, singleAlbum, singleArtist } from '../blobs/media'
|
||||
|
||||
describe('stores/artist', () => {
|
||||
beforeEach(() => artistStore.init(cloneDeep(artists)));
|
||||
afterEach(() => artistStore.state.artists = []);
|
||||
beforeEach(() => artistStore.init(cloneDeep(artists)))
|
||||
afterEach(() => artistStore.state.artists = [])
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers artists', () => {
|
||||
artistStore.state.artists.length.should.equal(3);
|
||||
});
|
||||
artistStore.state.artists.length.should.equal(3)
|
||||
})
|
||||
|
||||
it('correctly gets artist images', () => {
|
||||
artistStore.state.artists[0].image.should.equal('/public/img/covers/565c0f7067425.jpeg');
|
||||
});
|
||||
artistStore.state.artists[0].image.should.equal('/public/img/covers/565c0f7067425.jpeg')
|
||||
})
|
||||
|
||||
it('correctly counts songs by artists', () => {
|
||||
artistStore.state.artists[0].songCount = 3;
|
||||
});
|
||||
});
|
||||
artistStore.state.artists[0].songCount = 3
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getImage', () => {
|
||||
it('correctly gets an artist’s image', () => {
|
||||
artistStore.getImage(artistStore.state.artists[0]).should.equal('/public/img/covers/565c0f7067425.jpeg');
|
||||
});
|
||||
});
|
||||
artistStore.getImage(artistStore.state.artists[0]).should.equal('/public/img/covers/565c0f7067425.jpeg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#add', () => {
|
||||
beforeEach(() => artistStore.add(cloneDeep(singleArtist)));
|
||||
beforeEach(() => artistStore.add(cloneDeep(singleArtist)))
|
||||
|
||||
it('correctly adds an artist', () => {
|
||||
last(artistStore.state.artists).name.should.equal('John Cena');
|
||||
});
|
||||
});
|
||||
last(artistStore.state.artists).name.should.equal('John Cena')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove', () => {
|
||||
beforeEach(() => artistStore.remove(artistStore.state.artists[0]));
|
||||
beforeEach(() => artistStore.remove(artistStore.state.artists[0]))
|
||||
|
||||
it('correctly removes an artist', () => {
|
||||
artistStore.state.artists.length.should.equal(2);
|
||||
artistStore.state.artists[0].name.should.equal('Bob Dylan');
|
||||
});
|
||||
});
|
||||
artistStore.state.artists.length.should.equal(2)
|
||||
artistStore.state.artists[0].name.should.equal('Bob Dylan')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addAlbumsIntoArtist', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.addAlbumsIntoArtist(artistStore.state.artists[0], cloneDeep(singleAlbum));
|
||||
});
|
||||
artistStore.addAlbumsIntoArtist(artistStore.state.artists[0], cloneDeep(singleAlbum))
|
||||
})
|
||||
|
||||
it('correctly adds albums into an artist', () => {
|
||||
artistStore.state.artists[0].albums.length.should.equal(4);
|
||||
});
|
||||
artistStore.state.artists[0].albums.length.should.equal(4)
|
||||
})
|
||||
|
||||
it('correctly sets the album artist', () => {
|
||||
const addedAlbum = last(artistStore.state.artists[0].albums);
|
||||
addedAlbum.artist.should.equal(artistStore.state.artists[0]);
|
||||
addedAlbum.artist_id.should.equal(artistStore.state.artists[0].id);
|
||||
});
|
||||
});
|
||||
const addedAlbum = last(artistStore.state.artists[0].albums)
|
||||
addedAlbum.artist.should.equal(artistStore.state.artists[0])
|
||||
addedAlbum.artist_id.should.equal(artistStore.state.artists[0].id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#removeAlbumsFromArtist', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.removeAlbumsFromArtist(artistStore.state.artists[0], artistStore.state.artists[0].albums[0]);
|
||||
});
|
||||
artistStore.removeAlbumsFromArtist(artistStore.state.artists[0], artistStore.state.artists[0].albums[0])
|
||||
})
|
||||
|
||||
it('correctly removes an album from an artist', () => {
|
||||
artistStore.state.artists[0].albums.length.should.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
artistStore.state.artists[0].albums.length.should.equal(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
require('chai').should();
|
||||
require('chai').should()
|
||||
|
||||
import localStorage from 'local-storage';
|
||||
import { preferenceStore } from '../../stores';
|
||||
import localStorage from 'local-storage'
|
||||
import { preferenceStore } from '../../stores'
|
||||
|
||||
const user = { id: 0 };
|
||||
const user = { id: 0 }
|
||||
const preferences = {
|
||||
volume: 8,
|
||||
notify: false,
|
||||
};
|
||||
notify: false
|
||||
}
|
||||
|
||||
describe('stores/preference', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.set(`preferences_${user.id}`, preferences);
|
||||
preferenceStore.init(user);
|
||||
});
|
||||
localStorage.set(`preferences_${user.id}`, preferences)
|
||||
preferenceStore.init(user)
|
||||
})
|
||||
|
||||
describe("#set", () => {
|
||||
it('correctly sets preferences', () => {
|
||||
preferenceStore.set('volume', 5);
|
||||
localStorage.get(`preferences_${user.id}`).volume.should.equal(5);
|
||||
preferenceStore.set('volume', 5)
|
||||
localStorage.get(`preferences_${user.id}`).volume.should.equal(5)
|
||||
|
||||
// Test the proxy
|
||||
preferenceStore.volume = 6;
|
||||
localStorage.get(`preferences_${user.id}`).volume.should.equal(6);
|
||||
});
|
||||
});
|
||||
preferenceStore.volume = 6
|
||||
localStorage.get(`preferences_${user.id}`).volume.should.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#get", () => {
|
||||
it('returns correct preference values', () => {
|
||||
preferenceStore.get('volume').should.equal(8);
|
||||
preferenceStore.get('volume').should.equal(8)
|
||||
|
||||
// Test the proxy
|
||||
preferenceStore.volume.should.equal(8);
|
||||
});
|
||||
});
|
||||
});
|
||||
preferenceStore.volume.should.equal(8)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,107 +1,109 @@
|
|||
require('chai').should();
|
||||
require('chai').should()
|
||||
|
||||
import { queueStore } from '../../stores';
|
||||
import artists from '../blobs/media';
|
||||
import { queueStore } from '../../stores'
|
||||
import artists from '../blobs/media'
|
||||
|
||||
const songs = artists[2].albums[0].songs;
|
||||
const songs = artists[2].albums[0].songs
|
||||
|
||||
describe('stores/queue', () => {
|
||||
beforeEach(() => {
|
||||
queueStore.state.songs = songs;
|
||||
queueStore.state.current = songs[1];
|
||||
});
|
||||
queueStore.state.songs = songs
|
||||
queueStore.state.current = songs[1]
|
||||
})
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all queued songs', () => {
|
||||
queueStore.all.should.equal(songs);
|
||||
});
|
||||
});
|
||||
queueStore.all.should.equal(songs)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#first', () => {
|
||||
it('correctly returns the first queued song', () => {
|
||||
queueStore.first.title.should.equal('No bravery');
|
||||
});
|
||||
});
|
||||
queueStore.first.title.should.equal('No bravery')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#last', () => {
|
||||
it('correctly returns the last queued song', () => {
|
||||
queueStore.last.title.should.equal('Tears and rain');
|
||||
});
|
||||
});
|
||||
queueStore.last.title.should.equal('Tears and rain')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#queue', () => {
|
||||
beforeEach(() => queueStore.state.songs = songs);
|
||||
beforeEach(() => queueStore.state.songs = songs)
|
||||
|
||||
const song = artists[0].albums[0].songs[0];
|
||||
const song = artists[0].albums[0].songs[0]
|
||||
|
||||
it('correctly appends a song to end of the queue', () => {
|
||||
queueStore.queue(song);
|
||||
queueStore.last.title.should.equal('I Swear');
|
||||
});
|
||||
queueStore.queue(song)
|
||||
queueStore.last.title.should.equal('I Swear')
|
||||
})
|
||||
|
||||
it('correctly prepends a song to top of the queue', () => {
|
||||
queueStore.queue(song, false, true);
|
||||
queueStore.first.title.should.equal('I Swear');
|
||||
});
|
||||
queueStore.queue(song, false, true)
|
||||
queueStore.first.title.should.equal('I Swear')
|
||||
})
|
||||
|
||||
it('correctly replaces the whole queue', () => {
|
||||
queueStore.queue(song, true);
|
||||
queueStore.all.length.should.equal(1);
|
||||
queueStore.first.title.should.equal('I Swear');
|
||||
});
|
||||
});
|
||||
queueStore.queue(song, true)
|
||||
queueStore.all.length.should.equal(1)
|
||||
queueStore.first.title.should.equal('I Swear')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unqueue', () => {
|
||||
beforeEach(() => queueStore.state.songs = songs);
|
||||
beforeEach(() => queueStore.state.songs = songs)
|
||||
|
||||
it('correctly removes a song from queue', () => {
|
||||
queueStore.unqueue(queueStore.state.songs[0]);
|
||||
queueStore.unqueue(queueStore.state.songs[0])
|
||||
queueStore.first.title.should.equal('So long, Jimmy'); // Oh the irony.
|
||||
});
|
||||
})
|
||||
|
||||
it('correctly removes mutiple songs from queue', () => {
|
||||
queueStore.unqueue([queueStore.state.songs[0], queueStore.state.songs[1]]);
|
||||
queueStore.first.title.should.equal('Wisemen');
|
||||
});
|
||||
});
|
||||
queueStore.unqueue([queueStore.state.songs[0], queueStore.state.songs[1]])
|
||||
queueStore.first.title.should.equal('Wisemen')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#clear', () => {
|
||||
it('correctly clears all songs from queue', () => {
|
||||
queueStore.clear();
|
||||
queueStore.state.songs.length.should.equal(0);
|
||||
});
|
||||
});
|
||||
queueStore.clear()
|
||||
queueStore.state.songs.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#current', () => {
|
||||
it('returns the correct current song', () => {
|
||||
queueStore.current.title.should.equal('So long, Jimmy');
|
||||
});
|
||||
queueStore.current.title.should.equal('So long, Jimmy')
|
||||
})
|
||||
|
||||
it('successfully sets the current song', () => {
|
||||
queueStore.current = queueStore.state.songs[0];
|
||||
queueStore.current.title.should.equal('No bravery');
|
||||
});
|
||||
});
|
||||
queueStore.current = queueStore.state.songs[0]
|
||||
queueStore.current.title.should.equal('No bravery')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getNextSong', () => {
|
||||
it('correctly gets the next song in queue', () => {
|
||||
queueStore.next.title.should.equal('Wisemen');
|
||||
});
|
||||
queueStore.next.title.should.equal('Wisemen')
|
||||
})
|
||||
|
||||
it('correctly returns null if at end of queue', () => {
|
||||
queueStore.current = queueStore.state.songs[queueStore.state.songs.length - 1];
|
||||
(queueStore.next === null).should.be.true;
|
||||
});
|
||||
});
|
||||
queueStore.current = queueStore.state.songs[queueStore.state.songs.length - 1]
|
||||
var result = queueStore.next === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPrevSong', () => {
|
||||
it('correctly gets the previous song in queue', () => {
|
||||
queueStore.previous.title.should.equal('No bravery');
|
||||
});
|
||||
queueStore.previous.title.should.equal('No bravery')
|
||||
})
|
||||
|
||||
it('correctly returns null if at end of queue', () => {
|
||||
queueStore.current = queueStore.state.songs[0];
|
||||
(queueStore.previous === null).should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
queueStore.current = queueStore.state.songs[0]
|
||||
var result = queueStore.previous === null
|
||||
result.should.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
require('chai').should();
|
||||
import { cloneDeep, last } from 'lodash';
|
||||
require('chai').should()
|
||||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { songStore, albumStore, artistStore } from '../../stores';
|
||||
import artists from '../blobs/media';
|
||||
import interactions from '../blobs/interactions';
|
||||
import { songStore, albumStore, artistStore } from '../../stores'
|
||||
import artists from '../blobs/media'
|
||||
import interactions from '../blobs/interactions'
|
||||
|
||||
describe('stores/song', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.init(artists);
|
||||
});
|
||||
artistStore.init(artists)
|
||||
})
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers all songs', () => {
|
||||
songStore.state.songs.length.should.equal(14);
|
||||
});
|
||||
songStore.state.songs.length.should.equal(14)
|
||||
})
|
||||
|
||||
it ('coverts lengths to formatted lengths', () => {
|
||||
songStore.state.songs[0].fmtLength.should.be.a.string;
|
||||
});
|
||||
songStore.state.songs[0].fmtLength.should.be.a.string
|
||||
})
|
||||
|
||||
it('correctly sets albums', () => {
|
||||
songStore.state.songs[0].album.id.should.equal(1193);
|
||||
});
|
||||
});
|
||||
songStore.state.songs[0].album.id.should.equal(1193)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all songs', () => {
|
||||
songStore.all.length.should.equal(14);
|
||||
});
|
||||
});
|
||||
songStore.all.length.should.equal(14)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets a song by ID', () => {
|
||||
songStore.byId('e6d3977f3ffa147801ca5d1fdf6fa55e').title.should.equal('Like a rolling stone');
|
||||
});
|
||||
});
|
||||
songStore.byId('e6d3977f3ffa147801ca5d1fdf6fa55e').title.should.equal('Like a rolling stone')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#byIds', () => {
|
||||
it('correctly gets multiple songs by IDs', () => {
|
||||
const songs = songStore.byIds(['e6d3977f3ffa147801ca5d1fdf6fa55e', 'aa16bbef6a9710eb9a0f41ecc534fad5']);
|
||||
songs[0].title.should.equal('Like a rolling stone');
|
||||
songs[1].title.should.equal("Knockin' on heaven's door");
|
||||
});
|
||||
});
|
||||
const songs = songStore.byIds(['e6d3977f3ffa147801ca5d1fdf6fa55e', 'aa16bbef6a9710eb9a0f41ecc534fad5'])
|
||||
songs[0].title.should.equal('Like a rolling stone')
|
||||
songs[1].title.should.equal("Knockin' on heaven's door")
|
||||
})
|
||||
})
|
||||
|
||||
describe('#initInteractions', () => {
|
||||
beforeEach(() => songStore.initInteractions(interactions));
|
||||
beforeEach(() => songStore.initInteractions(interactions))
|
||||
|
||||
it('correctly sets interaction status', () => {
|
||||
const song = songStore.byId('cb7edeac1f097143e65b1b2cde102482');
|
||||
song.liked.should.be.true;
|
||||
song.playCount.should.equal(3);
|
||||
});
|
||||
});
|
||||
const song = songStore.byId('cb7edeac1f097143e65b1b2cde102482')
|
||||
song.liked.should.be.true
|
||||
song.playCount.should.equal(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#syncUpdatedSong', () => {
|
||||
beforeEach(() => artistStore.init(artists));
|
||||
beforeEach(() => artistStore.init(artists))
|
||||
|
||||
const updatedSong = {
|
||||
id: "39189f4545f9d5671fb3dc964f0080a0",
|
||||
|
@ -66,19 +66,19 @@ describe('stores/song', () => {
|
|||
arist_id: 1,
|
||||
artist: {
|
||||
id: 1,
|
||||
name: 'All-4-One',
|
||||
},
|
||||
},
|
||||
};
|
||||
name: 'All-4-One'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it ('correctly syncs an updated song with no album changes', () => {
|
||||
songStore.syncUpdatedSong(cloneDeep(updatedSong));
|
||||
songStore.byId(updatedSong.id).title.should.equal('I Swear A Lot');
|
||||
});
|
||||
songStore.syncUpdatedSong(cloneDeep(updatedSong))
|
||||
songStore.byId(updatedSong.id).title.should.equal('I Swear A Lot')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into an existing album of same artist', () => {
|
||||
const song = cloneDeep(updatedSong);
|
||||
song.album_id = 1194;
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 1194
|
||||
song.album = {
|
||||
id: 1194,
|
||||
artist_id: 1,
|
||||
|
@ -86,62 +86,62 @@ describe('stores/song', () => {
|
|||
id: 1,
|
||||
name: 'All-4-One',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song);
|
||||
songStore.byId(song.id).album.name.should.equal('And The Music Speaks');
|
||||
});
|
||||
songStore.syncUpdatedSong(song)
|
||||
songStore.byId(song.id).album.name.should.equal('And The Music Speaks')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into a new album of same artist', () => {
|
||||
const song = cloneDeep(updatedSong);
|
||||
song.album_id = 9999;
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 9999
|
||||
song.album = {
|
||||
id: 9999,
|
||||
artist_id: 1,
|
||||
name: 'Brand New Album from All-4-One',
|
||||
artist: {
|
||||
id: 1,
|
||||
name: 'All-4-One',
|
||||
},
|
||||
};
|
||||
name: 'All-4-One'
|
||||
}
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song);
|
||||
songStore.syncUpdatedSong(song)
|
||||
|
||||
// A new album should be created...
|
||||
last(albumStore.all).name.should.equal('Brand New Album from All-4-One');
|
||||
last(albumStore.all).name.should.equal('Brand New Album from All-4-One')
|
||||
|
||||
// ...and assigned with the song.
|
||||
songStore.byId(song.id).album.name.should.equal('Brand New Album from All-4-One');
|
||||
});
|
||||
songStore.byId(song.id).album.name.should.equal('Brand New Album from All-4-One')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into a new album of a new artist', () => {
|
||||
const song = cloneDeep(updatedSong);
|
||||
song.album_id = 10000;
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 10000
|
||||
song.album = {
|
||||
id: 10000,
|
||||
name: "It's... John Cena!!!",
|
||||
artist_id: 10000,
|
||||
artist: {
|
||||
id: 10000,
|
||||
name: 'John Cena',
|
||||
},
|
||||
};
|
||||
name: 'John Cena'
|
||||
}
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song);
|
||||
songStore.syncUpdatedSong(song)
|
||||
|
||||
// A new artist should be created...
|
||||
const lastArtist = last(artistStore.all);
|
||||
lastArtist.name.should.equal('John Cena');
|
||||
const lastArtist = last(artistStore.all)
|
||||
lastArtist.name.should.equal('John Cena')
|
||||
|
||||
// A new album should be created
|
||||
const lastAlbum = last(albumStore.all);
|
||||
lastAlbum.name.should.equal("It's... John Cena!!!");
|
||||
const lastAlbum = last(albumStore.all)
|
||||
lastAlbum.name.should.equal("It's... John Cena!!!")
|
||||
|
||||
// The album must belong to John Cena of course!
|
||||
last(lastArtist.albums).should.equal(lastAlbum);
|
||||
last(lastArtist.albums).should.equal(lastAlbum)
|
||||
|
||||
// And the song belongs to the album.
|
||||
songStore.byId(song.id).album.should.equal(lastAlbum);
|
||||
});
|
||||
});
|
||||
});
|
||||
songStore.byId(song.id).album.should.equal(lastAlbum)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,66 +1,50 @@
|
|||
require('chai').should();
|
||||
require('chai').should()
|
||||
|
||||
import { userStore } from '../../stores';
|
||||
import data from '../blobs/users';
|
||||
import { userStore } from '../../stores'
|
||||
import data from '../blobs/users'
|
||||
|
||||
describe('stores/user', () => {
|
||||
beforeEach(() => userStore.init(data.users, data.currentUser));
|
||||
beforeEach(() => userStore.init(data.users, data.currentUser))
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly sets data state', () => {
|
||||
userStore.state.users.should.equal(data.users);
|
||||
userStore.state.current.should.equal(data.currentUser);
|
||||
});
|
||||
});
|
||||
userStore.state.users.should.equal(data.users)
|
||||
userStore.state.current.should.equal(data.currentUser)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all users', () => {
|
||||
userStore.all.should.equal(data.users);
|
||||
});
|
||||
});
|
||||
userStore.all.should.equal(data.users)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets a user by ID', () => {
|
||||
userStore.byId(1).should.equal(data.users[0]);
|
||||
});
|
||||
});
|
||||
userStore.byId(1).should.equal(data.users[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#current', () => {
|
||||
it('correctly gets the current user', () => {
|
||||
userStore.current.id.should.equal(1);
|
||||
});
|
||||
userStore.current.id.should.equal(1)
|
||||
})
|
||||
|
||||
it('correctly sets the current user', () => {
|
||||
userStore.current = data.users[1];
|
||||
userStore.current.id.should.equal(2);
|
||||
});
|
||||
});
|
||||
userStore.current = data.users[1]
|
||||
userStore.current.id.should.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#setAvatar', () => {
|
||||
it('correctly sets the current user’s avatar', () => {
|
||||
userStore.setAvatar();
|
||||
userStore.current.avatar.should.equal('https://www.gravatar.com/avatar/b9611f1bba1aacbe6f5de5856695a202?s=256');
|
||||
});
|
||||
userStore.setAvatar()
|
||||
userStore.current.avatar.should.equal('https://www.gravatar.com/avatar/b9611f1bba1aacbe6f5de5856695a202?s=256')
|
||||
})
|
||||
|
||||
it('correctly sets a user’s avatar', () => {
|
||||
userStore.setAvatar(data.users[1]);
|
||||
data.users[1].avatar.should.equal('https://www.gravatar.com/avatar/5024672cfe53f113b746e1923e373058?s=256');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateProfile', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('#store', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('#update', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('#destroy', () => {
|
||||
|
||||
});
|
||||
});
|
||||
userStore.setAvatar(data.users[1])
|
||||
data.users[1].avatar.should.equal('https://www.gravatar.com/avatar/5024672cfe53f113b746e1923e373058?s=256')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
require('chai').should();
|
||||
require('chai').should()
|
||||
|
||||
import { secondsToHis, parseValidationError } from '../../utils';
|
||||
import { secondsToHis, parseValidationError } from '../../utils'
|
||||
|
||||
describe('services/utils', () => {
|
||||
describe('#secondsToHis', () => {
|
||||
it('correctly formats a duration to H:i:s', () => {
|
||||
secondsToHis(7547).should.equal('02:05:47');
|
||||
});
|
||||
secondsToHis(7547).should.equal('02:05:47')
|
||||
})
|
||||
|
||||
it('ommits hours from short duration when formats to H:i:s', () => {
|
||||
secondsToHis(314).should.equal('05:14');
|
||||
});
|
||||
});
|
||||
secondsToHis(314).should.equal('05:14')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#parseValidationError', () => {
|
||||
it('correctly parses single-level validation error', () => {
|
||||
const error = {
|
||||
err_1: ['Foo'],
|
||||
};
|
||||
err_1: ['Foo']
|
||||
}
|
||||
|
||||
parseValidationError(error).should.eql(['Foo']);
|
||||
});
|
||||
parseValidationError(error).should.eql(['Foo'])
|
||||
})
|
||||
|
||||
it('correctly parses multi-level validation error', () => {
|
||||
const error = {
|
||||
err_1: ['Foo', 'Bar'],
|
||||
err_2: ['Baz', 'Qux'],
|
||||
};
|
||||
err_2: ['Baz', 'Qux']
|
||||
}
|
||||
|
||||
parseValidationError(error).should.eql(['Foo', 'Bar', 'Baz', 'Qux']);
|
||||
});
|
||||
});
|
||||
});
|
||||
parseValidationError(error).should.eql(['Foo', 'Bar', 'Baz', 'Qux'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Other common methods.
|
||||
*/
|
||||
import select from 'select';
|
||||
import select from 'select'
|
||||
import { event } from '../utils'
|
||||
|
||||
/**
|
||||
|
@ -10,18 +10,18 @@ import { event } from '../utils'
|
|||
* @param {String} view The view, which can be found under components/main-wrapper/main-content.
|
||||
* @param {...*} Extra data to attach to the view.
|
||||
*/
|
||||
export function loadMainView(view, ...args) {
|
||||
event.emit('main-content-view:load', view, ...args);
|
||||
};
|
||||
export function loadMainView (view, ...args) {
|
||||
event.emit('main-content-view:load', view, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reloading window regardless of "Confirm before reload" setting.
|
||||
* This is handy for certain cases, for example Last.fm connect/disconnect.
|
||||
*/
|
||||
export function forceReloadWindow() {
|
||||
window.onbeforeunload = function() {};
|
||||
window.location.reload();
|
||||
};
|
||||
export function forceReloadWindow () {
|
||||
window.onbeforeunload = function () {}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overlay.
|
||||
|
@ -30,26 +30,26 @@ export function forceReloadWindow() {
|
|||
* @param {String} type
|
||||
* @param {Boolean} dismissable
|
||||
*/
|
||||
export function showOverlay(message = 'Just a little patience…', type = 'loading', dismissable = false) {
|
||||
event.emit('overlay:show', { message, type, dismissable });
|
||||
};
|
||||
export function showOverlay (message = 'Just a little patience…', type = 'loading', dismissable = false) {
|
||||
event.emit('overlay:show', { message, type, dismissable })
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overlay.
|
||||
*/
|
||||
export function hideOverlay() {
|
||||
event.emit('overlay:hide');
|
||||
};
|
||||
export function hideOverlay () {
|
||||
event.emit('overlay:hide')
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a text into clipboard.
|
||||
*
|
||||
* @param {string} txt
|
||||
*/
|
||||
export function copyText(txt) {
|
||||
const copyArea = document.querySelector('#copyArea');
|
||||
copyArea.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
|
||||
copyArea.value = txt;
|
||||
select(copyArea);
|
||||
document.execCommand('copy');
|
||||
};
|
||||
export function copyText (txt) {
|
||||
const copyArea = document.querySelector('#copyArea')
|
||||
copyArea.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px'
|
||||
copyArea.value = txt
|
||||
select(copyArea)
|
||||
document.execCommand('copy')
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue